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