View Javadoc

1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.rules.strings;
5   
6   import java.util.HashMap;
7   import java.util.HashSet;
8   import java.util.List;
9   import java.util.Map;
10  import java.util.Set;
11  
12  import net.sourceforge.pmd.AbstractRule;
13  import net.sourceforge.pmd.ast.ASTAdditiveExpression;
14  import net.sourceforge.pmd.ast.ASTBlockStatement;
15  import net.sourceforge.pmd.ast.ASTFieldDeclaration;
16  import net.sourceforge.pmd.ast.ASTFormalParameter;
17  import net.sourceforge.pmd.ast.ASTIfStatement;
18  import net.sourceforge.pmd.ast.ASTLiteral;
19  import net.sourceforge.pmd.ast.ASTMultiplicativeExpression;
20  import net.sourceforge.pmd.ast.ASTName;
21  import net.sourceforge.pmd.ast.ASTPrimaryExpression;
22  import net.sourceforge.pmd.ast.ASTPrimaryPrefix;
23  import net.sourceforge.pmd.ast.ASTPrimarySuffix;
24  import net.sourceforge.pmd.ast.ASTSwitchLabel;
25  import net.sourceforge.pmd.ast.ASTSwitchStatement;
26  import net.sourceforge.pmd.ast.ASTVariableDeclaratorId;
27  import net.sourceforge.pmd.ast.Node;
28  import net.sourceforge.pmd.ast.SimpleNode;
29  import net.sourceforge.pmd.symboltable.NameOccurrence;
30  import net.sourceforge.pmd.typeresolution.TypeHelper;
31  
32  /**
33   * This rule finds StringBuffers which may have been pre-sized incorrectly
34   * 
35   * See http://sourceforge.net/forum/forum.php?thread_id=1438119&forum_id=188194
36   * @author Allan Caplan
37   */
38  public class InsufficientStringBufferDeclaration extends AbstractRule {
39  
40      private final static Set<Class<? extends SimpleNode>> blockParents;
41  
42      static {
43          blockParents = new HashSet<Class<? extends SimpleNode>>();
44          blockParents.add(ASTIfStatement.class);
45          blockParents.add(ASTSwitchStatement.class);
46      }
47  
48      public Object visit(ASTVariableDeclaratorId node, Object data) {
49          if (!TypeHelper.isA(node.getNameDeclaration(), StringBuffer.class)) {
50              return data;
51          }
52          Node rootNode = node;
53          int anticipatedLength = 0;
54          int constructorLength = 16;
55  
56          constructorLength = getConstructorLength(node, constructorLength);
57          anticipatedLength = getInitialLength(node);
58          List<NameOccurrence> usage = node.getUsages();
59          Map<Node, Map<Node, Integer>> blocks = new HashMap<Node, Map<Node, Integer>>();
60          for (int ix = 0; ix < usage.size(); ix++) {
61              NameOccurrence no = usage.get(ix);
62              SimpleNode n = no.getLocation();
63              if (!InefficientStringBuffering.isInStringBufferOperation(n, 3, "append")) {
64  
65                  if (!no.isOnLeftHandSide() && !InefficientStringBuffering.isInStringBufferOperation(n, 3, "setLength")) {
66                      continue;
67                  }
68                  if (constructorLength != -1 && anticipatedLength > constructorLength) {
69                      anticipatedLength += processBlocks(blocks);
70                      String[] param = { String.valueOf(constructorLength), String.valueOf(anticipatedLength) };
71                      addViolation(data, rootNode, param);
72                  }
73                  constructorLength = getConstructorLength(n, constructorLength);
74                  rootNode = n;
75                  anticipatedLength = getInitialLength(node);
76              }
77              ASTPrimaryExpression s = n.getFirstParentOfType(ASTPrimaryExpression.class);
78              int numChildren = s.jjtGetNumChildren();
79              for (int jx = 0; jx < numChildren; jx++) {
80                  SimpleNode sn = (SimpleNode) s.jjtGetChild(jx);
81                  if (!(sn instanceof ASTPrimarySuffix) || sn.getImage() != null) {
82                      continue;
83                  }
84                  int thisSize = 0;
85                  Node block = getFirstParentBlock(sn);
86                  if (isAdditive(sn)) {
87                      thisSize = processAdditive(sn);
88                  } else {
89                      thisSize = processNode(sn);
90                  }
91                  if (block != null) {
92                      storeBlockStatistics(blocks, thisSize, block);
93                  } else {
94                      anticipatedLength += thisSize;
95                  }
96              }
97          }
98          anticipatedLength += processBlocks(blocks);
99          if (constructorLength != -1 && anticipatedLength > constructorLength) {
100             String[] param = { String.valueOf(constructorLength), String.valueOf(anticipatedLength) };
101             addViolation(data, rootNode, param);
102         }
103         return data;
104     }
105 
106     /**
107      * This rule is concerned with IF and Switch blocks. Process the block into
108      * a local Map, from which we can later determine which is the longest block
109      * inside
110      * 
111      * @param blocks
112      *            The map of blocks in the method being investigated
113      * @param thisSize
114      *            The size of the current block
115      * @param block
116      *            The block in question
117      */
118     private void storeBlockStatistics(Map<Node, Map<Node, Integer>> blocks, int thisSize, Node block) {
119         Node statement = block.jjtGetParent();
120         if (ASTIfStatement.class.equals(block.jjtGetParent().getClass())) {
121             // Else Ifs are their own subnode in AST. So we have to
122             // look a little farther up the tree to find the IF statement
123             Node possibleStatement = ((SimpleNode) statement).getFirstParentOfType(ASTIfStatement.class);
124             while(possibleStatement != null && possibleStatement.getClass().equals(ASTIfStatement.class)) {
125                 statement = possibleStatement;
126                 possibleStatement = ((SimpleNode) possibleStatement).getFirstParentOfType(ASTIfStatement.class);
127             }
128         }
129         Map<Node, Integer> thisBranch = blocks.get(statement);
130         if (thisBranch == null) {
131             thisBranch = new HashMap<Node, Integer>();
132             blocks.put(statement, thisBranch);
133         }
134         Integer x = thisBranch.get(block);
135         if (x != null) {
136             thisSize += x;
137         }
138         thisBranch.put(statement, thisSize);
139     }
140 
141     private int processBlocks(Map<Node, Map<Node, Integer>> blocks) {
142         int anticipatedLength = 0;
143         int ifLength = 0;
144         for (Map.Entry<Node, Map<Node, Integer>> entry: blocks.entrySet()) {
145             ifLength = 0;
146             for (Map.Entry<Node, Integer> entry2: entry.getValue().entrySet()) {
147                 Integer value = entry2.getValue();
148                 ifLength = Math.max(ifLength, value.intValue());
149             }
150             anticipatedLength += ifLength;
151         }
152         return anticipatedLength;
153     }
154 
155     private int processAdditive(SimpleNode sn) {
156         ASTAdditiveExpression additive = sn.getFirstChildOfType(ASTAdditiveExpression.class);
157         if (additive == null) {
158             return 0;
159         }
160         int anticipatedLength = 0;
161         for (int ix = 0; ix < additive.jjtGetNumChildren(); ix++) {
162             SimpleNode childNode = (SimpleNode) additive.jjtGetChild(ix);
163             ASTLiteral literal = childNode.getFirstChildOfType(ASTLiteral.class);
164             if (literal != null && literal.getImage() != null) {
165                 anticipatedLength += literal.getImage().length() - 2;
166             }
167         }
168 
169         return anticipatedLength;
170     }
171 
172     private static final boolean isLiteral(String str) {
173         if (str.length() == 0) {
174             return false;
175         }
176         char c = str.charAt(0);
177         return (c == '"' || c == '\'');
178     }
179 
180     private int processNode(SimpleNode sn) {
181         int anticipatedLength = 0;
182         ASTPrimaryPrefix xn = sn.getFirstChildOfType(ASTPrimaryPrefix.class);
183         if (xn.jjtGetNumChildren() != 0 && xn.jjtGetChild(0).getClass().equals(ASTLiteral.class)) {
184             String str = ((SimpleNode) xn.jjtGetChild(0)).getImage();
185             if (str != null) {
186 	            if(isLiteral(str)){
187 	                anticipatedLength += str.length() - 2;
188 	            } else if(str.startsWith("0x")){
189 	                anticipatedLength += 1;
190 	            } else {
191 	                anticipatedLength += str.length();
192 	            }
193             }
194         }
195         return anticipatedLength;
196     }
197 
198     private int getConstructorLength(SimpleNode node, int constructorLength) {
199         int iConstructorLength = constructorLength;
200         SimpleNode block = node.getFirstParentOfType(ASTBlockStatement.class);
201         List<ASTLiteral> literal;
202 
203         if (block == null) {
204             block = node.getFirstParentOfType(ASTFieldDeclaration.class);
205         }
206         if (block == null) {
207             block = node.getFirstParentOfType(ASTFormalParameter.class);
208             if (block != null) {
209                 iConstructorLength = -1;
210             }
211         }
212         
213         //if there is any addition/subtraction going on then just use the default.
214         ASTAdditiveExpression exp = block.getFirstChildOfType(ASTAdditiveExpression.class);
215         if(exp != null){
216             return 16;
217         }
218         ASTMultiplicativeExpression mult = block.getFirstChildOfType(ASTMultiplicativeExpression.class);
219         if(mult != null){
220             return 16;
221         }
222         
223         literal = block.findChildrenOfType(ASTLiteral.class);
224         if (literal.isEmpty()) {
225             List<ASTName> name = block.findChildrenOfType(ASTName.class);
226             if (!name.isEmpty()) {
227                 iConstructorLength = -1;
228             }
229         } else if (literal.size() == 1) {
230             String str = literal.get(0).getImage();
231             if (str == null) {
232                 iConstructorLength = 0;
233             } else if (isLiteral(str)) {
234                 // since it's not taken into account
235                 // anywhere. only count the extra 16
236                 // characters
237                 iConstructorLength = 14 + str.length(); // don't add the constructor's length,
238             } else {
239                 iConstructorLength = Integer.parseInt(str);
240             }
241         } else {
242             iConstructorLength = -1;
243         }
244         
245         if(iConstructorLength == 0){
246             iConstructorLength = 16;
247         }
248 
249         return iConstructorLength;
250     }
251 
252 
253     private int getInitialLength(SimpleNode node) {
254         SimpleNode block = node.getFirstParentOfType(ASTBlockStatement.class);
255 
256         if (block == null) {
257             block = node.getFirstParentOfType(ASTFieldDeclaration.class);
258             if (block == null) {
259                 block = node.getFirstParentOfType(ASTFormalParameter.class);
260             }
261         }
262         List<ASTLiteral> literal = block.findChildrenOfType(ASTLiteral.class);
263         if (literal.size() == 1) {
264             String str = literal.get(0).getImage();
265             if (str != null && isLiteral(str)) {
266                 return str.length() - 2; // take off the quotes
267             }
268         }
269         
270         return 0;
271     }
272 
273     private boolean isAdditive(SimpleNode n) {
274         return n.findChildrenOfType(ASTAdditiveExpression.class).size() >= 1;
275     }
276 
277     /**
278      * Locate the block that the given node is in, if any
279      * 
280      * @param node
281      *            The node we're looking for a parent of
282      * @return Node - The node that corresponds to any block that may be a
283      *         parent of this object
284      */
285     private Node getFirstParentBlock(Node node) {
286         Node parentNode = node.jjtGetParent();
287 
288         Node lastNode = node;
289         while (parentNode != null && !blockParents.contains(parentNode.getClass())) {
290             lastNode = parentNode;
291             parentNode = parentNode.jjtGetParent();
292         }
293         if (parentNode != null && ASTIfStatement.class.equals(parentNode.getClass())) {
294             parentNode = lastNode;
295         } else if (parentNode != null && parentNode.getClass().equals(ASTSwitchStatement.class)) {
296             parentNode = getSwitchParent(parentNode, lastNode);
297         }
298         return parentNode;
299     }
300 
301     /**
302      * Determine which SwitchLabel we belong to inside a switch
303      * 
304      * @param parentNode
305      *            The parent node we're looking at
306      * @param lastNode
307      *            The last node processed
308      * @return The parent node for the switch statement
309      */
310     private static Node getSwitchParent(Node parentNode, Node lastNode) {
311         int allChildren = parentNode.jjtGetNumChildren();
312         ASTSwitchLabel label = null;
313         for (int ix = 0; ix < allChildren; ix++) {
314             Node n = parentNode.jjtGetChild(ix);
315             if (n.getClass().equals(ASTSwitchLabel.class)) {
316                 label = (ASTSwitchLabel) n;
317             } else if (n.equals(lastNode)) {
318                 parentNode = label;
319                 break;
320             }
321         }
322         return parentNode;
323     }
324 
325 }