package quickfix;
import static edu.umd.cs.findbugs.plugin.eclipse.quickfix.util.ASTUtil.getASTNode;
import static util.TraversalUtil.backtrackToBlock;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
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.exception.BugResolutionException;
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.IBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
public class ConvertingStringLiteralsResolution extends BugResolution {
@Override
protected boolean resolveBindings() {
return true;
}
@Override
protected ASTVisitor getApplicabilityVisitor() {
return new StringLiteralVisitor();
}
@Override
protected void repairBug(ASTRewrite rewrite, CompilationUnit workingUnit, BugInstance bug) throws BugResolutionException {
ASTNode node = getASTNode(workingUnit, bug.getPrimarySourceLineAnnotation());
node = backtrackToBlock(node);
StringLiteralVisitor visitor = new StringLiteralVisitor();
node.accept(visitor);
StringLiteral newLiteral = rewrite.getAST().newStringLiteral();
newLiteral.setLiteralValue(visitor.fixedStringLiteral);
rewrite.replace(visitor.badMethodInvocation, newLiteral, null);
}
private static Set<String> dummyMethods = new HashSet<String>(3);
static {
dummyMethods.add("trim");
dummyMethods.add("toLowerCase");
dummyMethods.add("toUpperCase");
}
private class StringLiteralVisitor extends ASTVisitor implements ApplicabilityVisitor {
private MethodInvocation badMethodInvocation;
private String fixedStringLiteral;
private boolean isApplicable = false;
@SuppressWarnings("unchecked")
@Override
public boolean visit(MethodInvocation node) {
try {
if (isRedundantMethod(node)) {
if (isInvokedOnStringLiteral(node.getExpression())) {
isApplicable = true;
this.badMethodInvocation = node;
this.fixedStringLiteral = fixStringLiteral(node);
List<Expression> arguments = node.arguments();
if (arguments.size() > 0) {
isApplicable = parseLocale(arguments.get(0)) != null;
}
return false;
}
}
} catch (RuntimeException e) {
return false;
}
return true;
}
@SuppressWarnings("unchecked")
private String fixStringLiteral(MethodInvocation node) {
Expression expr = node.getExpression();
String fixedString = (String) expr.resolveConstantExpressionValue();
if (fixedString == null && expr instanceof MethodInvocation) {
fixedString = fixStringLiteral((MethodInvocation) expr);
}
if (fixedString == null) {
throw new RuntimeException("Could not find literal in " + node);
}
List<Expression> arguments = node.arguments();
switch (node.getName().getIdentifier()) {
case "trim":
fixedString = fixedString.trim();
break;
case "toLowerCase":
if (arguments.isEmpty()) {
fixedString = fixedString.toLowerCase();
} else {
Locale parsedLocale = parseLocale(arguments.get(0));
if (parsedLocale != null) {
fixedString = fixedString.toLowerCase(parsedLocale);
}
}
break;
case "toUpperCase":
if (arguments.isEmpty()) {
fixedString = fixedString.toUpperCase();
} else {
Locale parsedLocale = parseLocale(arguments.get(0));
if (parsedLocale != null) {
fixedString = fixedString.toUpperCase(parsedLocale);
}
}
break;
default:
break;
}
return fixedString;
}
private Locale parseLocale(Expression expression) {
if (expression instanceof Name) {
IBinding varBinding = ((Name) expression).resolveBinding();
String fieldName = ((IVariableBinding) varBinding).getName();
Class<Locale> localeClass = Locale.class;
try {
Field f = localeClass.getField(fieldName);
return (Locale) f.get(null); // gets the actual locale, if it exists
} catch (Exception e) {
return null;
}
}
return null;
}
private boolean isInvokedOnStringLiteral(Expression expression) {
if (!"String".equals(expression.resolveTypeBinding().getName())) {
return false;
}
if (expression.resolveConstantExpressionValue() != null) {
return true;
}
if (expression instanceof MethodInvocation) {
MethodInvocation mi = (MethodInvocation) expression;
return isRedundantMethod(mi) && isInvokedOnStringLiteral(mi.getExpression());
}
return false;
}
private boolean isRedundantMethod(MethodInvocation node) {
if (!dummyMethods.contains(node.getName().getIdentifier())) {
return false;
}
List<Expression> arguments = node.arguments();
if (arguments.isEmpty()) {
return true;
}
return !(arguments.get(0) instanceof MethodInvocation);
}
@Override
public boolean isApplicable() {
return isApplicable;
}
}
}