View Javadoc

    1   /**
    2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
    3    */
    4   package net.sourceforge.pmd.lang.java.typeresolution.rules;
    5
    6   import java.util.List;
    7
    8   import net.sourceforge.pmd.lang.ast.Node;
    9   import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
    10  import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
    11  import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
    12  import net.sourceforge.pmd.lang.java.ast.ASTExtendsList;
    13  import net.sourceforge.pmd.lang.java.ast.ASTImplementsList;
    14  import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration;
    15  import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
    16  import net.sourceforge.pmd.lang.java.ast.ASTName;
    17  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
    18  import net.sourceforge.pmd.lang.rule.properties.BooleanProperty;
    19
    20  /**
    21   * A method/constructor shouldn't explicitly throw java.lang.Exception, since it
    22   * is unclear which exceptions that can be thrown from the methods. It might be
    23   * difficult to document and understand the vague interfaces. Use either a class
    24   * derived from RuntimeException or a checked exception. This version uses PMD's
    25   * type resolution facilities, and can detect if the class implements or extends
    26   * TestCase class
    27   *
    28   * @author <a mailto:trondandersen@c2i.net>Trond Andersen</a>
    29   * @author acaplan
    30   * @author Wouter Zelle
    31   */
    32  public class SignatureDeclareThrowsException extends AbstractJavaRule {
    33
    34      private static final BooleanProperty IGNORE_JUNIT_COMPLETELY_DESCRIPTOR = new BooleanProperty("IgnoreJUnitCompletely",
    35          "Allow all methods in a JUnit testcase to throw Exceptions", false, 1.0f);
    36
    37      //Set to true when the class is determined to be a JUnit testcase
    38      private boolean junitImported = false;
    39
    40      public SignatureDeclareThrowsException() {
    41  	definePropertyDescriptor(IGNORE_JUNIT_COMPLETELY_DESCRIPTOR);
    42      }
    43
    44      @Override
    45      public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
    46          if (junitImported) {
    47  	    return super.visit(node, data);
    48  	}
    49
    50          ASTImplementsList impl = node.getFirstChildOfType(ASTImplementsList.class);
    51          if (impl != null && impl.jjtGetParent().equals(node)) {
    52              for (int ix = 0; ix < impl.jjtGetNumChildren(); ix++) {
    53                  ASTClassOrInterfaceType type = (ASTClassOrInterfaceType) impl.jjtGetChild(ix);
    54                  if (isJUnitTest(type)) {
    55                      junitImported = true;
    56                      return super.visit(node, data);
    57                  }
    58              }
    59          }
    60          if (node.jjtGetNumChildren() != 0 && node.jjtGetChild(0) instanceof ASTExtendsList) {
    61              ASTClassOrInterfaceType type = (ASTClassOrInterfaceType) node.jjtGetChild(0).jjtGetChild(0);
    62              if (isJUnitTest(type)) {
    63                  junitImported = true;
    64                  return super.visit(node, data);
    65              }
    66          }
    67
    68          return super.visit(node, data);
    69      }
    70
    71      private boolean isJUnitTest(ASTClassOrInterfaceType type) {
    72      	Class<?> clazz = type.getType();
    73          if (clazz == null) {
    74              if ("junit.framework.Test".equals(type.getImage())) {
    75              	return true;
    76              }
    77          } else if (isJUnitTest(clazz)) {
    78          	return true;
    79          } else {
    80          	while (clazz != null && !Object.class.equals(clazz)) {
    81  	        	for(Class<?> intf : clazz.getInterfaces()) {
    82  	        		if (isJUnitTest(intf)) {
    83  	        			return true;
    84  	        		}
    85  	        	}
    86                  clazz = clazz.getSuperclass();
    87          	}
    88          }
    89          return false;
    90      }
    91
    92      private boolean isJUnitTest(Class<?> clazz) {
    93      	return clazz.getName().equals("junit.framework.Test");
    94      }
    95
    96      @Override
    97      public Object visit(ASTImportDeclaration node, Object o) {
    98          if (node.getImportedName().indexOf("junit") != -1) {
    99              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 }