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 net.sourceforge.pmd.AbstractRule;
7 import net.sourceforge.pmd.PropertyDescriptor;
8 import net.sourceforge.pmd.ast.ASTAdditiveExpression;
9 import net.sourceforge.pmd.ast.ASTArgumentList;
10 import net.sourceforge.pmd.ast.ASTDoStatement;
11 import net.sourceforge.pmd.ast.ASTForStatement;
12 import net.sourceforge.pmd.ast.ASTIfStatement;
13 import net.sourceforge.pmd.ast.ASTLiteral;
14 import net.sourceforge.pmd.ast.ASTMethodDeclaration;
15 import net.sourceforge.pmd.ast.ASTName;
16 import net.sourceforge.pmd.ast.ASTPrimaryExpression;
17 import net.sourceforge.pmd.ast.ASTPrimarySuffix;
18 import net.sourceforge.pmd.ast.ASTSwitchLabel;
19 import net.sourceforge.pmd.ast.ASTSwitchStatement;
20 import net.sourceforge.pmd.ast.ASTVariableDeclaratorId;
21 import net.sourceforge.pmd.ast.ASTWhileStatement;
22 import net.sourceforge.pmd.ast.Node;
23 import net.sourceforge.pmd.ast.SimpleNode;
24 import net.sourceforge.pmd.properties.IntegerProperty;
25 import net.sourceforge.pmd.symboltable.NameOccurrence;
26
27 import java.util.HashSet;
28 import java.util.Iterator;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Set;
32
33 /***
34 * This rule finds concurrent calls to StringBuffer.append where String literals
35 * are used It would be much better to make these calls using one call to
36 * .append
37 * <p/>
38 * example:
39 * <p/>
40 * <pre>
41 * StringBuffer buf = new StringBuffer();
42 * buf.append("Hello");
43 * buf.append(" ").append("World");
44 * </pre>
45 * <p/>
46 * This would be more eloquently put as:
47 * <p/>
48 * <pre>
49 * StringBuffer buf = new StringBuffer();
50 * buf.append("Hello World");
51 * </pre>
52 * <p/>
53 * The rule takes one parameter, threshold, which defines the lower limit of
54 * consecutive appends before a violation is created. The default is 1.
55 */
56 public class ConsecutiveLiteralAppends extends AbstractRule {
57
58 private final static Set blockParents;
59
60 static {
61 blockParents = new HashSet();
62 blockParents.add(ASTForStatement.class);
63 blockParents.add(ASTWhileStatement.class);
64 blockParents.add(ASTDoStatement.class);
65 blockParents.add(ASTIfStatement.class);
66 blockParents.add(ASTSwitchStatement.class);
67 blockParents.add(ASTMethodDeclaration.class);
68 }
69
70 private static final PropertyDescriptor thresholdDescriptor = new IntegerProperty(
71 "threshold",
72 "?",
73 1,
74 1.0f
75 );
76
77 private static final Map propertyDescriptorsByName = asFixedMap(thresholdDescriptor);
78
79
80 private int threshold = 1;
81
82 public Object visit(ASTVariableDeclaratorId node, Object data) {
83
84 if (!isStringBuffer(node)) {
85 return data;
86 }
87 threshold = getIntProperty(thresholdDescriptor);
88
89 int concurrentCount = checkConstructor(node, data);
90 Node lastBlock = getFirstParentBlock(node);
91 Node currentBlock = lastBlock;
92 Map decls = node.getScope().getVariableDeclarations();
93 SimpleNode rootNode = null;
94
95 if (concurrentCount == 1) {
96 rootNode = node;
97 }
98 for (Iterator iter = decls.entrySet().iterator(); iter.hasNext();) {
99 Map.Entry entry = (Map.Entry) iter.next();
100 List decl = (List) entry.getValue();
101 for (int ix = 0; ix < decl.size(); ix++) {
102 NameOccurrence no = (NameOccurrence) decl.get(ix);
103 SimpleNode n = no.getLocation();
104
105 currentBlock = getFirstParentBlock(n);
106
107 if (!InefficientStringBuffering.isInStringBufferOperation(n, 3,"append")) {
108 if (!no.isPartOfQualifiedName()) {
109 checkForViolation(rootNode, data, concurrentCount);
110 concurrentCount = 0;
111 }
112 continue;
113 }
114 ASTPrimaryExpression s = (ASTPrimaryExpression) n
115 .getFirstParentOfType(ASTPrimaryExpression.class);
116 int numChildren = s.jjtGetNumChildren();
117 for (int jx = 0; jx < numChildren; jx++) {
118 SimpleNode sn = (SimpleNode) s.jjtGetChild(jx);
119 if (!(sn instanceof ASTPrimarySuffix)
120 || sn.getImage() != null) {
121 continue;
122 }
123
124
125 if ((currentBlock != null && lastBlock != null && !currentBlock
126 .equals(lastBlock))
127 || (currentBlock == null ^ lastBlock == null)) {
128 checkForViolation(rootNode, data, concurrentCount);
129 concurrentCount = 0;
130 }
131
132
133
134 if (concurrentCount == 0) {
135 rootNode = sn;
136 }
137 if (isAdditive(sn)) {
138 concurrentCount = processAdditive(data,
139 concurrentCount, sn, rootNode);
140 if (concurrentCount != 0) {
141 rootNode = sn;
142 }
143 } else if (!isAppendingStringLiteral(sn)) {
144 checkForViolation(rootNode, data, concurrentCount);
145 concurrentCount = 0;
146 } else {
147 concurrentCount++;
148 }
149 lastBlock = currentBlock;
150 }
151 }
152 }
153 checkForViolation(rootNode, data, concurrentCount);
154 return data;
155 }
156
157 /***
158 * Determie if the constructor contains (or ends with) a String Literal
159 *
160 * @param node
161 * @return 1 if the constructor contains string argument, else 0
162 */
163 private int checkConstructor(ASTVariableDeclaratorId node, Object data) {
164 Node parent = node.jjtGetParent();
165 if (parent.jjtGetNumChildren() >= 2) {
166 ASTArgumentList list = (ASTArgumentList) ((SimpleNode) parent
167 .jjtGetChild(1)).getFirstChildOfType(ASTArgumentList.class);
168 if (list != null) {
169 ASTLiteral literal = (ASTLiteral) list
170 .getFirstChildOfType(ASTLiteral.class);
171 if (!isAdditive(list) && literal != null
172 && literal.isStringLiteral()) {
173 return 1;
174 }
175 return processAdditive(data, 0, list, node);
176 }
177 }
178 return 0;
179 }
180
181 private int processAdditive(Object data, int concurrentCount,
182 SimpleNode sn, SimpleNode rootNode) {
183 ASTAdditiveExpression additive = (ASTAdditiveExpression) sn
184 .getFirstChildOfType(ASTAdditiveExpression.class);
185 if (additive == null) {
186 return 0;
187 }
188 int count = concurrentCount;
189 boolean found = false;
190 for (int ix = 0; ix < additive.jjtGetNumChildren(); ix++) {
191 SimpleNode childNode = (SimpleNode) additive.jjtGetChild(ix);
192 if (childNode.jjtGetNumChildren() != 1
193 || childNode.findChildrenOfType(ASTName.class).size() != 0) {
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 * Checks to see if there is string concatenation in the node.
215 *
216 * This method checks if it's additive with respect to the append method
217 * only.
218 *
219 * @param n
220 * Node to check
221 * @return true if the node has an additive expression (i.e. "Hello " +
222 * Const.WORLD)
223 */
224 private boolean isAdditive(SimpleNode n) {
225 List lstAdditive = n.findChildrenOfType(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 = (ASTAdditiveExpression) lstAdditive.get(ix);
234 if (expr.getParentsOfType(ASTArgumentList.class).size() != 1) {
235 return false;
236 }
237 }
238 return true;
239 }
240
241 /***
242 * Get the first parent. Keep track of the last node though. For If
243 * statements it's the only way we can differentiate between if's and else's
244 * For switches it's the only way we can differentiate between switches
245 *
246 * @param node The node to check
247 * @return The first parent block
248 */
249 private Node getFirstParentBlock(Node node) {
250 Node parentNode = node.jjtGetParent();
251
252 Node lastNode = node;
253 while (parentNode != null
254 && !blockParents.contains(parentNode.getClass())) {
255 lastNode = parentNode;
256 parentNode = parentNode.jjtGetParent();
257 }
258 if (parentNode != null
259 && parentNode.getClass().equals(ASTIfStatement.class)) {
260 parentNode = lastNode;
261 } else if (parentNode != null
262 && parentNode.getClass().equals(ASTSwitchStatement.class)) {
263 parentNode = getSwitchParent(parentNode, lastNode);
264 }
265 return parentNode;
266 }
267
268 /***
269 * Determine which SwitchLabel we belong to inside a switch
270 *
271 * @param parentNode The parent node we're looking at
272 * @param lastNode The last node processed
273 * @return The parent node for the switch statement
274 */
275 private Node getSwitchParent(Node parentNode, Node lastNode) {
276 int allChildren = parentNode.jjtGetNumChildren();
277 ASTSwitchLabel label = null;
278 for (int ix = 0; ix < allChildren; ix++) {
279 Node n = parentNode.jjtGetChild(ix);
280 if (n.getClass().equals(ASTSwitchLabel.class)) {
281 label = (ASTSwitchLabel) n;
282 } else if (n.equals(lastNode)) {
283 parentNode = label;
284 break;
285 }
286 }
287 return parentNode;
288 }
289
290 /***
291 * Helper method checks to see if a violation occured, and adds a
292 * RuleViolation if it did
293 */
294 private void checkForViolation(SimpleNode node, Object data,
295 int concurrentCount) {
296 if (concurrentCount > threshold) {
297 String[] param = {String.valueOf(concurrentCount)};
298 addViolation(data, node, param);
299 }
300 }
301
302 private boolean isAppendingStringLiteral(SimpleNode node) {
303 SimpleNode n = node;
304 while (n.jjtGetNumChildren() != 0
305 && !n.getClass().equals(ASTLiteral.class)) {
306 n = (SimpleNode) n.jjtGetChild(0);
307 }
308 return n.getClass().equals(ASTLiteral.class);
309 }
310
311 private static boolean isStringBuffer(ASTVariableDeclaratorId node) {
312 SimpleNode nn = node.getTypeNameNode();
313 if (nn.jjtGetNumChildren() == 0) {
314 return false;
315 }
316 return "StringBuffer".equals(((SimpleNode) nn.jjtGetChild(0)).getImage());
317 }
318
319 protected Map propertiesByName() {
320 return propertyDescriptorsByName;
321 }
322 }