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