UselessOperationOnImmutableRule.java
01 package net.sourceforge.pmd.lang.java.rule.unnecessary;
02 
03 import java.util.HashMap;
04 import java.util.Map;
05 import java.util.Set;
06 
07 import net.sourceforge.pmd.lang.ast.Node;
08 import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration;
09 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 }