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 net.sourceforge.pmd.AbstractRule;
7 import net.sourceforge.pmd.ast.ASTAdditiveExpression;
8 import net.sourceforge.pmd.ast.ASTBlockStatement;
9 import net.sourceforge.pmd.ast.ASTFieldDeclaration;
10 import net.sourceforge.pmd.ast.ASTFormalParameter;
11 import net.sourceforge.pmd.ast.ASTIfStatement;
12 import net.sourceforge.pmd.ast.ASTLiteral;
13 import net.sourceforge.pmd.ast.ASTName;
14 import net.sourceforge.pmd.ast.ASTPrimaryExpression;
15 import net.sourceforge.pmd.ast.ASTPrimaryPrefix;
16 import net.sourceforge.pmd.ast.ASTPrimarySuffix;
17 import net.sourceforge.pmd.ast.ASTSwitchLabel;
18 import net.sourceforge.pmd.ast.ASTSwitchStatement;
19 import net.sourceforge.pmd.ast.ASTVariableDeclaratorId;
20 import net.sourceforge.pmd.ast.Node;
21 import net.sourceforge.pmd.ast.SimpleNode;
22 import net.sourceforge.pmd.symboltable.NameOccurrence;
23
24 import java.util.HashMap;
25 import java.util.HashSet;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Set;
30
31 /***
32 * This rule finds StringBuffers which may have been pre-sized incorrectly
33 *
34 * See http://sourceforge.net/forum/forum.php?thread_id=1438119&forum_id=188194
35 * @author Allan Caplan
36 */
37 public class InsufficientStringBufferDeclaration extends AbstractRule {
38
39 private final static Set blockParents;
40
41 static {
42 blockParents = new HashSet();
43 blockParents.add(ASTIfStatement.class);
44 blockParents.add(ASTSwitchStatement.class);
45 }
46
47 public Object visit(ASTVariableDeclaratorId node, Object data) {
48
49 if (!"StringBuffer".equals(node.getNameDeclaration().getTypeImage())) {
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 usage = node.getUsages();
59 Map blocks = new HashMap();
60 for (int ix = 0; ix < usage.size(); ix++) {
61 NameOccurrence no = (NameOccurrence) 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 = (ASTPrimaryExpression) 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 blocks, int thisSize, Node block) {
119 Node statement = block.jjtGetParent();
120 if (ASTIfStatement.class.equals(block.jjtGetParent().getClass())) {
121
122
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 thisBranch = (Map) blocks.get(statement);
130 if (thisBranch == null) {
131 thisBranch = new HashMap();
132 blocks.put(statement, thisBranch);
133 }
134 Integer x = (Integer) thisBranch.get(block);
135 if (x != null) {
136 thisSize += x.intValue();
137 }
138 thisBranch.put(statement, new Integer(thisSize));
139 }
140
141 private int processBlocks(Map blocks) {
142 int anticipatedLength = 0;
143 int ifLength = 0;
144 for (Iterator iter = blocks.entrySet().iterator(); iter.hasNext();) {
145 Map.Entry entry = (Map.Entry) iter.next();
146 ifLength = 0;
147 for (Iterator iter2 = ((Map) entry.getValue()).entrySet().iterator(); iter2.hasNext();) {
148 Map.Entry entry2 = (Map.Entry) iter2.next();
149 Integer value = (Integer) entry2.getValue();
150 ifLength = Math.max(ifLength, value.intValue());
151 }
152 anticipatedLength += ifLength;
153 }
154 return anticipatedLength;
155 }
156
157 private int processAdditive(SimpleNode sn) {
158 ASTAdditiveExpression additive = (ASTAdditiveExpression) sn.getFirstChildOfType(ASTAdditiveExpression.class);
159 if (additive == null) {
160 return 0;
161 }
162 int anticipatedLength = 0;
163 for (int ix = 0; ix < additive.jjtGetNumChildren(); ix++) {
164 SimpleNode childNode = (SimpleNode) additive.jjtGetChild(ix);
165 ASTLiteral literal = (ASTLiteral) childNode.getFirstChildOfType(ASTLiteral.class);
166 if (literal != null && literal.getImage() != null) {
167 anticipatedLength += literal.getImage().length() - 2;
168 }
169 }
170
171 return anticipatedLength;
172 }
173
174 private static final boolean isLiteral(String str) {
175 if (str.length() == 0) {
176 return false;
177 }
178 char c = str.charAt(0);
179 return (c == '"' || c == '\'');
180 }
181
182 private int processNode(SimpleNode sn) {
183 int anticipatedLength = 0;
184 ASTPrimaryPrefix xn = (ASTPrimaryPrefix) sn.getFirstChildOfType(ASTPrimaryPrefix.class);
185 if (xn.jjtGetNumChildren() != 0 && xn.jjtGetChild(0).getClass().equals(ASTLiteral.class)) {
186 String str = ((SimpleNode) xn.jjtGetChild(0)).getImage();
187 if(isLiteral(str)){
188 anticipatedLength += str.length() - 2;
189 } else if(str.startsWith("0x")){
190 anticipatedLength += 1;
191 } else {
192 anticipatedLength += str.length();
193 }
194 }
195 return anticipatedLength;
196 }
197
198 private int getConstructorLength(SimpleNode node, int constructorLength) {
199 int iConstructorLength = constructorLength;
200 SimpleNode block = (SimpleNode) node.getFirstParentOfType(ASTBlockStatement.class);
201 List literal;
202
203 if (block == null) {
204 block = (ASTFieldDeclaration) node.getFirstParentOfType(ASTFieldDeclaration.class);
205 }
206 if (block == null) {
207 block = (ASTFormalParameter) node.getFirstParentOfType(ASTFormalParameter.class);
208 if (block != null) {
209 iConstructorLength = -1;
210 }
211 }
212 literal = (block.findChildrenOfType(ASTLiteral.class));
213 if (literal.isEmpty()) {
214 List name = (block.findChildrenOfType(ASTName.class));
215 if (!name.isEmpty()) {
216 iConstructorLength = -1;
217 }
218 } else if (literal.size() == 1) {
219 String str = ((SimpleNode) literal.get(0)).getImage();
220 if (str == null) {
221 iConstructorLength = 0;
222 } else if (isLiteral(str)) {
223
224
225
226 iConstructorLength = 14 + str.length();
227 } else {
228 iConstructorLength = Integer.parseInt(str);
229 }
230 } else {
231 iConstructorLength = -1;
232 }
233
234 if(iConstructorLength == 0){
235 iConstructorLength = 16;
236 }
237
238 return iConstructorLength;
239 }
240
241
242 private int getInitialLength(SimpleNode node) {
243 SimpleNode block = (SimpleNode) node.getFirstParentOfType(ASTBlockStatement.class);
244 List literal;
245
246 if (block == null) {
247 block = (ASTFieldDeclaration) node.getFirstParentOfType(ASTFieldDeclaration.class);
248 if (block == null) {
249 block = (ASTFormalParameter) node.getFirstParentOfType(ASTFormalParameter.class);
250 }
251 }
252 literal = (block.findChildrenOfType(ASTLiteral.class));
253 if (literal.size() == 1) {
254 String str = ((SimpleNode) literal.get(0)).getImage();
255 if (str != null && isLiteral(str)) {
256 return str.length() - 2;
257 }
258 }
259
260 return 0;
261 }
262
263 private boolean isAdditive(SimpleNode n) {
264 return n.findChildrenOfType(ASTAdditiveExpression.class).size() >= 1;
265 }
266
267 /***
268 * Locate the block that the given node is in, if any
269 *
270 * @param node
271 * The node we're looking for a parent of
272 * @return Node - The node that corresponds to any block that may be a
273 * parent of this object
274 */
275 private Node getFirstParentBlock(Node node) {
276 Node parentNode = node.jjtGetParent();
277
278 Node lastNode = node;
279 while (parentNode != null && !blockParents.contains(parentNode.getClass())) {
280 lastNode = parentNode;
281 parentNode = parentNode.jjtGetParent();
282 }
283 if (parentNode != null && ASTIfStatement.class.equals(parentNode.getClass())) {
284 parentNode = lastNode;
285 } else if (parentNode != null && parentNode.getClass().equals(ASTSwitchStatement.class)) {
286 parentNode = getSwitchParent(parentNode, lastNode);
287 }
288 return parentNode;
289 }
290
291 /***
292 * Determine which SwitchLabel we belong to inside a switch
293 *
294 * @param parentNode
295 * The parent node we're looking at
296 * @param lastNode
297 * The last node processed
298 * @return The parent node for the switch statement
299 */
300 private static Node getSwitchParent(Node parentNode, Node lastNode) {
301 int allChildren = parentNode.jjtGetNumChildren();
302 ASTSwitchLabel label = null;
303 for (int ix = 0; ix < allChildren; ix++) {
304 Node n = parentNode.jjtGetChild(ix);
305 if (n.getClass().equals(ASTSwitchLabel.class)) {
306 label = (ASTSwitchLabel) n;
307 } else if (n.equals(lastNode)) {
308 parentNode = label;
309 break;
310 }
311 }
312 return parentNode;
313 }
314
315 }