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 }