package quickfix;
import static edu.umd.cs.findbugs.plugin.eclipse.quickfix.util.ASTUtil.getASTNode;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
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.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.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.NumberLiteral;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
public class SQLOffByOneResolution extends BugResolution {
private boolean fixAll;
@Override
protected boolean resolveBindings() {
return true;
}
@Override
public void setOptions(@Nonnull Map<String, String> options) {
fixAll = Boolean.parseBoolean(options.get("replaceAll"));
}
@Override
protected void repairBug(ASTRewrite rewrite, CompilationUnit workingUnit, BugInstance bug) throws BugResolutionException {
ASTNode node = getASTNode(workingUnit, bug.getPrimarySourceLineAnnotation());
if (fixAll) {
// for fix all, we go up one node, which is the entire scope the first detected statement was in
node = node.getParent();
}
SQLVisitor visitor = new SQLVisitor(rewrite);
node.accept(visitor);
for (int i = 0; i < visitor.badMethodInvocations.size(); i++) {
MethodInvocation bad = visitor.badMethodInvocations.get(i);
MethodInvocation fixed = visitor.fixedMethodInvocations.get(i);
rewrite.replace(bad, fixed, null);
}
}
private static class SQLVisitor extends ASTVisitor {
public List<MethodInvocation> badMethodInvocations = new ArrayList<>();
public List<MethodInvocation> fixedMethodInvocations = new ArrayList<>();
private ASTRewrite rewrite;
private AST rootAST;
private static final Set<String> types = new HashSet<>();
static {
types.add("java.sql.PreparedStatement");
types.add("java.sql.ResultSet");
}
public SQLVisitor(ASTRewrite rewrite) {
this.rewrite = rewrite;
this.rootAST = rewrite.getAST();
}
@Override
public boolean visit(MethodInvocation node) {
if (isValidSQLSetUpdateOrGet(node)) {
badMethodInvocations.add(node);
fixedMethodInvocations.add(makeFixedMethodInvocation(node));
}
return true;
}
private boolean isValidSQLSetUpdateOrGet(MethodInvocation node) {
String typeName = node.getExpression().resolveTypeBinding().getQualifiedName();
if (!types.contains(typeName)) {
return false;
}
// ResultSet and PreparedStatement have lots of methods
// we don't want to change the wrong ones
String calledMethod = node.getName().getFullyQualifiedName();
if (typeName.contains("PreparedStatement")) {
// e.g. setInteger(int paramIndex, int val)
return calledMethod.startsWith("set") && node.arguments().size() == 2;
}
List<?> args = node.arguments();
if (!((calledMethod.startsWith("get") && args.size() == 1) || (calledMethod.startsWith("update") && args.size() == 2))) {
return false;
}
// there are two versions of resultSet.getString [and updateString]
// getString(String colName) and getString(int colIndex)
// we only want to update the latter
return args.get(0) instanceof NumberLiteral;
}
@SuppressWarnings("unchecked")
private MethodInvocation makeFixedMethodInvocation(MethodInvocation node) {
MethodInvocation fixedMethodInvocation = rootAST.newMethodInvocation();
// move targets didn't seem to work, so I'm using copy targets instead
fixedMethodInvocation.setExpression((Expression) rewrite.createCopyTarget(node.getExpression()));
fixedMethodInvocation.setName((SimpleName) rewrite.createCopyTarget(node.getName()));
List<Expression> oldArguments = node.arguments();
// we know from isValidSQLSetUpdateOrGet that the first arg is the numberLiteral
NumberLiteral intArg = (NumberLiteral) oldArguments.get(0);
String incrementedArg = Integer.toString(Integer.parseInt(intArg.getToken()) + 1);
List<Expression> newArguments = fixedMethodInvocation.arguments();
newArguments.add(rootAST.newNumberLiteral(incrementedArg));
for (int i = 1; i < oldArguments.size(); i++) {
newArguments.add((Expression) rewrite.createCopyTarget(oldArguments.get(i)));
}
return fixedMethodInvocation;
}
}
}