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 }