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