package quickfix;
import static edu.umd.cs.findbugs.plugin.eclipse.quickfix.util.ASTUtil.getASTNode;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
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.Expression;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.TryStatement;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import util.TraversalUtil;
public class HTTPClientResolution extends BugResolution {
private boolean appendToOrAddFinally;
@Override
protected boolean resolveBindings() {
return true;
}
@Override
public void setOptions(Map<String, String> options) {
this.appendToOrAddFinally = Boolean.parseBoolean(options.get("appendToOrAddFinally"));
}
@Override
protected ASTVisitor getApplicabilityVisitor() {
return new HCPVisitor();
}
@Override
protected ASTVisitor getCustomLabelVisitor() {
return new HCPVisitor();
}
@Override
protected void repairBug(ASTRewrite rewrite, CompilationUnit workingUnit, BugInstance bug) throws BugResolutionException {
ASTNode node = getASTNode(workingUnit, bug.getPrimarySourceLineAnnotation());
HCPVisitor visitor = new HCPVisitor();
node.accept(visitor);
TryStatement lastTryStatement = findLastTryStatementUsingVariable(visitor.badHTTPVerb);
ExpressionStatement callToReset = makeCallToReset(rewrite, visitor);
if (appendToOrAddFinally) {
Block finallyBlock = lastTryStatement.getFinally();
if (finallyBlock == null) {
finallyBlock = rewrite.getAST().newBlock();
}
// first add the call to reset
ListRewrite finallyRewrite = rewrite.getListRewrite(finallyBlock, Block.STATEMENTS_PROPERTY);
finallyRewrite.insertFirst(callToReset, null);
// replace/insert the finally block
rewrite.set(lastTryStatement, TryStatement.FINALLY_PROPERTY, finallyBlock, null);
} else {
// just stick it after the try
Block parentBlock = TraversalUtil.findClosestAncestor(lastTryStatement, Block.class);
ListRewrite statements = rewrite.getListRewrite(parentBlock, Block.STATEMENTS_PROPERTY);
statements.insertAfter(callToReset, lastTryStatement, null);
}
}
private ExpressionStatement makeCallToReset(ASTRewrite rewrite, HCPVisitor visitor) {
AST ast = rewrite.getAST();
MethodInvocation releaseMethodInvocation = ast.newMethodInvocation();
releaseMethodInvocation.setExpression((Expression) rewrite.createCopyTarget(visitor.badHTTPVerb));
releaseMethodInvocation.setName(ast.newSimpleName("releaseConnection"));
return ast.newExpressionStatement(releaseMethodInvocation);
}
@SuppressWarnings("unchecked")
private static TryStatement findLastTryStatementUsingVariable(SimpleName variable) {
// looks for the last try statement that has a reference to the variable referred to
// by the included simpleName.
// If we can't find such a try block, we give up trying to fix
Block parentBlock = TraversalUtil.findClosestAncestor(variable, Block.class);
List<Statement> statements = parentBlock.statements();
for (int i = statements.size() - 1; i >= 0; i--) {
Statement s = statements.get(i);
if (s instanceof TryStatement) {
TryStatement tryStatement = (TryStatement) s;
if (tryRefersToVariable(tryStatement, variable)) {
return tryStatement;
}
}
}
return null;
}
private static boolean tryRefersToVariable(TryStatement s, SimpleName badHTTPVerb) {
TryInspector inspector = new TryInspector(badHTTPVerb);
s.accept(inspector);
return inspector.foundName;
}
private static Set<String> httpVerbClasses = new HashSet<String>();
static
{
httpVerbClasses.add("org.apache.http.client.methods.HttpGet");
httpVerbClasses.add("org.apache.http.client.methods.HttpPut");
httpVerbClasses.add("org.apache.http.client.methods.HttpDelete");
httpVerbClasses.add("org.apache.http.client.methods.HttpPost");
httpVerbClasses.add("org.apache.http.client.methods.HttpPatch");
}
private class HCPVisitor extends ASTVisitor implements ApplicabilityVisitor, CustomLabelVisitor {
public SimpleName badHTTPVerb;
private TryStatement associatedTryStatement;
@Override
@SuppressFBWarnings(value = "PRMC_POSSIBLY_REDUNDANT_METHOD_CALLS", justification =
"node.getName() does not need to local - code's concise as is")
public boolean visit(VariableDeclarationFragment node) {
if (badHTTPVerb != null) {
return false;
}
IBinding binding = node.getName().resolveBinding();
if (binding instanceof IVariableBinding) {
if (httpVerbClasses.contains(((IVariableBinding) binding).getType().getQualifiedName())) {
badHTTPVerb = node.getName();
associatedTryStatement = findLastTryStatementUsingVariable(badHTTPVerb);
return false;
}
}
return true;
}
@Override
public String getLabelReplacement() {
if (associatedTryStatement.getFinally() != null) {
// a finally is defined
if (appendToOrAddFinally) {
return "Add call to httpGet.releaseConnection() to finally block";
} else {
// put resetConnection after "finally" block
return "finally";
}
} else {
// no finally defined
if (appendToOrAddFinally) {
return "Add finally block to release connections of httpGet";
} else {
// put resetConnection after "catch" block
return "catch";
}
}
}
@Override
public boolean isApplicable() {
// if we can't find a verb or a try statement, it's much too complex to fix
return null != badHTTPVerb && null != associatedTryStatement;
}
}
private static class TryInspector extends ASTVisitor {
// a simple visitor that simply keeps track of if we saw a simple name
// with the same identifier as the SimpleName passed in
private SimpleName nameSoughtAfter;
public boolean foundName;
public TryInspector(SimpleName nameSoughtAfter) {
this.nameSoughtAfter = nameSoughtAfter;
}
@Override
public boolean visit(SimpleName node) {
if (node.getIdentifier().equals(nameSoughtAfter.getIdentifier())) {
foundName = true;
}
return true;
}
}
}