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() != 1 || block.findDescendantsOfType(ASTStatement.class).size() != 1) {
130 return super.visit(node, data);
131 }
132
133 ASTStatement statement = (ASTStatement) block.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 = (ASTPrimaryExpression) statementGrandChild;
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 = (ASTArguments) primarySuffix.jjtGetChild(0);
171 ASTFormalParameters formalParameters = (ASTFormalParameters) methodDeclarator.jjtGetChild(0);
172 if (formalParameters.jjtGetNumChildren() != arguments.jjtGetNumChildren()) {
173 return super.visit(node, data);
174 }
175
176 if (!ignoreAnnotations) {
177 ASTClassOrInterfaceBodyDeclaration parent = (ASTClassOrInterfaceBodyDeclaration) node.jjtGetParent();
178 for (int i = 0; i < parent.jjtGetNumChildren(); i++) {
179 Node n = parent.jjtGetChild(i);
180 if (n instanceof ASTAnnotation) {
181 if (n.jjtGetChild(0) instanceof ASTMarkerAnnotation) {
182 // @Override is ignored
183 if ("Override".equals(((ASTName) n.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 = (ASTArgumentList) arguments.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 = (ASTPrimaryExpression) expressionChild;
203 ASTPrimaryPrefix argumentPrimaryPrefix = (ASTPrimaryPrefix) argumentPrimaryExpression.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 = (ASTName) argumentPrimaryPrefixChild;
217 ASTFormalParameter formalParameter = (ASTFormalParameter) formalParameters.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((T) node);
239 }
240
241 if (node instanceof ASTClassOrInterfaceDeclaration && ((ASTClassOrInterfaceDeclaration) node).isNested()) {
242 return;
243 }
244
245 if (node instanceof ASTClassOrInterfaceBodyDeclaration
246 && ((ASTClassOrInterfaceBodyDeclaration) node).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((T) child);
254 }
255 }
256 }
257 }
|