package quickfix; import static edu.umd.cs.findbugs.plugin.eclipse.quickfix.util.ASTUtil.getASTNode; import java.util.List; import java.util.Map; 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.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.Assignment; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.FieldAccess; import org.eclipse.jdt.core.dom.FieldDeclaration; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.ThisExpression; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.dom.VariableDeclarationFragment; import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; import util.TraversalUtil; public class DeadShadowStoreResolution extends BugResolution { public static final String DSS_DESC = "Turns the assignment to a local shadow variable into a field assignment " + "by adding a this. to the variable"; private boolean searchParentClass; @Override protected boolean resolveBindings() { return true; } @Override public void setOptions(Map<String, String> options) { searchParentClass = Boolean.parseBoolean(options.get("searchParentClass")); } @Override protected void repairBug(ASTRewrite rewrite, CompilationUnit workingUnit, BugInstance bug) throws BugResolutionException { ASTNode node = getASTNode(workingUnit, bug.getPrimarySourceLineAnnotation()); node = TraversalUtil.backtrackToBlock(node); DeadStoreVisitor visitor = new DeadStoreVisitor(); node.accept(visitor); SimpleName leftSide = visitor.badLeftSideName; if (leftSide != null) { FieldAccess newField = makeFieldAccess(rewrite, visitor); rewrite.replace(leftSide, newField, null); } else { System.err.println("Could not find a local assignment to replace."); } } @Override protected ASTVisitor getApplicabilityVisitor() { if (searchParentClass) { return new DeadStoreVisitor(); } // don't need applicability if we don't search the enclosing classes return null; } @Override protected ASTVisitor getCustomLabelVisitor() { if (searchParentClass) { return new DeadStoreVisitor(); } // don't need a custom label if we don't search the enclosing classes return null; } private FieldAccess makeFieldAccess(ASTRewrite rewrite, DeadStoreVisitor visitor) { AST ast = rewrite.getAST(); FieldAccess newField = ast.newFieldAccess(); ThisExpression thisExpression = ast.newThisExpression(); if (searchParentClass) { thisExpression.setQualifier(ast.newSimpleName(visitor.simpleNameOfEnclosingType)); } newField.setExpression(thisExpression); newField.setName((SimpleName) rewrite.createMoveTarget(visitor.badLeftSideName)); return newField; } @Override public String getDescription() { return DSS_DESC; } private class DeadStoreVisitor extends ASTVisitor implements ApplicabilityVisitor, CustomLabelVisitor { public SimpleName badLeftSideName; public String simpleNameOfEnclosingType; @Override public boolean visit(Assignment node) { if (badLeftSideName != null) { return false; } Expression left = node.getLeftHandSide(); if (left instanceof SimpleName) { badLeftSideName = (SimpleName) left; checkParentScope(node); return false; } return true; } private void checkParentScope(ASTNode node) { if (!searchParentClass || node == null) { return; } if (node instanceof TypeDeclaration) { if (searchForField((TypeDeclaration) node)) { simpleNameOfEnclosingType = ((TypeDeclaration) node).resolveBinding().getName(); return; } } checkParentScope(node.getParent()); } private boolean searchForField(TypeDeclaration node) { FieldDeclaration[] fields = node.getFields(); // enumerate fields for (FieldDeclaration field : fields) { @SuppressWarnings("unchecked") // each fieldDeclaration has 1 or more fragments that declare a variable. List<VariableDeclarationFragment> fragments = field.fragments(); for (VariableDeclarationFragment declaration : fragments) { // the normal .equals() only does reference equality, so we need to check // the name of the variables. if (badLeftSideName.getIdentifier().equals(declaration.getName().getIdentifier())) { simpleNameOfEnclosingType = node.getName().getIdentifier(); } } } return false; } @Override public boolean isApplicable() { return !searchParentClass || simpleNameOfEnclosingType != null; } @Override public String getLabelReplacement() { if (searchParentClass && simpleNameOfEnclosingType != null) { return simpleNameOfEnclosingType + ".this." + badLeftSideName.getIdentifier(); } return ""; } } }