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
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 }