View Javadoc

1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.rules;
5   
6   import java.util.ArrayList;
7   import java.util.List;
8   
9   import net.sourceforge.pmd.AbstractRule;
10  import net.sourceforge.pmd.ast.ASTArgumentList;
11  import net.sourceforge.pmd.ast.ASTArguments;
12  import net.sourceforge.pmd.ast.ASTBlock;
13  import net.sourceforge.pmd.ast.ASTClassOrInterfaceBodyDeclaration;
14  import net.sourceforge.pmd.ast.ASTClassOrInterfaceDeclaration;
15  import net.sourceforge.pmd.ast.ASTFormalParameter;
16  import net.sourceforge.pmd.ast.ASTFormalParameters;
17  import net.sourceforge.pmd.ast.ASTImplementsList;
18  import net.sourceforge.pmd.ast.ASTMethodDeclaration;
19  import net.sourceforge.pmd.ast.ASTMethodDeclarator;
20  import net.sourceforge.pmd.ast.ASTName;
21  import net.sourceforge.pmd.ast.ASTNameList;
22  import net.sourceforge.pmd.ast.ASTPrimaryExpression;
23  import net.sourceforge.pmd.ast.ASTPrimaryPrefix;
24  import net.sourceforge.pmd.ast.ASTPrimarySuffix;
25  import net.sourceforge.pmd.ast.ASTResultType;
26  import net.sourceforge.pmd.ast.ASTStatement;
27  import net.sourceforge.pmd.ast.ASTVariableDeclaratorId;
28  import net.sourceforge.pmd.ast.Node;
29  import net.sourceforge.pmd.ast.SimpleNode;
30  
31  import org.jaxen.JaxenException;
32  
33  /**
34   * @author Romain Pelisse, bugfix for [ 1522517 ] False +: UselessOverridingMethod
35   */
36  public class UselessOverridingMethod extends AbstractRule {
37  	private List<String> exceptions;
38  	private static final String CLONE = "clone";
39  	private static final String OBJECT = "Object";
40  
41  	public UselessOverridingMethod()
42  	{
43          exceptions = new ArrayList<String>(1);
44          exceptions.add("CloneNotSupportedException");
45  	}
46  
47  	public Object visit(ASTImplementsList clz, Object data)
48  	{
49  		return super.visit(clz,data);
50  	}
51  
52      public Object visit(ASTClassOrInterfaceDeclaration clz, Object data) {
53          if (clz.isInterface()) {
54              return data;
55          }
56          return super.visit(clz, data);
57      }
58  
59      //TODO: this method should be externalize into an utility class, shouldn't it ?
60      private boolean isMethodType(ASTMethodDeclaration node,String methodType)
61      {
62      	boolean result = false;
63      	ASTResultType type = node.getResultType();
64      	if ( type != null ) {
65      		List results = null;
66              try {
67  	            results = type.findChildNodesWithXPath("./Type/ReferenceType/ClassOrInterfaceType[@Image = '" + methodType + "']");
68              }
69              catch (JaxenException e) {
70  	            e.printStackTrace();
71              }
72      		if ( results != null && results.size() > 0 ) {
73      			result = true;
74      		}
75      	}
76      	return result;
77      }
78  
79      //TODO: this method should be externalize into an utility class, shouldn't it ?
80      private boolean isMethodThrowingType(ASTMethodDeclaration node, List<String> exceptedExceptions) {
81      	boolean result = false;
82  	    ASTNameList thrownsExceptions = node.getFirstChildOfType(ASTNameList.class);
83  	    if ( thrownsExceptions != null ) {
84  	    	List<ASTName> names = thrownsExceptions.findChildrenOfType(ASTName.class);
85  	    	for ( ASTName name : names ) {
86  	    		for ( String exceptedException : exceptedExceptions) {
87  		    		if ( exceptedException.equals(name.getImage()) )
88  		    			result = true;
89  	    		}
90  	    	}
91  	    }
92  	    return result;
93      }
94  
95  	private boolean hasArguments(ASTMethodDeclaration node) {
96  		boolean result = false;
97  		try
98  		{
99  			List parameters = node.findChildNodesWithXPath("./MethodDeclarator/FormalParameters/*");
100 			if ( parameters != null && parameters.size() < 0 ) {
101 				result = true;
102 			}
103 		} catch (JaxenException e) {
104 			e.printStackTrace();
105 		}
106 		return result;
107 	}
108 
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 practise' to
118         // implement them anyway ( see bug 1522517)
119         if ( CLONE.equals(node.getMethodName()) && node.isPublic() &&
120         	 ! this.hasArguments(node) &&
121         	 this.isMethodType(node, OBJECT) &&
122         	 this.isMethodThrowingType(node,exceptions) )
123         {
124         	return super.visit(node,data);
125         }
126 
127         ASTBlock block = node.getBlock();
128         if (block == null) {
129             return super.visit(node, data);
130         }
131         //Only process functions with one BlockStatement
132         if (block.jjtGetNumChildren() != 1 || block.findChildrenOfType(ASTStatement.class).size() != 1)
133             return super.visit(node, data);
134 
135         ASTStatement statement = (ASTStatement) block.jjtGetChild(0).jjtGetChild(0);
136         if (statement.jjtGetChild(0).jjtGetNumChildren() == 0) {
137             return data;     // skips empty return statements
138         }
139         SimpleNode statementGrandChild = (SimpleNode) statement.jjtGetChild(0).jjtGetChild(0);
140         ASTPrimaryExpression primaryExpression;
141 
142         if (statementGrandChild instanceof ASTPrimaryExpression)
143             primaryExpression = (ASTPrimaryExpression) statementGrandChild;
144         else {
145             List<ASTPrimaryExpression> primaryExpressions = findFirstDegreeChildrenOfType(statementGrandChild, ASTPrimaryExpression.class);
146             if (primaryExpressions.size() != 1)
147                 return super.visit(node, data);
148             primaryExpression = primaryExpressions.get(0);
149         }
150 
151         ASTPrimaryPrefix primaryPrefix = findFirstDegreeChildrenOfType(primaryExpression, ASTPrimaryPrefix.class).get(0);
152         if (!primaryPrefix.usesSuperModifier())
153             return super.visit(node, data);
154 
155         ASTMethodDeclarator methodDeclarator = findFirstDegreeChildrenOfType(node, ASTMethodDeclarator.class).get(0);
156         if (!primaryPrefix.hasImageEqualTo(methodDeclarator.getImage()))
157             return super.visit(node, data);
158 
159         //Process arguments
160         ASTPrimarySuffix primarySuffix = findFirstDegreeChildrenOfType(primaryExpression, ASTPrimarySuffix.class).get(0);
161         ASTArguments arguments = (ASTArguments) primarySuffix.jjtGetChild(0);
162         ASTFormalParameters formalParameters = (ASTFormalParameters) methodDeclarator.jjtGetChild(0);
163         if (formalParameters.jjtGetNumChildren() != arguments.jjtGetNumChildren())
164             return super.visit(node, data);
165 
166         if (arguments.jjtGetNumChildren() == 0) //No arguments to check
167             addViolation(data, node, getMessage());
168         else {
169             ASTArgumentList argumentList = (ASTArgumentList) arguments.jjtGetChild(0);
170             for (int i = 0; i < argumentList.jjtGetNumChildren(); i++) {
171                 Node ExpressionChild = argumentList.jjtGetChild(i).jjtGetChild(0);
172                 if (!(ExpressionChild instanceof ASTPrimaryExpression) || ExpressionChild.jjtGetNumChildren() != 1)
173                     return super.visit(node, data); //The arguments are not simply passed through
174 
175                 ASTPrimaryExpression argumentPrimaryExpression = (ASTPrimaryExpression) ExpressionChild;
176                 ASTPrimaryPrefix argumentPrimaryPrefix = (ASTPrimaryPrefix) argumentPrimaryExpression.jjtGetChild(0);
177                 if (argumentPrimaryPrefix.jjtGetNumChildren() == 0) {
178                     return super.visit(node, data); //The arguments are not simply passed through (using "this" for instance)
179                 }
180                 Node argumentPrimaryPrefixChild = argumentPrimaryPrefix.jjtGetChild(0);
181                 if (!(argumentPrimaryPrefixChild instanceof ASTName))
182                     return super.visit(node, data); //The arguments are not simply passed through
183 
184                 if (formalParameters.jjtGetNumChildren() < i + 1) {
185                     return super.visit(node, data); // different number of args
186                 }
187 
188                 ASTName argumentName = (ASTName) argumentPrimaryPrefixChild;
189                 ASTFormalParameter formalParameter = (ASTFormalParameter) formalParameters.jjtGetChild(i);
190                 ASTVariableDeclaratorId variableId = findFirstDegreeChildrenOfType(formalParameter, ASTVariableDeclaratorId.class).get(0);
191                 if (!argumentName.hasImageEqualTo(variableId.getImage())) {
192                     return super.visit(node, data); //The arguments are not simply passed through
193                 }
194 
195             }
196             addViolation(data, node, getMessage()); //All arguments are passed through directly
197         }
198         return super.visit(node, data);
199     }
200 
201 
202 
203 	public <T> List<T> findFirstDegreeChildrenOfType(SimpleNode n, Class<T> targetType) {
204         List<T> l = new ArrayList<T>();
205         lclFindChildrenOfType(n, targetType, l);
206         return l;
207     }
208 
209     private <T> void lclFindChildrenOfType(Node node, Class<T> targetType, List<T> results) {
210         if (node.getClass().equals(targetType)) {
211             results.add((T) node);
212         }
213 
214         if (node instanceof ASTClassOrInterfaceDeclaration && ((ASTClassOrInterfaceDeclaration) node).isNested()) {
215             return;
216         }
217 
218         if (node instanceof ASTClassOrInterfaceBodyDeclaration && ((ASTClassOrInterfaceBodyDeclaration) node).isAnonymousInnerClass()) {
219             return;
220         }
221 
222         for (int i = 0; i < node.jjtGetNumChildren(); i++) {
223             Node child = node.jjtGetChild(i);
224             if (child.getClass().equals(targetType)) {
225                 results.add((T) child);
226             }
227         }
228     }
229 }