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