package quickfix;
import static edu.umd.cs.findbugs.plugin.eclipse.quickfix.util.ASTUtil.getASTNode;
import java.util.List;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.plugin.eclipse.quickfix.ApplicabilityVisitor;
import edu.umd.cs.findbugs.plugin.eclipse.quickfix.BugResolution;
import edu.umd.cs.findbugs.plugin.eclipse.quickfix.CustomLabelVisitor;
import edu.umd.cs.findbugs.plugin.eclipse.quickfix.exception.BugResolutionException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ConditionalExpression;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.InfixExpression;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import util.TraversalUtil;
public class CompareFloatResolution extends BugResolution {
@Override
protected boolean resolveBindings() {
return true;
}
@Override
protected ASTVisitor getApplicabilityVisitor() {
return new CompareToVisitor();
}
@Override
protected ASTVisitor getCustomLabelVisitor() {
return new CompareToVisitor();
}
@Override
protected void repairBug(ASTRewrite rewrite, CompilationUnit workingUnit, BugInstance bug) throws BugResolutionException {
ASTNode node = getASTNode(workingUnit, bug.getPrimarySourceLineAnnotation());
node = TraversalUtil.backtrackToBlock(node);
CompareToVisitor visitor = new CompareToVisitor();
node.accept(visitor);
if (visitor.expressionToReplace != null) {
AST ast = rewrite.getAST();
MethodInvocation newMethod = ast.newMethodInvocation();
newMethod.setName(ast.newSimpleName("compare"));
newMethod.setExpression(ast.newSimpleName(visitor.floatOrDouble));
newMethod.arguments().add(rewrite.createCopyTarget(visitor.firstFloat));
newMethod.arguments().add(rewrite.createCopyTarget(visitor.secondFloat));
if (visitor.optionalTempVariableToDelete != null) {
rewrite.remove(visitor.optionalTempVariableToDelete, null);
}
rewrite.replace(visitor.expressionToReplace, newMethod, null);
}
}
private static class CompareToVisitor extends ASTVisitor implements ApplicabilityVisitor, CustomLabelVisitor {
ConditionalExpression expressionToReplace;
VariableDeclarationStatement optionalTempVariableToDelete;
Name firstFloat;
Name secondFloat;
String floatOrDouble;
@Override
public boolean visit(ConditionalExpression node) {
if (expressionToReplace != null) {
return false;
}
if (node.getExpression() instanceof InfixExpression) {
InfixExpression condExpr = (InfixExpression) node.getExpression();
boolean retVal = findFirstAndSecondFloat(node, condExpr);
if (condExpr.getOperator() == InfixExpression.Operator.GREATER) {
return retVal;
}
else if (condExpr.getOperator() == InfixExpression.Operator.LESS) {
swapFirstAndSecondFloat();
return retVal;
}
}
return true;
}
private boolean findFirstAndSecondFloat(ConditionalExpression node, InfixExpression condExpr) {
if (!handleTwoSimpleNames(node, condExpr)) {
// this is a if diff > 0 case
try {
if (condExpr.getLeftOperand() instanceof SimpleName) {
findDiffAndFloats((SimpleName) condExpr.getLeftOperand());
} else if (condExpr.getRightOperand() instanceof SimpleName) {
findDiffAndFloats((SimpleName) condExpr.getRightOperand());
} else {
return true; // unexpected comparison
}
floatOrDouble = getFloatOrDouble(firstFloat, secondFloat);
expressionToReplace = node;
} catch (CouldntFindDiffException e) {
return true; // keep nesting if we have a problem
}
}
return false;
}
private boolean handleTwoSimpleNames(ConditionalExpression node, InfixExpression condExpr) {
if (!areNames(condExpr.getLeftOperand(), condExpr.getRightOperand())) {
return false;
}
firstFloat = (Name) condExpr.getLeftOperand();
secondFloat = (Name) condExpr.getRightOperand();
expressionToReplace = node;
floatOrDouble = getFloatOrDouble(firstFloat, secondFloat);
return true;
}
private void swapFirstAndSecondFloat() {
Name temp = firstFloat;
firstFloat = secondFloat;
secondFloat = temp;
}
@SuppressWarnings("unchecked")
private void findDiffAndFloats(SimpleName diffName) throws CouldntFindDiffException {
ConditionalExpression originalLine = TraversalUtil.findClosestAncestor(diffName, ConditionalExpression.class);
if (originalLine == null || !(originalLine.getExpression() instanceof InfixExpression)) {
throw new CouldntFindDiffException();
}
Block surroundingBlock = TraversalUtil.findClosestAncestor(originalLine, Block.class);
List<Statement> blockStatements = surroundingBlock.statements();
for (int i = blockStatements.size() - 1; i >= 0; i--) {
Statement statement = blockStatements.get(i);
if (statement instanceof VariableDeclarationStatement) {
List<VariableDeclarationFragment> frags = ((VariableDeclarationStatement) statement).fragments();
// I won't fix the the diff variable if it's nested with other frags, if they exist
// but we do need to look at them
VariableDeclarationFragment fragment = frags.get(0);
if (fragment.getName().getIdentifier().equals(diffName.getIdentifier())) {
Expression initializer = fragment.getInitializer();
if (initializer instanceof InfixExpression) {
InfixExpression subtraction = (InfixExpression) initializer;
if (subtraction.getOperator() == InfixExpression.Operator.MINUS &&
areNames(subtraction.getLeftOperand(), subtraction.getRightOperand())) {
this.firstFloat = (Name) subtraction.getLeftOperand();
this.secondFloat = (Name) subtraction.getRightOperand();
if (frags.size() == 1) {
this.optionalTempVariableToDelete = (VariableDeclarationStatement) statement;
}
return;
}
}
}
}
}
throw new CouldntFindDiffException();
}
private String getFloatOrDouble(Name... variables) {
boolean isDouble = false;
for (Name v : variables) {
if ("double".equals(v.resolveTypeBinding().getQualifiedName())) {
isDouble = true;
}
}
return isDouble ? "Double" : "Float";
}
private boolean areNames(Expression... expressions) { // returns true if all expressions are simple names
for (Expression e : expressions) {
if (!(e instanceof Name)) {
return false;
}
}
return true;
}
@Override
public boolean isApplicable() {
return expressionToReplace != null;
}
@Override
public String getLabelReplacement() {
return String.format("%s.compare(%s, %s)", this.floatOrDouble, this.firstFloat, this.secondFloat);
}
}
private static class CouldntFindDiffException extends Exception {
}
}