View Javadoc

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(&quot;Hello&quot;);
45   * buf.append(&quot; &quot;).append(&quot;World&quot;);
46   * </pre>
47   * <p/>
48   * This would be more eloquently put as:
49   * <p/>
50   * <pre>
51   * StringBuffer buf = new StringBuffer();
52   * buf.append(&quot;Hello World&quot;);
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          // only want the constructor flagged if it's really containing strings
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                     // see if it changed blocks
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                     // if concurrent is 0 then we reset the root to report from
132                     // here
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         // The additive expression must of be type String to count
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         // no variables appended, compiler will take care of merging all the
203         // string concats, we really only have 1 then
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         // if there are more than 1 set of arguments above us we're not in the
228         // append
229         // but a sub-method call
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 }