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