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