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