View Javadoc

    1   package net.sourceforge.pmd.lang.java.rule.optimizations;
    2
    3   import java.util.ArrayList;
    4   import java.util.List;
    5
    6   import net.sourceforge.pmd.lang.ast.Node;
    7   import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement;
    8   import net.sourceforge.pmd.lang.java.ast.ASTForInit;
    9   import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration;
    10  import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
    11  import net.sourceforge.pmd.lang.java.ast.ASTName;
    12  import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement;
    13  import net.sourceforge.pmd.lang.java.ast.ASTThrowStatement;
    14  import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator;
    15  import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
    16  import net.sourceforge.pmd.lang.java.ast.AbstractJavaNode;
    17  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
    18
    19  /**
    20   * Checks for variables in methods that are defined before they are really needed.
    21   * A reference is deemed to be premature if it is created ahead of a block of code
    22   * that doesn't use it that also has the ability to return or throw an exception.
    23   *
    24   * @author Brian Remedios
    25   */
    26  public class PrematureDeclarationRule extends AbstractJavaRule {
    27
    28  	public PrematureDeclarationRule() { }
    29
    30      /**
    31       *
    32       * @param node ASTLocalVariableDeclaration
    33       * @param data Object
    34       * @return Object
    35       * @see net.sourceforge.pmd.lang.java.ast.JavaParserVisitor#visit(ASTLocalVariableDeclaration, Object)
    36       */
    37      public Object visit(ASTLocalVariableDeclaration node, Object data) {
    38
    39      	// is it part of a for-loop declaration?
    40      	if (node.jjtGetParent().getClass().equals(ASTForInit.class)) {
    41      	   return visit((AbstractJavaNode) node, data);	// yes, those don't count
    42      	}
    43
    44      	String varName = varNameIn(node);
    45
    46      	AbstractJavaNode grandparent = (AbstractJavaNode)node.jjtGetParent().jjtGetParent();
    47
    48         	List<Node> nextBlocks = blocksAfter(grandparent, node);
    49
    50         	ASTBlockStatement statement;
    51
    52         	for (Node block : nextBlocks) {
    53
    54         		statement = (ASTBlockStatement)block;
    55
    56         		if (hasReferencesIn(statement, varName)) break;
    57
    58         		if (hasExit(statement)) {
    59         			addViolation(data, node, varName);
    60         			break;
    61         		}
    62         	}
    63
    64          return visit((AbstractJavaNode) node, data);
    65      }
    66
    67      /**
    68       * Return whether a class of the specified type exists between the node argument
    69       * and the topParent argument.
    70       *
    71       * @param node Node
    72       * @param intermediateParentClass Class
    73       * @param topParent Node
    74       * @return boolean
    75       */
    76      public static boolean hasAsParentBetween(Node node, Class<?> intermediateParentClass, Node topParent) {
    77
    78      	Node currentParent = node.jjtGetParent();
    79
    80      	while (currentParent != topParent) {
    81      		currentParent = currentParent.jjtGetParent();
    82      		if (currentParent.getClass().equals(intermediateParentClass)) return true;
    83      	}
    84      	return false;
    85      }
    86
    87      /**
    88       * Returns whether the block contains a return call or throws an exception.
    89       * Exclude blocks that have these things as part of an inner class.
    90       *
    91       * @param block ASTBlockStatement
    92       * @return boolean
    93       */
    94      @SuppressWarnings({ "rawtypes", "unchecked" })
    95  	private boolean hasExit(ASTBlockStatement block) {
    96
    97      	List exitBlocks = block.findDescendantsOfType(ASTReturnStatement.class);
    98      	exitBlocks.addAll(block.findDescendantsOfType(ASTThrowStatement.class));
    99
    100     	if (exitBlocks.isEmpty()) return false;
    101
    102     	// now check to see if the ones we have are part of a method on a declared inner class
    103     	for (int i=0; i<exitBlocks.size(); i++) {
    104     		Node exitNode = (Node)exitBlocks.get(i);
    105     		if (hasAsParentBetween(exitNode, ASTMethodDeclaration.class, block)) continue;
    106     		return true;
    107     	}
    108
    109     	return false;
    110     }
    111
    112     /**
    113      * Returns whether the variable is mentioned within the statement block
    114      * or not.
    115      *
    116      * @param block ASTBlockStatement
    117      * @param varName String
    118      * @return boolean
    119      */
    120     private static boolean hasReferencesIn(ASTBlockStatement block, String varName) {
    121
    122     	List<ASTName> names = block.findDescendantsOfType(ASTName.class);
    123
    124     	for (ASTName name : names) {
    125     		if (isReference(varName, name.getImage())) return true;
    126     	}
    127     	return false;
    128     }
    129
    130     /**
    131      * Return whether the shortName is part of the compound name
    132      * by itself or as a method call receiver.
    133      *
    134      * @param shortName String
    135      * @param compoundName String
    136      * @return boolean
    137      */
    138     private static boolean isReference(String shortName, String compoundName) {
    139
    140     	int dotPos = compoundName.indexOf('.');
    141
    142     	return dotPos < 0 ?
    143     		shortName.equals(compoundName) :
    144     		shortName.endsWith(compoundName.substring(0, dotPos));
    145     }
    146
    147     /**
    148      * Return the name of the variable we just assigned something to.
    149      *
    150      * @param node ASTLocalVariableDeclaration
    151      * @return String
    152      */
    153     private static String varNameIn(ASTLocalVariableDeclaration node) {
    154         ASTVariableDeclarator declarator = node.getFirstChildOfType(ASTVariableDeclarator.class);
    155         return ((ASTVariableDeclaratorId) declarator.jjtGetChild(0)).getImage();
    156     }
    157
    158     /**
    159      * Returns the index of the node block in relation to its siblings.
    160      *
    161      * @param block SimpleJavaNode
    162      * @param node Node
    163      * @return int
    164      */
    165     private static int indexOf(AbstractJavaNode block, Node node) {
    166
    167     	int count = block.jjtGetNumChildren();
    168
    169     	for (int i=0; i<count; i++) {
    170     		if (node == block.jjtGetChild(i)) return i;
    171     	}
    172
    173     	return -1;
    174     }
    175
    176     /**
    177      * Returns all the blocks found right after the node supplied within
    178      * the its current scope.
    179      *
    180      * @param block SimpleJavaNode
    181      * @param node SimpleNode
    182      * @return List
    183      */
    184     private static List<Node> blocksAfter(AbstractJavaNode block, AbstractJavaNode node) {
    185
    186     	int count = block.jjtGetNumChildren();
    187     	int start = indexOf(block, node.jjtGetParent()) + 1;
    188
    189     	List<Node> nextBlocks = new ArrayList<Node>(count);
    190
    191     	for (int i=start; i<count; i++) {
    192     		nextBlocks.add(block.jjtGetChild(i));
    193     	}
    194
    195     	return nextBlocks;
    196     }
    197 }