UselessOverridingMethodRule.java
001 /**
002  * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003  */
004 package net.sourceforge.pmd.lang.java.rule.unnecessary;
005 
006 import java.util.ArrayList;
007 import java.util.List;
008 
009 import net.sourceforge.pmd.lang.ast.Node;
010 import net.sourceforge.pmd.lang.java.ast.ASTAnnotation;
011 import net.sourceforge.pmd.lang.java.ast.ASTArgumentList;
012 import net.sourceforge.pmd.lang.java.ast.ASTArguments;
013 import net.sourceforge.pmd.lang.java.ast.ASTBlock;
014 import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBodyDeclaration;
015 import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
016 import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
017 import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter;
018 import net.sourceforge.pmd.lang.java.ast.ASTFormalParameters;
019 import net.sourceforge.pmd.lang.java.ast.ASTImplementsList;
020 import net.sourceforge.pmd.lang.java.ast.ASTMarkerAnnotation;
021 import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
022 import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator;
023 import net.sourceforge.pmd.lang.java.ast.ASTName;
024 import net.sourceforge.pmd.lang.java.ast.ASTNameList;
025 import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
026 import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
027 import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix;
028 import net.sourceforge.pmd.lang.java.ast.ASTResultType;
029 import net.sourceforge.pmd.lang.java.ast.ASTStatement;
030 import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
031 import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
032 import net.sourceforge.pmd.lang.rule.properties.BooleanProperty;
033 
034 /**
035  @author Romain Pelisse, bugfix for [ 1522517 ] False +: UselessOverridingMethod
036  */
037 public class UselessOverridingMethodRule extends AbstractJavaRule {
038     private final List<String> exceptions;
039     private boolean ignoreAnnotations;
040     private static final String CLONE = "clone";
041     private static final String OBJECT = "Object";
042 
043     private static final BooleanProperty IGNORE_ANNOTATIONS_DESCRIPTOR = new BooleanProperty(
044                 "ignoreAnnotations""Ignore annotations", false, 1.0f);
045 
046     public UselessOverridingMethodRule() {
047         definePropertyDescriptor(IGNORE_ANNOTATIONS_DESCRIPTOR);
048 
049         exceptions = new ArrayList<String>(1);
050         exceptions.add("CloneNotSupportedException");
051     }
052 
053     @Override
054     public Object visit(ASTCompilationUnit node, Object data) {
055         init();
056         return super.visit(node, data);
057     }
058 
059     private void init() {
060         ignoreAnnotations = getProperty(IGNORE_ANNOTATIONS_DESCRIPTOR);
061     }
062 
063     @Override
064     public Object visit(ASTImplementsList clz, Object data) {
065         return super.visit(clz, data);
066     }
067 
068     @Override
069     public Object visit(ASTClassOrInterfaceDeclaration clz, Object data) {
070         if (clz.isInterface()) {
071             return data;
072         }
073         return super.visit(clz, data);
074     }
075 
076     //TODO: this method should be externalize into an utility class, shouldn't it ?
077     private boolean isMethodType(ASTMethodDeclaration node, String methodType) {
078         boolean result = false;
079         ASTResultType type = node.getResultType();
080         if (type != null) {
081             result = type.hasDescendantMatchingXPath("./Type/ReferenceType/ClassOrInterfaceType[@Image = '"
082                         + methodType + "']");
083         }
084         return result;
085     }
086 
087     //TODO: this method should be externalize into an utility class, shouldn't it ?
088     private boolean isMethodThrowingType(ASTMethodDeclaration node, List<String> exceptedExceptions) {
089         boolean result = false;
090         ASTNameList thrownsExceptions = node.getFirstChildOfType(ASTNameList.class);
091         if (thrownsExceptions != null) {
092             List<ASTName> names = thrownsExceptions.findChildrenOfType(ASTName.class);
093             for (ASTName name : names) {
094                 for (String exceptedException : exceptedExceptions) {
095                     if (exceptedException.equals(name.getImage())) {
096                         result = true;
097                     }
098                 }
099             }
100         }
101         return result;
102     }
103 
104     private boolean hasArguments(ASTMethodDeclaration node) {
105         return node.hasDescendantMatchingXPath("./MethodDeclarator/FormalParameters/*");
106     }
107 
108     @Override
109     public Object visit(ASTMethodDeclaration node, Object data) {
110         // Can skip abstract methods and methods whose only purpose is to
111         // guarantee that the inherited method is not changed by finalizing
112         // them.
113         if (node.isAbstract() || node.isFinal() || node.isNative() || node.isSynchronized()) {
114             return super.visit(node, data);
115         }
116         // We can also skip the 'clone' method as they are generally
117         // 'useless' but as it is considered a 'good practice' to
118         // implement them anyway ( see bug 1522517)
119         if (CLONE.equals(node.getMethodName()) && node.isPublic() && !this.hasArguments(node)
120                 && this.isMethodType(node, OBJECT&& this.isMethodThrowingType(node, exceptions)) {
121             return super.visit(node, data);
122         }
123 
124         ASTBlock block = node.getBlock();
125         if (block == null) {
126             return super.visit(node, data);
127         }
128         //Only process functions with one BlockStatement
129         if (block.jjtGetNumChildren() != || block.findDescendantsOfType(ASTStatement.class).size() != 1) {
130             return super.visit(node, data);
131         }
132 
133         ASTStatement statement = (ASTStatementblock.jjtGetChild(0).jjtGetChild(0);
134         if (statement.jjtGetChild(0).jjtGetNumChildren() == 0) {
135             return data; // skips empty return statements
136         }
137         Node statementGrandChild = statement.jjtGetChild(0).jjtGetChild(0);
138         ASTPrimaryExpression primaryExpression;
139 
140         if (statementGrandChild instanceof ASTPrimaryExpression) {
141             primaryExpression = (ASTPrimaryExpressionstatementGrandChild;
142         else {
143             List<ASTPrimaryExpression> primaryExpressions = findFirstDegreeChildrenOfType(statementGrandChild,
144                     ASTPrimaryExpression.class);
145             if (primaryExpressions.size() != 1) {
146                 return super.visit(node, data);
147             }
148             primaryExpression = primaryExpressions.get(0);
149         }
150 
151         ASTPrimaryPrefix primaryPrefix = findFirstDegreeChildrenOfType(primaryExpression, ASTPrimaryPrefix.class)
152                 .get(0);
153         if (!primaryPrefix.usesSuperModifier()) {
154             return super.visit(node, data);
155         }
156 
157         ASTMethodDeclarator methodDeclarator = findFirstDegreeChildrenOfType(node, ASTMethodDeclarator.class).get(0);
158         if (!primaryPrefix.hasImageEqualTo(methodDeclarator.getImage())) {
159             return super.visit(node, data);
160         }
161 
162         List<ASTPrimarySuffix> primarySuffixList = findFirstDegreeChildrenOfType(primaryExpression,
163                 ASTPrimarySuffix.class);
164         if (primarySuffixList.size() != 1) {
165             // extra method call on result of super method
166             return super.visit(node, data);
167         }
168         //Process arguments
169         ASTPrimarySuffix primarySuffix = primarySuffixList.get(0);
170         ASTArguments arguments = (ASTArgumentsprimarySuffix.jjtGetChild(0);
171         ASTFormalParameters formalParameters = (ASTFormalParametersmethodDeclarator.jjtGetChild(0);
172         if (formalParameters.jjtGetNumChildren() != arguments.jjtGetNumChildren()) {
173             return super.visit(node, data);
174         }
175 
176         if (!ignoreAnnotations) {
177             ASTClassOrInterfaceBodyDeclaration parent = (ASTClassOrInterfaceBodyDeclarationnode.jjtGetParent();
178             for (int i = 0; i < parent.jjtGetNumChildren(); i++) {
179                 Node n = parent.jjtGetChild(i);
180                 if (instanceof ASTAnnotation) {
181                     if (n.jjtGetChild(0instanceof ASTMarkerAnnotation) {
182                         // @Override is ignored
183                         if ("Override".equals(((ASTNamen.jjtGetChild(0).jjtGetChild(0)).getImage())) {
184                             continue;
185                         }
186                     }
187                     return super.visit(node, data);
188                 }
189             }
190         }
191 
192         if (arguments.jjtGetNumChildren() == 0) {
193             addViolation(data, node, getMessage());
194         else {
195             ASTArgumentList argumentList = (ASTArgumentListarguments.jjtGetChild(0);
196             for (int i = 0; i < argumentList.jjtGetNumChildren(); i++) {
197                 Node expressionChild = argumentList.jjtGetChild(i).jjtGetChild(0);
198                 if (!(expressionChild instanceof ASTPrimaryExpression|| expressionChild.jjtGetNumChildren() != 1) {
199                     return super.visit(node, data)//The arguments are not simply passed through
200                 }
201 
202                 ASTPrimaryExpression argumentPrimaryExpression = (ASTPrimaryExpressionexpressionChild;
203                 ASTPrimaryPrefix argumentPrimaryPrefix = (ASTPrimaryPrefixargumentPrimaryExpression.jjtGetChild(0);
204                 if (argumentPrimaryPrefix.jjtGetNumChildren() == 0) {
205                     return super.visit(node, data)//The arguments are not simply passed through (using "this" for instance)
206                 }
207                 Node argumentPrimaryPrefixChild = argumentPrimaryPrefix.jjtGetChild(0);
208                 if (!(argumentPrimaryPrefixChild instanceof ASTName)) {
209                     return super.visit(node, data)//The arguments are not simply passed through
210                 }
211 
212                 if (formalParameters.jjtGetNumChildren() < i + 1) {
213                     return super.visit(node, data)// different number of args
214                 }
215 
216                 ASTName argumentName = (ASTNameargumentPrimaryPrefixChild;
217                 ASTFormalParameter formalParameter = (ASTFormalParameterformalParameters.jjtGetChild(i);
218                 ASTVariableDeclaratorId variableId = findFirstDegreeChildrenOfType(formalParameter,
219                         ASTVariableDeclaratorId.class).get(0);
220                 if (!argumentName.hasImageEqualTo(variableId.getImage())) {
221                     return super.visit(node, data)//The arguments are not simply passed through
222                 }
223 
224             }
225             addViolation(data, node, getMessage())//All arguments are passed through directly
226         }
227         return super.visit(node, data);
228     }
229 
230     public <T> List<T> findFirstDegreeChildrenOfType(Node n, Class<T> targetType) {
231         List<T> l = new ArrayList<T>();
232         lclFindChildrenOfType(n, targetType, l);
233         return l;
234     }
235 
236     private <T> void lclFindChildrenOfType(Node node, Class<T> targetType, List<T> results) {
237         if (node.getClass().equals(targetType)) {
238             results.add((Tnode);
239         }
240 
241         if (node instanceof ASTClassOrInterfaceDeclaration && ((ASTClassOrInterfaceDeclarationnode).isNested()) {
242             return;
243         }
244 
245         if (node instanceof ASTClassOrInterfaceBodyDeclaration
246                 && ((ASTClassOrInterfaceBodyDeclarationnode).isAnonymousInnerClass()) {
247             return;
248         }
249 
250         for (int i = 0; i < node.jjtGetNumChildren(); i++) {
251             Node child = node.jjtGetChild(i);
252             if (child.getClass().equals(targetType)) {
253                 results.add((Tchild);
254             }
255         }
256     }
257 }