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
183 if (additive == null || (additive.getType() != null && !TypeHelper.isA(additive, String.class))) {
184 return 0;
185 }
186 int count = concurrentCount;
187 boolean found = false;
188 for (int ix = 0; ix < additive.jjtGetNumChildren(); ix++) {
189 SimpleNode childNode = (SimpleNode) additive.jjtGetChild(ix);
190 if (childNode.jjtGetNumChildren() != 1
191 || childNode.findChildrenOfType(ASTName.class).size() != 0) {
192 if (!found) {
193 checkForViolation(rootNode, data, count);
194 found = true;
195 }
196 count = 0;
197 } else {
198 count++;
199 }
200 }
201
202
203
204 if (!found) {
205 count = 1;
206 }
207
208 return count;
209 }
210
211 /**
212 * Checks to see if there is string concatenation in the node.
213 *
214 * This method checks if it's additive with respect to the append method
215 * only.
216 *
217 * @param n
218 * Node to check
219 * @return true if the node has an additive expression (i.e. "Hello " +
220 * Const.WORLD)
221 */
222 private boolean isAdditive(SimpleNode n) {
223 List lstAdditive = n.findChildrenOfType(ASTAdditiveExpression.class);
224 if (lstAdditive.isEmpty()) {
225 return false;
226 }
227
228
229
230 for (int ix = 0; ix < lstAdditive.size(); ix++) {
231 ASTAdditiveExpression expr = (ASTAdditiveExpression) lstAdditive.get(ix);
232 if (expr.getParentsOfType(ASTArgumentList.class).size() != 1) {
233 return false;
234 }
235 }
236 return true;
237 }
238
239 /**
240 * Get the first parent. Keep track of the last node though. For If
241 * statements it's the only way we can differentiate between if's and else's
242 * For switches it's the only way we can differentiate between switches
243 *
244 * @param node The node to check
245 * @return The first parent block
246 */
247 private Node getFirstParentBlock(Node node) {
248 Node parentNode = node.jjtGetParent();
249
250 Node lastNode = node;
251 while (parentNode != null
252 && !blockParents.contains(parentNode.getClass())) {
253 lastNode = parentNode;
254 parentNode = parentNode.jjtGetParent();
255 }
256 if (parentNode != null
257 && parentNode.getClass().equals(ASTIfStatement.class)) {
258 parentNode = lastNode;
259 } else if (parentNode != null
260 && parentNode.getClass().equals(ASTSwitchStatement.class)) {
261 parentNode = getSwitchParent(parentNode, lastNode);
262 }
263 return parentNode;
264 }
265
266 /**
267 * Determine which SwitchLabel we belong to inside a switch
268 *
269 * @param parentNode The parent node we're looking at
270 * @param lastNode The last node processed
271 * @return The parent node for the switch statement
272 */
273 private Node getSwitchParent(Node parentNode, Node lastNode) {
274 int allChildren = parentNode.jjtGetNumChildren();
275 ASTSwitchLabel label = null;
276 for (int ix = 0; ix < allChildren; ix++) {
277 Node n = parentNode.jjtGetChild(ix);
278 if (n.getClass().equals(ASTSwitchLabel.class)) {
279 label = (ASTSwitchLabel) n;
280 } else if (n.equals(lastNode)) {
281 parentNode = label;
282 break;
283 }
284 }
285 return parentNode;
286 }
287
288 /**
289 * Helper method checks to see if a violation occured, and adds a
290 * RuleViolation if it did
291 */
292 private void checkForViolation(SimpleNode node, Object data,
293 int concurrentCount) {
294 if (concurrentCount > threshold) {
295 String[] param = {String.valueOf(concurrentCount)};
296 addViolation(data, node, param);
297 }
298 }
299
300 private boolean isAppendingStringLiteral(SimpleNode node) {
301 SimpleNode n = node;
302 while (n.jjtGetNumChildren() != 0
303 && !n.getClass().equals(ASTLiteral.class)) {
304 n = (SimpleNode) n.jjtGetChild(0);
305 }
306 return n.getClass().equals(ASTLiteral.class);
307 }
308
309 private static boolean isStringBuffer(ASTVariableDeclaratorId node) {
310
311 if (node.getType() != null) {
312 return node.getType().equals(StringBuffer.class);
313 }
314 SimpleNode nn = node.getTypeNameNode();
315 if (nn.jjtGetNumChildren() == 0) {
316 return false;
317 }
318 return TypeHelper.isA((TypeNode)nn.jjtGetChild(0), StringBuffer.class);
319 }
320
321 protected Map<String, PropertyDescriptor> propertiesByName() {
322 return propertyDescriptorsByName;
323 }
324 }