package quickfix;
import static edu.umd.cs.findbugs.plugin.eclipse.quickfix.util.ASTUtil.getASTNode;
import java.util.List;
import java.util.Map;
import javax.annotation.Nonnull;
import edu.umd.cs.findbugs.BugInstance;
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.BooleanLiteral;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
public class NeedlessBoxingResolution extends BugResolution {
private boolean useBooleanConstants;
@Override
protected ASTVisitor getCustomLabelVisitor() {
return new NeedlessBoxingVisitor();
}
@Override
public void setOptions(@Nonnull Map<String, String> options) {
useBooleanConstants = Boolean.parseBoolean(options.get("useBooleanConstants"));
}
@Override
protected boolean resolveBindings() {
return true;
}
@Override
protected void repairBug(ASTRewrite rewrite, CompilationUnit workingUnit, BugInstance bug) throws BugResolutionException {
ASTNode node = getASTNode(workingUnit, bug.getPrimarySourceLineAnnotation());
NeedlessBoxingVisitor visitor = new NeedlessBoxingVisitor();
node.accept(visitor);
if (useBooleanConstants) {
Expression fixedBooleanConstant = makeFixedBooleanConstant(rewrite.getAST(), visitor);
if (visitor.badBooleanLiteral != null) {
rewrite.replace(visitor.badBooleanLiteral, fixedBooleanConstant, null);
} else {
rewrite.replace(visitor.badBooleanObjectLiteral, fixedBooleanConstant, null);
}
} else {
MethodInvocation fixedMethodInvocation = makeFixedMethodInvocation(rewrite, visitor);
rewrite.replace(visitor.methodInvocationToReplace, fixedMethodInvocation, null);
}
}
private Expression makeFixedBooleanConstant(AST ast, NeedlessBoxingVisitor visitor) {
if (visitor.badBooleanLiteral != null) {
// turn a BooleanLiteral into a qualified name
return ast.newName("Boolean." + visitor.makeTrueOrFalse());
}
return ast.newBooleanLiteral(Boolean.parseBoolean(visitor.makeTrueOrFalse()));
}
@SuppressWarnings("unchecked")
private MethodInvocation makeFixedMethodInvocation(ASTRewrite rewrite, NeedlessBoxingVisitor visitor) {
AST ast = rewrite.getAST();
MethodInvocation retVal = ast.newMethodInvocation();
MethodInvocation original = visitor.badMethodInvocation;
retVal.setExpression((Expression) rewrite.createMoveTarget(original.getExpression()));
retVal.setName(ast.newSimpleName(visitor.makeParseMethod()));
for (Object arg : original.arguments()) {
retVal.arguments().add(rewrite.createMoveTarget((ASTNode) arg));
}
return retVal;
}
private class NeedlessBoxingVisitor extends ASTVisitor implements CustomLabelVisitor {
public MethodInvocation badMethodInvocation;
public MethodInvocation methodInvocationToReplace; // may be the same as badMethodInvocation. May be parent if there is a call to booleanValue() or similar
public BooleanLiteral badBooleanLiteral;
public QualifiedName badBooleanObjectLiteral;
public String makeParseMethod() {
if (badMethodInvocation == null)
return "parseXXX";
String typeName = badMethodInvocation.resolveTypeBinding().getName();
if ("Boolean".equals(typeName)) {
return "parseBoolean";
} else if ("Byte".equals(typeName)) {
return "parseByte";
} else if ("Short".equals(typeName)) {
return "parseShort";
} else if ("Long".equals(typeName)) {
return "parseLong";
} else if ("Integer".equals(typeName)) {
return "parseInt";
} else if ("Double".equals(typeName)) {
return "parseDouble";
} else if ("Float".equals(typeName)) {
return "parseFloat";
}
return "parseXXX";
}
public String makeTrueOrFalse() {
if (badBooleanLiteral != null) {
return badBooleanLiteral.booleanValue() ? "TRUE" : "FALSE";
}
// This will be Boolean.TRUE or Boolean.FALSE
return badBooleanObjectLiteral.getName().getIdentifier().toLowerCase();
}
@Override
public boolean visit(MethodInvocation node) {
if (badMethodInvocation != null)
return false;
if ("valueOf".equals(node.getName().getIdentifier()) &&
node.getExpression().resolveTypeBinding().getQualifiedName().startsWith("java.lang")) {
badMethodInvocation = node;
methodInvocationToReplace = node;
ASTNode parent = badMethodInvocation.getParent();
if (parent instanceof MethodInvocation && ((MethodInvocation) parent).getName().getIdentifier().endsWith("Value")) {
methodInvocationToReplace = (MethodInvocation) parent;
}
return false;
}
return true;
}
@Override
public boolean visit(BooleanLiteral node) {
if (this.badBooleanLiteral != null) {
return false;
}
if (node.resolveBoxing()) { // did boxing happen? If so, that's our cue
badBooleanLiteral = node;
return false;
}
return true;
}
@Override
public boolean visit(QualifiedName node) {
if (this.badBooleanObjectLiteral != null) {
return false;
}
if (node.resolveUnboxing()) { // did unboxing happen? If so, that's our cue
badBooleanObjectLiteral = node;
return false;
}
return true;
}
@Override
public String getLabelReplacement() {
if (useBooleanConstants) {
if (badBooleanLiteral != null) {
return "Boolean." + makeTrueOrFalse();
}
return makeTrueOrFalse();
}
if (badMethodInvocation == null) {
return "the parse equivalent";
}
return badMethodInvocation.resolveTypeBinding().getName() + '.' +
makeParseMethod() + argsToString(badMethodInvocation.arguments());
}
private String argsToString(List<?> arguments) {
StringBuilder sb = new StringBuilder();
sb.append('(');
for (Object arg : arguments) {
if (arguments.size() > 1) {
sb.append(", ");
}
sb.append(arg);
}
return sb.append(')').toString();
}
}
}