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