001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018///////////////////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.design;
021
022import java.util.ArrayList;
023import java.util.Collection;
024import java.util.HashSet;
025import java.util.List;
026import java.util.Set;
027import java.util.regex.Pattern;
028import java.util.stream.Collectors;
029
030import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
031import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
032import com.puppycrawl.tools.checkstyle.api.DetailAST;
033import com.puppycrawl.tools.checkstyle.api.FullIdent;
034import com.puppycrawl.tools.checkstyle.api.TokenTypes;
035import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil;
036import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
037import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
038
039/**
040 * <div>
041 * Checks visibility of class members. Only static final, immutable or annotated
042 * by specified annotation members may be public;
043 * other class members must be private unless the property {@code protectedAllowed}
044 * or {@code packageAllowed} is set.
045 * </div>
046 *
047 * <p>
048 * Public members are not flagged if the name matches the public
049 * member regular expression (contains {@code "^serialVersionUID$"} by
050 * default).
051 * </p>
052 *
053 * <p>
054 * Note that Checkstyle 2 used to include {@code "^f[A-Z][a-zA-Z0-9]*$"} in the default pattern
055 * to allow names used in container-managed persistence for Enterprise JavaBeans (EJB) 1.1 with
056 * the default settings. With EJB 2.0 it is no longer necessary to have public access for
057 * persistent fields, so the default has been changed.
058 * </p>
059 *
060 * <p>
061 * Rationale: Enforce encapsulation.
062 * </p>
063 *
064 * <p>
065 * Check also has options making it less strict:
066 * </p>
067 *
068 * <p>
069 * <b>ignoreAnnotationCanonicalNames</b> - the list of annotations which ignore
070 * variables in consideration. If user want to provide short annotation name that
071 * type will match to any named the same type without consideration of package.
072 * </p>
073 *
074 * <p>
075 * <b>allowPublicFinalFields</b> - which allows public final fields.
076 * </p>
077 *
078 * <p>
079 * <b>allowPublicImmutableFields</b> - which allows immutable fields to be
080 * declared as public if defined in final class.
081 * </p>
082 *
083 * <p>
084 * Field is known to be immutable if:
085 * </p>
086 * <ul>
087 * <li>It's declared as final</li>
088 * <li>Has either a primitive type or instance of class user defined to be immutable
089 * (such as String, ImmutableCollection from Guava, etc.)</li>
090 * </ul>
091 *
092 * <p>
093 * Classes known to be immutable are listed in <b>immutableClassCanonicalNames</b>
094 * by their canonical names.
095 * </p>
096 *
097 * <p>
098 * Property Rationale: Forcing all fields of class to have private modifier by default is
099 * 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
122public 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     * Creates a new {@code VisibilityModifierCheck} instance.
221     */
222    public VisibilityModifierCheck() {
223        // no code by default
224    }
225
226    /**
227     * Setter to specify annotations canonical names which ignore variables
228     * in consideration.
229     *
230     * @param annotationNames array of ignore annotations canonical names.
231     * @since 6.5
232     */
233    public void setIgnoreAnnotationCanonicalNames(String... annotationNames) {
234        ignoreAnnotationCanonicalNames = Set.of(annotationNames);
235    }
236
237    /**
238     * Setter to control whether protected members are allowed.
239     *
240     * @param protectedAllowed whether protected members are allowed
241     * @since 3.0
242     */
243    public void setProtectedAllowed(boolean protectedAllowed) {
244        this.protectedAllowed = protectedAllowed;
245    }
246
247    /**
248     * Setter to control whether package visible members are allowed.
249     *
250     * @param packageAllowed whether package visible members are allowed
251     * @since 3.0
252     */
253    public void setPackageAllowed(boolean packageAllowed) {
254        this.packageAllowed = packageAllowed;
255    }
256
257    /**
258     * Setter to specify pattern for public members that should be ignored.
259     *
260     * @param pattern
261     *        pattern for public members to ignore.
262     * @since 3.0
263     */
264    public void setPublicMemberPattern(Pattern pattern) {
265        publicMemberPattern = pattern;
266    }
267
268    /**
269     * Setter to allow immutable fields to be declared as public if defined in final class.
270     *
271     * @param allow user's value.
272     * @since 6.4
273     */
274    public void setAllowPublicImmutableFields(boolean allow) {
275        allowPublicImmutableFields = allow;
276    }
277
278    /**
279     * Setter to allow final fields to be declared as public.
280     *
281     * @param allow user's value.
282     * @since 7.0
283     */
284    public void setAllowPublicFinalFields(boolean allow) {
285        allowPublicFinalFields = allow;
286    }
287
288    /**
289     * Setter to specify immutable classes canonical names.
290     *
291     * @param classNames array of immutable types canonical names.
292     * @since 6.4.1
293     */
294    public void setImmutableClassCanonicalNames(String... classNames) {
295        immutableClassCanonicalNames = Set.of(classNames);
296    }
297
298    @Override
299    public int[] getDefaultTokens() {
300        return getRequiredTokens();
301    }
302
303    @Override
304    public int[] getAcceptableTokens() {
305        return getRequiredTokens();
306    }
307
308    @Override
309    public int[] getRequiredTokens() {
310        return new int[] {
311            TokenTypes.VARIABLE_DEF,
312            TokenTypes.IMPORT,
313        };
314    }
315
316    @Override
317    public void beginTree(DetailAST rootAst) {
318        immutableClassShortNames = getClassShortNames(immutableClassCanonicalNames);
319        ignoreAnnotationShortNames = getClassShortNames(ignoreAnnotationCanonicalNames);
320    }
321
322    @Override
323    public void visitToken(DetailAST ast) {
324        switch (ast.getType()) {
325            case TokenTypes.VARIABLE_DEF -> {
326                if (!isAnonymousClassVariable(ast)) {
327                    visitVariableDef(ast);
328                }
329            }
330            case TokenTypes.IMPORT -> visitImport(ast);
331            default -> {
332                final String exceptionMsg = "Unexpected token type: " + ast.getText();
333                throw new IllegalArgumentException(exceptionMsg);
334            }
335        }
336    }
337
338    /**
339     * Checks if current variable definition is definition of an anonymous class.
340     *
341     * @param variableDef {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF}
342     * @return true if current variable definition is definition of an anonymous class.
343     */
344    private static boolean isAnonymousClassVariable(DetailAST variableDef) {
345        return variableDef.getParent().getType() != TokenTypes.OBJBLOCK;
346    }
347
348    /**
349     * Checks access modifier of given variable.
350     * If it is not proper according to Check - puts violation on it.
351     *
352     * @param variableDef variable to check.
353     */
354    private void visitVariableDef(DetailAST variableDef) {
355        final boolean inInterfaceOrAnnotationBlock =
356                ScopeUtil.isInInterfaceOrAnnotationBlock(variableDef);
357
358        if (!inInterfaceOrAnnotationBlock && !hasIgnoreAnnotation(variableDef)) {
359            final DetailAST varNameAST = variableDef.findFirstToken(TokenTypes.TYPE)
360                .getNextSibling();
361            final String varName = varNameAST.getText();
362            if (!hasProperAccessModifier(variableDef, varName)) {
363                log(varNameAST, MSG_KEY, varName);
364            }
365        }
366    }
367
368    /**
369     * Checks if variable def has ignore annotation.
370     *
371     * @param variableDef {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF}
372     * @return true if variable def has ignore annotation.
373     */
374    private boolean hasIgnoreAnnotation(DetailAST variableDef) {
375        final DetailAST firstIgnoreAnnotation =
376                 findMatchingAnnotation(variableDef);
377        return firstIgnoreAnnotation != null;
378    }
379
380    /**
381     * Checks imported type. If type's canonical name was not specified in
382     * <b>immutableClassCanonicalNames</b>, but its short name collides with one from
383     * <b>immutableClassShortNames</b> - removes it from the last one.
384     *
385     * @param importAst {@link TokenTypes#IMPORT Import}
386     */
387    private void visitImport(DetailAST importAst) {
388        if (!isStarImport(importAst)) {
389            final String canonicalName = getCanonicalName(importAst);
390            final String shortName = getClassShortName(canonicalName);
391
392            // If imported canonical class name is not specified as allowed immutable class,
393            // but its short name collides with one of specified class - removes the short name
394            // from list to avoid names collision
395            if (!immutableClassCanonicalNames.contains(canonicalName)) {
396                immutableClassShortNames.remove(shortName);
397            }
398            if (!ignoreAnnotationCanonicalNames.contains(canonicalName)) {
399                ignoreAnnotationShortNames.remove(shortName);
400            }
401        }
402    }
403
404    /**
405     * Checks if current import is star import. E.g.:
406     *
407     * <p>
408     * {@code
409     * import java.util.*;
410     * }
411     * </p>
412     *
413     * @param importAst {@link TokenTypes#IMPORT Import}
414     * @return true if it is star import
415     */
416    private static boolean isStarImport(DetailAST importAst) {
417        boolean result = false;
418        DetailAST toVisit = importAst;
419        while (toVisit != null) {
420            toVisit = getNextSubTreeNode(toVisit, importAst);
421            if (toVisit != null && toVisit.getType() == TokenTypes.STAR) {
422                result = true;
423                break;
424            }
425        }
426        return result;
427    }
428
429    /**
430     * Checks if current variable has proper access modifier according to Check's options.
431     *
432     * @param variableDef Variable definition node.
433     * @param variableName Variable's name.
434     * @return true if variable has proper access modifier.
435     */
436    private boolean hasProperAccessModifier(DetailAST variableDef, String variableName) {
437        boolean result = true;
438
439        final String variableScope = getVisibilityScope(variableDef);
440
441        if (!PRIVATE_ACCESS_MODIFIER.equals(variableScope)) {
442            result =
443                isStaticFinalVariable(variableDef)
444                || packageAllowed && PACKAGE_ACCESS_MODIFIER.equals(variableScope)
445                || protectedAllowed && PROTECTED_ACCESS_MODIFIER.equals(variableScope)
446                || isIgnoredPublicMember(variableName, variableScope)
447                || isAllowedPublicField(variableDef);
448        }
449
450        return result;
451    }
452
453    /**
454     * Checks whether variable has static final modifiers.
455     *
456     * @param variableDef Variable definition node.
457     * @return true of variable has static final modifiers.
458     */
459    private static boolean isStaticFinalVariable(DetailAST variableDef) {
460        final Set<String> modifiers = getModifiers(variableDef);
461        return modifiers.contains(STATIC_KEYWORD)
462                && modifiers.contains(FINAL_KEYWORD);
463    }
464
465    /**
466     * Checks whether variable belongs to public members that should be ignored.
467     *
468     * @param variableName Variable's name.
469     * @param variableScope Variable's scope.
470     * @return true if variable belongs to public members that should be ignored.
471     */
472    private boolean isIgnoredPublicMember(String variableName, String variableScope) {
473        return PUBLIC_ACCESS_MODIFIER.equals(variableScope)
474            && publicMemberPattern.matcher(variableName).find();
475    }
476
477    /**
478     * Checks whether the variable satisfies the public field check.
479     *
480     * @param variableDef Variable definition node.
481     * @return true if allowed.
482     */
483    private boolean isAllowedPublicField(DetailAST variableDef) {
484        return allowPublicFinalFields && isFinalField(variableDef)
485            || allowPublicImmutableFields && isImmutableFieldDefinedInFinalClass(variableDef);
486    }
487
488    /**
489     * Checks whether immutable field is defined in final class.
490     *
491     * @param variableDef Variable definition node.
492     * @return true if immutable field is defined in final class.
493     */
494    private boolean isImmutableFieldDefinedInFinalClass(DetailAST variableDef) {
495        final DetailAST classDef = variableDef.getParent().getParent();
496        final Set<String> classModifiers = getModifiers(classDef);
497        return (classModifiers.contains(FINAL_KEYWORD) || classDef.getType() == TokenTypes.ENUM_DEF)
498                && isImmutableField(variableDef);
499    }
500
501    /**
502     * Returns the set of modifier Strings for a VARIABLE_DEF or CLASS_DEF AST.
503     *
504     * @param defAST AST for a variable or class definition.
505     * @return the set of modifier Strings for defAST.
506     */
507    private static Set<String> getModifiers(DetailAST defAST) {
508        final DetailAST modifiersAST = defAST.findFirstToken(TokenTypes.MODIFIERS);
509        final Set<String> modifiersSet = new HashSet<>();
510        if (modifiersAST != null) {
511            DetailAST modifier = modifiersAST.getFirstChild();
512            while (modifier != null) {
513                modifiersSet.add(modifier.getText());
514                modifier = modifier.getNextSibling();
515            }
516        }
517        return modifiersSet;
518    }
519
520    /**
521     * Returns the visibility scope for the variable.
522     *
523     * @param variableDef Variable definition node.
524     * @return one of "public", "private", "protected", "package"
525     */
526    private static String getVisibilityScope(DetailAST variableDef) {
527        final Set<String> modifiers = getModifiers(variableDef);
528        String accessModifier = PACKAGE_ACCESS_MODIFIER;
529        for (final String modifier : EXPLICIT_MODS) {
530            if (modifiers.contains(modifier)) {
531                accessModifier = modifier;
532                break;
533            }
534        }
535        return accessModifier;
536    }
537
538    /**
539     * Checks if current field is immutable:
540     * has final modifier and either a primitive type or instance of class
541     * known to be immutable (such as String, ImmutableCollection from Guava, etc.).
542     * Classes known to be immutable are listed in
543     * {@link VisibilityModifierCheck#immutableClassCanonicalNames}
544     *
545     * @param variableDef Field in consideration.
546     * @return true if field is immutable.
547     */
548    private boolean isImmutableField(DetailAST variableDef) {
549        boolean result = false;
550        if (isFinalField(variableDef)) {
551            final DetailAST type = variableDef.findFirstToken(TokenTypes.TYPE);
552            final boolean isCanonicalName = isCanonicalName(type);
553            final String typeName = getCanonicalName(type);
554            if (immutableClassShortNames.contains(typeName)
555                    || isCanonicalName && immutableClassCanonicalNames.contains(typeName)) {
556                final DetailAST typeArgs = getGenericTypeArgs(type, isCanonicalName);
557
558                if (typeArgs == null) {
559                    result = true;
560                }
561                else {
562                    final List<String> argsClassNames = getTypeArgsClassNames(typeArgs);
563                    result = areImmutableTypeArguments(argsClassNames);
564                }
565            }
566            else {
567                result = !isCanonicalName && isPrimitive(type);
568            }
569        }
570        return result;
571    }
572
573    /**
574     * Checks whether type definition is in canonical form.
575     *
576     * @param type type definition token.
577     * @return true if type definition is in canonical form.
578     */
579    private static boolean isCanonicalName(DetailAST type) {
580        return type.getFirstChild().getType() == TokenTypes.DOT;
581    }
582
583    /**
584     * Returns generic type arguments token.
585     *
586     * @param type type token.
587     * @param isCanonicalName whether type name is in canonical form.
588     * @return generic type arguments token.
589     */
590    private static DetailAST getGenericTypeArgs(DetailAST type, boolean isCanonicalName) {
591        final DetailAST typeArgs;
592        if (isCanonicalName) {
593            // if type class name is in canonical form, abstract tree has specific structure
594            typeArgs = type.getFirstChild().findFirstToken(TokenTypes.TYPE_ARGUMENTS);
595        }
596        else {
597            typeArgs = type.findFirstToken(TokenTypes.TYPE_ARGUMENTS);
598        }
599        return typeArgs;
600    }
601
602    /**
603     * Returns a list of type parameters class names.
604     *
605     * @param typeArgs type arguments token.
606     * @return a list of type parameters class names.
607     */
608    private static List<String> getTypeArgsClassNames(DetailAST typeArgs) {
609        final List<String> typeClassNames = new ArrayList<>();
610        DetailAST type = typeArgs.findFirstToken(TokenTypes.TYPE_ARGUMENT);
611        DetailAST sibling;
612        do {
613            final String typeName = getCanonicalName(type);
614            typeClassNames.add(typeName);
615            sibling = type.getNextSibling();
616            type = sibling.getNextSibling();
617        } while (sibling.getType() == TokenTypes.COMMA);
618        return typeClassNames;
619    }
620
621    /**
622     * Checks whether all generic type arguments are immutable.
623     * If at least one argument is mutable, we assume that the whole list of type arguments
624     * is mutable.
625     *
626     * @param typeArgsClassNames type arguments class names.
627     * @return true if all generic type arguments are immutable.
628     */
629    private boolean areImmutableTypeArguments(Collection<String> typeArgsClassNames) {
630        return typeArgsClassNames.stream().noneMatch(
631            typeName -> {
632                return !immutableClassShortNames.contains(typeName)
633                    && !immutableClassCanonicalNames.contains(typeName);
634            });
635    }
636
637    /**
638     * Checks whether current field is final.
639     *
640     * @param variableDef field in consideration.
641     * @return true if current field is final.
642     */
643    private static boolean isFinalField(DetailAST variableDef) {
644        final DetailAST modifiers = variableDef.findFirstToken(TokenTypes.MODIFIERS);
645        return modifiers.findFirstToken(TokenTypes.FINAL) != null;
646    }
647
648    /**
649     * Checks if current type is primitive type (int, short, float, boolean, double, etc.).
650     * As primitive types have special tokens for each one, such as:
651     * LITERAL_INT, LITERAL_BOOLEAN, etc.
652     * So, if type's identifier differs from {@link TokenTypes#IDENT IDENT} token - it's a
653     * primitive type.
654     *
655     * @param type Ast {@link TokenTypes#TYPE TYPE} node.
656     * @return true if current type is primitive type.
657     */
658    private static boolean isPrimitive(DetailAST type) {
659        return type.getFirstChild().getType() != TokenTypes.IDENT;
660    }
661
662    /**
663     * Gets canonical type's name from given {@link TokenTypes#TYPE TYPE} node.
664     *
665     * @param type DetailAST {@link TokenTypes#TYPE TYPE} node.
666     * @return canonical type's name
667     */
668    private static String getCanonicalName(DetailAST type) {
669        final StringBuilder canonicalNameBuilder = new StringBuilder(256);
670        DetailAST toVisit = type;
671        while (toVisit != null) {
672            toVisit = getNextSubTreeNode(toVisit, type);
673            if (toVisit != null && toVisit.getType() == TokenTypes.IDENT) {
674                if (!canonicalNameBuilder.isEmpty()) {
675                    canonicalNameBuilder.append('.');
676                }
677                canonicalNameBuilder.append(toVisit.getText());
678                final DetailAST nextSubTreeNode = getNextSubTreeNode(toVisit, type);
679                if (nextSubTreeNode != null
680                        && nextSubTreeNode.getType() == TokenTypes.TYPE_ARGUMENTS) {
681                    break;
682                }
683            }
684        }
685        return canonicalNameBuilder.toString();
686    }
687
688    /**
689     * Gets the next node of a syntactical tree (child of a current node or
690     * sibling of a current node, or sibling of a parent of a current node).
691     *
692     * @param currentNodeAst Current node in considering
693     * @param subTreeRootAst SubTree root
694     * @return Current node after bypassing, if current node reached the root of a subtree
695     *        method returns null
696     */
697    private static DetailAST
698        getNextSubTreeNode(DetailAST currentNodeAst, DetailAST subTreeRootAst) {
699        DetailAST currentNode = currentNodeAst;
700        DetailAST toVisitAst = currentNode.getFirstChild();
701        while (toVisitAst == null) {
702            toVisitAst = currentNode.getNextSibling();
703            if (currentNode.getParent().getColumnNo() == subTreeRootAst.getColumnNo()) {
704                break;
705            }
706            currentNode = currentNode.getParent();
707        }
708        return toVisitAst;
709    }
710
711    /**
712     * Converts canonical class names to short names.
713     *
714     * @param canonicalClassNames the set of canonical class names.
715     * @return the set of short names of classes.
716     */
717    private static Set<String> getClassShortNames(Set<String> canonicalClassNames) {
718        return canonicalClassNames.stream()
719            .map(CommonUtil::baseClassName)
720            .collect(Collectors.toCollection(HashSet::new));
721    }
722
723    /**
724     * Gets the short class name from given canonical name.
725     *
726     * @param canonicalClassName canonical class name.
727     * @return short name of class.
728     */
729    private static String getClassShortName(String canonicalClassName) {
730        return canonicalClassName
731                .substring(canonicalClassName.lastIndexOf('.') + 1);
732    }
733
734    /**
735     * Checks whether the AST is annotated with
736     * an annotation containing the passed in regular
737     * expression and return the AST representing that
738     * annotation.
739     *
740     * <p>
741     * This method will not look for imports or package
742     * statements to detect the passed in annotation.
743     * </p>
744     *
745     * <p>
746     * To check if an AST contains a passed in annotation
747     * taking into account fully-qualified names
748     * (ex: java.lang.Override, Override)
749     * this method will need to be called twice. Once for each
750     * name given.
751     * </p>
752     *
753     * @param variableDef {@link TokenTypes#VARIABLE_DEF variable def node}.
754     * @return the AST representing the first such annotation or null if
755     *         no such annotation was found
756     */
757    private DetailAST findMatchingAnnotation(DetailAST variableDef) {
758        DetailAST matchingAnnotation = null;
759
760        final DetailAST holder = AnnotationUtil.getAnnotationHolder(variableDef);
761
762        for (DetailAST child = holder.getFirstChild();
763            child != null; child = child.getNextSibling()) {
764            if (child.getType() == TokenTypes.ANNOTATION) {
765                final DetailAST ast = child.getFirstChild();
766                final String name =
767                    FullIdent.createFullIdent(ast.getNextSibling()).getText();
768                if (ignoreAnnotationCanonicalNames.contains(name)
769                         || ignoreAnnotationShortNames.contains(name)) {
770                    matchingAnnotation = child;
771                    break;
772                }
773            }
774        }
775
776        return matchingAnnotation;
777    }
778
779}