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.ASTAllocationExpression;
9   import net.sourceforge.pmd.ast.ASTArgumentList;
10  import net.sourceforge.pmd.ast.ASTBlockStatement;
11  import net.sourceforge.pmd.ast.ASTClassOrInterfaceType;
12  import net.sourceforge.pmd.ast.ASTLiteral;
13  import net.sourceforge.pmd.ast.ASTName;
14  import net.sourceforge.pmd.ast.ASTStatementExpression;
15  import net.sourceforge.pmd.ast.Node;
16  import net.sourceforge.pmd.ast.SimpleNode;
17  import net.sourceforge.pmd.symboltable.VariableNameDeclaration;
18  
19  import java.util.Iterator;
20  import java.util.List;
21  
22  /*
23   * How this rule works:
24   * find additive expressions: +
25   * check that the addition is between anything other than two literals
26   * if true and also the parent is StringBuffer constructor or append,
27   * report a violation.
28   * 
29   * @author mgriffa
30   */
31  public class InefficientStringBuffering extends AbstractRule {
32  
33      public Object visit(ASTAdditiveExpression node, Object data) {
34          ASTBlockStatement bs = (ASTBlockStatement) node.getFirstParentOfType(ASTBlockStatement.class);
35          if (bs == null) {
36              return data;
37          }
38  
39          int immediateLiterals = 0;
40          List nodes = node.findChildrenOfType(ASTLiteral.class);
41          for (Iterator i = nodes.iterator();i.hasNext();) {
42              ASTLiteral literal = (ASTLiteral)i.next();
43              if (literal.jjtGetParent().jjtGetParent().jjtGetParent() instanceof ASTAdditiveExpression) {
44                  immediateLiterals++;
45              }
46              try {
47                  Integer.parseInt(literal.getImage());
48                  return data;
49              } catch (NumberFormatException nfe) {
50                  // NFE means new StringBuffer("a" + "b"), want to flag those
51              }
52          }
53  
54          if (immediateLiterals > 1) {
55              return data;
56          }
57  
58          // if literal + public static final, return
59          List nameNodes = node.findChildrenOfType(ASTName.class);
60          for (Iterator i = nameNodes.iterator(); i.hasNext();) {
61              ASTName name = (ASTName)i.next();
62              if (name.getNameDeclaration() instanceof VariableNameDeclaration) {
63                  VariableNameDeclaration vnd = (VariableNameDeclaration)name.getNameDeclaration();
64                  if (vnd.getAccessNodeParent().isFinal() && vnd.getAccessNodeParent().isStatic()) {
65                      return data;
66                  }
67              }
68          }
69  
70  
71          if (bs.isAllocation()) {
72              if (isAllocatedStringBuffer(node)) {
73                  addViolation(data, node);
74              }
75          } else if (isInStringBufferAppend(node, 6)) {
76              addViolation(data, node);
77          }
78          return data;
79      }
80  
81      protected static boolean isInStringBufferAppend(SimpleNode node, int length) {
82          if (!xParentIsStatementExpression(node, length)) {
83              return false;
84          }
85          ASTStatementExpression s = (ASTStatementExpression) node.getFirstParentOfType(ASTStatementExpression.class);
86          if (s == null) {
87              return false;
88          }
89          ASTName n = (ASTName)s.getFirstChildOfType(ASTName.class);
90  
91          if (n == null || n.getImage().indexOf("append") == -1 || !(n.getNameDeclaration() instanceof VariableNameDeclaration)) {
92              return false;
93          }
94  
95          // TODO having to hand-code this kind of dredging around is ridiculous
96          // we need something to support this in the framework
97          // but, "for now" (tm):
98          // if more than one arg to append(), skip it
99          ASTArgumentList argList = (ASTArgumentList)s.getFirstChildOfType(ASTArgumentList.class);
100         if (argList == null || argList.jjtGetNumChildren() > 1) {
101             return false;
102         }
103 
104         return ((VariableNameDeclaration)n.getNameDeclaration()).getTypeImage().equals("StringBuffer");
105     }
106 
107     // TODO move this method to SimpleNode
108     private static boolean xParentIsStatementExpression(SimpleNode node, int length) {
109         Node curr = node;
110         for (int i=0; i<length; i++) {
111             if (node.jjtGetParent() == null) {
112                 return false;
113             }
114             curr = curr.jjtGetParent();
115         }
116         return curr instanceof ASTStatementExpression;
117     }
118 
119     private boolean isAllocatedStringBuffer(ASTAdditiveExpression node) {
120         ASTAllocationExpression ao = (ASTAllocationExpression) node.getFirstParentOfType(ASTAllocationExpression.class);
121         if (ao == null) {
122             return false;
123         }
124         // note that the child can be an ArrayDimsAndInits, for example, from java.lang.FloatingDecimal:  t = new int[ nWords+wordcount+1 ];
125         ASTClassOrInterfaceType an = (ASTClassOrInterfaceType) ao.getFirstChildOfType(ASTClassOrInterfaceType.class);
126         return an != null && (an.getImage().endsWith("StringBuffer") || an.getImage().endsWith("StringBuilder"));
127     }
128 }
129