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