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.typeresolution.rules;
5   
6   import java.util.List;
7   
8   import net.sourceforge.pmd.lang.ast.Node;
9   import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
10  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
11  import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
12  import net.sourceforge.pmd.lang.java.ast.ASTExtendsList;
13  import net.sourceforge.pmd.lang.java.ast.ASTImplementsList;
14  import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration;
15  import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
16  import net.sourceforge.pmd.lang.java.ast.ASTName;
17  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
18  import net.sourceforge.pmd.lang.rule.properties.BooleanProperty;
19  
20  /**
21   * A method/constructor shouldn't explicitly throw java.lang.Exception, since it
22   * is unclear which exceptions that can be thrown from the methods. It might be
23   * difficult to document and understand the vague interfaces. Use either a class
24   * derived from RuntimeException or a checked exception. This version uses PMD's
25   * type resolution facilities, and can detect if the class implements or extends
26   * TestCase class
27   *
28   * @author <a mailto:trondandersen@c2i.net>Trond Andersen</a>
29   * @author acaplan
30   * @author Wouter Zelle
31   */
32  public class SignatureDeclareThrowsException extends AbstractJavaRule {
33  	
34      private static final BooleanProperty IGNORE_JUNIT_COMPLETELY_DESCRIPTOR = new BooleanProperty("IgnoreJUnitCompletely",
35          "Allow all methods in a JUnit testcase to throw Exceptions", false, 1.0f);
36  
37      //Set to true when the class is determined to be a JUnit testcase
38      private boolean junitImported = false;
39      
40      public SignatureDeclareThrowsException() {
41  	definePropertyDescriptor(IGNORE_JUNIT_COMPLETELY_DESCRIPTOR);
42      }
43      
44      @Override
45      public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
46          if (junitImported) {
47  	    return super.visit(node, data);
48  	}
49  
50          ASTImplementsList impl = node.getFirstChildOfType(ASTImplementsList.class);
51          if (impl != null && impl.jjtGetParent().equals(node)) {
52              for (int ix = 0; ix < impl.jjtGetNumChildren(); ix++) {
53                  ASTClassOrInterfaceType type = (ASTClassOrInterfaceType) impl.jjtGetChild(ix);
54                  if (isJUnitTest(type)) {
55                      junitImported = true;
56                      return super.visit(node, data);
57                  }
58              }
59          }
60          if (node.jjtGetNumChildren() != 0 && node.jjtGetChild(0) instanceof ASTExtendsList) {
61              ASTClassOrInterfaceType type = (ASTClassOrInterfaceType) node.jjtGetChild(0).jjtGetChild(0);
62              if (isJUnitTest(type)) {
63                  junitImported = true;
64                  return super.visit(node, data);
65              }
66          }
67  
68          return super.visit(node, data);
69      }
70  
71      private boolean isJUnitTest(ASTClassOrInterfaceType type) {
72      	Class<?> clazz = type.getType();
73          if (clazz == null) {
74              if ("junit.framework.Test".equals(type.getImage())) {
75              	return true;
76              }
77          } else if (isJUnitTest(clazz)) {
78          	return true;
79          } else {
80          	while (clazz != null && !Object.class.equals(clazz)) {
81  	        	for(Class<?> intf : clazz.getInterfaces()) {
82  	        		if (isJUnitTest(intf)) {
83  	        			return true;
84  	        		}
85  	        	}
86                  clazz = clazz.getSuperclass();
87          	}
88          }
89          return false;
90      }
91  
92      private boolean isJUnitTest(Class<?> clazz) {
93      	return clazz.getName().equals("junit.framework.Test");
94      }
95  
96      @Override
97      public Object visit(ASTImportDeclaration node, Object o) {
98          if (node.getImportedName().indexOf("junit") != -1) {
99              junitImported = true;
100         }
101         return super.visit(node, o);
102     }
103 
104 
105     @Override
106     public Object visit(ASTMethodDeclaration methodDeclaration, Object o) {
107         if (junitImported && isAllowedMethod(methodDeclaration)) {
108             return super.visit(methodDeclaration, o);
109         }
110 
111         checkExceptions(methodDeclaration, o);
112 
113         return super.visit(methodDeclaration, o);
114     }
115 
116     private boolean isAllowedMethod(ASTMethodDeclaration methodDeclaration) {
117         if (getProperty(IGNORE_JUNIT_COMPLETELY_DESCRIPTOR)) {
118 	    return true;
119 	} else {
120 	    return methodDeclaration.getMethodName().equals("setUp") || methodDeclaration
121                 .getMethodName().equals("tearDown");
122 	}
123     }
124 
125     @Override
126     public Object visit(ASTConstructorDeclaration constructorDeclaration, Object o) {
127         checkExceptions(constructorDeclaration, o);
128 
129         return super.visit(constructorDeclaration, o);
130     }
131 
132     /**
133      * Search the list of thrown exceptions for Exception
134      */
135     private void checkExceptions(Node method, Object o) {
136         List<ASTName> exceptionList = method.findDescendantsOfType(ASTName.class);
137         if (!exceptionList.isEmpty()) {
138             evaluateExceptions(exceptionList, o);
139         }
140     }
141 
142     /**
143      * Checks all exceptions for possible violation on the exception declaration.
144      *
145      * @param exceptionList containing all exception for declaration
146      * @param context
147      */
148     private void evaluateExceptions(List<ASTName> exceptionList, Object context) {
149         for (ASTName exception: exceptionList) {
150             if (hasDeclaredExceptionInSignature(exception)) {
151                 addViolation(context, exception);
152             }
153         }
154     }
155 
156     /**
157      * Checks if the given value is defined as <code>Exception</code> and the parent is either
158      * a method or constructor declaration.
159      *
160      * @param exception to evaluate
161      * @return true if <code>Exception</code> is declared and has proper parents
162      */
163     private boolean hasDeclaredExceptionInSignature(ASTName exception) {
164         return exception.hasImageEqualTo("Exception") && isParentSignatureDeclaration(exception);
165     }
166 
167     /**
168      * @param exception to evaluate
169      * @return true if parent node is either a method or constructor declaration
170      */
171     private boolean isParentSignatureDeclaration(ASTName exception) {
172         Node parent = exception.jjtGetParent().jjtGetParent();
173         return parent instanceof ASTMethodDeclaration || parent instanceof ASTConstructorDeclaration;
174     }
175 }