001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2018 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.design;
021
022import java.util.ArrayList;
023import java.util.Arrays;
024import java.util.Collections;
025import java.util.HashSet;
026import java.util.List;
027import java.util.Set;
028import java.util.regex.Pattern;
029import java.util.stream.Collectors;
030
031import antlr.collections.AST;
032import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
033import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
034import com.puppycrawl.tools.checkstyle.api.DetailAST;
035import com.puppycrawl.tools.checkstyle.api.FullIdent;
036import com.puppycrawl.tools.checkstyle.api.TokenTypes;
037import com.puppycrawl.tools.checkstyle.utils.AnnotationUtility;
038import com.puppycrawl.tools.checkstyle.utils.ScopeUtils;
039
040/**
041 * Checks visibility of class members. Only static final, immutable or annotated
042 * by specified annotation members may be public,
043 * other class members must be private unless allowProtected/Package is set.
044 * <p>
045 * Public members are not flagged if the name matches the public
046 * member regular expression (contains "^serialVersionUID$" by
047 * default).
048 * </p>
049 * Rationale: Enforce encapsulation.
050 * <p>
051 * Check also has options making it less strict:
052 * </p>
053 * <p>
054 * <b>ignoreAnnotationCanonicalNames</b> - the list of annotations canonical names
055 * which ignore variables in consideration, if user will provide short annotation name
056 * that type will match to any named the same type without consideration of package,
057 * list by default:
058 * </p>
059 * <ul>
060 * <li>org.junit.Rule</li>
061 * <li>org.junit.ClassRule</li>
062 * <li>com.google.common.annotations.VisibleForTesting</li>
063 * </ul>
064 * <p>
065 * For example such public field will be skipped by default value of list above:
066 * </p>
067 *
068 * <pre>
069 * {@code @org.junit.Rule
070 * public TemporaryFolder publicJUnitRule = new TemporaryFolder();
071 * }
072 * </pre>
073 *
074 * <p>
075 * <b>allowPublicFinalFields</b> - which allows public final fields. Default value is <b>false</b>.
076 * </p>
077 * <p>
078 * <b>allowPublicImmutableFields</b> - which allows immutable fields to be
079 * declared as public if defined in final class. Default value is <b>false</b>
080 * </p>
081 * <p>
082 * Field is known to be immutable if:
083 * </p>
084 * <ul>
085 * <li>It's declared as final</li>
086 * <li>Has either a primitive type or instance of class user defined to be immutable
087 * (such as String, ImmutableCollection from Guava and etc)</li>
088 * </ul>
089 * <p>
090 * Classes known to be immutable are listed in <b>immutableClassCanonicalNames</b> by their
091 * <b>canonical</b> names. List by default:
092 * </p>
093 * <ul>
094 * <li>java.lang.String</li>
095 * <li>java.lang.Integer</li>
096 * <li>java.lang.Byte</li>
097 * <li>java.lang.Character</li>
098 * <li>java.lang.Short</li>
099 * <li>java.lang.Boolean</li>
100 * <li>java.lang.Long</li>
101 * <li>java.lang.Double</li>
102 * <li>java.lang.Float</li>
103 * <li>java.lang.StackTraceElement</li>
104 * <li>java.lang.BigInteger</li>
105 * <li>java.lang.BigDecimal</li>
106 * <li>java.io.File</li>
107 * <li>java.util.Locale</li>
108 * <li>java.util.UUID</li>
109 * <li>java.net.URL</li>
110 * <li>java.net.URI</li>
111 * <li>java.net.Inet4Address</li>
112 * <li>java.net.Inet6Address</li>
113 * <li>java.net.InetSocketAddress</li>
114 * </ul>
115 * <p>
116 * User can override this list via adding <b>canonical</b> class names to
117 * <b>immutableClassCanonicalNames</b>, if user will provide short class name all
118 * that type will match to any named the same type without consideration of package.
119 * </p>
120 * <p>
121 * <b>Rationale</b>: Forcing all fields of class to have private modified by default is good
122 * in most cases, but in some cases it drawbacks in too much boilerplate get/set code.
123 * One of such cases are immutable classes.
124 * </p>
125 * <p>
126 * <b>Restriction</b>: Check doesn't check if class is immutable, there's no checking
127 * if accessory methods are missing and all fields are immutable, we only check
128 * <b>if current field is immutable by matching a name to user defined list of immutable classes
129 * and defined in final class</b>
130 * </p>
131 * <p>
132 * Star imports are out of scope of this Check. So if one of type imported via <b>star import</b>
133 * collides with user specified one by its short name - there won't be Check's violation.
134 * </p>
135 * Examples:
136 * <p>
137 * The check will rise 3 violations if it is run with default configuration against the following
138 * code example:
139 * </p>
140 *
141 * <pre>
142 * {@code
143 * public class ImmutableClass
144 * {
145 *     public int intValue; // violation
146 *     public java.lang.String notes; // violation
147 *     public BigDecimal value; // violation
148 *
149 *     public ImmutableClass(int intValue, BigDecimal value, String notes)
150 *     {
151 *         this.intValue = intValue;
152 *         this.value = value;
153 *         this.notes = notes;
154 *     }
155 * }
156 * }
157 * </pre>
158 *
159 * <p>
160 * To configure the Check passing fields of type com.google.common.collect.ImmutableSet and
161 * java.util.List:
162 * </p>
163 * <p>
164 * &lt;module name=&quot;VisibilityModifier&quot;&gt;
165 *   &lt;property name=&quot;allowPublicImmutableFields&quot; value=&quot;true&quot;/&gt;
166 *   &lt;property name=&quot;immutableClassCanonicalNames&quot; value=&quot;java.util.List,
167 *   com.google.common.collect.ImmutableSet&quot;/&gt;
168 * &lt;/module&gt;
169 * </p>
170 *
171 * <pre>
172 * {@code
173 * public final class ImmutableClass
174 * {
175 *     public final ImmutableSet&lt;String&gt; includes; // No warning
176 *     public final ImmutableSet&lt;String&gt; excludes; // No warning
177 *     public final BigDecimal value; // Warning here, type BigDecimal isn't specified as immutable
178 *
179 *     public ImmutableClass(Collection&lt;String&gt; includes, Collection&lt;String&gt; excludes,
180 *                  BigDecimal value)
181 *     {
182 *         this.includes = ImmutableSet.copyOf(includes);
183 *         this.excludes = ImmutableSet.copyOf(excludes);
184 *         this.value = value;
185 *         this.notes = notes;
186 *     }
187 * }
188 * }
189 * </pre>
190 *
191 * <p>
192 * To configure the Check passing fields annotated with
193 * </p>
194 * <pre>@com.annotation.CustomAnnotation</pre>:
195
196 * <p>
197 * &lt;module name=&quot;VisibilityModifier&quot;&gt;
198 *   &lt;property name=&quot;ignoreAnnotationCanonicalNames&quot; value=&quot;
199 *   com.annotation.CustomAnnotation&quot;/&gt;
200 * &lt;/module&gt;
201 * </p>
202 *
203 * <pre>
204 * {@code @com.annotation.CustomAnnotation
205 * String customAnnotated; // No warning
206 * }
207 * {@code @CustomAnnotation
208 * String shortCustomAnnotated; // No warning
209 * }
210 * </pre>
211 *
212 * <p>
213 * To configure the Check passing fields annotated with short annotation name
214 * </p>
215 * <pre>@CustomAnnotation</pre>:
216 *
217 * <p>
218 * &lt;module name=&quot;VisibilityModifier&quot;&gt;
219 *   &lt;property name=&quot;ignoreAnnotationCanonicalNames&quot;
220 *   value=&quot;CustomAnnotation&quot;/&gt;
221 * &lt;/module&gt;
222 * </p>
223 *
224 * <pre>
225 * {@code @CustomAnnotation
226 * String customAnnotated; // No warning
227 * }
228 * {@code @com.annotation.CustomAnnotation
229 * String customAnnotated1; // No warning
230 * }
231 * {@code @mypackage.annotation.CustomAnnotation
232 * String customAnnotatedAnotherPackage; // another package but short name matches
233 *                                       // so no violation
234 * }
235 * </pre>
236 *
237 *
238 * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a>
239 */
240@FileStatefulCheck
241public class VisibilityModifierCheck
242    extends AbstractCheck {
243
244    /**
245     * A key is pointing to the warning message text in "messages.properties"
246     * file.
247     */
248    public static final String MSG_KEY = "variable.notPrivate";
249
250    /** Default immutable types canonical names. */
251    private static final List<String> DEFAULT_IMMUTABLE_TYPES = Collections.unmodifiableList(
252        Arrays.stream(new String[] {
253            "java.lang.String",
254            "java.lang.Integer",
255            "java.lang.Byte",
256            "java.lang.Character",
257            "java.lang.Short",
258            "java.lang.Boolean",
259            "java.lang.Long",
260            "java.lang.Double",
261            "java.lang.Float",
262            "java.lang.StackTraceElement",
263            "java.math.BigInteger",
264            "java.math.BigDecimal",
265            "java.io.File",
266            "java.util.Locale",
267            "java.util.UUID",
268            "java.net.URL",
269            "java.net.URI",
270            "java.net.Inet4Address",
271            "java.net.Inet6Address",
272            "java.net.InetSocketAddress",
273        }).collect(Collectors.toList()));
274
275    /** Default ignore annotations canonical names. */
276    private static final List<String> DEFAULT_IGNORE_ANNOTATIONS = Collections.unmodifiableList(
277        Arrays.stream(new String[] {
278            "org.junit.Rule",
279            "org.junit.ClassRule",
280            "com.google.common.annotations.VisibleForTesting",
281        }).collect(Collectors.toList()));
282
283    /** Name for 'public' access modifier. */
284    private static final String PUBLIC_ACCESS_MODIFIER = "public";
285
286    /** Name for 'private' access modifier. */
287    private static final String PRIVATE_ACCESS_MODIFIER = "private";
288
289    /** Name for 'protected' access modifier. */
290    private static final String PROTECTED_ACCESS_MODIFIER = "protected";
291
292    /** Name for implicit 'package' access modifier. */
293    private static final String PACKAGE_ACCESS_MODIFIER = "package";
294
295    /** Name for 'static' keyword. */
296    private static final String STATIC_KEYWORD = "static";
297
298    /** Name for 'final' keyword. */
299    private static final String FINAL_KEYWORD = "final";
300
301    /** Contains explicit access modifiers. */
302    private static final String[] EXPLICIT_MODS = {
303        PUBLIC_ACCESS_MODIFIER,
304        PRIVATE_ACCESS_MODIFIER,
305        PROTECTED_ACCESS_MODIFIER,
306    };
307
308    /** Regexp for public members that should be ignored. Note:
309     * Earlier versions of checkstyle used ^f[A-Z][a-zA-Z0-9]*$ as the
310     * default to allow CMP for EJB 1.1 with the default settings.
311     * With EJB 2.0 it is not longer necessary to have public access
312     * for persistent fields.
313     */
314    private Pattern publicMemberPattern = Pattern.compile("^serialVersionUID$");
315
316    /** List of ignore annotations short names. */
317    private final List<String> ignoreAnnotationShortNames =
318            getClassShortNames(DEFAULT_IGNORE_ANNOTATIONS);
319
320    /** List of immutable classes short names. */
321    private final List<String> immutableClassShortNames =
322        getClassShortNames(DEFAULT_IMMUTABLE_TYPES);
323
324    /** List of ignore annotations canonical names. */
325    private List<String> ignoreAnnotationCanonicalNames =
326        new ArrayList<>(DEFAULT_IGNORE_ANNOTATIONS);
327
328    /** Whether protected members are allowed. */
329    private boolean protectedAllowed;
330
331    /** Whether package visible members are allowed. */
332    private boolean packageAllowed;
333
334    /** Allows immutable fields of final classes to be declared as public. */
335    private boolean allowPublicImmutableFields;
336
337    /** Allows final fields to be declared as public. */
338    private boolean allowPublicFinalFields;
339
340    /** List of immutable classes canonical names. */
341    private List<String> immutableClassCanonicalNames = new ArrayList<>(DEFAULT_IMMUTABLE_TYPES);
342
343    /**
344     * Set the list of ignore annotations.
345     * @param annotationNames array of ignore annotations canonical names.
346     */
347    public void setIgnoreAnnotationCanonicalNames(String... annotationNames) {
348        ignoreAnnotationCanonicalNames = Arrays.asList(annotationNames);
349    }
350
351    /**
352     * Set whether protected members are allowed.
353     * @param protectedAllowed whether protected members are allowed
354     */
355    public void setProtectedAllowed(boolean protectedAllowed) {
356        this.protectedAllowed = protectedAllowed;
357    }
358
359    /**
360     * Set whether package visible members are allowed.
361     * @param packageAllowed whether package visible members are allowed
362     */
363    public void setPackageAllowed(boolean packageAllowed) {
364        this.packageAllowed = packageAllowed;
365    }
366
367    /**
368     * Set the pattern for public members to ignore.
369     * @param pattern
370     *        pattern for public members to ignore.
371     */
372    public void setPublicMemberPattern(Pattern pattern) {
373        publicMemberPattern = pattern;
374    }
375
376    /**
377     * Sets whether public immutable fields are allowed.
378     * @param allow user's value.
379     */
380    public void setAllowPublicImmutableFields(boolean allow) {
381        allowPublicImmutableFields = allow;
382    }
383
384    /**
385     * Sets whether public final fields are allowed.
386     * @param allow user's value.
387     */
388    public void setAllowPublicFinalFields(boolean allow) {
389        allowPublicFinalFields = allow;
390    }
391
392    /**
393     * Set the list of immutable classes types names.
394     * @param classNames array of immutable types canonical names.
395     */
396    public void setImmutableClassCanonicalNames(String... classNames) {
397        immutableClassCanonicalNames = Arrays.asList(classNames);
398    }
399
400    @Override
401    public int[] getDefaultTokens() {
402        return getRequiredTokens();
403    }
404
405    @Override
406    public int[] getAcceptableTokens() {
407        return getRequiredTokens();
408    }
409
410    @Override
411    public int[] getRequiredTokens() {
412        return new int[] {
413            TokenTypes.VARIABLE_DEF,
414            TokenTypes.IMPORT,
415        };
416    }
417
418    @Override
419    public void beginTree(DetailAST rootAst) {
420        immutableClassShortNames.clear();
421        final List<String> classShortNames =
422                getClassShortNames(immutableClassCanonicalNames);
423        immutableClassShortNames.addAll(classShortNames);
424
425        ignoreAnnotationShortNames.clear();
426        final List<String> annotationShortNames =
427                getClassShortNames(ignoreAnnotationCanonicalNames);
428        ignoreAnnotationShortNames.addAll(annotationShortNames);
429    }
430
431    @Override
432    public void visitToken(DetailAST ast) {
433        switch (ast.getType()) {
434            case TokenTypes.VARIABLE_DEF:
435                if (!isAnonymousClassVariable(ast)) {
436                    visitVariableDef(ast);
437                }
438                break;
439            case TokenTypes.IMPORT:
440                visitImport(ast);
441                break;
442            default:
443                final String exceptionMsg = "Unexpected token type: " + ast.getText();
444                throw new IllegalArgumentException(exceptionMsg);
445        }
446    }
447
448    /**
449     * Checks if current variable definition is definition of an anonymous class.
450     * @param variableDef {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF}
451     * @return true if current variable definition is definition of an anonymous class.
452     */
453    private static boolean isAnonymousClassVariable(DetailAST variableDef) {
454        return variableDef.getParent().getType() != TokenTypes.OBJBLOCK;
455    }
456
457    /**
458     * Checks access modifier of given variable.
459     * If it is not proper according to Check - puts violation on it.
460     * @param variableDef variable to check.
461     */
462    private void visitVariableDef(DetailAST variableDef) {
463        final boolean inInterfaceOrAnnotationBlock =
464                ScopeUtils.isInInterfaceOrAnnotationBlock(variableDef);
465
466        if (!inInterfaceOrAnnotationBlock && !hasIgnoreAnnotation(variableDef)) {
467            final DetailAST varNameAST = variableDef.findFirstToken(TokenTypes.TYPE)
468                .getNextSibling();
469            final String varName = varNameAST.getText();
470            if (!hasProperAccessModifier(variableDef, varName)) {
471                log(varNameAST.getLineNo(), varNameAST.getColumnNo(),
472                        MSG_KEY, varName);
473            }
474        }
475    }
476
477    /**
478     * Checks if variable def has ignore annotation.
479     * @param variableDef {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF}
480     * @return true if variable def has ignore annotation.
481     */
482    private boolean hasIgnoreAnnotation(DetailAST variableDef) {
483        final DetailAST firstIgnoreAnnotation =
484                 findMatchingAnnotation(variableDef);
485        return firstIgnoreAnnotation != null;
486    }
487
488    /**
489     * Checks imported type. If type's canonical name was not specified in
490     * <b>immutableClassCanonicalNames</b>, but it's short name collides with one from
491     * <b>immutableClassShortNames</b> - removes it from the last one.
492     * @param importAst {@link TokenTypes#IMPORT Import}
493     */
494    private void visitImport(DetailAST importAst) {
495        if (!isStarImport(importAst)) {
496            final DetailAST type = importAst.getFirstChild();
497            final String canonicalName = getCanonicalName(type);
498            final String shortName = getClassShortName(canonicalName);
499
500            // If imported canonical class name is not specified as allowed immutable class,
501            // but its short name collides with one of specified class - removes the short name
502            // from list to avoid names collision
503            if (!immutableClassCanonicalNames.contains(canonicalName)
504                     && immutableClassShortNames.contains(shortName)) {
505                immutableClassShortNames.remove(shortName);
506            }
507            if (!ignoreAnnotationCanonicalNames.contains(canonicalName)
508                     && ignoreAnnotationShortNames.contains(shortName)) {
509                ignoreAnnotationShortNames.remove(shortName);
510            }
511        }
512    }
513
514    /**
515     * Checks if current import is star import. E.g.:
516     * <p>
517     * {@code
518     * import java.util.*;
519     * }
520     * </p>
521     * @param importAst {@link TokenTypes#IMPORT Import}
522     * @return true if it is star import
523     */
524    private static boolean isStarImport(DetailAST importAst) {
525        boolean result = false;
526        DetailAST toVisit = importAst;
527        while (toVisit != null) {
528            toVisit = getNextSubTreeNode(toVisit, importAst);
529            if (toVisit != null && toVisit.getType() == TokenTypes.STAR) {
530                result = true;
531                break;
532            }
533        }
534        return result;
535    }
536
537    /**
538     * Checks if current variable has proper access modifier according to Check's options.
539     * @param variableDef Variable definition node.
540     * @param variableName Variable's name.
541     * @return true if variable has proper access modifier.
542     */
543    private boolean hasProperAccessModifier(DetailAST variableDef, String variableName) {
544        boolean result = true;
545
546        final String variableScope = getVisibilityScope(variableDef);
547
548        if (!PRIVATE_ACCESS_MODIFIER.equals(variableScope)) {
549            result =
550                isStaticFinalVariable(variableDef)
551                || packageAllowed && PACKAGE_ACCESS_MODIFIER.equals(variableScope)
552                || protectedAllowed && PROTECTED_ACCESS_MODIFIER.equals(variableScope)
553                || isIgnoredPublicMember(variableName, variableScope)
554                || isAllowedPublicField(variableDef);
555        }
556
557        return result;
558    }
559
560    /**
561     * Checks whether variable has static final modifiers.
562     * @param variableDef Variable definition node.
563     * @return true of variable has static final modifiers.
564     */
565    private static boolean isStaticFinalVariable(DetailAST variableDef) {
566        final Set<String> modifiers = getModifiers(variableDef);
567        return modifiers.contains(STATIC_KEYWORD)
568                && modifiers.contains(FINAL_KEYWORD);
569    }
570
571    /**
572     * Checks whether variable belongs to public members that should be ignored.
573     * @param variableName Variable's name.
574     * @param variableScope Variable's scope.
575     * @return true if variable belongs to public members that should be ignored.
576     */
577    private boolean isIgnoredPublicMember(String variableName, String variableScope) {
578        return PUBLIC_ACCESS_MODIFIER.equals(variableScope)
579            && publicMemberPattern.matcher(variableName).find();
580    }
581
582    /**
583     * Checks whether the variable satisfies the public field check.
584     * @param variableDef Variable definition node.
585     * @return true if allowed.
586     */
587    private boolean isAllowedPublicField(DetailAST variableDef) {
588        return allowPublicFinalFields && isFinalField(variableDef)
589            || allowPublicImmutableFields && isImmutableFieldDefinedInFinalClass(variableDef);
590    }
591
592    /**
593     * Checks whether immutable field is defined in final class.
594     * @param variableDef Variable definition node.
595     * @return true if immutable field is defined in final class.
596     */
597    private boolean isImmutableFieldDefinedInFinalClass(DetailAST variableDef) {
598        final DetailAST classDef = variableDef.getParent().getParent();
599        final Set<String> classModifiers = getModifiers(classDef);
600        return (classModifiers.contains(FINAL_KEYWORD) || classDef.getType() == TokenTypes.ENUM_DEF)
601                && isImmutableField(variableDef);
602    }
603
604    /**
605     * Returns the set of modifier Strings for a VARIABLE_DEF or CLASS_DEF AST.
606     * @param defAST AST for a variable or class definition.
607     * @return the set of modifier Strings for defAST.
608     */
609    private static Set<String> getModifiers(DetailAST defAST) {
610        final AST modifiersAST = defAST.findFirstToken(TokenTypes.MODIFIERS);
611        final Set<String> modifiersSet = new HashSet<>();
612        if (modifiersAST != null) {
613            AST modifier = modifiersAST.getFirstChild();
614            while (modifier != null) {
615                modifiersSet.add(modifier.getText());
616                modifier = modifier.getNextSibling();
617            }
618        }
619        return modifiersSet;
620    }
621
622    /**
623     * Returns the visibility scope for the variable.
624     * @param variableDef Variable definition node.
625     * @return one of "public", "private", "protected", "package"
626     */
627    private static String getVisibilityScope(DetailAST variableDef) {
628        final Set<String> modifiers = getModifiers(variableDef);
629        String accessModifier = PACKAGE_ACCESS_MODIFIER;
630        for (final String modifier : EXPLICIT_MODS) {
631            if (modifiers.contains(modifier)) {
632                accessModifier = modifier;
633                break;
634            }
635        }
636        return accessModifier;
637    }
638
639    /**
640     * Checks if current field is immutable:
641     * has final modifier and either a primitive type or instance of class
642     * known to be immutable (such as String, ImmutableCollection from Guava and etc).
643     * Classes known to be immutable are listed in
644     * {@link VisibilityModifierCheck#immutableClassCanonicalNames}
645     * @param variableDef Field in consideration.
646     * @return true if field is immutable.
647     */
648    private boolean isImmutableField(DetailAST variableDef) {
649        boolean result = false;
650        if (isFinalField(variableDef)) {
651            final DetailAST type = variableDef.findFirstToken(TokenTypes.TYPE);
652            final boolean isCanonicalName = isCanonicalName(type);
653            final String typeName = getTypeName(type, isCanonicalName);
654            final DetailAST typeArgs = getGenericTypeArgs(type, isCanonicalName);
655            if (typeArgs == null) {
656                result = !isCanonicalName && isPrimitive(type)
657                    || immutableClassShortNames.contains(typeName)
658                    || isCanonicalName && immutableClassCanonicalNames.contains(typeName);
659            }
660            else {
661                final List<String> argsClassNames = getTypeArgsClassNames(typeArgs);
662                result = (immutableClassShortNames.contains(typeName)
663                    || isCanonicalName && immutableClassCanonicalNames.contains(typeName))
664                    && areImmutableTypeArguments(argsClassNames);
665            }
666        }
667        return result;
668    }
669
670    /**
671     * Checks whether type definition is in canonical form.
672     * @param type type definition token.
673     * @return true if type definition is in canonical form.
674     */
675    private static boolean isCanonicalName(DetailAST type) {
676        return type.getFirstChild().getType() == TokenTypes.DOT;
677    }
678
679    /**
680     * Returns generic type arguments token.
681     * @param type type token.
682     * @param isCanonicalName whether type name is in canonical form.
683     * @return generic type arguments token.
684     */
685    private static DetailAST getGenericTypeArgs(DetailAST type, boolean isCanonicalName) {
686        final DetailAST typeArgs;
687        if (isCanonicalName) {
688            // if type class name is in canonical form, abstract tree has specific structure
689            typeArgs = type.getFirstChild().findFirstToken(TokenTypes.TYPE_ARGUMENTS);
690        }
691        else {
692            typeArgs = type.findFirstToken(TokenTypes.TYPE_ARGUMENTS);
693        }
694        return typeArgs;
695    }
696
697    /**
698     * Returns a list of type parameters class names.
699     * @param typeArgs type arguments token.
700     * @return a list of type parameters class names.
701     */
702    private static List<String> getTypeArgsClassNames(DetailAST typeArgs) {
703        final List<String> typeClassNames = new ArrayList<>();
704        DetailAST type = typeArgs.findFirstToken(TokenTypes.TYPE_ARGUMENT);
705        boolean isCanonicalName = isCanonicalName(type);
706        String typeName = getTypeName(type, isCanonicalName);
707        typeClassNames.add(typeName);
708        DetailAST sibling = type.getNextSibling();
709        while (sibling.getType() == TokenTypes.COMMA) {
710            type = sibling.getNextSibling();
711            isCanonicalName = isCanonicalName(type);
712            typeName = getTypeName(type, isCanonicalName);
713            typeClassNames.add(typeName);
714            sibling = type.getNextSibling();
715        }
716        return typeClassNames;
717    }
718
719    /**
720     * Checks whether all of generic type arguments are immutable.
721     * If at least one argument is mutable, we assume that the whole list of type arguments
722     * is mutable.
723     * @param typeArgsClassNames type arguments class names.
724     * @return true if all of generic type arguments are immutable.
725     */
726    private boolean areImmutableTypeArguments(List<String> typeArgsClassNames) {
727        return typeArgsClassNames.stream().noneMatch(
728            typeName -> {
729                return !immutableClassShortNames.contains(typeName)
730                    && !immutableClassCanonicalNames.contains(typeName);
731            });
732    }
733
734    /**
735     * Checks whether current field is final.
736     * @param variableDef field in consideration.
737     * @return true if current field is final.
738     */
739    private static boolean isFinalField(DetailAST variableDef) {
740        final DetailAST modifiers = variableDef.findFirstToken(TokenTypes.MODIFIERS);
741        return modifiers.findFirstToken(TokenTypes.FINAL) != null;
742    }
743
744    /**
745     * Gets the name of type from given ast {@link TokenTypes#TYPE TYPE} node.
746     * If type is specified via its canonical name - canonical name will be returned,
747     * else - short type's name.
748     * @param type {@link TokenTypes#TYPE TYPE} node.
749     * @param isCanonicalName is given name canonical.
750     * @return String representation of given type's name.
751     */
752    private static String getTypeName(DetailAST type, boolean isCanonicalName) {
753        final String typeName;
754        if (isCanonicalName) {
755            typeName = getCanonicalName(type);
756        }
757        else {
758            typeName = type.getFirstChild().getText();
759        }
760        return typeName;
761    }
762
763    /**
764     * Checks if current type is primitive type (int, short, float, boolean, double, etc.).
765     * As primitive types have special tokens for each one, such as:
766     * LITERAL_INT, LITERAL_BOOLEAN, etc.
767     * So, if type's identifier differs from {@link TokenTypes#IDENT IDENT} token - it's a
768     * primitive type.
769     * @param type Ast {@link TokenTypes#TYPE TYPE} node.
770     * @return true if current type is primitive type.
771     */
772    private static boolean isPrimitive(DetailAST type) {
773        return type.getFirstChild().getType() != TokenTypes.IDENT;
774    }
775
776    /**
777     * Gets canonical type's name from given {@link TokenTypes#TYPE TYPE} node.
778     * @param type DetailAST {@link TokenTypes#TYPE TYPE} node.
779     * @return canonical type's name
780     */
781    private static String getCanonicalName(DetailAST type) {
782        final StringBuilder canonicalNameBuilder = new StringBuilder(256);
783        DetailAST toVisit = type.getFirstChild();
784        while (toVisit != null) {
785            toVisit = getNextSubTreeNode(toVisit, type);
786            if (toVisit != null && toVisit.getType() == TokenTypes.IDENT) {
787                canonicalNameBuilder.append(toVisit.getText());
788                final DetailAST nextSubTreeNode = getNextSubTreeNode(toVisit, type);
789                if (nextSubTreeNode != null) {
790                    if (nextSubTreeNode.getType() == TokenTypes.TYPE_ARGUMENTS) {
791                        break;
792                    }
793                    canonicalNameBuilder.append('.');
794                }
795            }
796        }
797        return canonicalNameBuilder.toString();
798    }
799
800    /**
801     * Gets the next node of a syntactical tree (child of a current node or
802     * sibling of a current node, or sibling of a parent of a current node).
803     * @param currentNodeAst Current node in considering
804     * @param subTreeRootAst SubTree root
805     * @return Current node after bypassing, if current node reached the root of a subtree
806     *        method returns null
807     */
808    private static DetailAST
809        getNextSubTreeNode(DetailAST currentNodeAst, DetailAST subTreeRootAst) {
810        DetailAST currentNode = currentNodeAst;
811        DetailAST toVisitAst = currentNode.getFirstChild();
812        while (toVisitAst == null) {
813            toVisitAst = currentNode.getNextSibling();
814            if (toVisitAst == null) {
815                if (currentNode.getParent().equals(subTreeRootAst)
816                         && currentNode.getParent().getColumnNo() == subTreeRootAst.getColumnNo()) {
817                    break;
818                }
819                currentNode = currentNode.getParent();
820            }
821        }
822        return toVisitAst;
823    }
824
825    /**
826     * Gets the list with short names classes.
827     * These names are taken from array of classes canonical names.
828     * @param canonicalClassNames canonical class names.
829     * @return the list of short names of classes.
830     */
831    private static List<String> getClassShortNames(List<String> canonicalClassNames) {
832        final List<String> shortNames = new ArrayList<>();
833        for (String canonicalClassName : canonicalClassNames) {
834            final String shortClassName = canonicalClassName
835                    .substring(canonicalClassName.lastIndexOf('.') + 1,
836                    canonicalClassName.length());
837            shortNames.add(shortClassName);
838        }
839        return shortNames;
840    }
841
842    /**
843     * Gets the short class name from given canonical name.
844     * @param canonicalClassName canonical class name.
845     * @return short name of class.
846     */
847    private static String getClassShortName(String canonicalClassName) {
848        return canonicalClassName
849                .substring(canonicalClassName.lastIndexOf('.') + 1,
850                canonicalClassName.length());
851    }
852
853    /**
854     * Checks whether the AST is annotated with
855     * an annotation containing the passed in regular
856     * expression and return the AST representing that
857     * annotation.
858     *
859     * <p>
860     * This method will not look for imports or package
861     * statements to detect the passed in annotation.
862     * </p>
863     *
864     * <p>
865     * To check if an AST contains a passed in annotation
866     * taking into account fully-qualified names
867     * (ex: java.lang.Override, Override)
868     * this method will need to be called twice. Once for each
869     * name given.
870     * </p>
871     *
872     * @param variableDef {@link TokenTypes#VARIABLE_DEF variable def node}.
873     * @return the AST representing the first such annotation or null if
874     *         no such annotation was found
875     */
876    private DetailAST findMatchingAnnotation(DetailAST variableDef) {
877        DetailAST matchingAnnotation = null;
878
879        final DetailAST holder = AnnotationUtility.getAnnotationHolder(variableDef);
880
881        for (DetailAST child = holder.getFirstChild();
882            child != null; child = child.getNextSibling()) {
883            if (child.getType() == TokenTypes.ANNOTATION) {
884                final DetailAST ast = child.getFirstChild();
885                final String name =
886                    FullIdent.createFullIdent(ast.getNextSibling()).getText();
887                if (ignoreAnnotationCanonicalNames.contains(name)
888                         || ignoreAnnotationShortNames.contains(name)) {
889                    matchingAnnotation = child;
890                    break;
891                }
892            }
893        }
894
895        return matchingAnnotation;
896    }
897
898}