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