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
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<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
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
235
236
237 iConstructorLength = 14 + str.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;
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 }