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.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          blockParents = new HashSet();
60          blockParents.add(ASTForStatement.class);
61          blockParents.add(ASTWhileStatement.class);
62          blockParents.add(ASTDoStatement.class);
63          blockParents.add(ASTIfStatement.class);
64          blockParents.add(ASTSwitchStatement.class);
65          blockParents.add(ASTMethodDeclaration.class);
66      }
67  
68      private int threshold = 1;
69  
70      public Object visit(ASTVariableDeclaratorId node, Object data) {
71  
72          if (!isStringBuffer(node)) {
73              return data;
74          }
75          threshold = getIntProperty("threshold");
76  
77          int concurrentCount = checkConstructor(node, data);
78          Node lastBlock = getFirstParentBlock(node);
79          Node currentBlock = lastBlock;
80          Map decls = node.getScope().getVariableDeclarations();
81          SimpleNode rootNode = null;
82          // only want the constructor flagged if it's really containing strings
83          if (concurrentCount == 1) {
84              rootNode = node;
85          }
86          for (Iterator iter = decls.entrySet().iterator(); iter.hasNext();) {
87              Map.Entry entry = (Map.Entry) iter.next();
88              List decl = (List) entry.getValue();
89              for (int ix = 0; ix < decl.size(); ix++) {
90                  NameOccurrence no = (NameOccurrence) decl.get(ix);
91                  SimpleNode n = no.getLocation();
92  
93                  currentBlock = getFirstParentBlock(n);
94  
95                  if (!InefficientStringBuffering.isInStringBufferAppend(n, 3)) {
96                      if (!no.isPartOfQualifiedName()) {
97                          checkForViolation(rootNode, data, concurrentCount);
98                          concurrentCount = 0;
99                      }
100                     continue;
101                 }
102                 ASTPrimaryExpression s = (ASTPrimaryExpression) n
103                         .getFirstParentOfType(ASTPrimaryExpression.class);
104                 int numChildren = s.jjtGetNumChildren();
105                 for (int jx = 0; jx < numChildren; jx++) {
106                     SimpleNode sn = (SimpleNode) s.jjtGetChild(jx);
107                     if (!(sn instanceof ASTPrimarySuffix)
108                             || sn.getImage() != null) {
109                         continue;
110                     }
111 
112                     // see if it changed blocks
113                     if ((currentBlock != null && lastBlock != null && !currentBlock
114                             .equals(lastBlock))
115                             || (currentBlock == null ^ lastBlock == null)) {
116                         checkForViolation(rootNode, data, concurrentCount);
117                         concurrentCount = 0;
118                     }
119 
120                     // if concurrent is 0 then we reset the root to report from
121                     // here
122                     if (concurrentCount == 0) {
123                         rootNode = sn;
124                     }
125                     if (isAdditive(sn)) {
126                         concurrentCount = processAdditive(data,
127                                 concurrentCount, sn, rootNode);
128                         if (concurrentCount != 0) {
129                             rootNode = sn;
130                         }
131                     } else if (!isAppendingStringLiteral(sn)) {
132                         checkForViolation(rootNode, data, concurrentCount);
133                         concurrentCount = 0;
134                     } else {
135                         concurrentCount++;
136                     }
137                     lastBlock = currentBlock;
138                 }
139             }
140         }
141         checkForViolation(rootNode, data, concurrentCount);
142         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     private int checkConstructor(ASTVariableDeclaratorId node, Object data) {
152         Node parent = node.jjtGetParent();
153         if (parent.jjtGetNumChildren() >= 2) {
154             ASTArgumentList list = (ASTArgumentList) ((SimpleNode) parent
155                     .jjtGetChild(1)).getFirstChildOfType(ASTArgumentList.class);
156             if (list != null) {
157                 ASTLiteral literal = (ASTLiteral) list
158                         .getFirstChildOfType(ASTLiteral.class);
159                 if (!isAdditive(list) && literal != null
160                         && literal.isStringLiteral()) {
161                     return 1;
162                 } 
163                 return processAdditive(data, 0, list, node);
164             }
165         }
166         return 0;
167     }
168 
169     private int processAdditive(Object data, int concurrentCount,
170                                 SimpleNode sn, SimpleNode rootNode) {
171         ASTAdditiveExpression additive = (ASTAdditiveExpression) sn
172                 .getFirstChildOfType(ASTAdditiveExpression.class);
173         if (additive == null) {
174             return 0;
175         }
176         int count = concurrentCount;
177         boolean found = false;
178         for (int ix = 0; ix < additive.jjtGetNumChildren(); ix++) {
179             SimpleNode childNode = (SimpleNode) additive.jjtGetChild(ix);
180             if (childNode.jjtGetNumChildren() != 1
181                     || childNode.findChildrenOfType(ASTName.class).size() != 0) {
182                 if (!found) {
183                     checkForViolation(rootNode, data, count);
184                     found = true;
185                 }
186                 count = 0;
187             } else {
188                 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         if (!found) {
195             count = 1;
196         }
197 
198         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     private boolean isAdditive(SimpleNode n) {
213         List lstAdditive = n.findChildrenOfType(ASTAdditiveExpression.class);
214         if (lstAdditive.size() == 0) {
215             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         for (int ix = 0; ix < lstAdditive.size(); ix++) {
221             ASTAdditiveExpression expr = (ASTAdditiveExpression) lstAdditive.get(ix);
222             if (expr.getParentsOfType(ASTArgumentList.class).size() != 1) {
223                 return false;
224             }
225         }
226         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     private Node getFirstParentBlock(Node node) {
238         Node parentNode = node.jjtGetParent();
239 
240         Node lastNode = node;
241         while (parentNode != null
242                 && !blockParents.contains(parentNode.getClass())) {
243             lastNode = parentNode;
244             parentNode = parentNode.jjtGetParent();
245         }
246         if (parentNode != null
247                 && parentNode.getClass().equals(ASTIfStatement.class)) {
248             parentNode = lastNode;
249         } else if (parentNode != null
250                 && parentNode.getClass().equals(ASTSwitchStatement.class)) {
251             parentNode = getSwitchParent(parentNode, lastNode);
252         }
253         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     private Node getSwitchParent(Node parentNode, Node lastNode) {
264         int allChildren = parentNode.jjtGetNumChildren();
265         ASTSwitchLabel label = null;
266         for (int ix = 0; ix < allChildren; ix++) {
267             Node n = parentNode.jjtGetChild(ix);
268             if (n.getClass().equals(ASTSwitchLabel.class)) {
269                 label = (ASTSwitchLabel) n;
270             } else if (n.equals(lastNode)) {
271                 parentNode = label;
272                 break;
273             }
274         }
275         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     private void checkForViolation(SimpleNode node, Object data,
283                                    int concurrentCount) {
284         if (concurrentCount > threshold) {
285             String[] param = {String.valueOf(concurrentCount)};
286             addViolation(data, node, param);
287         }
288     }
289 
290     private boolean isAppendingStringLiteral(SimpleNode node) {
291         SimpleNode n = node;
292         while (n.jjtGetNumChildren() != 0
293                 && !n.getClass().equals(ASTLiteral.class)) {
294             n = (SimpleNode) n.jjtGetChild(0);
295         }
296         return n.getClass().equals(ASTLiteral.class);
297     }
298 
299     private static boolean isStringBuffer(ASTVariableDeclaratorId node) {
300         SimpleNode nn = (SimpleNode) node.getTypeNameNode();
301         if (nn.jjtGetNumChildren() == 0) {
302             return false;
303         }
304         return "StringBuffer".equals(((SimpleNode) nn.jjtGetChild(0))
305                 .getImage());
306     }
307 
308 }