View Javadoc

    1   package net.sourceforge.pmd.lang.java.rule.strings;
    2
    3   import java.util.HashSet;
    4   import java.util.List;
    5   import java.util.Set;
    6
    7   import net.sourceforge.pmd.lang.ast.Node;
    8   import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
    9   import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
    10  import net.sourceforge.pmd.lang.java.ast.ASTName;
    11  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
    12  import net.sourceforge.pmd.lang.java.symboltable.NameDeclaration;
    13  import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
    14  import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper;
    15
    16
    17  /**
    18   * This rule finds places where StringBuffer.toString() is called just to see if
    19   * the string is 0 length by either using .equals("") or toString().length()
    20   * <p/>
    21   * <pre>
    22   * StringBuffer sb = new StringBuffer(&quot;some string&quot;);
    23   * if (sb.toString().equals(&quot;&quot;)) {
    24   *     // this is wrong
    25   * }
    26   * if (sb.length() == 0) {
    27   *     // this is right
    28   * }
    29   * </pre>
    30   *
    31   * @author acaplan
    32   */
    33  public class UseStringBufferLengthRule extends AbstractJavaRule {
    34
    35      // FIXME  Need to remove this somehow.
    36      /*
    37      Specifically, we need a symbol tree that can be traversed downwards, so that instead
    38      of visiting each name and then visiting the declaration for that name, we should visit all
    39      the declarations and check their usages.
    40      With that in place, this rule would be reduced to:
    41      - find all StringBuffer declarations
    42      - check each usage
    43      - flag those that involve variable.toString()
    44      */
    45      private Set<VariableNameDeclaration> alreadySeen = new HashSet<VariableNameDeclaration>();
    46
    47      @Override
    48      public Object visit(ASTMethodDeclaration acu, Object data) {
    49          alreadySeen.clear();
    50          return super.visit(acu, data);
    51      }
    52
    53      @Override
    54      public Object visit(ASTName decl, Object data) {
    55          if (!decl.getImage().endsWith("toString")) {
    56              return data;
    57          }
    58          NameDeclaration nd = decl.getNameDeclaration();
    59          if (!(nd instanceof VariableNameDeclaration)) {
    60              return data;
    61          }
    62          VariableNameDeclaration vnd = (VariableNameDeclaration) nd;
    63          if (alreadySeen.contains(vnd) ||
    64          		TypeHelper.isNeither(vnd, StringBuffer.class, StringBuilder.class)) {
    65              return data;
    66          }
    67          alreadySeen.add(vnd);
    68
    69          Node parent = decl.jjtGetParent().jjtGetParent();
    70          for (int jx = 0; jx < parent.jjtGetNumChildren(); jx++) {
    71              Node achild = parent.jjtGetChild(jx);
    72              if (isViolation(parent, achild)) {
    73                  addViolation(data, decl);
    74              }
    75          }
    76
    77          return data;
    78      }
    79
    80      /**
    81       * Check the given node if it calls either .equals or .length we need to check the target
    82       */
    83      private boolean isViolation(Node parent, Node achild) {
    84          if ("equals".equals(achild.getImage())) {
    85              List<ASTLiteral> literals = parent.findDescendantsOfType(ASTLiteral.class);
    86              return !literals.isEmpty() && "\"\"".equals(literals.get(0).getImage());
    87          } else if ("length".equals(achild.getImage())) {
    88              return true;
    89          }
    90          return false;
    91      }
    92  }