View Javadoc

1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.lang.java.rule.strings;
5   
6   import java.util.HashSet;
7   import java.util.List;
8   import java.util.Set;
9   
10  import net.sourceforge.pmd.lang.ast.Node;
11  import net.sourceforge.pmd.lang.java.ast.ASTArgumentList;
12  import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
13  import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
14  import net.sourceforge.pmd.lang.java.ast.ASTName;
15  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
16  import net.sourceforge.pmd.lang.java.symboltable.TypedNameDeclaration;
17  import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper;
18  import net.sourceforge.pmd.lang.symboltable.NameDeclaration;
19  
20  
21  /**
22   * This rule finds places where StringBuffer.toString() is called just to see if
23   * the string is 0 length by either using .equals("") or toString().length()
24   * <p/>
25   * <pre>
26   * StringBuffer sb = new StringBuffer(&quot;some string&quot;);
27   * if (sb.toString().equals(&quot;&quot;)) {
28   *     // this is wrong
29   * }
30   * if (sb.length() == 0) {
31   *     // this is right
32   * }
33   * </pre>
34   *
35   * @author acaplan
36   * @author Philip Graf
37   */
38  public class UseStringBufferLengthRule extends AbstractJavaRule {
39  
40      // FIXME  Need to remove this somehow.
41      /*
42      Specifically, we need a symbol tree that can be traversed downwards, so that instead
43      of visiting each name and then visiting the declaration for that name, we should visit all
44      the declarations and check their usages.
45      With that in place, this rule would be reduced to:
46      - find all StringBuffer declarations
47      - check each usage
48      - flag those that involve variable.toString()
49      */
50      private Set<NameDeclaration> alreadySeen = new HashSet<NameDeclaration>();
51  
52      @Override
53      public Object visit(ASTMethodDeclaration acu, Object data) {
54          alreadySeen.clear();
55          return super.visit(acu, data);
56      }
57  
58      @Override
59      public Object visit(ASTName decl, Object data) {
60          if (!decl.getImage().endsWith("toString")) {
61              return data;
62          }
63          NameDeclaration nd = decl.getNameDeclaration();
64          if (nd == null) {
65              return data;
66          }
67          if (alreadySeen.contains(nd) || 
68                  !(nd instanceof TypedNameDeclaration) ||
69                  (nd instanceof TypedNameDeclaration && TypeHelper.isNeither((TypedNameDeclaration)nd, StringBuffer.class, StringBuilder.class))) {
70              return data;
71          }
72          alreadySeen.add(nd);
73  
74          if (isViolation(decl)) {
75              addViolation(data, decl);
76          }
77  
78          return data;
79      }
80  
81      /**
82       * Returns true for the following violations:
83       * 
84       * <pre>
85       * StringBuffer sb = new StringBuffer(&quot;some string&quot;);
86       * if (sb.toString().equals(&quot;&quot;)) {
87       *     // this is a violation
88       * }
89       * if (sb.toString().length() == 0) {
90       *     // this is a violation
91       * }
92       * if (sb.length() == 0) {
93       *     // this is ok
94       * }
95       * </pre>
96       */
97      private boolean isViolation(ASTName decl) {
98          // the (grand)parent of a violation has four children
99          Node parent = decl.jjtGetParent().jjtGetParent();
100         if (parent.jjtGetNumChildren() == 4) {
101             // 1. child: sb.toString where sb is a VariableNameDeclaration for a StringBuffer or StringBuilder
102             if (parent.jjtGetChild(0).getFirstChildOfType(ASTName.class).getImage().endsWith(".toString")) {
103                 // 2. child: the arguments of toString
104                 // no need to check as both StringBuffer and StringBuilder only have one toString method
105                 // 3. child: equals or length, 4. child: their arguments
106                 return isEqualsViolation(parent) || isLengthViolation(parent);
107             }
108         }
109         return false;
110     }
111     
112     private boolean isEqualsViolation(Node parent) {
113         // 3. child: equals
114         if (parent.jjtGetChild(2).hasImageEqualTo("equals")) {
115             // 4. child: the arguments of equals, there must be exactly one and it must be ""
116             List<ASTArgumentList> argList = parent.jjtGetChild(3).findDescendantsOfType(ASTArgumentList.class);
117             if (argList.size() == 1) {
118                 List<ASTLiteral> literals = argList.get(0).findDescendantsOfType(ASTLiteral.class);
119                 return literals.size() == 1 && literals.get(0).hasImageEqualTo("\"\"");
120             }
121         }
122         return false;
123     }
124     
125     private boolean isLengthViolation(Node parent) {
126         // 3. child: length
127         return parent.jjtGetChild(2).hasImageEqualTo("length");
128         // 4. child: the arguments of length
129         // no need to check as String has only one length method
130     }
131     
132 }