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