001: package net.sourceforge.pmd.rules;
002:
003: import java.util.Iterator;
004: import java.util.List;
005: import java.util.Set;
006:
007: import net.sourceforge.pmd.AbstractRule;
008: import net.sourceforge.pmd.ast.ASTConditionalExpression;
009: import net.sourceforge.pmd.ast.ASTExpression;
010: import net.sourceforge.pmd.ast.ASTLocalVariableDeclaration;
011: import net.sourceforge.pmd.ast.ASTPrimaryExpression;
012: import net.sourceforge.pmd.ast.ASTPrimarySuffix;
013: import net.sourceforge.pmd.ast.ASTType;
014: import net.sourceforge.pmd.ast.ASTVariableDeclaratorId;
015: import net.sourceforge.pmd.ast.Node;
016: import net.sourceforge.pmd.ast.SimpleNode;
017: import net.sourceforge.pmd.symboltable.NameOccurrence;
018: import net.sourceforge.pmd.util.CollectionUtil;
019:
020: /**
021: * An operation on an Immutable object (BigDecimal or BigInteger) won't change
022: * the object itself. The result of the operation is a new object. Therefore,
023: * ignoring the operation result is an error.
024: */
025: public class UselessOperationOnImmutable extends AbstractRule {
026: /**
027: * These are the methods which are immutable
028: */
029: private static final Set<String> targetMethods = CollectionUtil
030: .asSet(new String[] { ".add", ".multiply", ".divide",
031: ".subtract", ".setScale", ".negate",
032: ".movePointLeft", ".movePointRight", ".pow",
033: ".shiftLeft", ".shiftRight" });
034:
035: /**
036: * These are the classes that the rule can apply to
037: */
038: private static final Set<String> targetClasses = CollectionUtil
039: .asSet(new String[] { "java.math.BigDecimal", "BigDecimal",
040: "java.math.BigInteger", "BigInteger" });
041:
042: public Object visit(ASTLocalVariableDeclaration node, Object data) {
043:
044: ASTVariableDeclaratorId var = getDeclaration(node);
045: if (var == null) {
046: return super .visit(node, data);
047: }
048: String variableName = var.getImage();
049: for (NameOccurrence no : var.getUsages()) {
050: // FIXME - getUsages will return everything with the same name as the variable,
051: // see JUnit test, case 6. Changing to SimpleNode below, revisit when getUsages is fixed
052: SimpleNode sn = no.getLocation();
053: Node primaryExpression = sn.jjtGetParent().jjtGetParent();
054: Class<? extends Node> parentClass = primaryExpression
055: .jjtGetParent().getClass();
056: if (!(parentClass.equals(ASTExpression.class)
057: || parentClass
058: .equals(ASTConditionalExpression.class) || hasComparisons(primaryExpression))) {
059: String methodCall = sn.getImage().substring(
060: variableName.length());
061: if (targetMethods.contains(methodCall)) {
062: addViolation(data, sn);
063: }
064: }
065: }
066: return super .visit(node, data);
067: }
068:
069: /**
070: * Check whether the Immutable is compareTo'd something
071: */
072: private boolean hasComparisons(Node primaryExpression) {
073: if (primaryExpression.getClass().equals(
074: ASTPrimaryExpression.class)) {
075: List<ASTPrimarySuffix> suffixes = ((ASTPrimaryExpression) primaryExpression)
076: .findChildrenOfType(ASTPrimarySuffix.class);
077: for (Iterator<ASTPrimarySuffix> iterator = suffixes
078: .iterator(); iterator.hasNext();) {
079: ASTPrimarySuffix suffix = iterator.next();
080: if ("compareTo".equals(suffix.getImage()))
081: return true;
082: }
083: } else {
084: //Some weird usage of the Immutable
085: }
086: return false; //No comparison
087: }
088:
089: /**
090: * This method checks the variable declaration if it is on a class we care
091: * about. If it is, it returns the DeclaratorId
092: *
093: * @param node
094: * The ASTLocalVariableDeclaration which is a problem
095: * @return ASTVariableDeclaratorId
096: */
097: private ASTVariableDeclaratorId getDeclaration(
098: ASTLocalVariableDeclaration node) {
099: ASTType type = node.getTypeNode();
100: if (targetClasses.contains(type.getTypeImage())) {
101: return (ASTVariableDeclaratorId) node.jjtGetChild(1)
102: .jjtGetChild(0);
103: }
104: return null;
105: }
106: }
|