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