View Javadoc

1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.lang.java.rule.unnecessary;
5   
6   import java.util.ArrayList;
7   import java.util.List;
8   
9   import net.sourceforge.pmd.lang.ast.Node;
10  import net.sourceforge.pmd.lang.java.ast.ASTAnnotation;
11  import net.sourceforge.pmd.lang.java.ast.ASTArgumentList;
12  import net.sourceforge.pmd.lang.java.ast.ASTArguments;
13  import net.sourceforge.pmd.lang.java.ast.ASTBlock;
14  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBodyDeclaration;
15  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
16  import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
17  import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter;
18  import net.sourceforge.pmd.lang.java.ast.ASTFormalParameters;
19  import net.sourceforge.pmd.lang.java.ast.ASTImplementsList;
20  import net.sourceforge.pmd.lang.java.ast.ASTMarkerAnnotation;
21  import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
22  import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator;
23  import net.sourceforge.pmd.lang.java.ast.ASTName;
24  import net.sourceforge.pmd.lang.java.ast.ASTNameList;
25  import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
26  import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
27  import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix;
28  import net.sourceforge.pmd.lang.java.ast.ASTResultType;
29  import net.sourceforge.pmd.lang.java.ast.ASTStatement;
30  import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
31  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
32  import net.sourceforge.pmd.lang.rule.properties.BooleanProperty;
33  
34  /**
35   * @author Romain Pelisse, bugfix for [ 1522517 ] False +: UselessOverridingMethod
36   */
37  public class UselessOverridingMethodRule extends AbstractJavaRule {
38      private final List<String> exceptions;
39      private boolean ignoreAnnotations;
40      private static final String CLONE = "clone";
41      private static final String OBJECT = "Object";
42  
43      private static final BooleanProperty IGNORE_ANNOTATIONS_DESCRIPTOR = new BooleanProperty(
44                  "ignoreAnnotations", "Ignore annotations", false, 1.0f);
45  
46      public UselessOverridingMethodRule() {
47          definePropertyDescriptor(IGNORE_ANNOTATIONS_DESCRIPTOR);
48  
49          exceptions = new ArrayList<String>(1);
50          exceptions.add("CloneNotSupportedException");
51      }
52  
53      @Override
54      public Object visit(ASTCompilationUnit node, Object data) {
55          init();
56          return super.visit(node, data);
57      }
58  
59      private void init() {
60          ignoreAnnotations = getProperty(IGNORE_ANNOTATIONS_DESCRIPTOR);
61      }
62  
63      @Override
64      public Object visit(ASTImplementsList clz, Object data) {
65          return super.visit(clz, data);
66      }
67  
68      @Override
69      public Object visit(ASTClassOrInterfaceDeclaration clz, Object data) {
70          if (clz.isInterface()) {
71              return data;
72          }
73          return super.visit(clz, data);
74      }
75  
76      //TODO: this method should be externalize into an utility class, shouldn't it ?
77      private boolean isMethodType(ASTMethodDeclaration node, String methodType) {
78          boolean result = false;
79          ASTResultType type = node.getResultType();
80          if (type != null) {
81              result = type.hasDescendantMatchingXPath("./Type/ReferenceType/ClassOrInterfaceType[@Image = '"
82                          + methodType + "']");
83          }
84          return result;
85      }
86  
87      //TODO: this method should be externalize into an utility class, shouldn't it ?
88      private boolean isMethodThrowingType(ASTMethodDeclaration node, List<String> exceptedExceptions) {
89          boolean result = false;
90          ASTNameList thrownsExceptions = node.getFirstChildOfType(ASTNameList.class);
91          if (thrownsExceptions != null) {
92              List<ASTName> names = thrownsExceptions.findChildrenOfType(ASTName.class);
93              for (ASTName name : names) {
94                  for (String exceptedException : exceptedExceptions) {
95                      if (exceptedException.equals(name.getImage())) {
96                          result = true;
97                      }
98                  }
99              }
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         List<ASTPrimarySuffix> primarySuffixList = findFirstDegreeChildrenOfType(primaryExpression,
158                 ASTPrimarySuffix.class);
159         if (primarySuffixList.size() != 2) {
160             // extra method call on result of super method
161             return super.visit(node, data);
162         }
163 
164         ASTMethodDeclarator methodDeclarator = findFirstDegreeChildrenOfType(node, ASTMethodDeclarator.class).get(0);
165         ASTPrimarySuffix primarySuffix = primarySuffixList.get(0);
166         if (!primarySuffix.hasImageEqualTo(methodDeclarator.getImage())) {
167         	return super.visit(node, data);
168         }
169         //Process arguments
170         primarySuffix = primarySuffixList.get(1);
171         ASTArguments arguments = (ASTArguments) primarySuffix.jjtGetChild(0);
172         ASTFormalParameters formalParameters = (ASTFormalParameters) methodDeclarator.jjtGetChild(0);
173         if (formalParameters.jjtGetNumChildren() != arguments.jjtGetNumChildren()) {
174             return super.visit(node, data);
175         }
176 
177         if (!ignoreAnnotations) {
178             ASTClassOrInterfaceBodyDeclaration parent = (ASTClassOrInterfaceBodyDeclaration) node.jjtGetParent();
179             for (int i = 0; i < parent.jjtGetNumChildren(); i++) {
180                 Node n = parent.jjtGetChild(i);
181                 if (n instanceof ASTAnnotation) {
182                     if (n.jjtGetChild(0) instanceof ASTMarkerAnnotation) {
183                         // @Override is ignored
184                         if ("Override".equals(((ASTName) n.jjtGetChild(0).jjtGetChild(0)).getImage())) {
185                             continue;
186                         }
187                     }
188                     return super.visit(node, data);
189                 }
190             }
191         }
192 
193         if (arguments.jjtGetNumChildren() == 0) {
194             addViolation(data, node, getMessage());
195         } else {
196             ASTArgumentList argumentList = (ASTArgumentList) arguments.jjtGetChild(0);
197             for (int i = 0; i < argumentList.jjtGetNumChildren(); i++) {
198                 Node expressionChild = argumentList.jjtGetChild(i).jjtGetChild(0);
199                 if (!(expressionChild instanceof ASTPrimaryExpression) || expressionChild.jjtGetNumChildren() != 1) {
200                     return super.visit(node, data); //The arguments are not simply passed through
201                 }
202 
203                 ASTPrimaryExpression argumentPrimaryExpression = (ASTPrimaryExpression) expressionChild;
204                 ASTPrimaryPrefix argumentPrimaryPrefix = (ASTPrimaryPrefix) argumentPrimaryExpression.jjtGetChild(0);
205                 if (argumentPrimaryPrefix.jjtGetNumChildren() == 0) {
206                     return super.visit(node, data); //The arguments are not simply passed through (using "this" for instance)
207                 }
208                 Node argumentPrimaryPrefixChild = argumentPrimaryPrefix.jjtGetChild(0);
209                 if (!(argumentPrimaryPrefixChild instanceof ASTName)) {
210                     return super.visit(node, data); //The arguments are not simply passed through
211                 }
212 
213                 if (formalParameters.jjtGetNumChildren() < i + 1) {
214                     return super.visit(node, data); // different number of args
215                 }
216 
217                 ASTName argumentName = (ASTName) argumentPrimaryPrefixChild;
218                 ASTFormalParameter formalParameter = (ASTFormalParameter) formalParameters.jjtGetChild(i);
219                 ASTVariableDeclaratorId variableId = findFirstDegreeChildrenOfType(formalParameter,
220                         ASTVariableDeclaratorId.class).get(0);
221                 if (!argumentName.hasImageEqualTo(variableId.getImage())) {
222                     return super.visit(node, data); //The arguments are not simply passed through
223                 }
224 
225             }
226             addViolation(data, node, getMessage()); //All arguments are passed through directly
227         }
228         return super.visit(node, data);
229     }
230 
231     public <T> List<T> findFirstDegreeChildrenOfType(Node n, Class<T> targetType) {
232         List<T> l = new ArrayList<T>();
233         lclFindChildrenOfType(n, targetType, l);
234         return l;
235     }
236 
237     private <T> void lclFindChildrenOfType(Node node, Class<T> targetType, List<T> results) {
238         if (node.getClass().equals(targetType)) {
239             results.add((T) node);
240         }
241 
242         if (node instanceof ASTClassOrInterfaceDeclaration && ((ASTClassOrInterfaceDeclaration) node).isNested()) {
243             return;
244         }
245 
246         if (node instanceof ASTClassOrInterfaceBodyDeclaration
247                 && ((ASTClassOrInterfaceBodyDeclaration) node).isAnonymousInnerClass()) {
248             return;
249         }
250 
251         for (int i = 0; i < node.jjtGetNumChildren(); i++) {
252             Node child = node.jjtGetChild(i);
253             if (child.getClass().equals(targetType)) {
254                 results.add((T) child);
255             }
256         }
257     }
258 }