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.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.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.MethodDeclaration; import org.eclipse.jdt.core.dom.ReturnStatement; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.Statement; import org.eclipse.jdt.core.dom.SwitchCase; import org.eclipse.jdt.core.dom.SwitchStatement; import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; import org.eclipse.jdt.core.dom.rewrite.ListRewrite; import util.TraversalUtil; public class SwitchFallThroughResolution extends BugResolution { public static final String RETURN_FIELD = "Adds <code>return YYY;</code> to close off the case statement"; public static final String BREAK_DESCRIPTION = "Adds <code>break;</code> to close off the case statement"; private boolean shouldUseBreak; private String description; @Override protected boolean resolveBindings() { return true; } @Override public void setOptions(Map<String, String> options) { this.shouldUseBreak = Boolean.parseBoolean(options.get("useBreak")); } @Override @SuppressFBWarnings(value = "BAS_BLOATED_ASSIGNMENT_SCOPE", justification = "the call to getLabel() is needed to fill out information for custom descriptions") public String getDescription() { if (shouldUseBreak) return BREAK_DESCRIPTION; if (description == null) { String label = getLabel(); // force traversing, which fills in description if (description == null) { return label; // something funky is happening, description shouldn't be null // We'll be safe and return label (which is not null) } } return description; } @Override protected ASTVisitor getApplicabilityVisitor() { return new FallThroughVisitor(); } @Override protected ASTVisitor getCustomLabelVisitor() { return new FallThroughVisitor(); } @Override protected void repairBug(ASTRewrite rewrite, CompilationUnit workingUnit, BugInstance bug) throws BugResolutionException { ASTNode node = getASTNode(workingUnit, bug.getPrimarySourceLineAnnotation()); FallThroughVisitor visitor = new FallThroughVisitor(); node.accept(visitor); if (visitor.badSwitchStatement != null && visitor.caseFellThrough != null) { SwitchStatement ss = visitor.badSwitchStatement; // easiest way to insert a new statement into the old statements is // to make a listRewrite from the statements ListRewrite switchRewrite = rewrite.getListRewrite(ss, SwitchStatement.STATEMENTS_PROPERTY); Statement newStatement = makeFixedStatement(rewrite, visitor); switchRewrite.insertBefore(newStatement, visitor.caseFellThrough, null); } } private Statement makeFixedStatement(ASTRewrite rewrite, FallThroughVisitor visitor) { AST ast = rewrite.getAST(); if (shouldUseBreak) { return ast.newBreakStatement(); } ReturnStatement retVal = ast.newReturnStatement(); // this allows the fallThroughField to be either a FieldAccess (i.e. with this) // or a SimpleName retVal.setExpression((Expression) rewrite.createCopyTarget(visitor.fallThroughField)); return retVal; } private class FallThroughVisitor extends ASTVisitor implements CustomLabelVisitor, ApplicabilityVisitor { public SwitchCase caseFellThrough; public SwitchStatement badSwitchStatement; public Expression fallThroughField; @Override public boolean visit(Assignment node) { if (badSwitchStatement != null) { return false; } badSwitchStatement = TraversalUtil.findClosestAncestor(node, SwitchStatement.class); caseFellThrough = findBadFallThroughCase(TraversalUtil.findClosestAncestor(node, Statement.class)); if (!shouldUseBreak) { findFieldName(node.getLeftHandSide()); } return false; } private void findFieldName(Expression leftHandSide) { if ((leftHandSide instanceof FieldAccess) || (leftHandSide instanceof SimpleName && TraversalUtil.nameRefersToField((SimpleName) leftHandSide))) { boolean equals = doesThisTypeMatchMethodReturnType(leftHandSide); if (equals) { this.fallThroughField = leftHandSide; } } } private boolean doesThisTypeMatchMethodReturnType(Expression expression) { MethodDeclaration enclosingMethod = TraversalUtil.findClosestAncestor(expression, MethodDeclaration.class); String returnMethodType = enclosingMethod.getReturnType2().resolveBinding().getQualifiedName(); String storedObjectType = expression.resolveTypeBinding().getQualifiedName(); // if the type of the object we are storing return storedObjectType.equals(returnMethodType); } @SuppressWarnings("unchecked") private SwitchCase findBadFallThroughCase(Statement lookingForStatement) { List<Statement> switchStatements = badSwitchStatement.statements(); SwitchCase lastSwitchCase = null; for (Statement statement : switchStatements) { if (statement instanceof SwitchCase) { lastSwitchCase = (SwitchCase) statement; } if (statement == lookingForStatement) { return lastSwitchCase; } } return null; } @Override public boolean isApplicable() { return shouldUseBreak || fallThroughField != null; } @Override public String getLabelReplacement() { if (shouldUseBreak) { return ""; } String fieldString = fallThroughField == null ? "" : fallThroughField.toString(); description = RETURN_FIELD.replace("YYY", fieldString); return fieldString; } } }