View Javadoc

    1   package net.sourceforge.pmd.lang.java.rule.unnecessary;
    2
    3   import java.util.HashMap;
    4   import java.util.Map;
    5   import java.util.Set;
    6
    7   import net.sourceforge.pmd.lang.ast.Node;
    8   import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration;
    9   import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression;
    10  import net.sourceforge.pmd.lang.java.ast.ASTType;
    11  import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
    12  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
    13  import net.sourceforge.pmd.lang.java.symboltable.NameOccurrence;
    14  import net.sourceforge.pmd.util.CollectionUtil;
    15
    16  /**
    17   * An operation on an Immutable object (String, BigDecimal or BigInteger) won't change
    18   * the object itself. The result of the operation is a new object. Therefore,
    19   * ignoring the operation result is an error.
    20   */
    21  public class UselessOperationOnImmutableRule extends AbstractJavaRule {
    22
    23      /**
    24       * These are the BigDecimal methods which are immutable
    25       */
    26      private static final Set<String> BIG_DECIMAL_METHODS = CollectionUtil.asSet(new String[] { ".abs", ".add", ".divide", ".divideToIntegralValue", ".max", ".min", ".movePointLeft", ".movePointRight", ".multiply", ".negate", ".plus", ".pow", ".remainder", ".round", ".scaleByPowerOfTen", ".setScale", ".stripTrailingZeros", ".subtract", ".ulp" });
    27
    28      /**
    29       * These are the BigInteger methods which are immutable
    30       */
    31      private static final Set<String> BIG_INTEGER_METHODS = CollectionUtil.asSet(new String[] { ".abs", ".add", ".and", ".andNot", ".clearBit", ".divide", ".flipBit", ".gcd", ".max", ".min", ".mod", ".modInverse", ".modPow", ".multiply", ".negate", ".nextProbablePrine", ".not", ".or", ".pow", ".remainder", ".setBit", ".shiftLeft", ".shiftRight", ".subtract", ".xor" });
    32
    33      /**
    34       * These are the String methods which are immutable
    35       */
    36      private static final Set<String> STRING_METHODS = CollectionUtil.asSet(new String[] { ".concat", ".intern", ".replace", ".replaceAll", ".replaceFirst", ".substring", ".toLowerCase", ".toString", ".toUpperCase", ".trim" });
    37
    38      /**
    39       * These are the classes that the rule can apply to
    40       */
    41      private static final Map<String, Set<String>> MAP_CLASSES = new HashMap<String, Set<String>>();
    42      static {
    43          MAP_CLASSES.put("java.math.BigDecimal", BIG_DECIMAL_METHODS);
    44          MAP_CLASSES.put("BigDecimal", BIG_DECIMAL_METHODS);
    45          MAP_CLASSES.put("java.math.BigInteger", BIG_INTEGER_METHODS);
    46          MAP_CLASSES.put("BigInteger", BIG_INTEGER_METHODS);
    47          MAP_CLASSES.put("java.lang.String", STRING_METHODS);
    48          MAP_CLASSES.put("String", STRING_METHODS);
    49      }
    50
    51      @Override
    52      public Object visit(ASTLocalVariableDeclaration node, Object data) {
    53
    54          ASTVariableDeclaratorId var = getDeclaration(node);
    55          if (var == null) {
    56              return super.visit(node, data);
    57          }
    58          String variableName = var.getImage();
    59          for (NameOccurrence no: var.getUsages()) {
    60              // FIXME - getUsages will return everything with the same name as the variable,
    61              // see JUnit test, case 6. Changing to Node below, revisit when getUsages is fixed
    62              Node sn = no.getLocation();
    63              Node primaryExpression = sn.jjtGetParent().jjtGetParent();
    64              Class<? extends Node> parentClass = primaryExpression.jjtGetParent().getClass();
    65              if (parentClass.equals(ASTStatementExpression.class)) {
    66                  String methodCall = sn.getImage().substring(variableName.length());
    67                  ASTType nodeType = node.getTypeNode();
    68                  if ( nodeType != null ) {
    69                      if ( MAP_CLASSES.get(nodeType.getTypeImage()).contains(methodCall)) {
    70                          addViolation(data, sn);
    71                      }
    72                  }
    73              }
    74          }
    75          return super.visit(node, data);
    76      }
    77
    78      /**
    79       * This method checks the variable declaration if it is on a class we care
    80       * about. If it is, it returns the DeclaratorId
    81       *
    82       * @param node
    83       *            The ASTLocalVariableDeclaration which is a problem
    84       * @return ASTVariableDeclaratorId
    85       */
    86      private ASTVariableDeclaratorId getDeclaration(ASTLocalVariableDeclaration node) {
    87          ASTType type = node.getTypeNode();
    88          if (MAP_CLASSES.keySet().contains(type.getTypeImage())) {
    89              return node.getFirstDescendantOfType(ASTVariableDeclaratorId.class);
    90          }
    91          return null;
    92      }
    93  }