/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.rule.unnecessary;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression;
import net.sourceforge.pmd.lang.java.ast.ASTType;
import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
import net.sourceforge.pmd.lang.symboltable.NameOccurrence;
import net.sourceforge.pmd.util.CollectionUtil;
/**
* An operation on an Immutable object (String, BigDecimal or BigInteger) won't
* change the object itself. The result of the operation is a new object.
* Therefore, ignoring the operation result is an error.
*/
public class UselessOperationOnImmutableRule extends AbstractJavaRule {
/**
* These are the BigDecimal methods which are immutable
*/
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", });
/**
* These are the BigInteger methods which are immutable
*/
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", });
/**
* These are the String methods which are immutable
*/
private static final Set<String> STRING_METHODS = CollectionUtil
.asSet(new String[] { ".concat", ".intern", ".replace", ".replaceAll", ".replaceFirst", ".substring",
".toLowerCase", ".toString", ".toUpperCase", ".trim", });
/**
* These are the classes that the rule can apply to
*/
private static final Map<String, Set<String>> MAP_CLASSES = new HashMap<>();
static {
MAP_CLASSES.put("java.math.BigDecimal", BIG_DECIMAL_METHODS);
MAP_CLASSES.put("BigDecimal", BIG_DECIMAL_METHODS);
MAP_CLASSES.put("java.math.BigInteger", BIG_INTEGER_METHODS);
MAP_CLASSES.put("BigInteger", BIG_INTEGER_METHODS);
MAP_CLASSES.put("java.lang.String", STRING_METHODS);
MAP_CLASSES.put("String", STRING_METHODS);
}
@Override
public Object visit(ASTLocalVariableDeclaration node, Object data) {
ASTVariableDeclaratorId var = getDeclaration(node);
if (var == null) {
return super.visit(node, data);
}
String variableName = var.getImage();
for (NameOccurrence no : var.getUsages()) {
// FIXME - getUsages will return everything with the same name as
// the variable,
// see JUnit test, case 6. Changing to Node below, revisit when
// getUsages is fixed
Node sn = no.getLocation();
Node primaryExpression = sn.jjtGetParent().jjtGetParent();
Class<? extends Node> parentClass = primaryExpression.jjtGetParent().getClass();
if (parentClass.equals(ASTStatementExpression.class)) {
String methodCall = sn.getImage().substring(variableName.length());
ASTType nodeType = node.getTypeNode();
if (nodeType != null) {
if (MAP_CLASSES.get(nodeType.getTypeImage()).contains(methodCall)) {
addViolation(data, sn);
}
}
}
}
return super.visit(node, data);
}
/**
* This method checks the variable declaration if it is on a class we care
* about. If it is, it returns the DeclaratorId
*
* @param node
* The ASTLocalVariableDeclaration which is a problem
* @return ASTVariableDeclaratorId
*/
private ASTVariableDeclaratorId getDeclaration(ASTLocalVariableDeclaration node) {
ASTType type = node.getTypeNode();
if (MAP_CLASSES.keySet().contains(type.getTypeImage())) {
return node.getFirstDescendantOfType(ASTVariableDeclaratorId.class);
}
return null;
}
}