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.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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.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.StringLiteral;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
public class FormatStringResolution extends BugResolution {
@Override
protected boolean resolveBindings() {
return true;
}
@Override
protected void repairBug(ASTRewrite rewrite, CompilationUnit workingUnit, BugInstance bug) throws BugResolutionException {
ASTNode node = getASTNode(workingUnit, bug.getPrimarySourceLineAnnotation());
FormatVisitor visitor = new FormatVisitor();
node.accept(visitor);
StringLiteral newStringLiteral = rewrite.getAST().newStringLiteral();
newStringLiteral.setLiteralValue(visitor.fixedString);
rewrite.replace(visitor.badStringLiteral, newStringLiteral, null);
}
private static class FormatVisitor extends ASTVisitor {
public StringLiteral badStringLiteral = null;
public String fixedString = null;
// The same code from Formatter.format
// %[argument_index$][flags][width][.precision][t]conversion
private static final String formatSpecifierRegex = "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])";
private static Pattern fsPattern = Pattern.compile(formatSpecifierRegex);
private static Set<String> integralSpecifiers = new HashSet<String>();
private static Set<String> integralTypes = new HashSet<String>();
private static Set<String> floatSpecifiers = new HashSet<String>();
private static Set<String> floatTypes = new HashSet<String>();
static {
// From http://docs.oracle.com/javase/7/docs/api/java/util/Formatter.html
integralSpecifiers.add("d");
integralSpecifiers.add("o");
integralSpecifiers.add("x");
integralSpecifiers.add("X");
integralTypes.add("int");
integralTypes.add("long");
integralTypes.add("short");
integralTypes.add("byte");
floatSpecifiers.add("e");
floatSpecifiers.add("E");
floatSpecifiers.add("f");
floatSpecifiers.add("g");
floatSpecifiers.add("G");
floatSpecifiers.add("a");
floatSpecifiers.add("A");
floatTypes.addAll(integralTypes);
floatTypes.add("float");
floatTypes.add("double");
}
@Override
public boolean visit(MethodInvocation node) {
if (badStringLiteral != null) {
return false;
}
@SuppressWarnings("unchecked")
List<Expression> arguments = node.arguments();
if (arguments.size() < 2) {
return true;
}
Expression firstArg = arguments.get(0);
if (firstArg instanceof StringLiteral) {
badStringLiteral = (StringLiteral) firstArg;
fixedString = correctStringFormat(badStringLiteral.getLiteralValue(), arguments);
return false;
}
return true;
}
private String correctStringFormat(String original, List<Expression> arguments) {
StringBuilder builder = new StringBuilder(original);
Matcher m = fsPattern.matcher(original);
int argumentIndex = 1;
for (int i = 0, len = original.length(); i < len && argumentIndex < arguments.size();) {
if (m.find(i)) {
String type = m.group(6); // 7th group is the thing like d in %d
String argumentType = arguments.get(argumentIndex).resolveTypeBinding().getQualifiedName();
if (("b".equalsIgnoreCase(type) && !"boolean".equals(argumentType)) ||
(integralSpecifiers.contains(type) && !integralTypes.contains(argumentType)) ||
(floatSpecifiers.contains(type) && !floatTypes.contains(argumentType))) {
builder.setCharAt(m.end() - 1, 's');
}
argumentIndex++;
i = m.end();
} else {
// No more valid format specifiers.
break;
}
}
return builder.toString().replace("\n", "%n"); // fix \n
}
}
}