Skip to content

Commit

Permalink
[NETBEANS-1675]Java Hint to fix error :different case kinds used in s… (
Browse files Browse the repository at this point in the history
#1126)

* [NETBEANS-1675]Java Hint to fix error :different case kinds used in switch expressions

* Use getBody method

* Add review comment changes

* Change hint message
  • Loading branch information
vikasprabhakar authored and lkishalmi committed Mar 6, 2019
1 parent f718d2a commit a91ade4
Show file tree
Hide file tree
Showing 18 changed files with 1,073 additions and 196 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,5 @@ FIX_AccessError_PROTECTED=Make {0} protected
FIX_AccessError_PACKAGE_PRIVATE=Make {0} package private
ImportClassCustomizer.organizeImports.text=Format and sort imports
FIX_VarCompDeclaration=Split compound declaration
FIX_DifferentCaseKinds=Convert to rule switch case

Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.java.hints.errors;

import com.sun.source.tree.CaseTree;
import com.sun.source.tree.SwitchTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.TreePath;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.netbeans.api.java.queries.CompilerOptionsQuery;
import org.netbeans.api.java.source.CompilationInfo;
import org.netbeans.modules.java.hints.spi.ErrorRule;
import org.netbeans.modules.java.source.TreeShims;
import org.netbeans.spi.editor.hints.Fix;
import org.netbeans.spi.java.hints.JavaFix;
import org.netbeans.spi.java.hints.JavaFix.TransformationContext;
import org.openide.util.NbBundle;

/**
* Handle error rule "compiler.err.switch.mixing.case.types" and provide the
* fix.
*
* @author vkprabha
*/
public class DifferentCaseKindsFix implements ErrorRule<Void> {

private static final Set<String> ERROR_CODES = new HashSet<String>(Arrays.asList(
"compiler.err.switch.mixing.case.types")); // NOI18N

@Override
public Set<String> getCodes() {
return Collections.unmodifiableSet(ERROR_CODES);
}

@Override
public List<Fix> run(CompilationInfo info, String diagnosticKey, int offset, TreePath treePath, Data<Void> data) {
if (!CompilerOptionsQuery.getOptions(info.getFileObject()).getArguments().contains("--enable-preview")) {
return null;
}
TreePath parentPath = treePath.getParentPath();
List<? extends CaseTree> caseTrees = null;
boolean flag = false;
if(parentPath.getLeaf().getKind().toString().equals("SWITCH_EXPRESSION")){
caseTrees = TreeShims.getCases(parentPath.getLeaf());
} else {
flag = true;
caseTrees = ((SwitchTree) treePath.getParentPath().getLeaf()).getCases();
}
boolean completesNormally = false;
boolean wasDefault = false;
boolean wasEmpty = false;
for (CaseTree ct : caseTrees) {
if (ct.getStatements() == null && TreeShims.getBody(ct) == null) {
return null;
} else if (flag && ct.getStatements() != null) {
if (completesNormally) {
if (!wasEmpty) {//fall-through from a non-empty case
return null;
}
if (wasDefault) {//fall-through from default to a case
return null;
}
if (!wasDefault && ct.getExpression() == null) {//fall-through from a case to default
return null;
}
}
completesNormally = Utilities.completesNormally(info, new TreePath(treePath.getParentPath(), ct));
wasDefault = ct.getExpression() == null;
wasEmpty = ct.getStatements().isEmpty();
}
}

return Collections.<Fix>singletonList(new DifferentCaseKindsFix.FixImpl(info, treePath).toEditorFix());
}

@Override
public String getId() {
return DifferentCaseKindsFix.class.getName();
}

@Override
public String getDisplayName() {
return NbBundle.getMessage(DifferentCaseKindsFix.class, "FIX_DifferentCaseKinds"); // NOI18N
}

public String getDescription() {
return NbBundle.getMessage(DifferentCaseKindsFix.class, "FIX_DifferentCaseKinds"); // NOI18N
}

@Override
public void cancel() {
}

private static final class FixImpl extends JavaFix {

CompilationInfo info;
TreePath path;

public FixImpl(CompilationInfo info, TreePath path) {
super(info, path);
this.info = info;
this.path = path;
}

@Override
protected String getText() {
return NbBundle.getMessage(DifferentCaseKindsFix.class, "FIX_DifferentCaseKinds"); // NOI18N
}

public String toDebugString() {
return NbBundle.getMessage(DifferentCaseKindsFix.class, "FIX_DifferentCaseKinds"); // NOI18N
}

@Override
protected void performRewrite(TransformationContext ctx) {
TreePath tp = ctx.getPath();
Tree switchBlock = tp.getParentPath().getLeaf();
Utilities.performRewriteRuleSwitch(ctx, tp, switchBlock);
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,10 @@

import static com.sun.source.tree.Tree.Kind.*;
import com.sun.source.tree.UnaryTree;
import com.sun.source.util.TreePathScanner;
import com.sun.tools.javac.api.JavacScope;
import com.sun.tools.javac.api.JavacTaskImpl;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.comp.ArgumentAttr;
import com.sun.tools.javac.comp.Attr;
import com.sun.tools.javac.comp.AttrContext;
import com.sun.tools.javac.comp.Enter;
Expand All @@ -149,6 +149,8 @@
import org.netbeans.api.java.source.CodeStyleUtils;
import org.netbeans.api.java.source.TreeMaker;
import org.netbeans.modules.java.source.JavaSourceAccessor;
import org.netbeans.modules.java.source.TreeShims;
import org.netbeans.spi.java.hints.JavaFix;
import org.netbeans.spi.java.hints.JavaFixUtilities;
import org.openide.util.Pair;

Expand Down Expand Up @@ -3033,4 +3035,195 @@ public static ExecutableElement getFunctionalMethodFromElement(CompilationInfo i
return elementToReturn;
}

public static boolean completesNormally(CompilationInfo info, TreePath tp) {
class Scanner extends TreePathScanner<Void, Void> {

private boolean completesNormally = true;
private Set<Tree> seenTrees = new HashSet<>();

@Override
public Void visitReturn(ReturnTree node, Void p) {
completesNormally = false;
return null;
}

@Override
public Void visitBreak(BreakTree node, Void p) {
completesNormally &= seenTrees.contains(info.getTreeUtilities().getBreakContinueTarget(getCurrentPath()));
return null;
}

@Override
public Void visitContinue(ContinueTree node, Void p) {
completesNormally &= seenTrees.contains(info.getTreeUtilities().getBreakContinueTarget(getCurrentPath()));
return null;
}

@Override
public Void visitThrow(ThrowTree node, Void p) {
completesNormally = false;
return null;
}

@Override
public Void visitIf(IfTree node, Void p) {
boolean origCompletesNormally = completesNormally;
scan(node.getThenStatement(), p);
boolean afterThen = completesNormally;
completesNormally = origCompletesNormally;
scan(node.getElseStatement(), p);
completesNormally |= afterThen;
return null;
}

@Override
public Void visitSwitch(SwitchTree node, Void p) {
//exhaustiveness: (TODO)
boolean hasDefault = node.getCases().stream().anyMatch(c -> c.getExpression() == null);
if (node.getCases().size() > 0) {
scan(node.getCases().get(node.getCases().size() - 1), p);
}
completesNormally |= !hasDefault;
return null;
}

//TODO: loops
@Override
public Void scan(Tree tree, Void p) {
seenTrees.add(tree);
return super.scan(tree, p);
}

@Override
public Void visitLambdaExpression(LambdaExpressionTree node, Void p) {
return null;
}

@Override
public Void visitClass(ClassTree node, Void p) {
return null;
}
}

Scanner scanner = new Scanner();

scanner.scan(tp, null);
return scanner.completesNormally;
}

public static void performRewriteRuleSwitch(JavaFix.TransformationContext ctx, TreePath tp, Tree st) {
WorkingCopy wc = ctx.getWorkingCopy();
TreeMaker make = wc.getTreeMaker();
List<CaseTree> newCases = new ArrayList<>();
List<? extends CaseTree> cases;
Set<VariableElement> variablesDeclaredInOtherCases = new HashSet<>();
List<ExpressionTree> patterns = new ArrayList<>();
boolean switchExpressionFlag = st.getKind().toString().equals("SWITCH_EXPRESSION");
if (switchExpressionFlag) {
cases = TreeShims.getCases(st);
} else {
cases = ((SwitchTree) st).getCases();
}
for (Iterator<? extends CaseTree> it = cases.iterator(); it.hasNext();) {
CaseTree ct = it.next();
TreePath casePath = new TreePath(tp, ct);
patterns.addAll(TreeShims.getExpressions(ct));
List<StatementTree> statements;
if (ct.getStatements() == null) {
statements = new ArrayList<>(((JCTree.JCCase) ct).stats);//Collections.singletonList((StatementTree) TreeShims.getBody(ct));
} else {
statements = new ArrayList<>(ct.getStatements());
}
if (statements.isEmpty()) {
if (it.hasNext()) {
continue;
}
//last case, no break
} else if (!switchExpressionFlag && statements.get(statements.size() - 1).getKind() == Tree.Kind.BREAK
&& ctx.getWorkingCopy().getTreeUtilities().getBreakContinueTarget(new TreePath(new TreePath(tp, ct), statements.get(statements.size() - 1))) == st) {
statements.remove(statements.size() - 1);
} else {
new TreePathScanner<Void, Void>() {
@Override
public Void visitBlock(BlockTree node, Void p) {
if (!node.getStatements().isEmpty()
&& node.getStatements().get(node.getStatements().size() - 1).getKind() == Tree.Kind.BREAK
&& ctx.getWorkingCopy().getTreeUtilities().getBreakContinueTarget(new TreePath(getCurrentPath(), node.getStatements().get(node.getStatements().size() - 1))) == st) {
wc.rewrite(node, make.removeBlockStatement(node, node.getStatements().get(node.getStatements().size() - 1)));
//TODO: optimize ifs?
}
return super.visitBlock(node, p);
}
}.scan(new TreePath(new TreePath(tp, ct), statements.get(statements.size() - 1)), null);
}
Set<Element> seenVariables = new HashSet<>();
int idx = 0;
for (StatementTree statement : new ArrayList<>(statements)) {
TreePath statementPath = new TreePath(casePath, statement);
if (statement.getKind() == Tree.Kind.EXPRESSION_STATEMENT) {
ExpressionTree expr = ((ExpressionStatementTree) statement).getExpression();
if (expr.getKind() == Tree.Kind.ASSIGNMENT) {
AssignmentTree at = (AssignmentTree) expr;
Element var = wc.getTrees().getElement(new TreePath(new TreePath(statementPath, at), at.getVariable()));
if (variablesDeclaredInOtherCases.contains(var)) {
seenVariables.add(var);
//XXX: take type from the original variable
wc.rewrite(statement,
make.Variable(make.Modifiers(EnumSet.noneOf(Modifier.class)), var.getSimpleName(), make.Type(var.asType()), at.getExpression()));
}
}
}
Set<Element> thisStatementSeenVariables = new HashSet<>();
new TreePathScanner<Void, Void>() {
@Override
public Void visitIdentifier(IdentifierTree node, Void p) {
Element el = wc.getTrees().getElement(getCurrentPath());
if (variablesDeclaredInOtherCases.contains(el) && seenVariables.add(el)) {
thisStatementSeenVariables.add(el);
}
return super.visitIdentifier(node, p);
}
}.scan(statementPath, null);

if (!thisStatementSeenVariables.isEmpty()) {
for (Element el : thisStatementSeenVariables) {
VariableElement var = (VariableElement) el;
statements.add(idx++, make.Variable(make.Modifiers(EnumSet.noneOf(Modifier.class)), var.getSimpleName(), make.Type(var.asType()), null));
}
}
idx++;
}
Tree body = make.Block(statements, false);
if (statements.size() == 1) {
if (statements.get(0).getKind() == Tree.Kind.EXPRESSION_STATEMENT
|| statements.get(0).getKind() == Tree.Kind.THROW
|| statements.get(0).getKind() == Tree.Kind.BLOCK) {
body = statements.get(0);
}
}
newCases.add(make.Case(patterns, body));
patterns = new ArrayList<>();
for (StatementTree statement : getSwitchStatement(ct)) {
if (statement.getKind() == Tree.Kind.VARIABLE) {
variablesDeclaredInOtherCases.add((VariableElement) wc.getTrees().getElement(new TreePath(casePath, statement)));
}
}
}
if (switchExpressionFlag) {
wc.rewrite(st, make.SwitchExpression(TreeShims.getExpressions(st).get(0), newCases));
} else {
wc.rewrite((SwitchTree) st, make.Switch(((SwitchTree) st).getExpression(), newCases));
}
}

private static List<? extends StatementTree> getSwitchStatement(CaseTree ct) {
if (ct.getStatements() != null) {
return ct.getStatements();
} else if (ct instanceof JCTree.JCCase) {
return ((JCTree.JCCase) ct).stats;
} else {
return null;
}
}

}
Loading

0 comments on commit a91ade4

Please sign in to comment.