1 package net.sourceforge.pmd.rules;
2
3 import java.util.Iterator;
4 import java.util.List;
5 import java.util.Set;
6
7 import net.sourceforge.pmd.AbstractRule;
8 import net.sourceforge.pmd.ast.ASTConditionalExpression;
9 import net.sourceforge.pmd.ast.ASTExpression;
10 import net.sourceforge.pmd.ast.ASTLocalVariableDeclaration;
11 import net.sourceforge.pmd.ast.ASTPrimaryExpression;
12 import net.sourceforge.pmd.ast.ASTPrimarySuffix;
13 import net.sourceforge.pmd.ast.ASTType;
14 import net.sourceforge.pmd.ast.ASTVariableDeclaratorId;
15 import net.sourceforge.pmd.ast.Node;
16 import net.sourceforge.pmd.ast.SimpleNode;
17 import net.sourceforge.pmd.symboltable.NameOccurrence;
18 import net.sourceforge.pmd.util.CollectionUtil;
19
20 /**
21 * An operation on an Immutable object (BigDecimal or BigInteger) won't change
22 * the object itself. The result of the operation is a new object. Therefore,
23 * ignoring the operation result is an error.
24 */
25 public class UselessOperationOnImmutable extends AbstractRule {
26 /**
27 * These are the methods which are immutable
28 */
29 private static final Set<String> targetMethods = CollectionUtil.asSet(new String[] { ".add", ".multiply", ".divide", ".subtract", ".setScale", ".negate", ".movePointLeft", ".movePointRight", ".pow", ".shiftLeft", ".shiftRight" });
30
31 /**
32 * These are the classes that the rule can apply to
33 */
34 private static final Set<String> targetClasses = CollectionUtil.asSet(new String[] { "java.math.BigDecimal", "BigDecimal", "java.math.BigInteger", "BigInteger" });
35
36 public Object visit(ASTLocalVariableDeclaration node, Object data) {
37
38 ASTVariableDeclaratorId var = getDeclaration(node);
39 if (var == null) {
40 return super.visit(node, data);
41 }
42 String variableName = var.getImage();
43 for (NameOccurrence no: var.getUsages()) {
44
45
46 SimpleNode sn = no.getLocation();
47 Node primaryExpression = sn.jjtGetParent().jjtGetParent();
48 Class<? extends Node> parentClass = primaryExpression.jjtGetParent().getClass();
49 if (!(parentClass.equals(ASTExpression.class) || parentClass.equals(ASTConditionalExpression.class) ||
50 hasComparisons(primaryExpression))) {
51 String methodCall = sn.getImage().substring(variableName.length());
52 if (targetMethods.contains(methodCall)) {
53 addViolation(data, sn);
54 }
55 }
56 }
57 return super.visit(node, data);
58 }
59
60 /**
61 * Check whether the Immutable is compareTo'd something
62 */
63 private boolean hasComparisons(Node primaryExpression) {
64 if (primaryExpression.getClass().equals(ASTPrimaryExpression.class)) {
65 List<ASTPrimarySuffix> suffixes = ((ASTPrimaryExpression)primaryExpression).findChildrenOfType(ASTPrimarySuffix.class);
66 for (Iterator<ASTPrimarySuffix> iterator = suffixes.iterator(); iterator.hasNext();) {
67 ASTPrimarySuffix suffix = iterator.next();
68 if ("compareTo".equals(suffix.getImage()))
69 return true;
70 }
71 } else {
72
73 }
74 return false;
75 }
76
77 /**
78 * This method checks the variable declaration if it is on a class we care
79 * about. If it is, it returns the DeclaratorId
80 *
81 * @param node
82 * The ASTLocalVariableDeclaration which is a problem
83 * @return ASTVariableDeclaratorId
84 */
85 private ASTVariableDeclaratorId getDeclaration(ASTLocalVariableDeclaration node) {
86 ASTType type = node.getTypeNode();
87 if (targetClasses.contains(type.getTypeImage())) {
88 return (ASTVariableDeclaratorId) node.jjtGetChild(1).jjtGetChild(0);
89 }
90 return null;
91 }
92 }