com.puppycrawl.tools.checkstyle.checks.design.VisibilityModifierCheck Maven / Gradle / Ivy
Show all versions of checkstyle Show documentation
////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2021 the original author or authors.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
////////////////////////////////////////////////////////////////////////////////
package com.puppycrawl.tools.checkstyle.checks.design;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.FullIdent;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil;
import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
/**
*
* Checks visibility of class members. Only static final, immutable or annotated
* by specified annotation members may be public;
* other class members must be private unless the property {@code protectedAllowed}
* or {@code packageAllowed} is set.
*
*
* Public members are not flagged if the name matches the public
* member regular expression (contains {@code "^serialVersionUID$"} by
* default).
*
*
* Note that Checkstyle 2 used to include {@code "^f[A-Z][a-zA-Z0-9]*$"} in the default pattern
* to allow names used in container-managed persistence for Enterprise JavaBeans (EJB) 1.1 with
* the default settings. With EJB 2.0 it is no longer necessary to have public access for
* persistent fields, so the default has been changed.
*
*
* Rationale: Enforce encapsulation.
*
*
* Check also has options making it less strict:
*
*
* ignoreAnnotationCanonicalNames- the list of annotations which ignore
* variables in consideration. If user will provide short annotation name that
* type will match to any named the same type without consideration of package.
*
*
* allowPublicFinalFields- which allows public final fields.
*
*
* allowPublicImmutableFields- which allows immutable fields to be
* declared as public if defined in final class.
*
*
* Field is known to be immutable if:
*
*
* - It's declared as final
* - Has either a primitive type or instance of class user defined to be immutable
* (such as String, ImmutableCollection from Guava and etc)
*
*
* Classes known to be immutable are listed in immutableClassCanonicalNames
* by their canonical names.
*
*
* Property Rationale: Forcing all fields of class to have private modifier by default is
* good in most cases, but in some cases it drawbacks in too much boilerplate get/set code.
* One of such cases are immutable classes.
*
*
* Restriction: Check doesn't check if class is immutable, there's no checking
* if accessory methods are missing and all fields are immutable, we only check
* if current field is immutable or final.
* Under the flag allowPublicImmutableFields, the enclosing class must
* also be final, to encourage immutability.
* Under the flag allowPublicFinalFields, the final modifier
* on the enclosing class is optional.
*
*
* Star imports are out of scope of this Check. So if one of type imported via
* star import collides with user specified one by its short name - there
* won't be Check's violation.
*
*
* -
* Property {@code packageAllowed} - Control whether package visible members are allowed.
* Type is {@code boolean}.
* Default value is {@code false}.
*
* -
* Property {@code protectedAllowed} - Control whether protected members are allowed.
* Type is {@code boolean}.
* Default value is {@code false}.
*
* -
* Property {@code publicMemberPattern} - Specify pattern for public members that should be ignored.
* Type is {@code java.util.regex.Pattern}.
* Default value is {@code "^serialVersionUID$"}.
*
* -
* Property {@code allowPublicFinalFields} - Allow final fields to be declared as public.
* Type is {@code boolean}.
* Default value is {@code false}.
*
* -
* Property {@code allowPublicImmutableFields} - Allow immutable fields to be
* declared as public if defined in final class.
* Type is {@code boolean}.
* Default value is {@code false}.
*
* -
* Property {@code immutableClassCanonicalNames} - Specify immutable classes canonical names.
* Type is {@code java.lang.String[]}.
* Default value is {@code java.io.File, java.lang.Boolean, java.lang.Byte,
* java.lang.Character, java.lang.Double, java.lang.Float, java.lang.Integer,
* java.lang.Long, java.lang.Short, java.lang.StackTraceElement, java.lang.String,
* java.math.BigDecimal, java.math.BigInteger, java.net.Inet4Address, java.net.Inet6Address,
* java.net.InetSocketAddress, java.net.URI, java.net.URL, java.util.Locale, java.util.UUID}.
*
* -
* Property {@code ignoreAnnotationCanonicalNames} - Specify the list of annotations canonical
* names which ignore variables in consideration.
* Type is {@code java.lang.String[]}.
* Default value is {@code com.google.common.annotations.VisibleForTesting,
* org.junit.ClassRule, org.junit.Rule}.
*
*
*
* To configure the check:
*
*
* <module name="VisibilityModifier"/>
*
*
* To configure the check so that it allows package visible members:
*
*
* <module name="VisibilityModifier">
* <property name="packageAllowed" value="true"/>
* </module>
*
*
* To configure the check so that it allows no public members:
*
*
* <module name="VisibilityModifier">
* <property name="publicMemberPattern" value="^$"/>
* </module>
*
*
* To configure the Check so that it allows public immutable fields (mostly for immutable classes):
*
*
* <module name="VisibilityModifier">
* <property name="allowPublicImmutableFields" value="true"/>
* </module>
*
*
* Example of allowed public immutable fields:
*
*
* public class ImmutableClass
* {
* public final ImmutableSet<String> includes; // No warning
* public final ImmutableSet<String> excludes; // No warning
* public final java.lang.String notes; // No warning
* public final BigDecimal value; // No warning
*
* public ImmutableClass(Collection<String> includes, Collection<String> excludes,
* BigDecimal value, String notes)
* {
* this.includes = ImmutableSet.copyOf(includes);
* this.excludes = ImmutableSet.copyOf(excludes);
* this.value = value;
* this.notes = notes;
* }
* }
*
*
* To configure the Check in order to allow user specified immutable class names:
*
*
* <module name="VisibilityModifier">
* <property name="allowPublicImmutableFields" value="true"/>
* <property name="immutableClassCanonicalNames" value="
* com.google.common.collect.ImmutableSet"/>
* </module>
*
*
* Example of allowed public immutable fields:
*
*
* public class ImmutableClass
* {
* public final ImmutableSet<String> includes; // No warning
* public final ImmutableSet<String> excludes; // No warning
* public final java.lang.String notes; // Warning here because
* //'java.lang.String' wasn't specified as allowed class
* public final int someValue; // No warning
*
* public ImmutableClass(Collection<String> includes, Collection<String> excludes,
* String notes, int someValue)
* {
* this.includes = ImmutableSet.copyOf(includes);
* this.excludes = ImmutableSet.copyOf(excludes);
* this.value = value;
* this.notes = notes;
* this.someValue = someValue;
* }
* }
*
*
* Note, if allowPublicImmutableFields is set to true, the check will also check
* whether generic type parameters are immutable. If at least one generic type
* parameter is mutable, there will be a violation.
*
*
* <module name="VisibilityModifier">
* <property name="allowPublicImmutableFields" value="true"/>
* <property name="immutableClassCanonicalNames"
* value="com.google.common.collect.ImmutableSet, com.google.common.collect.ImmutableMap,
* java.lang.String"/>
* </module>
*
*
* Example of how the check works:
*
*
* public final class Test {
* public final String s;
* public final ImmutableSet<String> names;
* public final ImmutableSet<Object> objects; // violation (Object class is mutable)
* public final ImmutableMap<String, Object> links; // violation (Object class is mutable)
*
* public Test() {
* s = "Hello!";
* names = ImmutableSet.of();
* objects = ImmutableSet.of();
* links = ImmutableMap.of();
* }
* }
*
*
* To configure the Check passing fields annotated with @com.annotation.CustomAnnotation:
*
*
* <module name="VisibilityModifier">
* <property name="ignoreAnnotationCanonicalNames" value=
* "com.annotation.CustomAnnotation"/>
* </module>
*
*
* Example of allowed field:
*
*
* class SomeClass
* {
* @com.annotation.CustomAnnotation
* String annotatedString; // no warning
* @CustomAnnotation
* String shortCustomAnnotated; // no warning
* }
*
*
* To configure the Check passing fields annotated with @org.junit.Rule,
* @org.junit.ClassRule and @com.google.common.annotations.VisibleForTesting annotations:
*
*
* <module name="VisibilityModifier"/>
*
*
* Example of allowed fields:
*
*
* class SomeClass
* {
* @org.junit.Rule
* public TemporaryFolder publicJUnitRule = new TemporaryFolder(); // no warning
* @org.junit.ClassRule
* public static TemporaryFolder publicJUnitClassRule = new TemporaryFolder(); // no warning
* @com.google.common.annotations.VisibleForTesting
* public String testString = ""; // no warning
* }
*
*
* To configure the Check passing fields annotated with short annotation name:
*
*
* <module name="VisibilityModifier">
* <property name="ignoreAnnotationCanonicalNames"
* value="CustomAnnotation"/>
* </module>
*
*
* Example of allowed fields:
*
*
* class SomeClass
* {
* @CustomAnnotation
* String customAnnotated; // no warning
* @com.annotation.CustomAnnotation
* String customAnnotated1; // no warning
* @mypackage.annotation.CustomAnnotation
* String customAnnotatedAnotherPackage; // another package but short name matches
* // so no violation
* }
*
*
* To understand the difference between allowPublicImmutableFields and allowPublicFinalFields
* options, please, study the following examples.
*
*
* 1) To configure the check to use only 'allowPublicImmutableFields' option:
*
*
* <module name="VisibilityModifier">
* <property name="allowPublicImmutableFields" value="true"/>
* </module>
*
*
* Code example:
*
*
* public class InputPublicImmutable {
* public final int someIntValue; // violation
* public final ImmutableSet<String> includes; // violation
* public final java.lang.String notes; // violation
* public final BigDecimal value; // violation
* public final List list; // violation
*
* public InputPublicImmutable(Collection<String> includes,
* BigDecimal value, String notes, int someValue, List l) {
* this.includes = ImmutableSet.copyOf(includes);
* this.value = value;
* this.notes = notes;
* this.someIntValue = someValue;
* this.list = l;
* }
* }
*
*
* 2) To configure the check to use only 'allowPublicFinalFields' option:
*
*
* <module name="VisibilityModifier">
* <property name="allowPublicFinalFields" value="true"/>
* </module>
*
*
* Code example:
*
*
* public class InputPublicImmutable {
* public final int someIntValue;
* public final ImmutableSet<String> includes;
* public final java.lang.String notes;
* public final BigDecimal value;
* public final List list;
*
* public InputPublicImmutable(Collection<String> includes,
* BigDecimal value, String notes, int someValue, List l) {
* this.includes = ImmutableSet.copyOf(includes);
* this.value = value;
* this.notes = notes;
* this.someIntValue = someValue;
* this.list = l;
* }
* }
*
*
* Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
*
*
* Violation Message Keys:
*
*
* -
* {@code variable.notPrivate}
*
*
*
* @since 3.0
*/
@FileStatefulCheck
public class VisibilityModifierCheck
extends AbstractCheck {
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_KEY = "variable.notPrivate";
/** Default immutable types canonical names. */
private static final List DEFAULT_IMMUTABLE_TYPES = Collections.unmodifiableList(
Arrays.stream(new String[] {
"java.lang.String",
"java.lang.Integer",
"java.lang.Byte",
"java.lang.Character",
"java.lang.Short",
"java.lang.Boolean",
"java.lang.Long",
"java.lang.Double",
"java.lang.Float",
"java.lang.StackTraceElement",
"java.math.BigInteger",
"java.math.BigDecimal",
"java.io.File",
"java.util.Locale",
"java.util.UUID",
"java.net.URL",
"java.net.URI",
"java.net.Inet4Address",
"java.net.Inet6Address",
"java.net.InetSocketAddress",
}).collect(Collectors.toList()));
/** Default ignore annotations canonical names. */
private static final List DEFAULT_IGNORE_ANNOTATIONS = Collections.unmodifiableList(
Arrays.stream(new String[] {
"org.junit.Rule",
"org.junit.ClassRule",
"com.google.common.annotations.VisibleForTesting",
}).collect(Collectors.toList()));
/** Name for 'public' access modifier. */
private static final String PUBLIC_ACCESS_MODIFIER = "public";
/** Name for 'private' access modifier. */
private static final String PRIVATE_ACCESS_MODIFIER = "private";
/** Name for 'protected' access modifier. */
private static final String PROTECTED_ACCESS_MODIFIER = "protected";
/** Name for implicit 'package' access modifier. */
private static final String PACKAGE_ACCESS_MODIFIER = "package";
/** Name for 'static' keyword. */
private static final String STATIC_KEYWORD = "static";
/** Name for 'final' keyword. */
private static final String FINAL_KEYWORD = "final";
/** Contains explicit access modifiers. */
private static final String[] EXPLICIT_MODS = {
PUBLIC_ACCESS_MODIFIER,
PRIVATE_ACCESS_MODIFIER,
PROTECTED_ACCESS_MODIFIER,
};
/**
* Specify pattern for public members that should be ignored.
*/
private Pattern publicMemberPattern = Pattern.compile("^serialVersionUID$");
/** List of ignore annotations short names. */
private final List ignoreAnnotationShortNames =
getClassShortNames(DEFAULT_IGNORE_ANNOTATIONS);
/** List of immutable classes short names. */
private final List immutableClassShortNames =
getClassShortNames(DEFAULT_IMMUTABLE_TYPES);
/**
* Specify the list of annotations canonical names which ignore variables in
* consideration.
*/
private List ignoreAnnotationCanonicalNames =
new ArrayList<>(DEFAULT_IGNORE_ANNOTATIONS);
/** Control whether protected members are allowed. */
private boolean protectedAllowed;
/** Control whether package visible members are allowed. */
private boolean packageAllowed;
/** Allow immutable fields to be declared as public if defined in final class. */
private boolean allowPublicImmutableFields;
/** Allow final fields to be declared as public. */
private boolean allowPublicFinalFields;
/** Specify immutable classes canonical names. */
private List immutableClassCanonicalNames = new ArrayList<>(DEFAULT_IMMUTABLE_TYPES);
/**
* Setter to specify the list of annotations canonical names which ignore variables
* in consideration.
*
* @param annotationNames array of ignore annotations canonical names.
*/
public void setIgnoreAnnotationCanonicalNames(String... annotationNames) {
ignoreAnnotationCanonicalNames = Arrays.asList(annotationNames);
}
/**
* Setter to control whether protected members are allowed.
*
* @param protectedAllowed whether protected members are allowed
*/
public void setProtectedAllowed(boolean protectedAllowed) {
this.protectedAllowed = protectedAllowed;
}
/**
* Setter to control whether package visible members are allowed.
*
* @param packageAllowed whether package visible members are allowed
*/
public void setPackageAllowed(boolean packageAllowed) {
this.packageAllowed = packageAllowed;
}
/**
* Setter to specify pattern for public members that should be ignored.
*
* @param pattern
* pattern for public members to ignore.
*/
public void setPublicMemberPattern(Pattern pattern) {
publicMemberPattern = pattern;
}
/**
* Setter to allow immutable fields to be declared as public if defined in final class.
*
* @param allow user's value.
*/
public void setAllowPublicImmutableFields(boolean allow) {
allowPublicImmutableFields = allow;
}
/**
* Setter to allow final fields to be declared as public.
*
* @param allow user's value.
*/
public void setAllowPublicFinalFields(boolean allow) {
allowPublicFinalFields = allow;
}
/**
* Setter to specify immutable classes canonical names.
*
* @param classNames array of immutable types canonical names.
*/
public void setImmutableClassCanonicalNames(String... classNames) {
immutableClassCanonicalNames = Arrays.asList(classNames);
}
@Override
public int[] getDefaultTokens() {
return getRequiredTokens();
}
@Override
public int[] getAcceptableTokens() {
return getRequiredTokens();
}
@Override
public int[] getRequiredTokens() {
return new int[] {
TokenTypes.VARIABLE_DEF,
TokenTypes.IMPORT,
};
}
@Override
public void beginTree(DetailAST rootAst) {
immutableClassShortNames.clear();
final List classShortNames =
getClassShortNames(immutableClassCanonicalNames);
immutableClassShortNames.addAll(classShortNames);
ignoreAnnotationShortNames.clear();
final List annotationShortNames =
getClassShortNames(ignoreAnnotationCanonicalNames);
ignoreAnnotationShortNames.addAll(annotationShortNames);
}
@Override
public void visitToken(DetailAST ast) {
switch (ast.getType()) {
case TokenTypes.VARIABLE_DEF:
if (!isAnonymousClassVariable(ast)) {
visitVariableDef(ast);
}
break;
case TokenTypes.IMPORT:
visitImport(ast);
break;
default:
final String exceptionMsg = "Unexpected token type: " + ast.getText();
throw new IllegalArgumentException(exceptionMsg);
}
}
/**
* Checks if current variable definition is definition of an anonymous class.
*
* @param variableDef {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF}
* @return true if current variable definition is definition of an anonymous class.
*/
private static boolean isAnonymousClassVariable(DetailAST variableDef) {
return variableDef.getParent().getType() != TokenTypes.OBJBLOCK;
}
/**
* Checks access modifier of given variable.
* If it is not proper according to Check - puts violation on it.
*
* @param variableDef variable to check.
*/
private void visitVariableDef(DetailAST variableDef) {
final boolean inInterfaceOrAnnotationBlock =
ScopeUtil.isInInterfaceOrAnnotationBlock(variableDef);
if (!inInterfaceOrAnnotationBlock && !hasIgnoreAnnotation(variableDef)) {
final DetailAST varNameAST = variableDef.findFirstToken(TokenTypes.TYPE)
.getNextSibling();
final String varName = varNameAST.getText();
if (!hasProperAccessModifier(variableDef, varName)) {
log(varNameAST, MSG_KEY, varName);
}
}
}
/**
* Checks if variable def has ignore annotation.
*
* @param variableDef {@link TokenTypes#VARIABLE_DEF VARIABLE_DEF}
* @return true if variable def has ignore annotation.
*/
private boolean hasIgnoreAnnotation(DetailAST variableDef) {
final DetailAST firstIgnoreAnnotation =
findMatchingAnnotation(variableDef);
return firstIgnoreAnnotation != null;
}
/**
* Checks imported type. If type's canonical name was not specified in
* immutableClassCanonicalNames, but it's short name collides with one from
* immutableClassShortNames - removes it from the last one.
*
* @param importAst {@link TokenTypes#IMPORT Import}
*/
private void visitImport(DetailAST importAst) {
if (!isStarImport(importAst)) {
final DetailAST type = importAst.getFirstChild();
final String canonicalName = getCanonicalName(type);
final String shortName = getClassShortName(canonicalName);
// If imported canonical class name is not specified as allowed immutable class,
// but its short name collides with one of specified class - removes the short name
// from list to avoid names collision
if (!immutableClassCanonicalNames.contains(canonicalName)) {
immutableClassShortNames.remove(shortName);
}
if (!ignoreAnnotationCanonicalNames.contains(canonicalName)) {
ignoreAnnotationShortNames.remove(shortName);
}
}
}
/**
* Checks if current import is star import. E.g.:
*
* {@code
* import java.util.*;
* }
*
*
* @param importAst {@link TokenTypes#IMPORT Import}
* @return true if it is star import
*/
private static boolean isStarImport(DetailAST importAst) {
boolean result = false;
DetailAST toVisit = importAst;
while (toVisit != null) {
toVisit = getNextSubTreeNode(toVisit, importAst);
if (toVisit != null && toVisit.getType() == TokenTypes.STAR) {
result = true;
break;
}
}
return result;
}
/**
* Checks if current variable has proper access modifier according to Check's options.
*
* @param variableDef Variable definition node.
* @param variableName Variable's name.
* @return true if variable has proper access modifier.
*/
private boolean hasProperAccessModifier(DetailAST variableDef, String variableName) {
boolean result = true;
final String variableScope = getVisibilityScope(variableDef);
if (!PRIVATE_ACCESS_MODIFIER.equals(variableScope)) {
result =
isStaticFinalVariable(variableDef)
|| packageAllowed && PACKAGE_ACCESS_MODIFIER.equals(variableScope)
|| protectedAllowed && PROTECTED_ACCESS_MODIFIER.equals(variableScope)
|| isIgnoredPublicMember(variableName, variableScope)
|| isAllowedPublicField(variableDef);
}
return result;
}
/**
* Checks whether variable has static final modifiers.
*
* @param variableDef Variable definition node.
* @return true of variable has static final modifiers.
*/
private static boolean isStaticFinalVariable(DetailAST variableDef) {
final Set modifiers = getModifiers(variableDef);
return modifiers.contains(STATIC_KEYWORD)
&& modifiers.contains(FINAL_KEYWORD);
}
/**
* Checks whether variable belongs to public members that should be ignored.
*
* @param variableName Variable's name.
* @param variableScope Variable's scope.
* @return true if variable belongs to public members that should be ignored.
*/
private boolean isIgnoredPublicMember(String variableName, String variableScope) {
return PUBLIC_ACCESS_MODIFIER.equals(variableScope)
&& publicMemberPattern.matcher(variableName).find();
}
/**
* Checks whether the variable satisfies the public field check.
*
* @param variableDef Variable definition node.
* @return true if allowed.
*/
private boolean isAllowedPublicField(DetailAST variableDef) {
return allowPublicFinalFields && isFinalField(variableDef)
|| allowPublicImmutableFields && isImmutableFieldDefinedInFinalClass(variableDef);
}
/**
* Checks whether immutable field is defined in final class.
*
* @param variableDef Variable definition node.
* @return true if immutable field is defined in final class.
*/
private boolean isImmutableFieldDefinedInFinalClass(DetailAST variableDef) {
final DetailAST classDef = variableDef.getParent().getParent();
final Set classModifiers = getModifiers(classDef);
return (classModifiers.contains(FINAL_KEYWORD) || classDef.getType() == TokenTypes.ENUM_DEF)
&& isImmutableField(variableDef);
}
/**
* Returns the set of modifier Strings for a VARIABLE_DEF or CLASS_DEF AST.
*
* @param defAST AST for a variable or class definition.
* @return the set of modifier Strings for defAST.
*/
private static Set getModifiers(DetailAST defAST) {
final DetailAST modifiersAST = defAST.findFirstToken(TokenTypes.MODIFIERS);
final Set modifiersSet = new HashSet<>();
if (modifiersAST != null) {
DetailAST modifier = modifiersAST.getFirstChild();
while (modifier != null) {
modifiersSet.add(modifier.getText());
modifier = modifier.getNextSibling();
}
}
return modifiersSet;
}
/**
* Returns the visibility scope for the variable.
*
* @param variableDef Variable definition node.
* @return one of "public", "private", "protected", "package"
*/
private static String getVisibilityScope(DetailAST variableDef) {
final Set modifiers = getModifiers(variableDef);
String accessModifier = PACKAGE_ACCESS_MODIFIER;
for (final String modifier : EXPLICIT_MODS) {
if (modifiers.contains(modifier)) {
accessModifier = modifier;
break;
}
}
return accessModifier;
}
/**
* Checks if current field is immutable:
* has final modifier and either a primitive type or instance of class
* known to be immutable (such as String, ImmutableCollection from Guava and etc).
* Classes known to be immutable are listed in
* {@link VisibilityModifierCheck#immutableClassCanonicalNames}
*
* @param variableDef Field in consideration.
* @return true if field is immutable.
*/
private boolean isImmutableField(DetailAST variableDef) {
boolean result = false;
if (isFinalField(variableDef)) {
final DetailAST type = variableDef.findFirstToken(TokenTypes.TYPE);
final boolean isCanonicalName = isCanonicalName(type);
final String typeName = getTypeName(type, isCanonicalName);
if (immutableClassShortNames.contains(typeName)
|| isCanonicalName && immutableClassCanonicalNames.contains(typeName)) {
final DetailAST typeArgs = getGenericTypeArgs(type, isCanonicalName);
if (typeArgs == null) {
result = true;
}
else {
final List argsClassNames = getTypeArgsClassNames(typeArgs);
result = areImmutableTypeArguments(argsClassNames);
}
}
else {
result = !isCanonicalName && isPrimitive(type);
}
}
return result;
}
/**
* Checks whether type definition is in canonical form.
*
* @param type type definition token.
* @return true if type definition is in canonical form.
*/
private static boolean isCanonicalName(DetailAST type) {
return type.getFirstChild().getType() == TokenTypes.DOT;
}
/**
* Returns generic type arguments token.
*
* @param type type token.
* @param isCanonicalName whether type name is in canonical form.
* @return generic type arguments token.
*/
private static DetailAST getGenericTypeArgs(DetailAST type, boolean isCanonicalName) {
final DetailAST typeArgs;
if (isCanonicalName) {
// if type class name is in canonical form, abstract tree has specific structure
typeArgs = type.getFirstChild().findFirstToken(TokenTypes.TYPE_ARGUMENTS);
}
else {
typeArgs = type.findFirstToken(TokenTypes.TYPE_ARGUMENTS);
}
return typeArgs;
}
/**
* Returns a list of type parameters class names.
*
* @param typeArgs type arguments token.
* @return a list of type parameters class names.
*/
private static List getTypeArgsClassNames(DetailAST typeArgs) {
final List typeClassNames = new ArrayList<>();
DetailAST type = typeArgs.findFirstToken(TokenTypes.TYPE_ARGUMENT);
boolean isCanonicalName = isCanonicalName(type);
String typeName = getTypeName(type, isCanonicalName);
typeClassNames.add(typeName);
DetailAST sibling = type.getNextSibling();
while (sibling.getType() == TokenTypes.COMMA) {
type = sibling.getNextSibling();
isCanonicalName = isCanonicalName(type);
typeName = getTypeName(type, isCanonicalName);
typeClassNames.add(typeName);
sibling = type.getNextSibling();
}
return typeClassNames;
}
/**
* Checks whether all of generic type arguments are immutable.
* If at least one argument is mutable, we assume that the whole list of type arguments
* is mutable.
*
* @param typeArgsClassNames type arguments class names.
* @return true if all of generic type arguments are immutable.
*/
private boolean areImmutableTypeArguments(List typeArgsClassNames) {
return typeArgsClassNames.stream().noneMatch(
typeName -> {
return !immutableClassShortNames.contains(typeName)
&& !immutableClassCanonicalNames.contains(typeName);
});
}
/**
* Checks whether current field is final.
*
* @param variableDef field in consideration.
* @return true if current field is final.
*/
private static boolean isFinalField(DetailAST variableDef) {
final DetailAST modifiers = variableDef.findFirstToken(TokenTypes.MODIFIERS);
return modifiers.findFirstToken(TokenTypes.FINAL) != null;
}
/**
* Gets the name of type from given ast {@link TokenTypes#TYPE TYPE} node.
* If type is specified via its canonical name - canonical name will be returned,
* else - short type's name.
*
* @param type {@link TokenTypes#TYPE TYPE} node.
* @param isCanonicalName is given name canonical.
* @return String representation of given type's name.
*/
private static String getTypeName(DetailAST type, boolean isCanonicalName) {
final String typeName;
if (isCanonicalName) {
typeName = getCanonicalName(type);
}
else {
typeName = type.getFirstChild().getText();
}
return typeName;
}
/**
* Checks if current type is primitive type (int, short, float, boolean, double, etc.).
* As primitive types have special tokens for each one, such as:
* LITERAL_INT, LITERAL_BOOLEAN, etc.
* So, if type's identifier differs from {@link TokenTypes#IDENT IDENT} token - it's a
* primitive type.
*
* @param type Ast {@link TokenTypes#TYPE TYPE} node.
* @return true if current type is primitive type.
*/
private static boolean isPrimitive(DetailAST type) {
return type.getFirstChild().getType() != TokenTypes.IDENT;
}
/**
* Gets canonical type's name from given {@link TokenTypes#TYPE TYPE} node.
*
* @param type DetailAST {@link TokenTypes#TYPE TYPE} node.
* @return canonical type's name
*/
private static String getCanonicalName(DetailAST type) {
final StringBuilder canonicalNameBuilder = new StringBuilder(256);
DetailAST toVisit = type.getFirstChild();
while (toVisit != null) {
toVisit = getNextSubTreeNode(toVisit, type);
if (toVisit != null && toVisit.getType() == TokenTypes.IDENT) {
if (canonicalNameBuilder.length() > 0) {
canonicalNameBuilder.append('.');
}
canonicalNameBuilder.append(toVisit.getText());
final DetailAST nextSubTreeNode = getNextSubTreeNode(toVisit, type);
if (nextSubTreeNode != null
&& nextSubTreeNode.getType() == TokenTypes.TYPE_ARGUMENTS) {
break;
}
}
}
return canonicalNameBuilder.toString();
}
/**
* Gets the next node of a syntactical tree (child of a current node or
* sibling of a current node, or sibling of a parent of a current node).
*
* @param currentNodeAst Current node in considering
* @param subTreeRootAst SubTree root
* @return Current node after bypassing, if current node reached the root of a subtree
* method returns null
*/
private static DetailAST
getNextSubTreeNode(DetailAST currentNodeAst, DetailAST subTreeRootAst) {
DetailAST currentNode = currentNodeAst;
DetailAST toVisitAst = currentNode.getFirstChild();
while (toVisitAst == null) {
toVisitAst = currentNode.getNextSibling();
if (currentNode.getParent().getColumnNo() == subTreeRootAst.getColumnNo()) {
break;
}
currentNode = currentNode.getParent();
}
return toVisitAst;
}
/**
* Gets the list with short names classes.
* These names are taken from array of classes canonical names.
*
* @param canonicalClassNames canonical class names.
* @return the list of short names of classes.
*/
private static List getClassShortNames(List canonicalClassNames) {
final List shortNames = new ArrayList<>();
for (String canonicalClassName : canonicalClassNames) {
final String shortClassName = canonicalClassName
.substring(canonicalClassName.lastIndexOf('.') + 1);
shortNames.add(shortClassName);
}
return shortNames;
}
/**
* Gets the short class name from given canonical name.
*
* @param canonicalClassName canonical class name.
* @return short name of class.
*/
private static String getClassShortName(String canonicalClassName) {
return canonicalClassName
.substring(canonicalClassName.lastIndexOf('.') + 1);
}
/**
* Checks whether the AST is annotated with
* an annotation containing the passed in regular
* expression and return the AST representing that
* annotation.
*
*
* This method will not look for imports or package
* statements to detect the passed in annotation.
*
*
*
* To check if an AST contains a passed in annotation
* taking into account fully-qualified names
* (ex: java.lang.Override, Override)
* this method will need to be called twice. Once for each
* name given.
*
*
* @param variableDef {@link TokenTypes#VARIABLE_DEF variable def node}.
* @return the AST representing the first such annotation or null if
* no such annotation was found
*/
private DetailAST findMatchingAnnotation(DetailAST variableDef) {
DetailAST matchingAnnotation = null;
final DetailAST holder = AnnotationUtil.getAnnotationHolder(variableDef);
for (DetailAST child = holder.getFirstChild();
child != null; child = child.getNextSibling()) {
if (child.getType() == TokenTypes.ANNOTATION) {
final DetailAST ast = child.getFirstChild();
final String name =
FullIdent.createFullIdent(ast.getNextSibling()).getText();
if (ignoreAnnotationCanonicalNames.contains(name)
|| ignoreAnnotationShortNames.contains(name)) {
matchingAnnotation = child;
break;
}
}
}
return matchingAnnotation;
}
}