View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2017 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.design;
21  
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.Collections;
25  import java.util.HashSet;
26  import java.util.List;
27  import java.util.Set;
28  import java.util.regex.Pattern;
29  import java.util.stream.Collectors;
30  
31  import antlr.collections.AST;
32  import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
33  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
34  import com.puppycrawl.tools.checkstyle.api.DetailAST;
35  import com.puppycrawl.tools.checkstyle.api.FullIdent;
36  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
37  import com.puppycrawl.tools.checkstyle.utils.AnnotationUtility;
38  import com.puppycrawl.tools.checkstyle.utils.ScopeUtils;
39  
40  /**
41   * Checks visibility of class members. Only static final, immutable or annotated
42   * by specified annotation members may be public,
43   * other class members must be private unless allowProtected/Package is set.
44   * <p>
45   * Public members are not flagged if the name matches the public
46   * member regular expression (contains "^serialVersionUID$" by
47   * default).
48   * </p>
49   * Rationale: Enforce encapsulation.
50   * <p>
51   * Check also has options making it less strict:
52   * </p>
53   * <p>
54   * <b>ignoreAnnotationCanonicalNames</b> - the list of annotations canonical names
55   * which ignore variables in consideration, if user will provide short annotation name
56   * that type will match to any named the same type without consideration of package,
57   * list by default:
58   * </p>
59   * <ul>
60   * <li>org.junit.Rule</li>
61   * <li>org.junit.ClassRule</li>
62   * <li>com.google.common.annotations.VisibleForTesting</li>
63   * </ul>
64   * <p>
65   * For example such public field will be skipped by default value of list above:
66   * </p>
67   *
68   * <pre>
69   * {@code @org.junit.Rule
70   * public TemporaryFolder publicJUnitRule = new TemporaryFolder();
71   * }
72   * </pre>
73   *
74   * <p>
75   * <b>allowPublicFinalFields</b> - which allows public final fields. Default value is <b>false</b>.
76   * </p>
77   * <p>
78   * <b>allowPublicImmutableFields</b> - which allows immutable fields to be
79   * declared as public if defined in final class. Default value is <b>false</b>
80   * </p>
81   * <p>
82   * Field is known to be immutable if:
83   * </p>
84   * <ul>
85   * <li>It's declared as final</li>
86   * <li>Has either a primitive type or instance of class user defined to be immutable
87   * (such as String, ImmutableCollection from Guava and etc)</li>
88   * </ul>
89   * <p>
90   * Classes known to be immutable are listed in <b>immutableClassCanonicalNames</b> by their
91   * <b>canonical</b> names. List by default:
92   * </p>
93   * <ul>
94   * <li>java.lang.String</li>
95   * <li>java.lang.Integer</li>
96   * <li>java.lang.Byte</li>
97   * <li>java.lang.Character</li>
98   * <li>java.lang.Short</li>
99   * <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
241 public 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 }