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