001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2018 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.AnnotationUtility;
038import com.puppycrawl.tools.checkstyle.utils.ScopeUtils;
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                ScopeUtils.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.getLineNo(), varNameAST.getColumnNo(),
471                        MSG_KEY, varName);
472            }
473        }
474    }
475
476    /**
477     * Checks if variable def has ignore annotation.
478     * @param variableDef {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF}
479     * @return true if variable def has ignore annotation.
480     */
481    private boolean hasIgnoreAnnotation(DetailAST variableDef) {
482        final DetailAST firstIgnoreAnnotation =
483                 findMatchingAnnotation(variableDef);
484        return firstIgnoreAnnotation != null;
485    }
486
487    /**
488     * Checks imported type. If type's canonical name was not specified in
489     * <b>immutableClassCanonicalNames</b>, but it's short name collides with one from
490     * <b>immutableClassShortNames</b> - removes it from the last one.
491     * @param importAst {@link TokenTypes#IMPORT Import}
492     */
493    private void visitImport(DetailAST importAst) {
494        if (!isStarImport(importAst)) {
495            final DetailAST type = importAst.getFirstChild();
496            final String canonicalName = getCanonicalName(type);
497            final String shortName = getClassShortName(canonicalName);
498
499            // If imported canonical class name is not specified as allowed immutable class,
500            // but its short name collides with one of specified class - removes the short name
501            // from list to avoid names collision
502            if (!immutableClassCanonicalNames.contains(canonicalName)
503                     && immutableClassShortNames.contains(shortName)) {
504                immutableClassShortNames.remove(shortName);
505            }
506            if (!ignoreAnnotationCanonicalNames.contains(canonicalName)
507                     && ignoreAnnotationShortNames.contains(shortName)) {
508                ignoreAnnotationShortNames.remove(shortName);
509            }
510        }
511    }
512
513    /**
514     * Checks if current import is star import. E.g.:
515     * <p>
516     * {@code
517     * import java.util.*;
518     * }
519     * </p>
520     * @param importAst {@link TokenTypes#IMPORT Import}
521     * @return true if it is star import
522     */
523    private static boolean isStarImport(DetailAST importAst) {
524        boolean result = false;
525        DetailAST toVisit = importAst;
526        while (toVisit != null) {
527            toVisit = getNextSubTreeNode(toVisit, importAst);
528            if (toVisit != null && toVisit.getType() == TokenTypes.STAR) {
529                result = true;
530                break;
531            }
532        }
533        return result;
534    }
535
536    /**
537     * Checks if current variable has proper access modifier according to Check's options.
538     * @param variableDef Variable definition node.
539     * @param variableName Variable's name.
540     * @return true if variable has proper access modifier.
541     */
542    private boolean hasProperAccessModifier(DetailAST variableDef, String variableName) {
543        boolean result = true;
544
545        final String variableScope = getVisibilityScope(variableDef);
546
547        if (!PRIVATE_ACCESS_MODIFIER.equals(variableScope)) {
548            result =
549                isStaticFinalVariable(variableDef)
550                || packageAllowed && PACKAGE_ACCESS_MODIFIER.equals(variableScope)
551                || protectedAllowed && PROTECTED_ACCESS_MODIFIER.equals(variableScope)
552                || isIgnoredPublicMember(variableName, variableScope)
553                || isAllowedPublicField(variableDef);
554        }
555
556        return result;
557    }
558
559    /**
560     * Checks whether variable has static final modifiers.
561     * @param variableDef Variable definition node.
562     * @return true of variable has static final modifiers.
563     */
564    private static boolean isStaticFinalVariable(DetailAST variableDef) {
565        final Set<String> modifiers = getModifiers(variableDef);
566        return modifiers.contains(STATIC_KEYWORD)
567                && modifiers.contains(FINAL_KEYWORD);
568    }
569
570    /**
571     * Checks whether variable belongs to public members that should be ignored.
572     * @param variableName Variable's name.
573     * @param variableScope Variable's scope.
574     * @return true if variable belongs to public members that should be ignored.
575     */
576    private boolean isIgnoredPublicMember(String variableName, String variableScope) {
577        return PUBLIC_ACCESS_MODIFIER.equals(variableScope)
578            && publicMemberPattern.matcher(variableName).find();
579    }
580
581    /**
582     * Checks whether the variable satisfies the public field check.
583     * @param variableDef Variable definition node.
584     * @return true if allowed.
585     */
586    private boolean isAllowedPublicField(DetailAST variableDef) {
587        return allowPublicFinalFields && isFinalField(variableDef)
588            || allowPublicImmutableFields && isImmutableFieldDefinedInFinalClass(variableDef);
589    }
590
591    /**
592     * Checks whether immutable field is defined in final class.
593     * @param variableDef Variable definition node.
594     * @return true if immutable field is defined in final class.
595     */
596    private boolean isImmutableFieldDefinedInFinalClass(DetailAST variableDef) {
597        final DetailAST classDef = variableDef.getParent().getParent();
598        final Set<String> classModifiers = getModifiers(classDef);
599        return (classModifiers.contains(FINAL_KEYWORD) || classDef.getType() == TokenTypes.ENUM_DEF)
600                && isImmutableField(variableDef);
601    }
602
603    /**
604     * Returns the set of modifier Strings for a VARIABLE_DEF or CLASS_DEF AST.
605     * @param defAST AST for a variable or class definition.
606     * @return the set of modifier Strings for defAST.
607     */
608    private static Set<String> getModifiers(DetailAST defAST) {
609        final AST modifiersAST = defAST.findFirstToken(TokenTypes.MODIFIERS);
610        final Set<String> modifiersSet = new HashSet<>();
611        if (modifiersAST != null) {
612            AST modifier = modifiersAST.getFirstChild();
613            while (modifier != null) {
614                modifiersSet.add(modifier.getText());
615                modifier = modifier.getNextSibling();
616            }
617        }
618        return modifiersSet;
619    }
620
621    /**
622     * Returns the visibility scope for the variable.
623     * @param variableDef Variable definition node.
624     * @return one of "public", "private", "protected", "package"
625     */
626    private static String getVisibilityScope(DetailAST variableDef) {
627        final Set<String> modifiers = getModifiers(variableDef);
628        String accessModifier = PACKAGE_ACCESS_MODIFIER;
629        for (final String modifier : EXPLICIT_MODS) {
630            if (modifiers.contains(modifier)) {
631                accessModifier = modifier;
632                break;
633            }
634        }
635        return accessModifier;
636    }
637
638    /**
639     * Checks if current field is immutable:
640     * has final modifier and either a primitive type or instance of class
641     * known to be immutable (such as String, ImmutableCollection from Guava and etc).
642     * Classes known to be immutable are listed in
643     * {@link VisibilityModifierCheck#immutableClassCanonicalNames}
644     * @param variableDef Field in consideration.
645     * @return true if field is immutable.
646     */
647    private boolean isImmutableField(DetailAST variableDef) {
648        boolean result = false;
649        if (isFinalField(variableDef)) {
650            final DetailAST type = variableDef.findFirstToken(TokenTypes.TYPE);
651            final boolean isCanonicalName = isCanonicalName(type);
652            final String typeName = getTypeName(type, isCanonicalName);
653            final DetailAST typeArgs = getGenericTypeArgs(type, isCanonicalName);
654            if (typeArgs == null) {
655                result = !isCanonicalName && isPrimitive(type)
656                    || immutableClassShortNames.contains(typeName)
657                    || isCanonicalName && immutableClassCanonicalNames.contains(typeName);
658            }
659            else {
660                final List<String> argsClassNames = getTypeArgsClassNames(typeArgs);
661                result = (immutableClassShortNames.contains(typeName)
662                    || isCanonicalName && immutableClassCanonicalNames.contains(typeName))
663                    && areImmutableTypeArguments(argsClassNames);
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                canonicalNameBuilder.append(toVisit.getText());
787                final DetailAST nextSubTreeNode = getNextSubTreeNode(toVisit, type);
788                if (nextSubTreeNode != null) {
789                    if (nextSubTreeNode.getType() == TokenTypes.TYPE_ARGUMENTS) {
790                        break;
791                    }
792                    canonicalNameBuilder.append('.');
793                }
794            }
795        }
796        return canonicalNameBuilder.toString();
797    }
798
799    /**
800     * Gets the next node of a syntactical tree (child of a current node or
801     * sibling of a current node, or sibling of a parent of a current node).
802     * @param currentNodeAst Current node in considering
803     * @param subTreeRootAst SubTree root
804     * @return Current node after bypassing, if current node reached the root of a subtree
805     *        method returns null
806     */
807    private static DetailAST
808        getNextSubTreeNode(DetailAST currentNodeAst, DetailAST subTreeRootAst) {
809        DetailAST currentNode = currentNodeAst;
810        DetailAST toVisitAst = currentNode.getFirstChild();
811        while (toVisitAst == null) {
812            toVisitAst = currentNode.getNextSibling();
813            if (toVisitAst == null) {
814                if (currentNode.getParent().equals(subTreeRootAst)
815                         && currentNode.getParent().getColumnNo() == subTreeRootAst.getColumnNo()) {
816                    break;
817                }
818                currentNode = currentNode.getParent();
819            }
820        }
821        return toVisitAst;
822    }
823
824    /**
825     * Gets the list with short names classes.
826     * These names are taken from array of classes canonical names.
827     * @param canonicalClassNames canonical class names.
828     * @return the list of short names of classes.
829     */
830    private static List<String> getClassShortNames(List<String> canonicalClassNames) {
831        final List<String> shortNames = new ArrayList<>();
832        for (String canonicalClassName : canonicalClassNames) {
833            final String shortClassName = canonicalClassName
834                    .substring(canonicalClassName.lastIndexOf('.') + 1,
835                    canonicalClassName.length());
836            shortNames.add(shortClassName);
837        }
838        return shortNames;
839    }
840
841    /**
842     * Gets the short class name from given canonical name.
843     * @param canonicalClassName canonical class name.
844     * @return short name of class.
845     */
846    private static String getClassShortName(String canonicalClassName) {
847        return canonicalClassName
848                .substring(canonicalClassName.lastIndexOf('.') + 1,
849                canonicalClassName.length());
850    }
851
852    /**
853     * Checks whether the AST is annotated with
854     * an annotation containing the passed in regular
855     * expression and return the AST representing that
856     * annotation.
857     *
858     * <p>
859     * This method will not look for imports or package
860     * statements to detect the passed in annotation.
861     * </p>
862     *
863     * <p>
864     * To check if an AST contains a passed in annotation
865     * taking into account fully-qualified names
866     * (ex: java.lang.Override, Override)
867     * this method will need to be called twice. Once for each
868     * name given.
869     * </p>
870     *
871     * @param variableDef {@link TokenTypes#VARIABLE_DEF variable def node}.
872     * @return the AST representing the first such annotation or null if
873     *         no such annotation was found
874     */
875    private DetailAST findMatchingAnnotation(DetailAST variableDef) {
876        DetailAST matchingAnnotation = null;
877
878        final DetailAST holder = AnnotationUtility.getAnnotationHolder(variableDef);
879
880        for (DetailAST child = holder.getFirstChild();
881            child != null; child = child.getNextSibling()) {
882            if (child.getType() == TokenTypes.ANNOTATION) {
883                final DetailAST ast = child.getFirstChild();
884                final String name =
885                    FullIdent.createFullIdent(ast.getNextSibling()).getText();
886                if (ignoreAnnotationCanonicalNames.contains(name)
887                         || ignoreAnnotationShortNames.contains(name)) {
888                    matchingAnnotation = child;
889                    break;
890                }
891            }
892        }
893
894        return matchingAnnotation;
895    }
896
897}