/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.rule;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.java.ast.ASTEqualityExpression;
import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
import net.sourceforge.pmd.lang.java.ast.ASTPrimitiveType;
import net.sourceforge.pmd.lang.java.ast.ASTRelationalExpression;
import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
import net.sourceforge.pmd.lang.java.symboltable.JavaNameOccurrence;
import net.sourceforge.pmd.lang.symboltable.NameOccurrence;
/**
* This is an abstract rule for patterns which compare a method invocation to 0.
* It could be further abstracted to find code that compares something to
* another definable pattern
*
* @author acaplan
*/
public abstract class AbstractInefficientZeroCheck extends AbstractJavaRule {
private static Map<String, String> inverse = new HashMap<>();
public abstract boolean appliesToClassName(String name);
public abstract boolean isTargetMethod(JavaNameOccurrence occ);
/**
* For each relation/equality operator, comparison targets need to define.
*
* @return map
*/
public Map<String, List<String>> getComparisonTargets() {
Map<String, List<String>> rules = new HashMap<>();
rules.put("==", Arrays.asList("0"));
rules.put("!=", Arrays.asList("0"));
rules.put(">", Arrays.asList("0"));
rules.put("<", Arrays.asList("0"));
return rules;
}
static {
inverse.put("<", ">");
inverse.put(">", "<");
inverse.put("<=", ">=");
inverse.put(">=", "<=");
inverse.put("==", "==");
inverse.put("!=", "!=");
}
@Override
public Object visit(ASTVariableDeclaratorId node, Object data) {
Node nameNode = node.getTypeNameNode();
if (nameNode == null || nameNode instanceof ASTPrimitiveType
|| !appliesToClassName(node.getNameDeclaration().getTypeImage())) {
return data;
}
List<NameOccurrence> declars = node.getUsages();
for (NameOccurrence occ : declars) {
JavaNameOccurrence jocc = (JavaNameOccurrence) occ;
if (!isTargetMethod(jocc)) {
continue;
}
Node expr = jocc.getLocation().jjtGetParent().jjtGetParent().jjtGetParent();
checkNodeAndReport(data, jocc.getLocation(), expr);
}
return data;
}
/**
* Checks whether the given expression is a equality/relation expression
* that compares with a size() call.
*
* @param data
* the rule context
* @param location
* the node location to report
* @param expr
* the ==, <, > expression
*/
protected void checkNodeAndReport(Object data, Node location, Node expr) {
if ((expr instanceof ASTEqualityExpression
|| expr instanceof ASTRelationalExpression && getComparisonTargets().containsKey(expr.getImage()))
&& isCompare(expr)) {
addViolation(data, location);
}
}
/**
* We only need to report if this is comparing against one of the comparison
* targets
*
* @param equality
* @return true if this is comparing to one of the comparison targets else
* false
* @see #getComparisonTargets()
*/
private boolean isCompare(Node equality) {
if (isLiteralLeftHand(equality)) {
return checkComparison(inverse.get(equality.getImage()), equality, 0);
} else if (isLiteralRightHand(equality)) {
return checkComparison(equality.getImage(), equality, 1);
}
return false;
}
private boolean isLiteralLeftHand(Node equality) {
return isLiteral(equality, 0);
}
private boolean isLiteralRightHand(Node equality) {
return isLiteral(equality, 1);
}
private boolean isLiteral(Node equality, int child) {
Node target = equality.jjtGetChild(child);
target = getFirstChildOrThis(target);
target = getFirstChildOrThis(target);
return target instanceof ASTLiteral;
}
private Node getFirstChildOrThis(Node node) {
if (node.jjtGetNumChildren() > 0) {
return node.jjtGetChild(0);
}
return node;
}
/**
* Checks if the equality expression passed in is of comparing against the
* value passed in as i
*
* @param equality
* @param i
* The ordinal in the equality expression to check
* @return true if the value in position i is one of the comparison targets,
* else false
* @see #getComparisonTargets()
*/
private boolean checkComparison(String operator, Node equality, int i) {
Node target = equality.jjtGetChild(i).jjtGetChild(0).jjtGetChild(0);
return target instanceof ASTLiteral && getComparisonTargets().get(operator).contains(target.getImage());
}
}