View Javadoc

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