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