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 (isInStringBufferOperation(node, 6, "append")) {
    76              addViolation(data, node);
    77          }
    78          return data;
    79      }
    80
    81      protected static boolean isInStringBufferOperation(SimpleNode node, int length, String methodName) {
    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          if (n == null || n.getImage().indexOf(methodName) == -1 || !(n.getNameDeclaration() instanceof VariableNameDeclaration)) {
    91              return false;
    92          }
    93
    94          // TODO having to hand-code this kind of dredging around is ridiculous
    95          // we need something to support this in the framework
    96          // but, "for now" (tm):
    97          // if more than one arg to append(), skip it
    98          ASTArgumentList argList = (ASTArgumentList)s.getFirstChildOfType(ASTArgumentList.class);
    99          if (argList == null || argList.jjtGetNumChildren() > 1) {
    100             return false;
    101         }
    102
    103         return ((VariableNameDeclaration)n.getNameDeclaration()).getTypeImage().equals("StringBuffer");
    104     }
    105
    106     // TODO move this method to SimpleNode
    107     private static boolean xParentIsStatementExpression(SimpleNode node, int length) {
    108         Node curr = node;
    109         for (int i=0; i<length; i++) {
    110             if (node.jjtGetParent() == null) {
    111                 return false;
    112             }
    113             curr = curr.jjtGetParent();
    114         }
    115         return curr instanceof ASTStatementExpression;
    116     }
    117
    118     private boolean isAllocatedStringBuffer(ASTAdditiveExpression node) {
    119         ASTAllocationExpression ao = (ASTAllocationExpression) node.getFirstParentOfType(ASTAllocationExpression.class);
    120         if (ao == null) {
    121             return false;
    122         }
    123         // note that the child can be an ArrayDimsAndInits, for example, from java.lang.FloatingDecimal:  t = new int[ nWords+wordcount+1 ];
    124         ASTClassOrInterfaceType an = (ASTClassOrInterfaceType) ao.getFirstChildOfType(ASTClassOrInterfaceType.class);
    125         return an != null && (an.getImage().endsWith("StringBuffer") || an.getImage().endsWith("StringBuilder"));
    126     }
    127 }
    128