/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.rule.unnecessary;
import java.util.ArrayList;
import java.util.List;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.java.ast.ASTAnnotation;
import net.sourceforge.pmd.lang.java.ast.ASTArgumentList;
import net.sourceforge.pmd.lang.java.ast.ASTArguments;
import net.sourceforge.pmd.lang.java.ast.ASTBlock;
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBodyDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter;
import net.sourceforge.pmd.lang.java.ast.ASTFormalParameters;
import net.sourceforge.pmd.lang.java.ast.ASTMarkerAnnotation;
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator;
import net.sourceforge.pmd.lang.java.ast.ASTName;
import net.sourceforge.pmd.lang.java.ast.ASTNameList;
import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix;
import net.sourceforge.pmd.lang.java.ast.ASTResultType;
import net.sourceforge.pmd.lang.java.ast.ASTStatement;
import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
import net.sourceforge.pmd.lang.rule.properties.BooleanProperty;
/**
* @author Romain Pelisse, bugfix for [ 1522517 ] False +:
* UselessOverridingMethod
*/
public class UselessOverridingMethodRule extends AbstractJavaRule {
private final List<String> exceptions;
private boolean ignoreAnnotations;
private static final String CLONE = "clone";
private static final String OBJECT = "Object";
private static final BooleanProperty IGNORE_ANNOTATIONS_DESCRIPTOR = new BooleanProperty("ignoreAnnotations",
"Ignore annotations", false, 1.0f);
public UselessOverridingMethodRule() {
definePropertyDescriptor(IGNORE_ANNOTATIONS_DESCRIPTOR);
exceptions = new ArrayList<>(1);
exceptions.add("CloneNotSupportedException");
}
@Override
public Object visit(ASTCompilationUnit node, Object data) {
init();
return super.visit(node, data);
}
private void init() {
ignoreAnnotations = getProperty(IGNORE_ANNOTATIONS_DESCRIPTOR);
}
@Override
public Object visit(ASTClassOrInterfaceDeclaration clz, Object data) {
if (clz.isInterface()) {
return data;
}
return super.visit(clz, data);
}
// TODO: this method should be externalize into an utility class, shouldn't it ?
private boolean isMethodType(ASTMethodDeclaration node, String methodType) {
boolean result = false;
ASTResultType type = node.getResultType();
if (type != null) {
result = type.hasDescendantMatchingXPath(
"./Type/ReferenceType/ClassOrInterfaceType[@Image = '" + methodType + "']");
}
return result;
}
// TODO: this method should be externalize into an utility class, shouldn't it ?
private boolean isMethodThrowingType(ASTMethodDeclaration node, List<String> exceptedExceptions) {
boolean result = false;
ASTNameList thrownsExceptions = node.getFirstChildOfType(ASTNameList.class);
if (thrownsExceptions != null) {
List<ASTName> names = thrownsExceptions.findChildrenOfType(ASTName.class);
for (ASTName name : names) {
for (String exceptedException : exceptedExceptions) {
if (exceptedException.equals(name.getImage())) {
result = true;
}
}
}
}
return result;
}
private boolean hasArguments(ASTMethodDeclaration node) {
return node.hasDescendantMatchingXPath("./MethodDeclarator/FormalParameters/*");
}
@Override
public Object visit(ASTMethodDeclaration node, Object data) {
// Can skip abstract methods and methods whose only purpose is to
// guarantee that the inherited method is not changed by finalizing
// them.
if (node.isAbstract() || node.isFinal() || node.isNative() || node.isSynchronized()) {
return super.visit(node, data);
}
// We can also skip the 'clone' method as they are generally
// 'useless' but as it is considered a 'good practice' to
// implement them anyway ( see bug 1522517)
if (CLONE.equals(node.getMethodName()) && node.isPublic() && !this.hasArguments(node)
&& this.isMethodType(node, OBJECT) && this.isMethodThrowingType(node, exceptions)) {
return super.visit(node, data);
}
ASTBlock block = node.getBlock();
if (block == null) {
return super.visit(node, data);
}
// Only process functions with one BlockStatement
if (block.jjtGetNumChildren() != 1 || block.findDescendantsOfType(ASTStatement.class).size() != 1) {
return super.visit(node, data);
}
Node statement = block.jjtGetChild(0).jjtGetChild(0);
if (statement.jjtGetChild(0).jjtGetNumChildren() == 0) {
return data; // skips empty return statements
}
Node statementGrandChild = statement.jjtGetChild(0).jjtGetChild(0);
ASTPrimaryExpression primaryExpression;
if (statementGrandChild instanceof ASTPrimaryExpression) {
primaryExpression = (ASTPrimaryExpression) statementGrandChild;
} else {
List<ASTPrimaryExpression> primaryExpressions = findFirstDegreeChildrenOfType(statementGrandChild,
ASTPrimaryExpression.class);
if (primaryExpressions.size() != 1) {
return super.visit(node, data);
}
primaryExpression = primaryExpressions.get(0);
}
ASTPrimaryPrefix primaryPrefix = findFirstDegreeChildrenOfType(primaryExpression, ASTPrimaryPrefix.class)
.get(0);
if (!primaryPrefix.usesSuperModifier()) {
return super.visit(node, data);
}
List<ASTPrimarySuffix> primarySuffixList = findFirstDegreeChildrenOfType(primaryExpression,
ASTPrimarySuffix.class);
if (primarySuffixList.size() != 2) {
// extra method call on result of super method
return super.visit(node, data);
}
ASTMethodDeclarator methodDeclarator = findFirstDegreeChildrenOfType(node, ASTMethodDeclarator.class).get(0);
ASTPrimarySuffix primarySuffix = primarySuffixList.get(0);
if (!primarySuffix.hasImageEqualTo(methodDeclarator.getImage())) {
return super.visit(node, data);
}
// Process arguments
primarySuffix = primarySuffixList.get(1);
ASTArguments arguments = (ASTArguments) primarySuffix.jjtGetChild(0);
ASTFormalParameters formalParameters = (ASTFormalParameters) methodDeclarator.jjtGetChild(0);
if (formalParameters.jjtGetNumChildren() != arguments.jjtGetNumChildren()) {
return super.visit(node, data);
}
if (!ignoreAnnotations) {
ASTClassOrInterfaceBodyDeclaration parent = (ASTClassOrInterfaceBodyDeclaration) node.jjtGetParent();
for (int i = 0; i < parent.jjtGetNumChildren(); i++) {
Node n = parent.jjtGetChild(i);
if (n instanceof ASTAnnotation) {
if (n.jjtGetChild(0) instanceof ASTMarkerAnnotation) {
// @Override is ignored
if ("Override".equals(((ASTName) n.jjtGetChild(0).jjtGetChild(0)).getImage())) {
continue;
}
}
return super.visit(node, data);
}
}
}
if (arguments.jjtGetNumChildren() == 0) {
addViolation(data, node, getMessage());
} else {
ASTArgumentList argumentList = (ASTArgumentList) arguments.jjtGetChild(0);
for (int i = 0; i < argumentList.jjtGetNumChildren(); i++) {
Node expressionChild = argumentList.jjtGetChild(i).jjtGetChild(0);
if (!(expressionChild instanceof ASTPrimaryExpression) || expressionChild.jjtGetNumChildren() != 1) {
// The arguments are not simply passed through
return super.visit(node, data);
}
ASTPrimaryExpression argumentPrimaryExpression = (ASTPrimaryExpression) expressionChild;
ASTPrimaryPrefix argumentPrimaryPrefix = (ASTPrimaryPrefix) argumentPrimaryExpression.jjtGetChild(0);
if (argumentPrimaryPrefix.jjtGetNumChildren() == 0) {
// The arguments are not simply passed through (using "this" for instance)
return super.visit(node, data);
}
Node argumentPrimaryPrefixChild = argumentPrimaryPrefix.jjtGetChild(0);
if (!(argumentPrimaryPrefixChild instanceof ASTName)) {
// The arguments are not simply passed through
return super.visit(node, data);
}
if (formalParameters.jjtGetNumChildren() < i + 1) {
return super.visit(node, data); // different number of args
}
ASTName argumentName = (ASTName) argumentPrimaryPrefixChild;
ASTFormalParameter formalParameter = (ASTFormalParameter) formalParameters.jjtGetChild(i);
ASTVariableDeclaratorId variableId = findFirstDegreeChildrenOfType(formalParameter,
ASTVariableDeclaratorId.class).get(0);
if (!argumentName.hasImageEqualTo(variableId.getImage())) {
// The arguments are not simply passed through
return super.visit(node, data);
}
}
// All arguments are passed through directly
addViolation(data, node, getMessage());
}
return super.visit(node, data);
}
public <T> List<T> findFirstDegreeChildrenOfType(Node n, Class<T> targetType) {
List<T> l = new ArrayList<>();
lclFindChildrenOfType(n, targetType, l);
return l;
}
private <T> void lclFindChildrenOfType(Node node, Class<T> targetType, List<T> results) {
if (node.getClass().equals(targetType)) {
results.add((T) node);
}
if (node instanceof ASTClassOrInterfaceDeclaration && ((ASTClassOrInterfaceDeclaration) node).isNested()) {
return;
}
if (node instanceof ASTClassOrInterfaceBodyDeclaration
&& ((ASTClassOrInterfaceBodyDeclaration) node).isAnonymousInnerClass()) {
return;
}
for (int i = 0; i < node.jjtGetNumChildren(); i++) {
Node child = node.jjtGetChild(i);
if (child.getClass().equals(targetType)) {
results.add((T) child);
}
}
}
}