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