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