Clover coverage report - PMD - 3.7 Coverage timestamp: Wed May 31 2006 09:25:59 EDT FRAMES NO FRAMES |
file stats: | LOC: | 308 |  | Methods: | 9
| NCLOC: | 211 |  | Classes: | 1
Source file | Conditionals | Statements | Methods | TOTAL
ConsecutiveLiteralAppends.java | 95.5% | 100% | 100% | 98.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.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(&quot;Hello&quot;);
41 |  | * buf.append(&quot; &quot;).append(&quot;World&quot;);
42 |  | * </pre>
43 |  | * <p/>
44 |  | * This would be more eloquently put as:
45 |  | * <p/>
46 |  | * <pre>
47 |  | * StringBuffer buf = new StringBuffer();
48 |  | * buf.append(&quot;Hello World&quot;);
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 | 12 | blockParents = new HashSet();
60 | 12 | blockParents.add(ASTForStatement.class);
61 | 12 | blockParents.add(ASTWhileStatement.class);
62 | 12 | blockParents.add(ASTDoStatement.class);
63 | 12 | blockParents.add(ASTIfStatement.class);
64 | 12 | blockParents.add(ASTSwitchStatement.class);
65 | 12 | blockParents.add(ASTMethodDeclaration.class);
66 |  | }
67 |  |
68 |  | private int threshold = 1;
69 |  |
70 | 78 | public Object visit(ASTVariableDeclaratorId node, Object data) {
71 |  |
72 | 78 | if (!isStringBuffer(node)) {
73 | 35 | return data;
74 |  | }
75 | 43 | threshold = getIntProperty("threshold");
76 |  |
77 | 43 | int concurrentCount = checkConstructor(node, data);
78 | 43 | Node lastBlock = getFirstParentBlock(node);
79 | 43 | Node currentBlock = lastBlock;
80 | 43 | Map decls = node.getScope().getVariableDeclarations();
81 | 43 | SimpleNode rootNode = null;
82 |  | // only want the constructor flagged if it's really containing strings
83 | 43 | if (concurrentCount == 1) {
84 | 2 | rootNode = node;
85 |  | }
86 | 43 | for (Iterator iter = decls.entrySet().iterator(); iter.hasNext();) {
87 | 58 | Map.Entry entry = (Map.Entry) iter.next();
88 | 58 | List decl = (List) entry.getValue();
89 | 58 | for (int ix = 0; ix < decl.size(); ix++) {
90 | 141 | NameOccurrence no = (NameOccurrence) decl.get(ix);
91 | 141 | SimpleNode n = no.getLocation();
92 |  |
93 | 141 | currentBlock = getFirstParentBlock(n);
94 |  |
95 | 141 | if (!InefficientStringBuffering.isInStringBufferAppend(n, 3)) {
96 | 26 | if (!no.isPartOfQualifiedName()) {
97 | 14 | checkForViolation(rootNode, data, concurrentCount);
98 | 14 | concurrentCount = 0;
99 |  | }
100 | 26 | continue;
101 |  | }
102 | 115 | ASTPrimaryExpression s = (ASTPrimaryExpression) n
103 |  | .getFirstParentOfType(ASTPrimaryExpression.class);
104 | 115 | int numChildren = s.jjtGetNumChildren();
105 | 115 | for (int jx = 0; jx < numChildren; jx++) {
106 | 246 | SimpleNode sn = (SimpleNode) s.jjtGetChild(jx);
107 | 246 | if (!(sn instanceof ASTPrimarySuffix)
108 |  | || sn.getImage() != null) {
109 | 123 | continue;
110 |  | }
111 |  |
112 |  | // see if it changed blocks
113 | 123 | if ((currentBlock != null && lastBlock != null && !currentBlock
114 |  | .equals(lastBlock))
115 |  | || (currentBlock == null ^ lastBlock == null)) {
116 | 46 | checkForViolation(rootNode, data, concurrentCount);
117 | 46 | concurrentCount = 0;
118 |  | }
119 |  |
120 |  | // if concurrent is 0 then we reset the root to report from
121 |  | // here
122 | 123 | if (concurrentCount == 0) {
123 | 93 | rootNode = sn;
124 |  | }
125 | 123 | if (isAdditive(sn)) {
126 | 7 | concurrentCount = processAdditive(data,
127 |  | concurrentCount, sn, rootNode);
128 | 7 | if (concurrentCount != 0) {
129 | 4 | rootNode = sn;
130 |  | }
131 | 116 | } else if (!isAppendingStringLiteral(sn)) {
132 | 13 | checkForViolation(rootNode, data, concurrentCount);
133 | 13 | concurrentCount = 0;
134 |  | } else {
135 | 103 | concurrentCount++;
136 |  | }
137 | 123 | lastBlock = currentBlock;
138 |  | }
139 |  | }
140 |  | }
141 | 43 | checkForViolation(rootNode, data, concurrentCount);
142 | 43 | 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 | 43 | private int checkConstructor(ASTVariableDeclaratorId node, Object data) {
152 | 43 | Node parent = node.jjtGetParent();
153 | 43 | if (parent.jjtGetNumChildren() >= 2) {
154 | 43 | ASTArgumentList list = (ASTArgumentList) ((SimpleNode) parent
155 |  | .jjtGetChild(1)).getFirstChildOfType(ASTArgumentList.class);
156 | 43 | if (list != null) {
157 | 4 | ASTLiteral literal = (ASTLiteral) list
158 |  | .getFirstChildOfType(ASTLiteral.class);
159 | 4 | if (!isAdditive(list) && literal != null
160 |  | && literal.isStringLiteral()) {
161 | 1 | return 1;
162 |  | }
163 | 3 | return processAdditive(data, 0, list, node);
164 |  | }
165 |  | }
166 | 39 | return 0;
167 |  | }
168 |  |
169 | 10 | private int processAdditive(Object data, int concurrentCount,
170 |  | SimpleNode sn, SimpleNode rootNode) {
171 | 10 | ASTAdditiveExpression additive = (ASTAdditiveExpression) sn
172 |  | .getFirstChildOfType(ASTAdditiveExpression.class);
173 | 10 | if (additive == null) {
174 | 1 | return 0;
175 |  | }
176 | 9 | int count = concurrentCount;
177 | 9 | boolean found = false;
178 | 9 | for (int ix = 0; ix < additive.jjtGetNumChildren(); ix++) {
179 | 20 | SimpleNode childNode = (SimpleNode) additive.jjtGetChild(ix);
180 | 20 | if (childNode.jjtGetNumChildren() != 1
181 |  | || childNode.findChildrenOfType(ASTName.class).size() != 0) {
182 | 7 | if (!found) {
183 | 7 | checkForViolation(rootNode, data, count);
184 | 7 | found = true;
185 |  | }
186 | 7 | count = 0;
187 |  | } else {
188 | 13 | count++;
189 |  | }
190 |  | }
191 |  |
192 |  | // no variables appended, compiler will take care of merging all the
193 |  | // string concats, we really only have 1 then
194 | 9 | if (!found) {
195 | 2 | count = 1;
196 |  | }
197 |  |
198 | 9 | 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 | 127 | private boolean isAdditive(SimpleNode n) {
213 | 127 | List lstAdditive = n.findChildrenOfType(ASTAdditiveExpression.class);
214 | 127 | if (lstAdditive.size() == 0) {
215 | 116 | return false;
216 |  | }
217 |  | // if there are more than 1 set of arguments above us we're not in the
218 |  | // append
219 |  | // but a sub-method call
220 | 11 | for (int ix = 0; ix < lstAdditive.size(); ix++) {
221 | 11 | ASTAdditiveExpression expr = (ASTAdditiveExpression) lstAdditive.get(ix);
222 | 11 | if (expr.getParentsOfType(ASTArgumentList.class).size() != 1) {
223 | 2 | return false;
224 |  | }
225 |  | }
226 | 9 | 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 | 184 | private Node getFirstParentBlock(Node node) {
238 | 184 | Node parentNode = node.jjtGetParent();
239 |  |
240 | 184 | Node lastNode = node;
241 | 184 | while (parentNode != null
242 |  | && !blockParents.contains(parentNode.getClass())) {
243 | 1139 | lastNode = parentNode;
244 | 1139 | parentNode = parentNode.jjtGetParent();
245 |  | }
246 | 184 | if (parentNode != null
247 |  | && parentNode.getClass().equals(ASTIfStatement.class)) {
248 | 17 | parentNode = lastNode;
249 | 167 | } else if (parentNode != null
250 |  | && parentNode.getClass().equals(ASTSwitchStatement.class)) {
251 | 10 | parentNode = getSwitchParent(parentNode, lastNode);
252 |  | }
253 | 184 | 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 | 10 | private Node getSwitchParent(Node parentNode, Node lastNode) {
264 | 10 | int allChildren = parentNode.jjtGetNumChildren();
265 | 10 | ASTSwitchLabel label = null;
266 | 103 | for (int ix = 0; ix < allChildren; ix++) {
267 | 103 | Node n = parentNode.jjtGetChild(ix);
268 | 103 | if (n.getClass().equals(ASTSwitchLabel.class)) {
269 | 36 | label = (ASTSwitchLabel) n;
270 | 67 | } else if (n.equals(lastNode)) {
271 | 10 | parentNode = label;
272 | 10 | break;
273 |  | }
274 |  | }
275 | 10 | 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 | 123 | private void checkForViolation(SimpleNode node, Object data,
283 |  | int concurrentCount) {
284 | 123 | if (concurrentCount > threshold) {
285 | 20 | String[] param = {String.valueOf(concurrentCount)};
286 | 20 | addViolation(data, node, param);
287 |  | }
288 |  | }
289 |  |
290 | 116 | private boolean isAppendingStringLiteral(SimpleNode node) {
291 | 116 | SimpleNode n = node;
292 | 116 | while (n.jjtGetNumChildren() != 0
293 |  | && !n.getClass().equals(ASTLiteral.class)) {
294 | 696 | n = (SimpleNode) n.jjtGetChild(0);
295 |  | }
296 | 116 | return n.getClass().equals(ASTLiteral.class);
297 |  | }
298 |  |
299 | 78 | private static boolean isStringBuffer(ASTVariableDeclaratorId node) {
300 | 78 | SimpleNode nn = (SimpleNode) node.getTypeNameNode();
301 | 78 | if (nn.jjtGetNumChildren() == 0) {
302 | 9 | return false;
303 |  | }
304 | 69 | return "StringBuffer".equals(((SimpleNode) nn.jjtGetChild(0))
305 |  | .getImage());
306 |  | }
307 |  |
308 |  | }