1
2
3
4 package net.sourceforge.pmd.lang.java.rule.strings;
5
6 import java.util.HashSet;
7 import java.util.List;
8 import java.util.Map;
9 import java.util.Set;
10
11 import net.sourceforge.pmd.lang.ast.Node;
12 import net.sourceforge.pmd.lang.java.ast.ASTAdditiveExpression;
13 import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression;
14 import net.sourceforge.pmd.lang.java.ast.ASTArgumentList;
15 import net.sourceforge.pmd.lang.java.ast.ASTDoStatement;
16 import net.sourceforge.pmd.lang.java.ast.ASTForStatement;
17 import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
18 import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
19 import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
20 import net.sourceforge.pmd.lang.java.ast.ASTName;
21 import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
22 import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix;
23 import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel;
24 import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement;
25 import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
26 import net.sourceforge.pmd.lang.java.ast.ASTVariableInitializer;
27 import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement;
28 import net.sourceforge.pmd.lang.java.ast.TypeNode;
29 import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
30 import net.sourceforge.pmd.lang.java.symboltable.JavaNameOccurrence;
31 import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
32 import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper;
33 import net.sourceforge.pmd.lang.rule.properties.IntegerProperty;
34 import net.sourceforge.pmd.lang.symboltable.NameOccurrence;
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61 public class ConsecutiveLiteralAppendsRule extends AbstractJavaRule {
62
63 private final static Set<Class<?>> BLOCK_PARENTS;
64
65 static {
66 BLOCK_PARENTS = new HashSet<Class<?>>();
67 BLOCK_PARENTS.add(ASTForStatement.class);
68 BLOCK_PARENTS.add(ASTWhileStatement.class);
69 BLOCK_PARENTS.add(ASTDoStatement.class);
70 BLOCK_PARENTS.add(ASTIfStatement.class);
71 BLOCK_PARENTS.add(ASTSwitchStatement.class);
72 BLOCK_PARENTS.add(ASTMethodDeclaration.class);
73 }
74
75 private static final IntegerProperty THRESHOLD_DESCRIPTOR = new IntegerProperty("threshold",
76 "Max consecutive appends", 1, 10, 1, 1.0f);
77
78 private int threshold = 1;
79
80 public ConsecutiveLiteralAppendsRule() {
81 definePropertyDescriptor(THRESHOLD_DESCRIPTOR);
82 }
83
84 @Override
85 public Object visit(ASTVariableDeclaratorId node, Object data) {
86
87 if (!isStringBuffer(node)) {
88 return data;
89 }
90 threshold = getProperty(THRESHOLD_DESCRIPTOR);
91
92 int concurrentCount = checkConstructor(node, data);
93 if (hasInitializer(node)) {
94 concurrentCount += checkInitializerExpressions(node);
95 }
96 Node lastBlock = getFirstParentBlock(node);
97 Node currentBlock = lastBlock;
98 Map<VariableNameDeclaration, List<NameOccurrence>> decls = node.getScope().getDeclarations(
99 VariableNameDeclaration.class);
100 Node rootNode = null;
101
102 if (concurrentCount >= 1) {
103 rootNode = node;
104 }
105 for (List<NameOccurrence> decl : decls.values()) {
106 for (NameOccurrence no : decl) {
107 JavaNameOccurrence jno = (JavaNameOccurrence) no;
108 Node n = jno.getLocation();
109
110
111 if (!node.getImage().equals(jno.getImage())) {
112 continue;
113 }
114
115 currentBlock = getFirstParentBlock(n);
116
117 if (!InefficientStringBufferingRule.isInStringBufferOperation(n, 3, "append")) {
118 if (!jno.isPartOfQualifiedName()) {
119 checkForViolation(rootNode, data, concurrentCount);
120 concurrentCount = 0;
121 }
122 continue;
123 }
124 ASTPrimaryExpression s = n.getFirstParentOfType(ASTPrimaryExpression.class);
125 int numChildren = s.jjtGetNumChildren();
126 for (int jx = 0; jx < numChildren; jx++) {
127 Node sn = s.jjtGetChild(jx);
128 if (!(sn instanceof ASTPrimarySuffix) || sn.getImage() != null) {
129 continue;
130 }
131
132
133 if (currentBlock != null && lastBlock != null && !currentBlock.equals(lastBlock)
134 || currentBlock == null ^ lastBlock == null) {
135 checkForViolation(rootNode, data, concurrentCount);
136 concurrentCount = 0;
137 }
138
139
140
141 if (concurrentCount == 0) {
142 rootNode = sn;
143 }
144 if (isAdditive(sn)) {
145 concurrentCount = processAdditive(data, concurrentCount, sn, rootNode);
146 if (concurrentCount != 0) {
147 rootNode = sn;
148 }
149 } else if (!isAppendingStringLiteral(sn)) {
150 checkForViolation(rootNode, data, concurrentCount);
151 concurrentCount = 0;
152 } else {
153 concurrentCount++;
154 }
155 lastBlock = currentBlock;
156 }
157 }
158 }
159 checkForViolation(rootNode, data, concurrentCount);
160 return data;
161 }
162
163
164
165
166
167
168
169 private int checkConstructor(ASTVariableDeclaratorId node, Object data) {
170 Node parent = node.jjtGetParent();
171 if (parent.jjtGetNumChildren() >= 2) {
172 ASTAllocationExpression allocationExpression = parent.jjtGetChild(1).getFirstDescendantOfType(ASTAllocationExpression.class);
173 ASTArgumentList list = null;
174 if (allocationExpression != null) {
175 list = allocationExpression.getFirstDescendantOfType(ASTArgumentList.class);
176 }
177
178 if (list != null) {
179 ASTLiteral literal = list.getFirstDescendantOfType(ASTLiteral.class);
180 if (!isAdditive(list) && literal != null && literal.isStringLiteral()) {
181 return 1;
182 }
183 return processAdditive(data, 0, list, node);
184 }
185 }
186 return 0;
187 }
188
189
190
191
192
193
194
195 private int checkInitializerExpressions(ASTVariableDeclaratorId node) {
196 ASTVariableInitializer initializer = node.jjtGetParent().getFirstChildOfType(ASTVariableInitializer.class);
197 ASTPrimaryExpression primary = initializer.getFirstDescendantOfType(ASTPrimaryExpression.class);
198
199 int result = 0;
200 boolean previousWasAppend = false;
201 for (int i = 0; i < primary.jjtGetNumChildren(); i++) {
202 Node child = primary.jjtGetChild(i);
203 if (child.jjtGetNumChildren() > 0 && child.jjtGetChild(0) instanceof ASTAllocationExpression) {
204 continue;
205 }
206 if (child instanceof ASTPrimarySuffix) {
207 ASTPrimarySuffix suffix = (ASTPrimarySuffix)child;
208 if (suffix.jjtGetNumChildren() == 0 && suffix.hasImageEqualTo("append")) {
209 previousWasAppend = true;
210 } else if (suffix.jjtGetNumChildren() > 0 && previousWasAppend) {
211 previousWasAppend = false;
212
213 ASTLiteral literal = suffix.getFirstDescendantOfType(ASTLiteral.class);
214 if (literal != null && literal.isStringLiteral()) {
215 result++;
216 } else {
217
218
219
220 break;
221 }
222 }
223 }
224 }
225
226 return result;
227 }
228
229 private boolean hasInitializer(ASTVariableDeclaratorId node) {
230 return node.jjtGetParent().hasDescendantOfType(ASTVariableInitializer.class);
231 }
232
233 private int processAdditive(Object data, int concurrentCount, Node sn, Node rootNode) {
234 ASTAdditiveExpression additive = sn.getFirstDescendantOfType(ASTAdditiveExpression.class);
235
236 if (additive == null || additive.getType() != null && !TypeHelper.isA(additive, String.class)) {
237 return 0;
238 }
239
240 List<ASTLiteral> literals = additive.findDescendantsOfType(ASTLiteral.class);
241 boolean stringLiteralFound = false;
242 for (ASTLiteral l : literals) {
243 if (l.isCharLiteral() || l.isStringLiteral()) {
244 stringLiteralFound = true;
245 break;
246 }
247 }
248 if (!stringLiteralFound) {
249 return 0;
250 }
251
252 int count = concurrentCount;
253 boolean found = false;
254 for (int ix = 0; ix < additive.jjtGetNumChildren(); ix++) {
255 Node childNode = additive.jjtGetChild(ix);
256 if (childNode.jjtGetNumChildren() != 1 || childNode.hasDescendantOfType(ASTName.class)) {
257 if (!found) {
258 checkForViolation(rootNode, data, count);
259 found = true;
260 }
261 count = 0;
262 } else {
263 count++;
264 }
265 }
266
267
268
269 if (!found) {
270 count = 1;
271 }
272
273 return count;
274 }
275
276
277
278
279
280
281
282
283
284
285
286
287 private boolean isAdditive(Node n) {
288 List<ASTAdditiveExpression> lstAdditive = n.findDescendantsOfType(ASTAdditiveExpression.class);
289 if (lstAdditive.isEmpty()) {
290 return false;
291 }
292
293
294
295 for (int ix = 0; ix < lstAdditive.size(); ix++) {
296 ASTAdditiveExpression expr = lstAdditive.get(ix);
297 if (expr.getParentsOfType(ASTArgumentList.class).size() != 1) {
298 return false;
299 }
300 }
301 return true;
302 }
303
304
305
306
307
308
309
310
311
312
313 private Node getFirstParentBlock(Node node) {
314 Node parentNode = node.jjtGetParent();
315
316 Node lastNode = node;
317 while (parentNode != null && !BLOCK_PARENTS.contains(parentNode.getClass())) {
318 lastNode = parentNode;
319 parentNode = parentNode.jjtGetParent();
320 }
321 if (parentNode instanceof ASTIfStatement) {
322 parentNode = lastNode;
323 } else if (parentNode instanceof ASTSwitchStatement) {
324 parentNode = getSwitchParent(parentNode, lastNode);
325 }
326 return parentNode;
327 }
328
329
330
331
332
333
334
335
336
337
338 private Node getSwitchParent(Node parentNode, Node lastNode) {
339 int allChildren = parentNode.jjtGetNumChildren();
340 ASTSwitchLabel label = null;
341 for (int ix = 0; ix < allChildren; ix++) {
342 Node n = parentNode.jjtGetChild(ix);
343 if (n instanceof ASTSwitchLabel) {
344 label = (ASTSwitchLabel) n;
345 } else if (n.equals(lastNode)) {
346 parentNode = label;
347 break;
348 }
349 }
350 return parentNode;
351 }
352
353
354
355
356
357 private void checkForViolation(Node node, Object data, int concurrentCount) {
358 if (concurrentCount > threshold) {
359 String[] param = { String.valueOf(concurrentCount) };
360 addViolation(data, node, param);
361 }
362 }
363
364 private boolean isAppendingStringLiteral(Node node) {
365 Node n = node;
366 while (n.jjtGetNumChildren() != 0 && !(n instanceof ASTLiteral)) {
367 n = n.jjtGetChild(0);
368 }
369 return n instanceof ASTLiteral;
370 }
371
372 private static boolean isStringBuffer(ASTVariableDeclaratorId node) {
373
374 if (node.getType() != null) {
375
376 return TypeHelper.isEither(node, StringBuffer.class, StringBuilder.class);
377 }
378 Node nn = node.getTypeNameNode();
379 if (nn == null || nn.jjtGetNumChildren() == 0) {
380 return false;
381 }
382 return TypeHelper.isEither((TypeNode) nn.jjtGetChild(0), StringBuffer.class, StringBuilder.class);
383 }
384 }