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