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   import java.util.Map;
9   
10  import net.sourceforge.pmd.AbstractRule;
11  import net.sourceforge.pmd.PropertyDescriptor;
12  import net.sourceforge.pmd.ast.ASTAnnotation;
13  import net.sourceforge.pmd.ast.ASTArgumentList;
14  import net.sourceforge.pmd.ast.ASTArguments;
15  import net.sourceforge.pmd.ast.ASTBlock;
16  import net.sourceforge.pmd.ast.ASTClassOrInterfaceBodyDeclaration;
17  import net.sourceforge.pmd.ast.ASTClassOrInterfaceDeclaration;
18  import net.sourceforge.pmd.ast.ASTFormalParameter;
19  import net.sourceforge.pmd.ast.ASTFormalParameters;
20  import net.sourceforge.pmd.ast.ASTImplementsList;
21  import net.sourceforge.pmd.ast.ASTMarkerAnnotation;
22  import net.sourceforge.pmd.ast.ASTMethodDeclaration;
23  import net.sourceforge.pmd.ast.ASTMethodDeclarator;
24  import net.sourceforge.pmd.ast.ASTName;
25  import net.sourceforge.pmd.ast.ASTNameList;
26  import net.sourceforge.pmd.ast.ASTPrimaryExpression;
27  import net.sourceforge.pmd.ast.ASTPrimaryPrefix;
28  import net.sourceforge.pmd.ast.ASTPrimarySuffix;
29  import net.sourceforge.pmd.ast.ASTResultType;
30  import net.sourceforge.pmd.ast.ASTStatement;
31  import net.sourceforge.pmd.ast.ASTVariableDeclaratorId;
32  import net.sourceforge.pmd.ast.Node;
33  import net.sourceforge.pmd.ast.SimpleNode;
34  import net.sourceforge.pmd.properties.BooleanProperty;
35  
36  import org.jaxen.JaxenException;
37  
38  /**
39   * @author Romain Pelisse, bugfix for [ 1522517 ] False +: UselessOverridingMethod
40   */
41  public class UselessOverridingMethod extends AbstractRule {
42  
43      private final List<String> exceptions;
44      private boolean ignoreAnnotations;
45      private static final String CLONE = "clone";
46      private static final String OBJECT = "Object";
47  
48      private static final PropertyDescriptor ignoreAnnotationsDescriptor = new BooleanProperty(
49              "ignoreAnnotations", "Ignore annotations", false, 1.0f);
50  
51      private static final Map<String, PropertyDescriptor> propertyDescriptorsByName = asFixedMap(new PropertyDescriptor[] { ignoreAnnotationsDescriptor });
52  
53      @Override
54      protected Map<String, PropertyDescriptor> propertiesByName() {
55          return propertyDescriptorsByName;
56      }
57  
58      public UselessOverridingMethod() {
59          exceptions = new ArrayList<String>(1);
60          exceptions.add("CloneNotSupportedException");
61      }
62  
63  	@Override
64      public Object visit(ASTImplementsList clz, Object data)
65  	{
66  		return super.visit(clz,data);
67  	}
68  
69      @Override
70      public Object visit(ASTClassOrInterfaceDeclaration clz, Object data) {
71          if (clz.isInterface()) {
72              return data;
73          }
74          ignoreAnnotations = getBooleanProperty(ignoreAnnotationsDescriptor);
75          return super.visit(clz, data);
76      }
77  
78      //TODO: this method should be externalize into an utility class, shouldn't it ?
79      private boolean isMethodType(ASTMethodDeclaration node,String methodType)
80      {
81      	boolean result = false;
82      	ASTResultType type = node.getResultType();
83      	if ( type != null ) {
84      		List results = null;
85              try {
86  	            results = type.findChildNodesWithXPath("./Type/ReferenceType/ClassOrInterfaceType[@Image = '" + methodType + "']");
87              }
88              catch (JaxenException e) {
89  	            e.printStackTrace();
90              }
91      		if ( results != null && results.size() > 0 ) {
92      			result = true;
93      		}
94      	}
95      	return result;
96      }
97  
98      //TODO: this method should be externalize into an utility class, shouldn't it ?
99      private boolean isMethodThrowingType(ASTMethodDeclaration node, List<String> exceptedExceptions) {
100     	boolean result = false;
101 	    ASTNameList thrownsExceptions = node.getFirstChildOfType(ASTNameList.class);
102 	    if ( thrownsExceptions != null ) {
103 	    	List<ASTName> names = thrownsExceptions.findChildrenOfType(ASTName.class);
104 	    	for ( ASTName name : names ) {
105 	    		for ( String exceptedException : exceptedExceptions) {
106 		    		if ( exceptedException.equals(name.getImage()) )
107 		    			result = true;
108 	    		}
109 	    	}
110 	    }
111 	    return result;
112     }
113 
114 	private boolean hasArguments(ASTMethodDeclaration node) {
115 		boolean result = false;
116 		try
117 		{
118 			List parameters = node.findChildNodesWithXPath("./MethodDeclarator/FormalParameters/*");
119 			if ( parameters != null && parameters.size() > 0 ) {
120 				result = true;
121 			}
122 		} catch (JaxenException e) {
123 			e.printStackTrace();
124 		}
125 		return result;
126 	}
127 
128     @Override
129     public Object visit(ASTMethodDeclaration node, Object data) {
130         // Can skip abstract methods and methods whose only purpose is to
131         // guarantee that the inherited method is not changed by finalizing
132         // them.
133         if (node.isAbstract() || node.isFinal() || node.isNative() || node.isSynchronized()) {
134             return super.visit(node, data);
135         }
136         // We can also skip the 'clone' method as they are generally
137         // 'useless' but as it is considered a 'good practice' to
138         // implement them anyway ( see bug 1522517)
139         if ( CLONE.equals(node.getMethodName()) && node.isPublic() &&
140         	 ! this.hasArguments(node) &&
141         	 this.isMethodType(node, OBJECT) &&
142         	 this.isMethodThrowingType(node,exceptions) )
143         {
144         	return super.visit(node,data);
145         }
146 
147         ASTBlock block = node.getBlock();
148         if (block == null) {
149             return super.visit(node, data);
150         }
151         //Only process functions with one BlockStatement
152         if (block.jjtGetNumChildren() != 1 || block.findChildrenOfType(ASTStatement.class).size() != 1)
153             return super.visit(node, data);
154 
155         ASTStatement statement = (ASTStatement) block.jjtGetChild(0).jjtGetChild(0);
156         if (statement.jjtGetChild(0).jjtGetNumChildren() == 0) {
157             return data;     // skips empty return statements
158         }
159         SimpleNode statementGrandChild = (SimpleNode) statement.jjtGetChild(0).jjtGetChild(0);
160         ASTPrimaryExpression primaryExpression;
161 
162         if (statementGrandChild instanceof ASTPrimaryExpression)
163             primaryExpression = (ASTPrimaryExpression) statementGrandChild;
164         else {
165             List<ASTPrimaryExpression> primaryExpressions = findFirstDegreeChildrenOfType(statementGrandChild, ASTPrimaryExpression.class);
166             if (primaryExpressions.size() != 1)
167                 return super.visit(node, data);
168             primaryExpression = primaryExpressions.get(0);
169         }
170 
171         ASTPrimaryPrefix primaryPrefix = findFirstDegreeChildrenOfType(primaryExpression, ASTPrimaryPrefix.class).get(0);
172         if (!primaryPrefix.usesSuperModifier())
173             return super.visit(node, data);
174 
175         ASTMethodDeclarator methodDeclarator = findFirstDegreeChildrenOfType(node, ASTMethodDeclarator.class).get(0);
176         if (!primaryPrefix.hasImageEqualTo(methodDeclarator.getImage()))
177             return super.visit(node, data);
178 
179         List<ASTPrimarySuffix> primarySuffixList = findFirstDegreeChildrenOfType(primaryExpression, ASTPrimarySuffix.class);
180         if (primarySuffixList.size() != 1) {
181             // extra method call on result of super method
182             return super.visit(node, data);
183         }
184         //Process arguments
185         ASTPrimarySuffix primarySuffix = primarySuffixList.get(0);
186         ASTArguments arguments = (ASTArguments) primarySuffix.jjtGetChild(0);
187         ASTFormalParameters formalParameters = (ASTFormalParameters) methodDeclarator.jjtGetChild(0);
188         if (formalParameters.jjtGetNumChildren() != arguments.jjtGetNumChildren())
189             return super.visit(node, data);
190 
191         if (!ignoreAnnotations) {
192             ASTClassOrInterfaceBodyDeclaration parent = (ASTClassOrInterfaceBodyDeclaration) node.jjtGetParent();
193             for (int i = 0; i < parent.jjtGetNumChildren(); i++) {
194                 Node n = parent.jjtGetChild(i);
195                 if (n instanceof ASTAnnotation) {
196                     if (n.jjtGetChild(0) instanceof ASTMarkerAnnotation) {
197                         // @Override is ignored
198                         if ("Override".equals(((ASTName) n.jjtGetChild(0).jjtGetChild(0)).getImage())) {
199                             continue;
200                         }
201                     }
202                     return super.visit(node, data);
203                 }
204             }
205         }
206 
207         if (arguments.jjtGetNumChildren() == 0) //No arguments to check
208             addViolation(data, node, getMessage());
209         else {
210             ASTArgumentList argumentList = (ASTArgumentList) arguments.jjtGetChild(0);
211             for (int i = 0; i < argumentList.jjtGetNumChildren(); i++) {
212                 Node ExpressionChild = argumentList.jjtGetChild(i).jjtGetChild(0);
213                 if (!(ExpressionChild instanceof ASTPrimaryExpression) || ExpressionChild.jjtGetNumChildren() != 1)
214                     return super.visit(node, data); //The arguments are not simply passed through
215 
216                 ASTPrimaryExpression argumentPrimaryExpression = (ASTPrimaryExpression) ExpressionChild;
217                 ASTPrimaryPrefix argumentPrimaryPrefix = (ASTPrimaryPrefix) argumentPrimaryExpression.jjtGetChild(0);
218                 if (argumentPrimaryPrefix.jjtGetNumChildren() == 0) {
219                     return super.visit(node, data); //The arguments are not simply passed through (using "this" for instance)
220                 }
221                 Node argumentPrimaryPrefixChild = argumentPrimaryPrefix.jjtGetChild(0);
222                 if (!(argumentPrimaryPrefixChild instanceof ASTName))
223                     return super.visit(node, data); //The arguments are not simply passed through
224 
225                 if (formalParameters.jjtGetNumChildren() < i + 1) {
226                     return super.visit(node, data); // different number of args
227                 }
228 
229                 ASTName argumentName = (ASTName) argumentPrimaryPrefixChild;
230                 ASTFormalParameter formalParameter = (ASTFormalParameter) formalParameters.jjtGetChild(i);
231                 ASTVariableDeclaratorId variableId = findFirstDegreeChildrenOfType(formalParameter, ASTVariableDeclaratorId.class).get(0);
232                 if (!argumentName.hasImageEqualTo(variableId.getImage())) {
233                     return super.visit(node, data); //The arguments are not simply passed through
234                 }
235 
236             }
237             addViolation(data, node, getMessage()); //All arguments are passed through directly
238         }
239         return super.visit(node, data);
240     }
241 
242 
243 
244 	public <T> List<T> findFirstDegreeChildrenOfType(SimpleNode n, Class<T> targetType) {
245         List<T> l = new ArrayList<T>();
246         lclFindChildrenOfType(n, targetType, l);
247         return l;
248     }
249 
250     private <T> void lclFindChildrenOfType(Node node, Class<T> targetType, List<T> results) {
251         if (node.getClass().equals(targetType)) {
252             results.add((T) node);
253         }
254 
255         if (node instanceof ASTClassOrInterfaceDeclaration && ((ASTClassOrInterfaceDeclaration) node).isNested()) {
256             return;
257         }
258 
259         if (node instanceof ASTClassOrInterfaceBodyDeclaration && ((ASTClassOrInterfaceBodyDeclaration) node).isAnonymousInnerClass()) {
260             return;
261         }
262 
263         for (int i = 0; i < node.jjtGetNumChildren(); i++) {
264             Node child = node.jjtGetChild(i);
265             if (child.getClass().equals(targetType)) {
266                 results.add((T) child);
267             }
268         }
269     }
270 }