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