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