Clover coverage report - PMD - 3.9 Coverage timestamp: Tue Dec 19 2006 09:38:44 EST FRAMES NO FRAMES |
file stats: | LOC: | 322 |  | Methods: | 10
| NCLOC: | 222 |  | Classes: | 1
Source file | Conditionals | Statements | Methods | TOTAL
ConsecutiveLiteralAppends.java | 95.5% | 99.1% | 90% | 97.4% |

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(&quot;Hello&quot;);
43 |  | * buf.append(&quot; &quot;).append(&quot;World&quot;);
44 |  | * </pre>
45 |  | * <p/>
46 |  | * This would be more eloquently put as:
47 |  | * <p/>
48 |  | * <pre>
49 |  | * StringBuffer buf = new StringBuffer();
50 |  | * buf.append(&quot;Hello World&quot;);
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 | 14 | blockParents = new HashSet();
62 | 14 | blockParents.add(ASTForStatement.class);
63 | 14 | blockParents.add(ASTWhileStatement.class);
64 | 14 | blockParents.add(ASTDoStatement.class);
65 | 14 | blockParents.add(ASTIfStatement.class);
66 | 14 | blockParents.add(ASTSwitchStatement.class);
67 | 14 | 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 | 78 | public Object visit(ASTVariableDeclaratorId node, Object data) {
83 |  |
84 | 78 | if (!isStringBuffer(node)) {
85 | 35 | return data;
86 |  | }
87 | 43 | threshold = getIntProperty(thresholdDescriptor);
88 |  |
89 | 43 | int concurrentCount = checkConstructor(node, data);
90 | 43 | Node lastBlock = getFirstParentBlock(node);
91 | 43 | Node currentBlock = lastBlock;
92 | 43 | Map decls = node.getScope().getVariableDeclarations();
93 | 43 | SimpleNode rootNode = null;
94 |  | // only want the constructor flagged if it's really containing strings
95 | 43 | if (concurrentCount == 1) {
96 | 2 | rootNode = node;
97 |  | }
98 | 43 | for (Iterator iter = decls.entrySet().iterator(); iter.hasNext();) {
99 | 58 | Map.Entry entry = (Map.Entry) iter.next();
100 | 58 | List decl = (List) entry.getValue();
101 | 58 | for (int ix = 0; ix < decl.size(); ix++) {
102 | 141 | NameOccurrence no = (NameOccurrence) decl.get(ix);
103 | 141 | SimpleNode n = no.getLocation();
104 |  |
105 | 141 | currentBlock = getFirstParentBlock(n);
106 |  |
107 | 141 | if (!InefficientStringBuffering.isInStringBufferOperation(n, 3,"append")) {
108 | 26 | if (!no.isPartOfQualifiedName()) {
109 | 14 | checkForViolation(rootNode, data, concurrentCount);
110 | 14 | concurrentCount = 0;
111 |  | }
112 | 26 | continue;
113 |  | }
114 | 115 | ASTPrimaryExpression s = (ASTPrimaryExpression) n
115 |  | .getFirstParentOfType(ASTPrimaryExpression.class);
116 | 115 | int numChildren = s.jjtGetNumChildren();
117 | 115 | for (int jx = 0; jx < numChildren; jx++) {
118 | 246 | SimpleNode sn = (SimpleNode) s.jjtGetChild(jx);
119 | 246 | if (!(sn instanceof ASTPrimarySuffix)
120 |  | || sn.getImage() != null) {
121 | 123 | continue;
122 |  | }
123 |  |
124 |  | // see if it changed blocks
125 | 123 | if ((currentBlock != null && lastBlock != null && !currentBlock
126 |  | .equals(lastBlock))
127 |  | || (currentBlock == null ^ lastBlock == null)) {
128 | 46 | checkForViolation(rootNode, data, concurrentCount);
129 | 46 | concurrentCount = 0;
130 |  | }
131 |  |
132 |  | // if concurrent is 0 then we reset the root to report from
133 |  | // here
134 | 123 | if (concurrentCount == 0) {
135 | 93 | rootNode = sn;
136 |  | }
137 | 123 | if (isAdditive(sn)) {
138 | 7 | concurrentCount = processAdditive(data,
139 |  | concurrentCount, sn, rootNode);
140 | 7 | if (concurrentCount != 0) {
141 | 4 | rootNode = sn;
142 |  | }
143 | 116 | } else if (!isAppendingStringLiteral(sn)) {
144 | 13 | checkForViolation(rootNode, data, concurrentCount);
145 | 13 | concurrentCount = 0;
146 |  | } else {
147 | 103 | concurrentCount++;
148 |  | }
149 | 123 | lastBlock = currentBlock;
150 |  | }
151 |  | }
152 |  | }
153 | 43 | checkForViolation(rootNode, data, concurrentCount);
154 | 43 | 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 | 43 | private int checkConstructor(ASTVariableDeclaratorId node, Object data) {
164 | 43 | Node parent = node.jjtGetParent();
165 | 43 | if (parent.jjtGetNumChildren() >= 2) {
166 | 43 | ASTArgumentList list = (ASTArgumentList) ((SimpleNode) parent
167 |  | .jjtGetChild(1)).getFirstChildOfType(ASTArgumentList.class);
168 | 43 | if (list != null) {
169 | 4 | ASTLiteral literal = (ASTLiteral) list
170 |  | .getFirstChildOfType(ASTLiteral.class);
171 | 4 | if (!isAdditive(list) && literal != null
172 |  | && literal.isStringLiteral()) {
173 | 1 | return 1;
174 |  | }
175 | 3 | return processAdditive(data, 0, list, node);
176 |  | }
177 |  | }
178 | 39 | return 0;
179 |  | }
180 |  |
181 | 10 | private int processAdditive(Object data, int concurrentCount,
182 |  | SimpleNode sn, SimpleNode rootNode) {
183 | 10 | ASTAdditiveExpression additive = (ASTAdditiveExpression) sn
184 |  | .getFirstChildOfType(ASTAdditiveExpression.class);
185 | 10 | if (additive == null) {
186 | 1 | return 0;
187 |  | }
188 | 9 | int count = concurrentCount;
189 | 9 | boolean found = false;
190 | 9 | for (int ix = 0; ix < additive.jjtGetNumChildren(); ix++) {
191 | 20 | SimpleNode childNode = (SimpleNode) additive.jjtGetChild(ix);
192 | 20 | if (childNode.jjtGetNumChildren() != 1
193 |  | || childNode.findChildrenOfType(ASTName.class).size() != 0) {
194 | 7 | if (!found) {
195 | 7 | checkForViolation(rootNode, data, count);
196 | 7 | found = true;
197 |  | }
198 | 7 | count = 0;
199 |  | } else {
200 | 13 | count++;
201 |  | }
202 |  | }
203 |  |
204 |  | // no variables appended, compiler will take care of merging all the
205 |  | // string concats, we really only have 1 then
206 | 9 | if (!found) {
207 | 2 | count = 1;
208 |  | }
209 |  |
210 | 9 | 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 | 127 | private boolean isAdditive(SimpleNode n) {
225 | 127 | List lstAdditive = n.findChildrenOfType(ASTAdditiveExpression.class);
226 | 127 | if (lstAdditive.isEmpty()) {
227 | 116 | return false;
228 |  | }
229 |  | // if there are more than 1 set of arguments above us we're not in the
230 |  | // append
231 |  | // but a sub-method call
232 | 11 | for (int ix = 0; ix < lstAdditive.size(); ix++) {
233 | 11 | ASTAdditiveExpression expr = (ASTAdditiveExpression) lstAdditive.get(ix);
234 | 11 | if (expr.getParentsOfType(ASTArgumentList.class).size() != 1) {
235 | 2 | return false;
236 |  | }
237 |  | }
238 | 9 | 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 | 184 | private Node getFirstParentBlock(Node node) {
250 | 184 | Node parentNode = node.jjtGetParent();
251 |  |
252 | 184 | Node lastNode = node;
253 | 184 | while (parentNode != null
254 |  | && !blockParents.contains(parentNode.getClass())) {
255 | 1139 | lastNode = parentNode;
256 | 1139 | parentNode = parentNode.jjtGetParent();
257 |  | }
258 | 184 | if (parentNode != null
259 |  | && parentNode.getClass().equals(ASTIfStatement.class)) {
260 | 17 | parentNode = lastNode;
261 | 167 | } else if (parentNode != null
262 |  | && parentNode.getClass().equals(ASTSwitchStatement.class)) {
263 | 10 | parentNode = getSwitchParent(parentNode, lastNode);
264 |  | }
265 | 184 | 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 | 10 | private Node getSwitchParent(Node parentNode, Node lastNode) {
276 | 10 | int allChildren = parentNode.jjtGetNumChildren();
277 | 10 | ASTSwitchLabel label = null;
278 | 103 | for (int ix = 0; ix < allChildren; ix++) {
279 | 103 | Node n = parentNode.jjtGetChild(ix);
280 | 103 | if (n.getClass().equals(ASTSwitchLabel.class)) {
281 | 36 | label = (ASTSwitchLabel) n;
282 | 67 | } else if (n.equals(lastNode)) {
283 | 10 | parentNode = label;
284 | 10 | break;
285 |  | }
286 |  | }
287 | 10 | 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 | 123 | private void checkForViolation(SimpleNode node, Object data,
295 |  | int concurrentCount) {
296 | 123 | if (concurrentCount > threshold) {
297 | 20 | String[] param = {String.valueOf(concurrentCount)};
298 | 20 | addViolation(data, node, param);
299 |  | }
300 |  | }
301 |  |
302 | 116 | private boolean isAppendingStringLiteral(SimpleNode node) {
303 | 116 | SimpleNode n = node;
304 | 116 | while (n.jjtGetNumChildren() != 0
305 |  | && !n.getClass().equals(ASTLiteral.class)) {
306 | 696 | n = (SimpleNode) n.jjtGetChild(0);
307 |  | }
308 | 116 | return n.getClass().equals(ASTLiteral.class);
309 |  | }
310 |  |
311 | 78 | private static boolean isStringBuffer(ASTVariableDeclaratorId node) {
312 | 78 | SimpleNode nn = node.getTypeNameNode();
313 | 78 | if (nn.jjtGetNumChildren() == 0) {
314 | 9 | return false;
315 |  | }
316 | 69 | return "StringBuffer".equals(((SimpleNode) nn.jjtGetChild(0)).getImage());
317 |  | }
318 |  |
319 | 0 | protected Map propertiesByName() {
320 | 0 | return propertyDescriptorsByName;
321 |  | }
322 |  | }