SignatureDeclareThrowsException.java
001 /**
002  * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003  */
004 package net.sourceforge.pmd.lang.java.typeresolution.rules;
005 
006 import java.util.List;
007 
008 import net.sourceforge.pmd.lang.ast.Node;
009 import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
010 import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
011 import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
012 import net.sourceforge.pmd.lang.java.ast.ASTExtendsList;
013 import net.sourceforge.pmd.lang.java.ast.ASTImplementsList;
014 import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration;
015 import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
016 import net.sourceforge.pmd.lang.java.ast.ASTName;
017 import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
018 import net.sourceforge.pmd.lang.rule.properties.BooleanProperty;
019 
020 /**
021  * A method/constructor shouldn't explicitly throw java.lang.Exception, since it
022  * is unclear which exceptions that can be thrown from the methods. It might be
023  * difficult to document and understand the vague interfaces. Use either a class
024  * derived from RuntimeException or a checked exception. This version uses PMD's
025  * type resolution facilities, and can detect if the class implements or extends
026  * TestCase class
027  *
028  @author <a mailto:trondandersen@c2i.net>Trond Andersen</a>
029  @author acaplan
030  @author Wouter Zelle
031  */
032 public class SignatureDeclareThrowsException extends AbstractJavaRule {
033   
034     private static final BooleanProperty IGNORE_JUNIT_COMPLETELY_DESCRIPTOR = new BooleanProperty("IgnoreJUnitCompletely",
035         "Allow all methods in a JUnit testcase to throw Exceptions", false, 1.0f);
036 
037     //Set to true when the class is determined to be a JUnit testcase
038     private boolean junitImported = false;
039     
040     public SignatureDeclareThrowsException() {
041   definePropertyDescriptor(IGNORE_JUNIT_COMPLETELY_DESCRIPTOR);
042     }
043     
044     @Override
045     public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
046         if (junitImported == true) {
047       return super.visit(node, data);
048   }
049 
050         ASTImplementsList impl = node.getFirstChildOfType(ASTImplementsList.class);
051         if (impl != null && impl.jjtGetParent().equals(node)) {
052             for (int ix = 0; ix < impl.jjtGetNumChildren(); ix++) {
053                 ASTClassOrInterfaceType type = (ASTClassOrInterfaceTypeimpl.jjtGetChild(ix);
054                 if (isJUnitTest(type)) {
055                     junitImported = true;
056                     return super.visit(node, data);
057                 }
058             }
059         }
060         if (node.jjtGetNumChildren() != && node.jjtGetChild(0instanceof ASTExtendsList) {
061             ASTClassOrInterfaceType type = (ASTClassOrInterfaceTypenode.jjtGetChild(0).jjtGetChild(0);
062             if (isJUnitTest(type)) {
063                 junitImported = true;
064                 return super.visit(node, data);
065             }
066         }
067 
068         return super.visit(node, data);
069     }
070 
071     private boolean isJUnitTest(ASTClassOrInterfaceType type) {
072       Class<?> clazz = type.getType();
073         if (clazz == null) {
074             if ("junit.framework.Test".equals(type.getImage())) {
075               return true;
076             }
077         else if (isJUnitTest(clazz)) {
078           return true;
079         else {
080           while (clazz != null && !Object.class.equals(clazz)) {
081             for(Class<?> intf : clazz.getInterfaces()) {
082               if (isJUnitTest(intf)) {
083                 return true;
084               }
085             }
086                 clazz = clazz.getSuperclass();
087           }
088         }
089         return false;
090     }
091 
092     private boolean isJUnitTest(Class<?> clazz) {
093       return clazz.getName().equals("junit.framework.Test");
094     }
095 
096     @Override
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 
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 }