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