001: /**
002: * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003: */package net.sourceforge.pmd.typeresolution.rules;
004:
005: import java.util.List;
006:
007: import net.sourceforge.pmd.AbstractJavaRule;
008: import net.sourceforge.pmd.PropertyDescriptor;
009: import net.sourceforge.pmd.ast.ASTClassOrInterfaceDeclaration;
010: import net.sourceforge.pmd.ast.ASTClassOrInterfaceType;
011: import net.sourceforge.pmd.ast.ASTConstructorDeclaration;
012: import net.sourceforge.pmd.ast.ASTExtendsList;
013: import net.sourceforge.pmd.ast.ASTImplementsList;
014: import net.sourceforge.pmd.ast.ASTImportDeclaration;
015: import net.sourceforge.pmd.ast.ASTMethodDeclaration;
016: import net.sourceforge.pmd.ast.ASTName;
017: import net.sourceforge.pmd.ast.Node;
018: import net.sourceforge.pmd.ast.SimpleNode;
019: import net.sourceforge.pmd.properties.BooleanProperty;
020:
021: /**
022: * A method/constructor shouldn't explicitly throw java.lang.Exception, since it
023: * is unclear which exceptions that can be thrown from the methods. It might be
024: * difficult to document and understand the vague interfaces. Use either a class
025: * derived from RuntimeException or a checked exception. This version uses PMD's
026: * type resolution facilities, and can detect if the class implements or extends
027: * TestCase class
028: *
029: * @author <a mailto:trondandersen@c2i.net>Trond Andersen</a>
030: * @author acaplan
031: * @author Wouter Zelle
032: */
033: public class SignatureDeclareThrowsException extends AbstractJavaRule {
034: private static final PropertyDescriptor ignoreJUnitCompletelyDescriptor = new BooleanProperty(
035: "IgnoreJUnitCompletely",
036: "If true, all methods in a JUnit testcase may throw Exception",
037: false, 1.0f);
038:
039: //Set to true when the class is determined to be a JUnit testcase
040: private boolean junitImported = false;
041:
042: public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
043: if (junitImported == true)
044: return super .visit(node, data);
045:
046: ASTImplementsList impl = node
047: .getFirstChildOfType(ASTImplementsList.class);
048: if (impl != null && impl.jjtGetParent().equals(node)) {
049: for (int ix = 0; ix < impl.jjtGetNumChildren(); ix++) {
050: ASTClassOrInterfaceType type = (ASTClassOrInterfaceType) impl
051: .jjtGetChild(ix);
052: if (isJUnitTest(type)) {
053: junitImported = true;
054: return super .visit(node, data);
055: }
056: }
057: }
058: if (node.jjtGetNumChildren() != 0
059: && node.jjtGetChild(0).getClass().equals(
060: ASTExtendsList.class)) {
061: ASTClassOrInterfaceType type = (ASTClassOrInterfaceType) ((SimpleNode) node
062: .jjtGetChild(0)).jjtGetChild(0);
063: if (isJUnitTest(type)) {
064: junitImported = true;
065: return super .visit(node, data);
066: }
067: }
068:
069: return super .visit(node, data);
070: }
071:
072: private boolean isJUnitTest(ASTClassOrInterfaceType type) {
073: Class<?> clazz = type.getType();
074: if (clazz == null) {
075: if ("junit.framework.Test".equals(type.getImage())) {
076: return true;
077: }
078: } else if (isJUnitTest(clazz)) {
079: return true;
080: } else {
081: while (clazz != null && !Object.class.equals(clazz)) {
082: for (Class<?> intf : clazz.getInterfaces()) {
083: if (isJUnitTest(intf)) {
084: return true;
085: }
086: }
087: clazz = clazz.getSuperclass();
088: }
089: }
090: return false;
091: }
092:
093: private boolean isJUnitTest(Class<?> clazz) {
094: return clazz.getName().equals("junit.framework.Test");
095: }
096:
097: public Object visit(ASTImportDeclaration node, Object o) {
098: if (node.getImportedName().indexOf("junit") != -1) {
099: junitImported = true;
100: }
101: return super .visit(node, o);
102: }
103:
104: public Object visit(ASTMethodDeclaration methodDeclaration, Object o) {
105: if (junitImported && isAllowedMethod(methodDeclaration)) {
106: return super .visit(methodDeclaration, o);
107: }
108:
109: checkExceptions(methodDeclaration, o);
110:
111: return super .visit(methodDeclaration, o);
112: }
113:
114: private boolean isAllowedMethod(
115: ASTMethodDeclaration methodDeclaration) {
116: if (getBooleanProperty(ignoreJUnitCompletelyDescriptor))
117: return true;
118: else
119: return (methodDeclaration.getMethodName().equals("setUp") || methodDeclaration
120: .getMethodName().equals("tearDown"));
121: }
122:
123: public Object visit(
124: ASTConstructorDeclaration constructorDeclaration, Object o) {
125: checkExceptions(constructorDeclaration, o);
126:
127: return super .visit(constructorDeclaration, o);
128: }
129:
130: /**
131: * Search the list of thrown exceptions for Exception
132: */
133: private void checkExceptions(SimpleNode method, Object o) {
134: List<ASTName> exceptionList = method
135: .findChildrenOfType(ASTName.class);
136: if (!exceptionList.isEmpty()) {
137: evaluateExceptions(exceptionList, o);
138: }
139: }
140:
141: /**
142: * Checks all exceptions for possible violation on the exception declaration.
143: *
144: * @param exceptionList containing all exception for declaration
145: * @param context
146: */
147: private void evaluateExceptions(List<ASTName> exceptionList,
148: 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")
165: && isParentSignatureDeclaration(exception);
166: }
167:
168: /**
169: * @param exception to evaluate
170: * @return true if parent node is either a method or constructor declaration
171: */
172: private boolean isParentSignatureDeclaration(ASTName exception) {
173: Node parent = exception.jjtGetParent().jjtGetParent();
174: return parent instanceof ASTMethodDeclaration
175: || parent instanceof ASTConstructorDeclaration;
176: }
177: }
|