////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2017 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.modifier;
import java.util.ArrayList;
import java.util.List;
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
/**
* Checks for redundant modifiers in interface and annotation definitions,
* final modifier on methods of final classes, inner <code>interface</code>
* declarations that are declared as <code>static</code>, non public class
* constructors and enum constructors, nested enum definitions that are declared
* as <code>static</code>.
*
* <p>Interfaces by definition are abstract so the <code>abstract</code>
* modifier on the interface is redundant.
*
* <p>Classes inside of interfaces by definition are public and static,
* so the <code>public</code> and <code>static</code> modifiers
* on the inner classes are redundant. On the other hand, classes
* inside of interfaces can be abstract or non abstract.
* So, <code>abstract</code> modifier is allowed.
*
* <p>Fields in interfaces and annotations are automatically
* public, static and final, so these modifiers are redundant as
* well.</p>
*
* <p>As annotations are a form of interface, their fields are also
* automatically public, static and final just as their
* annotation fields are automatically public and abstract.</p>
*
* <p>Enums by definition are static implicit subclasses of java.lang.Enum<E>.
* So, the <code>static</code> modifier on the enums is redundant. In addition,
* if enum is inside of interface, <code>public</code> modifier is also redundant.</p>
*
* <p>Enums can also contain abstract methods and methods which can be overridden by the declared
* enumeration fields.
* See the following example:</p>
* <pre>
* public enum EnumClass {
* FIELD_1,
* FIELD_2 {
* @Override
* public final void method1() {} // violation expected
* };
*
* public void method1() {}
* public final void method2() {} // no violation expected
* }
* </pre>
*
* <p>Since these methods can be overridden in these situations, the final methods are not
* marked as redundant even though they can't be extended by other classes/enums.</p>
*
* <p>Final classes by definition cannot be extended so the <code>final</code>
* modifier on the method of a final class is redundant.
*
* <p>Public modifier for constructors in non-public non-protected classes
* is always obsolete: </p>
*
* <pre>
* public class PublicClass {
* public PublicClass() {} // OK
* }
*
* class PackagePrivateClass {
* public PackagePrivateClass() {} // violation expected
* }
* </pre>
*
* <p>There is no violation in the following example,
* because removing public modifier from ProtectedInnerClass
* constructor will make this code not compiling: </p>
*
* <pre>
* package a;
* public class ClassExample {
* protected class ProtectedInnerClass {
* public ProtectedInnerClass () {}
* }
* }
*
* package b;
* import a.ClassExample;
* public class ClassExtending extends ClassExample {
* ProtectedInnerClass pc = new ProtectedInnerClass();
* }
* </pre>
*
* @author lkuehne
* @author <a href="mailto:piotr.listkiewicz@gmail.com">liscju</a>
* @author <a href="mailto:andreyselkin@gmail.com">Andrei Selkin</a>
* @author Vladislav Lisetskiy
*/
public class RedundantModifierCheck
extends AbstractCheck {
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_KEY = "redundantModifier";
/**
* An array of tokens for interface modifiers.
*/
private static final int[] TOKENS_FOR_INTERFACE_MODIFIERS = {
TokenTypes.LITERAL_STATIC,
TokenTypes.ABSTRACT,
};
@Override
public int[] getDefaultTokens() {
return getAcceptableTokens();
}
@Override
public int[] getRequiredTokens() {
return CommonUtils.EMPTY_INT_ARRAY;
}
@Override
public int[] getAcceptableTokens() {
return new int[] {
TokenTypes.METHOD_DEF,
TokenTypes.VARIABLE_DEF,
TokenTypes.ANNOTATION_FIELD_DEF,
TokenTypes.INTERFACE_DEF,
TokenTypes.CTOR_DEF,
TokenTypes.CLASS_DEF,
TokenTypes.ENUM_DEF,
TokenTypes.RESOURCE,
};
}
@Override
public void visitToken(DetailAST ast) {
if (ast.getType() == TokenTypes.INTERFACE_DEF) {
checkInterfaceModifiers(ast);
}
else if (ast.getType() == TokenTypes.ENUM_DEF) {
checkEnumDef(ast);
}
else {
if (ast.getType() == TokenTypes.CTOR_DEF) {
if (isEnumMember(ast)) {
checkEnumConstructorModifiers(ast);
}
else {
checkClassConstructorModifiers(ast);
}
}
else if (ast.getType() == TokenTypes.METHOD_DEF) {
processMethods(ast);
}
else if (ast.getType() == TokenTypes.RESOURCE) {
processResources(ast);
}
if (isInterfaceOrAnnotationMember(ast)) {
processInterfaceOrAnnotation(ast);
}
}
}
/**
* Checks if interface has proper modifiers.
* @param ast interface to check
*/
private void checkInterfaceModifiers(DetailAST ast) {
final DetailAST modifiers =
ast.findFirstToken(TokenTypes.MODIFIERS);
for (final int tokenType : TOKENS_FOR_INTERFACE_MODIFIERS) {
final DetailAST modifier =
modifiers.findFirstToken(tokenType);
if (modifier != null) {
log(modifier.getLineNo(), modifier.getColumnNo(),
MSG_KEY, modifier.getText());
}
}
}
/**
* Check if enum constructor has proper modifiers.
* @param ast constructor of enum
*/
private void checkEnumConstructorModifiers(DetailAST ast) {
final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
final DetailAST modifier = modifiers.getFirstChild();
if (modifier != null) {
log(modifier.getLineNo(), modifier.getColumnNo(),
MSG_KEY, modifier.getText());
}
}
/**
* Checks whether enum has proper modifiers.
* @param ast enum definition.
*/
private void checkEnumDef(DetailAST ast) {
if (isInterfaceOrAnnotationMember(ast)) {
processInterfaceOrAnnotation(ast);
}
else if (ast.getParent() != null) {
checkForRedundantModifier(ast, TokenTypes.LITERAL_STATIC);
}
}
/**
* Do validation of interface of annotation.
* @param ast token AST
*/
private void processInterfaceOrAnnotation(DetailAST ast) {
final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
DetailAST modifier = modifiers.getFirstChild();
while (modifier != null) {
// javac does not allow final or static in interface methods
// order annotation fields hence no need to check that this
// is not a method or annotation field
final int type = modifier.getType();
if (type == TokenTypes.LITERAL_PUBLIC
|| type == TokenTypes.LITERAL_STATIC
&& ast.getType() != TokenTypes.METHOD_DEF
|| type == TokenTypes.ABSTRACT
&& ast.getType() != TokenTypes.CLASS_DEF
|| type == TokenTypes.FINAL
&& ast.getType() != TokenTypes.CLASS_DEF) {
log(modifier.getLineNo(), modifier.getColumnNo(),
MSG_KEY, modifier.getText());
break;
}
modifier = modifier.getNextSibling();
}
}
/**
* Process validation of Methods.
* @param ast method AST
*/
private void processMethods(DetailAST ast) {
final DetailAST modifiers =
ast.findFirstToken(TokenTypes.MODIFIERS);
// private method?
boolean checkFinal =
modifiers.branchContains(TokenTypes.LITERAL_PRIVATE);
// declared in a final class?
DetailAST parent = ast.getParent();
while (parent != null) {
if (parent.getType() == TokenTypes.CLASS_DEF) {
final DetailAST classModifiers =
parent.findFirstToken(TokenTypes.MODIFIERS);
checkFinal = checkFinal || classModifiers.branchContains(TokenTypes.FINAL);
parent = null;
}
else if (parent.getType() == TokenTypes.LITERAL_NEW
|| parent.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
checkFinal = true;
parent = null;
}
else {
parent = parent.getParent();
}
}
if (checkFinal && !isAnnotatedWithSafeVarargs(ast)) {
checkForRedundantModifier(ast, TokenTypes.FINAL);
}
if (!ast.branchContains(TokenTypes.SLIST)) {
processAbstractMethodParameters(ast);
}
}
/**
* Process validation of parameters for Methods with no definition.
* @param ast method AST
*/
private void processAbstractMethodParameters(DetailAST ast) {
final DetailAST parameters = ast.findFirstToken(TokenTypes.PARAMETERS);
for (DetailAST child = parameters.getFirstChild(); child != null; child = child
.getNextSibling()) {
if (child.getType() == TokenTypes.PARAMETER_DEF) {
checkForRedundantModifier(child, TokenTypes.FINAL);
}
}
}
/**
* Check if class constructor has proper modifiers.
* @param classCtorAst class constructor ast
*/
private void checkClassConstructorModifiers(DetailAST classCtorAst) {
final DetailAST classDef = classCtorAst.getParent().getParent();
if (!isClassPublic(classDef) && !isClassProtected(classDef)) {
checkForRedundantModifier(classCtorAst, TokenTypes.LITERAL_PUBLIC);
}
}
/**
* Checks if given resource has redundant modifiers.
* @param ast ast
*/
private void processResources(DetailAST ast) {
checkForRedundantModifier(ast, TokenTypes.FINAL);
}
/**
* Checks if given ast has a redundant modifier.
* @param ast ast
* @param modifierType The modifier to check for.
*/
private void checkForRedundantModifier(DetailAST ast, int modifierType) {
final DetailAST astModifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
DetailAST astModifier = astModifiers.getFirstChild();
while (astModifier != null) {
if (astModifier.getType() == modifierType) {
log(astModifier.getLineNo(), astModifier.getColumnNo(),
MSG_KEY, astModifier.getText());
}
astModifier = astModifier.getNextSibling();
}
}
/**
* Checks if given class ast has protected modifier.
* @param classDef class ast
* @return true if class is protected, false otherwise
*/
private static boolean isClassProtected(DetailAST classDef) {
final DetailAST classModifiers =
classDef.findFirstToken(TokenTypes.MODIFIERS);
return classModifiers.branchContains(TokenTypes.LITERAL_PROTECTED);
}
/**
* Checks if given class is accessible from "public" scope.
* @param ast class def to check
* @return true if class is accessible from public scope,false otherwise
*/
private static boolean isClassPublic(DetailAST ast) {
boolean isAccessibleFromPublic = false;
final boolean isMostOuterScope = ast.getParent() == null;
final DetailAST modifiersAst = ast.findFirstToken(TokenTypes.MODIFIERS);
final boolean hasPublicModifier = modifiersAst.branchContains(TokenTypes.LITERAL_PUBLIC);
if (isMostOuterScope) {
isAccessibleFromPublic = hasPublicModifier;
}
else {
final DetailAST parentClassAst = ast.getParent().getParent();
if (hasPublicModifier || parentClassAst.getType() == TokenTypes.INTERFACE_DEF) {
isAccessibleFromPublic = isClassPublic(parentClassAst);
}
}
return isAccessibleFromPublic;
}
/**
* Checks if current AST node is member of Enum.
* @param ast AST node
* @return true if it is an enum member
*/
private static boolean isEnumMember(DetailAST ast) {
final DetailAST parentTypeDef = ast.getParent().getParent();
return parentTypeDef.getType() == TokenTypes.ENUM_DEF;
}
/**
* Checks if current AST node is member of Interface or Annotation, not of their subnodes.
* @param ast AST node
* @return true or false
*/
private static boolean isInterfaceOrAnnotationMember(DetailAST ast) {
DetailAST parentTypeDef = ast.getParent();
if (parentTypeDef != null) {
parentTypeDef = parentTypeDef.getParent();
}
return parentTypeDef != null
&& (parentTypeDef.getType() == TokenTypes.INTERFACE_DEF
|| parentTypeDef.getType() == TokenTypes.ANNOTATION_DEF);
}
/**
* Checks if method definition is annotated with
* <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/SafeVarargs.html">
* SafeVarargs</a> annotation
* @param methodDef method definition node
* @return true or false
*/
private static boolean isAnnotatedWithSafeVarargs(DetailAST methodDef) {
boolean result = false;
final List<DetailAST> methodAnnotationsList = getMethodAnnotationsList(methodDef);
for (DetailAST annotationNode : methodAnnotationsList) {
if ("SafeVarargs".equals(annotationNode.getLastChild().getText())) {
result = true;
break;
}
}
return result;
}
/**
* Gets the list of annotations on method definition.
* @param methodDef method definition node
* @return List of annotations
*/
private static List<DetailAST> getMethodAnnotationsList(DetailAST methodDef) {
final List<DetailAST> annotationsList = new ArrayList<>();
final DetailAST modifiers = methodDef.findFirstToken(TokenTypes.MODIFIERS);
DetailAST modifier = modifiers.getFirstChild();
while (modifier != null) {
if (modifier.getType() == TokenTypes.ANNOTATION) {
annotationsList.add(modifier);
}
modifier = modifier.getNextSibling();
}
return annotationsList;
}
}