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 concurrentCount += checkInitializerExpressions(node);
94 Node lastBlock = getFirstParentBlock(node);
95 Node currentBlock = lastBlock;
96 Map<VariableNameDeclaration, List<NameOccurrence>> decls = node.getScope().getDeclarations(
97 VariableNameDeclaration.class);
98 Node rootNode = null;
99
100 if (concurrentCount >= 1) {
101 rootNode = node;
102 }
103 for (List<NameOccurrence> decl : decls.values()) {
104 for (NameOccurrence no : decl) {
105 JavaNameOccurrence jno = (JavaNameOccurrence) no;
106 Node n = jno.getLocation();
107
108
109 if (!node.getImage().equals(jno.getImage())) {
110 continue;
111 }
112
113 currentBlock = getFirstParentBlock(n);
114
115 if (!InefficientStringBufferingRule.isInStringBufferOperation(n, 3, "append")) {
116 if (!jno.isPartOfQualifiedName()) {
117 checkForViolation(rootNode, data, concurrentCount);
118 concurrentCount = 0;
119 }
120 continue;
121 }
122 ASTPrimaryExpression s = n.getFirstParentOfType(ASTPrimaryExpression.class);
123 int numChildren = s.jjtGetNumChildren();
124 for (int jx = 0; jx < numChildren; jx++) {
125 Node sn = s.jjtGetChild(jx);
126 if (!(sn instanceof ASTPrimarySuffix) || sn.getImage() != null) {
127 continue;
128 }
129
130
131 if (currentBlock != null && lastBlock != null && !currentBlock.equals(lastBlock)
132 || currentBlock == null ^ lastBlock == null) {
133 checkForViolation(rootNode, data, concurrentCount);
134 concurrentCount = 0;
135 }
136
137
138
139 if (concurrentCount == 0) {
140 rootNode = sn;
141 }
142 if (isAdditive(sn)) {
143 concurrentCount = processAdditive(data, concurrentCount, sn, rootNode);
144 if (concurrentCount != 0) {
145 rootNode = sn;
146 }
147 } else if (!isAppendingStringLiteral(sn)) {
148 checkForViolation(rootNode, data, concurrentCount);
149 concurrentCount = 0;
150 } else {
151 concurrentCount++;
152 }
153 lastBlock = currentBlock;
154 }
155 }
156 }
157 checkForViolation(rootNode, data, concurrentCount);
158 return data;
159 }
160
161
162
163
164
165
166
167 private int checkConstructor(ASTVariableDeclaratorId node, Object data) {
168 Node parent = node.jjtGetParent();
169 if (parent.jjtGetNumChildren() >= 2) {
170 ASTAllocationExpression allocationExpression = parent.jjtGetChild(1).getFirstDescendantOfType(ASTAllocationExpression.class);
171 ASTArgumentList list = null;
172 if (allocationExpression != null) {
173 list = allocationExpression.getFirstDescendantOfType(ASTArgumentList.class);
174 }
175
176 if (list != null) {
177 ASTLiteral literal = list.getFirstDescendantOfType(ASTLiteral.class);
178 if (!isAdditive(list) && literal != null && literal.isStringLiteral()) {
179 return 1;
180 }
181 return processAdditive(data, 0, list, node);
182 }
183 }
184 return 0;
185 }
186
187
188
189
190
191
192
193 private int checkInitializerExpressions(ASTVariableDeclaratorId node) {
194 ASTVariableInitializer initializer = node.jjtGetParent().getFirstChildOfType(ASTVariableInitializer.class);
195 ASTPrimaryExpression primary = initializer.getFirstDescendantOfType(ASTPrimaryExpression.class);
196
197 int result = 0;
198 boolean previousWasAppend = false;
199 for (int i = 0; i < primary.jjtGetNumChildren(); i++) {
200 Node child = primary.jjtGetChild(i);
201 if (child.jjtGetNumChildren() > 0 && child.jjtGetChild(0) instanceof ASTAllocationExpression) {
202 continue;
203 }
204 if (child instanceof ASTPrimarySuffix) {
205 ASTPrimarySuffix suffix = (ASTPrimarySuffix)child;
206 if (suffix.jjtGetNumChildren() == 0 && suffix.hasImageEqualTo("append")) {
207 previousWasAppend = true;
208 } else if (suffix.jjtGetNumChildren() > 0 && previousWasAppend) {
209 previousWasAppend = false;
210
211 ASTLiteral literal = suffix.getFirstDescendantOfType(ASTLiteral.class);
212 if (literal != null && literal.isStringLiteral()) {
213 result++;
214 }
215 }
216 }
217 }
218
219 return result;
220 }
221
222 private int processAdditive(Object data, int concurrentCount, Node sn, Node rootNode) {
223 ASTAdditiveExpression additive = sn.getFirstDescendantOfType(ASTAdditiveExpression.class);
224
225 if (additive == null || additive.getType() != null && !TypeHelper.isA(additive, String.class)) {
226 return 0;
227 }
228
229 List<ASTLiteral> literals = additive.findDescendantsOfType(ASTLiteral.class);
230 boolean stringLiteralFound = false;
231 for (ASTLiteral l : literals) {
232 if (l.isCharLiteral() || l.isStringLiteral()) {
233 stringLiteralFound = true;
234 break;
235 }
236 }
237 if (!stringLiteralFound) {
238 return 0;
239 }
240
241 int count = concurrentCount;
242 boolean found = false;
243 for (int ix = 0; ix < additive.jjtGetNumChildren(); ix++) {
244 Node childNode = additive.jjtGetChild(ix);
245 if (childNode.jjtGetNumChildren() != 1 || childNode.hasDescendantOfType(ASTName.class)) {
246 if (!found) {
247 checkForViolation(rootNode, data, count);
248 found = true;
249 }
250 count = 0;
251 } else {
252 count++;
253 }
254 }
255
256
257
258 if (!found) {
259 count = 1;
260 }
261
262 return count;
263 }
264
265
266
267
268
269
270
271
272
273
274
275
276 private boolean isAdditive(Node n) {
277 List<ASTAdditiveExpression> lstAdditive = n.findDescendantsOfType(ASTAdditiveExpression.class);
278 if (lstAdditive.isEmpty()) {
279 return false;
280 }
281
282
283
284 for (int ix = 0; ix < lstAdditive.size(); ix++) {
285 ASTAdditiveExpression expr = lstAdditive.get(ix);
286 if (expr.getParentsOfType(ASTArgumentList.class).size() != 1) {
287 return false;
288 }
289 }
290 return true;
291 }
292
293
294
295
296
297
298
299
300
301
302 private Node getFirstParentBlock(Node node) {
303 Node parentNode = node.jjtGetParent();
304
305 Node lastNode = node;
306 while (parentNode != null && !BLOCK_PARENTS.contains(parentNode.getClass())) {
307 lastNode = parentNode;
308 parentNode = parentNode.jjtGetParent();
309 }
310 if (parentNode instanceof ASTIfStatement) {
311 parentNode = lastNode;
312 } else if (parentNode instanceof ASTSwitchStatement) {
313 parentNode = getSwitchParent(parentNode, lastNode);
314 }
315 return parentNode;
316 }
317
318
319
320
321
322
323
324
325
326
327 private Node getSwitchParent(Node parentNode, Node lastNode) {
328 int allChildren = parentNode.jjtGetNumChildren();
329 ASTSwitchLabel label = null;
330 for (int ix = 0; ix < allChildren; ix++) {
331 Node n = parentNode.jjtGetChild(ix);
332 if (n instanceof ASTSwitchLabel) {
333 label = (ASTSwitchLabel) n;
334 } else if (n.equals(lastNode)) {
335 parentNode = label;
336 break;
337 }
338 }
339 return parentNode;
340 }
341
342
343
344
345
346 private void checkForViolation(Node node, Object data, int concurrentCount) {
347 if (concurrentCount > threshold) {
348 String[] param = { String.valueOf(concurrentCount) };
349 addViolation(data, node, param);
350 }
351 }
352
353 private boolean isAppendingStringLiteral(Node node) {
354 Node n = node;
355 while (n.jjtGetNumChildren() != 0 && !(n instanceof ASTLiteral)) {
356 n = n.jjtGetChild(0);
357 }
358 return n instanceof ASTLiteral;
359 }
360
361 private static boolean isStringBuffer(ASTVariableDeclaratorId node) {
362
363 if (node.getType() != null) {
364
365 return TypeHelper.isEither(node, StringBuffer.class, StringBuilder.class);
366 }
367 Node nn = node.getTypeNameNode();
368 if (nn == null || nn.jjtGetNumChildren() == 0) {
369 return false;
370 }
371 return TypeHelper.isEither((TypeNode) nn.jjtGetChild(0), StringBuffer.class, StringBuilder.class);
372 }
373 }