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