InsufficientStringBufferDeclarationRule.java
001 /**
002  * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003  */
004 package net.sourceforge.pmd.lang.java.rule.strings;
005 
006 import java.util.HashMap;
007 import java.util.HashSet;
008 import java.util.List;
009 import java.util.Map;
010 import java.util.Set;
011 
012 import net.sourceforge.pmd.lang.ast.Node;
013 import net.sourceforge.pmd.lang.java.ast.ASTAdditiveExpression;
014 import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement;
015 import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
016 import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter;
017 import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
018 import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
019 import net.sourceforge.pmd.lang.java.ast.ASTMultiplicativeExpression;
020 import net.sourceforge.pmd.lang.java.ast.ASTName;
021 import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
022 import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
023 import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix;
024 import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel;
025 import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement;
026 import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
027 import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
028 import net.sourceforge.pmd.lang.java.symboltable.NameOccurrence;
029 import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper;
030 
031 /**
032  * This rule finds StringBuffers which may have been pre-sized incorrectly
033  *
034  * See http://sourceforge.net/forum/forum.php?thread_id=1438119&forum_id=188194
035  @author Allan Caplan
036  */
037 public class InsufficientStringBufferDeclarationRule extends AbstractJavaRule {
038 
039     private final static Set<Class<? extends Node>> BLOCK_PARENTS;
040 
041     static {
042         BLOCK_PARENTS = new HashSet<Class<? extends Node>>();
043         BLOCK_PARENTS.add(ASTIfStatement.class);
044         BLOCK_PARENTS.add(ASTSwitchStatement.class);
045     }
046 
047     @Override
048     public Object visit(ASTVariableDeclaratorId node, Object data) {
049         if (!TypeHelper.isA(node.getNameDeclaration(), StringBuffer.class)) {
050             return data;
051         }
052         Node rootNode = node;
053         int anticipatedLength = 0;
054         int constructorLength = 16;
055 
056         constructorLength = getConstructorLength(node, constructorLength);
057         anticipatedLength = getInitialLength(node);
058         List<NameOccurrence> usage = node.getUsages();
059         Map<Node, Map<Node, Integer>> blocks = new HashMap<Node, Map<Node, Integer>>();
060         for (int ix = 0; ix < usage.size(); ix++) {
061             NameOccurrence no = usage.get(ix);
062             Node n = no.getLocation();
063             if (!InefficientStringBufferingRule.isInStringBufferOperation(n, 3"append")) {
064 
065                 if (!no.isOnLeftHandSide() && !InefficientStringBufferingRule.isInStringBufferOperation(n, 3"setLength")) {
066                     continue;
067                 }
068                 if (constructorLength != -&& anticipatedLength > constructorLength) {
069                     anticipatedLength += processBlocks(blocks);
070                     String[] param = String.valueOf(constructorLength), String.valueOf(anticipatedLength) };
071                     addViolation(data, rootNode, param);
072                 }
073                 constructorLength = getConstructorLength(n, constructorLength);
074                 rootNode = n;
075                 anticipatedLength = getInitialLength(node);
076             }
077             ASTPrimaryExpression s = n.getFirstParentOfType(ASTPrimaryExpression.class);
078             int numChildren = s.jjtGetNumChildren();
079             for (int jx = 0; jx < numChildren; jx++) {
080           Node sn = s.jjtGetChild(jx);
081                 if (!(sn instanceof ASTPrimarySuffix|| sn.getImage() != null) {
082                     continue;
083                 }
084                 int thisSize = 0;
085                 Node block = getFirstParentBlock(sn);
086                 if (isAdditive(sn)) {
087                     thisSize = processAdditive(sn);
088                 else {
089                     thisSize = processNode(sn);
090                 }
091                 if (block != null) {
092                     storeBlockStatistics(blocks, thisSize, block);
093                 else {
094                     anticipatedLength += thisSize;
095                 }
096             }
097         }
098         anticipatedLength += processBlocks(blocks);
099         if (constructorLength != -&& 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 (block.jjtGetParent() instanceof ASTIfStatement) {
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 = statement.getFirstParentOfType(ASTIfStatement.class);
124             while (possibleStatement instanceof ASTIfStatement) {
125                 statement = possibleStatement;
126                 possibleStatement = 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(Node sn) {
156         ASTAdditiveExpression additive = sn.getFirstDescendantOfType(ASTAdditiveExpression.class);
157         if (additive == null) {
158             return 0;
159         }
160         int anticipatedLength = 0;
161         for (int ix = 0; ix < additive.jjtGetNumChildren(); ix++) {
162             Node childNode = additive.jjtGetChild(ix);
163             ASTLiteral literal = childNode.getFirstDescendantOfType(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(Node sn) {
181         int anticipatedLength = 0;
182         ASTPrimaryPrefix xn = sn.getFirstDescendantOfType(ASTPrimaryPrefix.class);
183         if (xn.jjtGetNumChildren() != && xn.jjtGetChild(0instanceof ASTLiteral) {
184             String str = 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(Node node, int constructorLength) {
199         int iConstructorLength = constructorLength;
200         Node 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.getFirstDescendantOfType(ASTAdditiveExpression.class);
215         if(exp != null){
216             return 16;
217         }
218         ASTMultiplicativeExpression mult = block.getFirstDescendantOfType(ASTMultiplicativeExpression.class);
219         if(mult != null){
220             return 16;
221         }
222 
223         literal = block.findDescendantsOfType(ASTLiteral.class);
224         if (literal.isEmpty()) {
225             List<ASTName> name = block.findDescendantsOfType(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             if (constructorLength == -1) {
247           iConstructorLength = 16;
248             else {
249           iConstructorLength = constructorLength;
250             }
251         }
252 
253         return iConstructorLength;
254     }
255 
256 
257     private int getInitialLength(Node node) {
258   Node block = node.getFirstParentOfType(ASTBlockStatement.class);
259 
260         if (block == null) {
261             block = node.getFirstParentOfType(ASTFieldDeclaration.class);
262             if (block == null) {
263                 block = node.getFirstParentOfType(ASTFormalParameter.class);
264             }
265         }
266         List<ASTLiteral> literal = block.findDescendantsOfType(ASTLiteral.class);
267         if (literal.size() == 1) {
268             String str = literal.get(0).getImage();
269             if (str != null && isLiteral(str)) {
270                 return str.length() 2// take off the quotes
271             }
272         }
273 
274         return 0;
275     }
276 
277     private boolean isAdditive(Node n) {
278         return n.hasDescendantOfType(ASTAdditiveExpression.class);
279     }
280 
281     /**
282      * Locate the block that the given node is in, if any
283      *
284      @param node
285      *            The node we're looking for a parent of
286      @return Node - The node that corresponds to any block that may be a
287      *         parent of this object
288      */
289     private Node getFirstParentBlock(Node node) {
290         Node parentNode = node.jjtGetParent();
291 
292         Node lastNode = node;
293         while (parentNode != null && !BLOCK_PARENTS.contains(parentNode.getClass())) {
294             lastNode = parentNode;
295             parentNode = parentNode.jjtGetParent();
296         }
297         if (parentNode instanceof ASTIfStatement) {
298             parentNode = lastNode;
299         else if (parentNode instanceof ASTSwitchStatement) {
300             parentNode = getSwitchParent(parentNode, lastNode);
301         }
302         return parentNode;
303     }
304 
305     /**
306      * Determine which SwitchLabel we belong to inside a switch
307      *
308      @param parentNode
309      *            The parent node we're looking at
310      @param lastNode
311      *            The last node processed
312      @return The parent node for the switch statement
313      */
314     private static Node getSwitchParent(Node parentNode, Node lastNode) {
315         int allChildren = parentNode.jjtGetNumChildren();
316         ASTSwitchLabel label = null;
317         for (int ix = 0; ix < allChildren; ix++) {
318             Node n = parentNode.jjtGetChild(ix);
319             if (instanceof ASTSwitchLabel) {
320                 label = (ASTSwitchLabeln;
321             else if (n.equals(lastNode)) {
322                 parentNode = label;
323                 break;
324             }
325         }
326         return parentNode;
327     }
328 
329 }