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