package quickfix;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import edu.umd.cs.findbugs.BugAnnotation;
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 edu.umd.cs.findbugs.plugin.eclipse.quickfix.util.ASTUtil;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.ParameterizedType;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import util.TraversalUtil;
public class OverlyConcreteParametersResolution extends BugResolution {
private Pattern annotationParser = Pattern.compile(".*parameter '(.+)' could be declared as (.+) instead.*");
private String paramName;
private String newDotSeparatedClass;
private AST ast;
private Type badParamType;
@Override
protected boolean resolveBindings() {
return true;
}
@Override
protected void repairBug(ASTRewrite rewrite, CompilationUnit workingUnit, BugInstance bug) throws BugResolutionException {
MethodDeclaration method = TraversalUtil.findEnclosingMethod(workingUnit, bug.getPrimarySourceLineAnnotation());
ast = rewrite.getAST();
// this is an odd resolution as we don't have to use a visitor - all the information comes
// from FindBugs (fb-contrib) and the starting node
parseParamAndNewClass(bug.getAnnotations());
findBadParameter(method);
// Use the unqualified class name, otherwise the java.util.Set or whatever looks unusual
// There may be an edge case (I'm thinking of java.util.List/java.awt.List
String unqualifiedClass = newDotSeparatedClass.substring(newDotSeparatedClass.lastIndexOf('.') + 1);
Type fixedType = makeFixedType(unqualifiedClass);
rewrite.replace(badParamType, fixedType, null);
ASTUtil.addImports(rewrite, workingUnit, newDotSeparatedClass);
}
private Type makeFixedType(String unqualifiedClass) {
Type newBaseType = ast.newSimpleType(ast.newName(unqualifiedClass));
if (badParamType.isParameterizedType()) {
ParameterizedType newPType = ast.newParameterizedType(newBaseType);
transferTypeArguments((ParameterizedType) badParamType, newPType);
return newPType;
}
return newBaseType;
}
@SuppressWarnings("unchecked")
private void findBadParameter(MethodDeclaration method) {
List<SingleVariableDeclaration> params = method.parameters();
for (SingleVariableDeclaration param : params) {
if (param.getName().getIdentifier().equals(paramName)) {
badParamType = param.getType();
break;
}
}
}
@SuppressWarnings("unchecked")
private void transferTypeArguments(ParameterizedType existingType, ParameterizedType newType) {
// This is similar to the implementation from EntrySetResolution
List<Type> oldTypeArgs = existingType.typeArguments();
while (!oldTypeArgs.isEmpty()) {
// transfer the type from one to the other.
Type oldType = oldTypeArgs.get(0);
oldType.delete();
// oldType is okay to add now w/o a clone, because it is detached.
newType.typeArguments().add(oldType);
}
}
private void parseParamAndNewClass(List<? extends BugAnnotation> annotations) {
BugAnnotation annotationWithFix = annotations.get(3);
// this should be like Bug: 1st parameter 's' could be declared as java.util.Set instead
String toParse = annotationWithFix.toString();
Matcher matcher = annotationParser.matcher(toParse);
if (matcher.matches()) {
paramName = matcher.group(1);
newDotSeparatedClass = matcher.group(2);
}
}
}