This is an automated email from the ASF dual-hosted git repository. lkishalmi pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/incubator-netbeans.git
The following commit(s) were added to refs/heads/master by this push: new a91ade4 [NETBEANS-1675]Java Hint to fix error :different case kinds used in s… (#1126) a91ade4 is described below commit a91ade4963d0322b575402375b3a169c1eabc11b Author: Vikas Prabhakar <vikas.prabha...@oracle.com> AuthorDate: Wed Mar 6 20:26:33 2019 +0530 [NETBEANS-1675]Java Hint to fix error :different case kinds used in s… (#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 --- .../modules/java/hints/errors/Bundle.properties | 2 + .../java/hints/errors/DifferentCaseKindsFix.java | 143 ++++++++ .../modules/java/hints/errors/Utilities.java | 195 +++++++++- .../java/hints/jdk/ConvertSwitchToRuleSwitch.java | 185 +--------- .../modules/java/hints/resources/layer.xml | 1 + .../java/hints/errors/Bundle_test.properties | 1 + .../hints/errors/DifferentCaseKindsFixTest.java | 397 +++++++++++++++++++++ java/java.source.base/apichanges.xml | 12 + java/java.source.base/nbproject/project.properties | 2 +- .../org/netbeans/api/java/source/TreeMaker.java | 14 +- .../org/netbeans/api/java/source/WorkingCopy.java | 6 + .../netbeans/modules/java/source/TreeShims.java | 15 + .../modules/java/source/builder/TreeFactory.java | 7 +- .../modules/java/source/matching/CopyFinder.java | 11 +- .../modules/java/source/pretty/VeryPretty.java | 19 +- .../modules/java/source/save/CasualDiff.java | 31 ++ .../source/transform/ImmutableTreeTranslator.java | 13 + .../api/java/source/gen/SwitchExpressionTest.java | 215 +++++++++++ 18 files changed, 1073 insertions(+), 196 deletions(-) diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/errors/Bundle.properties b/java/java.hints/src/org/netbeans/modules/java/hints/errors/Bundle.properties index 4b69f81..6f8a035 100644 --- a/java/java.hints/src/org/netbeans/modules/java/hints/errors/Bundle.properties +++ b/java/java.hints/src/org/netbeans/modules/java/hints/errors/Bundle.properties @@ -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 + diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/errors/DifferentCaseKindsFix.java b/java/java.hints/src/org/netbeans/modules/java/hints/errors/DifferentCaseKindsFix.java new file mode 100644 index 0000000..c91f65e1 --- /dev/null +++ b/java/java.hints/src/org/netbeans/modules/java/hints/errors/DifferentCaseKindsFix.java @@ -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); + } + + } + +} diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/errors/Utilities.java b/java/java.hints/src/org/netbeans/modules/java/hints/errors/Utilities.java index fbb6a15..43886a2 100644 --- a/java/java.hints/src/org/netbeans/modules/java/hints/errors/Utilities.java +++ b/java/java.hints/src/org/netbeans/modules/java/hints/errors/Utilities.java @@ -125,10 +125,10 @@ import org.openide.util.Exceptions; 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; @@ -149,6 +149,8 @@ import org.netbeans.api.java.source.CodeStyle; 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; @@ -3033,4 +3035,195 @@ public class Utilities { 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; + } + } + } diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/jdk/ConvertSwitchToRuleSwitch.java b/java/java.hints/src/org/netbeans/modules/java/hints/jdk/ConvertSwitchToRuleSwitch.java index 4f8955d..c5b5302 100644 --- a/java/java.hints/src/org/netbeans/modules/java/hints/jdk/ConvertSwitchToRuleSwitch.java +++ b/java/java.hints/src/org/netbeans/modules/java/hints/jdk/ConvertSwitchToRuleSwitch.java @@ -18,40 +18,13 @@ */ package org.netbeans.modules.java.hints.jdk; -import com.sun.source.tree.AssignmentTree; -import com.sun.source.tree.BlockTree; -import com.sun.source.tree.BreakTree; import com.sun.source.tree.CaseTree; -import com.sun.source.tree.ClassTree; -import com.sun.source.tree.ContinueTree; -import com.sun.source.tree.ExpressionStatementTree; -import com.sun.source.tree.ExpressionTree; -import com.sun.source.tree.IdentifierTree; -import com.sun.source.tree.IfTree; -import com.sun.source.tree.LambdaExpressionTree; -import com.sun.source.tree.ReturnTree; -import com.sun.source.tree.StatementTree; import com.sun.source.tree.SwitchTree; -import com.sun.source.tree.ThrowTree; import com.sun.source.tree.Tree; -import com.sun.source.tree.Tree.Kind; import com.sun.source.util.TreePath; -import com.sun.source.util.TreePathScanner; -import com.sun.source.util.TreeScanner; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; -import javax.lang.model.element.Element; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.VariableElement; import org.netbeans.api.java.queries.CompilerOptionsQuery; import org.netbeans.api.java.source.CompilationInfo; -import org.netbeans.api.java.source.TreeMaker; -import org.netbeans.api.java.source.WorkingCopy; -import org.netbeans.modules.java.hints.TreeShims; +import org.netbeans.modules.java.hints.errors.Utilities; import org.netbeans.spi.editor.hints.ErrorDescription; import org.netbeans.spi.java.hints.ErrorDescriptionFactory; import org.netbeans.spi.java.hints.Hint; @@ -89,77 +62,12 @@ public class ConvertSwitchToRuleSwitch { if (!wasDefault && ct.getExpression() == null) //fall-through from a case to default return null; } - completesNormally = completesNormally(ctx.getInfo(), new TreePath(ctx.getPath(), ct)); + completesNormally = Utilities.completesNormally(ctx.getInfo(), new TreePath(ctx.getPath(), ct)); wasDefault = ct.getExpression() == null; wasEmpty = ct.getStatements().isEmpty(); } return ErrorDescriptionFactory.forName(ctx, ctx.getPath(), Bundle.ERR_ConverSwitchToRuleSwitch(), new FixImpl(ctx.getInfo(), ctx.getPath()).toEditorFix()); } - private 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; - } private static final class FixImpl extends JavaFix { @@ -175,96 +83,9 @@ public class ConvertSwitchToRuleSwitch { @Override protected void performRewrite(TransformationContext ctx) { - WorkingCopy wc = ctx.getWorkingCopy(); TreePath tp = ctx.getPath(); - TreeMaker make = wc.getTreeMaker(); SwitchTree st = (SwitchTree) tp.getLeaf(); - List<CaseTree> newCases = new ArrayList<>(); - List<ExpressionTree> patterns = new ArrayList<>(); - Set<VariableElement> variablesDeclaredInOtherCases = new HashSet<>(); - - for (Iterator<? extends CaseTree> it = st.getCases().iterator(); it.hasNext();) { - CaseTree ct = it.next(); - TreePath casePath = new TreePath(tp, ct); - patterns.addAll(TreeShims.getExpressions(ct)); - List<StatementTree> statements = new ArrayList<>(ct.getStatements()); - if (statements.isEmpty()) { - if (it.hasNext()) { - continue; - } - //last case, no break - } else if (statements.get(statements.size() - 1).getKind() == 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() == 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() == Kind.EXPRESSION_STATEMENT) { - ExpressionTree expr = ((ExpressionStatementTree) statement).getExpression(); - if (expr.getKind() == 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() == Kind.EXPRESSION_STATEMENT || - statements.get(0).getKind() == Kind.THROW || - statements.get(0).getKind() == Kind.BLOCK) { - body = statements.get(0); - } - } - newCases.add(make.Case(patterns, body)); - patterns = new ArrayList<>(); - for (StatementTree statement : ct.getStatements()) { - if (statement.getKind() == Kind.VARIABLE) { - variablesDeclaredInOtherCases.add((VariableElement) wc.getTrees().getElement(new TreePath(casePath, statement))); - } - } - } - - wc.rewrite(st, make.Switch(st.getExpression(), newCases)); + Utilities.performRewriteRuleSwitch(ctx, tp, st); } } diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/resources/layer.xml b/java/java.hints/src/org/netbeans/modules/java/hints/resources/layer.xml index aa9d4e9..96786f7 100644 --- a/java/java.hints/src/org/netbeans/modules/java/hints/resources/layer.xml +++ b/java/java.hints/src/org/netbeans/modules/java/hints/resources/layer.xml @@ -148,6 +148,7 @@ <file name="org-netbeans-modules-java-hints-errors-ImportClass.instance"/> <file name="org-netbeans-modules-java-hints-errors-AddCast.instance"/> <file name="org-netbeans-modules-java-hints-errors-VarCompDeclaration.instance"/> + <file name="org-netbeans-modules-java-hints-errors-DifferentCaseKindsFix.instance"/> <file name="org-netbeans-modules-java-hints-errors-CreateElement.instance"/> <file name="org-netbeans-modules-java-hints-errors-ChangeMethodParameters.instance"/> <file name="org-netbeans-modules-java-hints-errors-RenameConstructor.instance"/> diff --git a/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/errors/Bundle_test.properties b/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/errors/Bundle_test.properties index 334ec9c..c0a0acc 100644 --- a/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/errors/Bundle_test.properties +++ b/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/errors/Bundle_test.properties @@ -52,6 +52,7 @@ FIX_AccessError_PUBLIC=FIX_AccessError_PUBLIC:{0} FIX_AccessError_PROTECTED=FIX_AccessError_PROTECTED:{0} FIX_AccessError_PACKAGE_PRIVATE=FIX_AccessError_PACKAGE_PRIVATE:{0} FIX_VarCompDeclaration=FIX_VarCompDeclaration +FIX_DifferentCaseKinds=FIX_DifferentCaseKinds LBL_Impl_Abstract_Methods=LBL_Impl_Abstract_Methods ERR_CannotOverrideAbstractMethods=ERR_CannotOverrideAbstractMethods diff --git a/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/errors/DifferentCaseKindsFixTest.java b/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/errors/DifferentCaseKindsFixTest.java new file mode 100644 index 0000000..9193cfb --- /dev/null +++ b/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/errors/DifferentCaseKindsFixTest.java @@ -0,0 +1,397 @@ +/* + * 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.util.TreePath; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import javax.swing.event.ChangeListener; +import org.netbeans.api.java.source.CompilationInfo; +import org.netbeans.modules.java.hints.infrastructure.ErrorHintsTestBase; +import org.netbeans.modules.java.source.parsing.JavacParser; +import org.netbeans.spi.editor.hints.Fix; +import org.netbeans.spi.java.queries.CompilerOptionsQueryImplementation; +import org.openide.filesystems.FileObject; +import org.openide.util.NbBundle; +import org.openide.util.lookup.ServiceProvider; + +/** + * Test cases for the Different Case Kinds errors fix. + * + */ +public class DifferentCaseKindsFixTest extends ErrorHintsTestBase { + + private static final List<String> EXTRA_OPTIONS = new ArrayList<>(); + + public DifferentCaseKindsFixTest(String name) { + super(name, DifferentCaseKindsFix.class); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + sourceLevel = "12"; + JavacParser.DISABLE_SOURCE_LEVEL_DOWNGRADE = true; + EXTRA_OPTIONS.add("--enable-preview"); + } + + @ServiceProvider(service = CompilerOptionsQueryImplementation.class, position = 100) + public static class TestCompilerOptionsQueryImplementation implements CompilerOptionsQueryImplementation { + + @Override + public CompilerOptionsQueryImplementation.Result getOptions(FileObject file) { + return new CompilerOptionsQueryImplementation.Result() { + @Override + public List<? extends String> getArguments() { + return EXTRA_OPTIONS; + } + + @Override + public void addChangeListener(ChangeListener listener) { + } + + @Override + public void removeChangeListener(ChangeListener listener) { + } + }; + } + + } + + public void testCase1() throws Exception { + performFixTest("test/Test.java", + "package test; \n" + + "public class Test {\n" + + " private void test(int p) {\n" + + " String result;\n" + + " switch (p) {\n" + + " case 1: result = \"1\"; break;\n" + + " case 2 -> result = \"2\";\n" + + " default -> result = \"3\";\n" + + " }\n" + + " }\n" + + "}\n", + -1, + NbBundle.getMessage(DifferentCaseKindsFix.class, "FIX_DifferentCaseKinds"), + ("package test; \n" + + "public class Test {\n" + + " private void test(int p) {\n" + + " String result;\n" + + " switch (p) {\n" + + " case 1 -> result = \"1\";\n" + + " case 2 -> result = \"2\";\n" + + " default -> result = \"3\";\n" + + " }\n" + + " }\n" + + "}\n").replaceAll("[\\s]+", " ")); + } + + public void testCase2() throws Exception { + performFixTest("test/Test.java", + "package test; \n" + + "public class Test {\n" + + " private void test(int p) {\n" + + " String result;\n" + + " switch (p) {\n" + + " case 1 -> result = \"1\"; break;\n" + + " case 2 -> result = \"2\";\n" + + " default : result = \"3\"; break;\n" + + " }\n" + + " }\n" + + "}\n", + -1, + NbBundle.getMessage(DifferentCaseKindsFix.class, "FIX_DifferentCaseKinds"), + ("package test; \n" + + "public class Test {\n" + + " private void test(int p) {\n" + + " String result;\n" + + " switch (p) {\n" + + " case 1 -> result = \"1\";\n" + + " case 2 -> result = \"2\";\n" + + " default -> result = \"3\";\n" + + " }\n" + + " }\n" + + "}\n").replaceAll("[\\s]+", " ")); + } + + public void testCase3() throws Exception { + performFixTest("test/Test.java", + "package test; \n" + + "public class Test {\n" + + " private void test(int p) {\n" + + " String result;\n" + + " switch (p) {\n" + + " case 1: result = \"1\"; break;\n" + + " case 2: if (true) result = \"2\"; break;\n" + + " case 3 -> { System.err.println(3); result = \"3\";}\n" + + " }\n" + + " }\n" + + "}\n", + -1, + NbBundle.getMessage(DifferentCaseKindsFix.class, "FIX_DifferentCaseKinds"), + ("package test; \n" + + "public class Test {\n" + + " private void test(int p) {\n" + + " String result;\n" + + " switch (p) {\n" + + " case 1 -> result = \"1\";\n" + + " case 2 -> { if (true) result = \"2\"; }\n" + + " case 3 -> { System.err.println(3); result = \"3\";}\n" + + " }\n" + + " }\n" + + "}\n").replaceAll("[\\s]+", " ")); + } + + public void testCase4() throws Exception { + performFixTest("test/Test.java", + "package test; \n" + + "public class Test {\n" + + " private void test(int p) {\n" + + " String result;\n" + + " switch (p) {\n" + + " case 0:\n" + + " case 1: result = \"1\"; break;\n" + + " case 2 -> result = \"2\";\n" + + " }\n" + + " }\n" + + "}\n", + -1, + NbBundle.getMessage(DifferentCaseKindsFix.class, "FIX_DifferentCaseKinds"), + ("package test; \n" + + "public class Test {\n" + + " private void test(int p) {\n" + + " String result;\n" + + " switch (p) {\n" + + " case 0, 1 -> result = \"1\";\n" + + " case 2 -> result = \"2\";\n" + + " }\n" + + " }\n" + + "}\n").replaceAll("[\\s]+", " ")); + } + + public void testCase5() throws Exception { + performFixTest("test/Test.java", + "package test; \n" + + "public class Test {\n" + + " private void test(int p) {\n" + + " String result;\n" + + " switch (p) {\n" + + " case 0 -> {\n" + + " int i = 0;\n" + + " int j = 0;\n" + + " }\n" + + " default:\n" + + " i = 0;\n" + + " System.err.println(i);\n" + + " System.err.println(j = 15);\n" + + " break;\n" + + " }\n" + + " }\n" + + "}\n", + -1, + NbBundle.getMessage(DifferentCaseKindsFix.class, "FIX_DifferentCaseKinds"), + ("package test; \n" + + "public class Test {\n" + + " private void test(int p) {\n" + + " String result;\n" + + " switch (p) {\n" + + " case 0 -> {\n" + + " int i = 0;\n" + + " int j = 0;\n" + + " }\n" + + " default -> {\n" + + " i = 0;\n" + + " System.err.println(i);\n" + + " System.err.println(j = 15);\n" + + " }\n" + + " }\n" + + " }\n" + + "}\n").replaceAll("[\\s]+", " ")); + } + + public void testCase6() throws Exception { + performFixTest("test/Test.java", + "package test; \n" + + "public class Test {\n" + + " private void test(int p) {\n" + + " var result = \n" + + " switch (p) {\n" + + " case 1: break 1;\n" + + " case 2 -> 2;\n" + + " default -> 3;\n" + + " }\n" + + " }\n" + + "}\n", + -1, + NbBundle.getMessage(DifferentCaseKindsFix.class, "FIX_DifferentCaseKinds"), + ("package test; \n" + + "public class Test {\n" + + " private void test(int p) {\n" + + " var result =\n" + + " switch (p) {\n" + + " case 1 -> { break 1; }\n" + + " case 2 -> { break 2; }\n" + + " default -> { break 3; }\n" + + " }\n" + + " }\n" + + "}\n").replaceAll("[\\s]+", " ")); + } + + public void testCase7() throws Exception { + performFixTest("test/Test.java", + "package test; \n" + + "public class Test {\n" + + " private void test(int p) {\n" + + " var result = \n" + + " switch (p) {\n" + + " case 1 -> 1;\n" + + " case 2 -> 2;\n" + + " default : break 3;\n" + + " }\n" + + " }\n" + + "}\n", + -1, + NbBundle.getMessage(DifferentCaseKindsFix.class, "FIX_DifferentCaseKinds"), + ("package test; \n" + + "public class Test {\n" + + " private void test(int p) {\n" + + " var result = \n" + + " switch (p) {\n" + + " case 1 -> { break 1; }\n" + + " case 2 -> { break 2; }\n" + + " default -> { break 3; }\n" + + " }\n" + + " }\n" + + "}\n").replaceAll("[\\s]+", " ")); + } + + public void testCase8() throws Exception { + performFixTest("test/Test.java", + "package test; \n" + + "public class Test {\n" + + " private void test(int p) {\n" + + " var result = \n" + + " switch (p) {\n" + + " case 1: break 1; \n" + + " case 2: break getTest();\n" + + " case 3 -> { System.err.println(3); break 3;}\n" + + " }\n" + + " }\n" + + " private int getTest() {\n" + + " return 10;\n" + + " }\n" + + "}\n", + -1, + NbBundle.getMessage(DifferentCaseKindsFix.class, "FIX_DifferentCaseKinds"), + ("package test; \n" + + "public class Test {\n" + + " private void test(int p) {\n" + + " var result = \n" + + " switch (p) {\n" + + " case 1 -> { break 1; }\n" + + " case 2 -> { break getTest(); }\n" + + " case 3 -> { System.err.println(3); break 3;}\n" + + " }\n" + + " }\n" + + " private int getTest() {\n" + + " return 10;\n" + + " }\n" + + "}\n").replaceAll("[\\s]+", " ")); + } + + public void testCase9() throws Exception { + performFixTest("test/Test.java", + "package test; \n" + + "public class Test {\n" + + " private void test(int p) {\n" + + " String result = \n" + + " switch (p) {\n" + + " case 0:\n" + + " case 1: break \"1\"; \n" + + " case 2 -> \"2\";\n" + + " }\n" + + " }\n" + + "}\n", + -1, + NbBundle.getMessage(DifferentCaseKindsFix.class, "FIX_DifferentCaseKinds"), + ("package test; \n" + + "public class Test {\n" + + " private void test(int p) {\n" + + " String result = \n" + + " switch (p) {\n" + + " case 0, 1 -> { break \"1\"; }\n" + + " case 2 -> { break \"2\"; }\n" + + " }\n" + + " }\n" + + "}\n").replaceAll("[\\s]+", " ")); + } + + public void testCase10() throws Exception { + performFixTest("test/Test.java", + "package test; \n" + + "public class Test {\n" + + " private void test(int p) {\n" + + " int result;\n" + + " result = switch (p) {\n" + + " case 1 : \n" + + " int x = 1;\n" + + " break x;\n" + + " default -> {\n" + + " int y = 1;\n" + + " break 3;\n" + + " }\n" + + " }\n" + + " }\n" + + "}\n", + -1, + NbBundle.getMessage(DifferentCaseKindsFix.class, "FIX_DifferentCaseKinds"), + ("package test; \n" + + "public class Test {\n" + + " private void test(int p) {\n" + + " int result;\n" + + " result = switch (p) {\n" + + " case 1 -> {\n" + + " int x = 1;\n" + + " break x;\n" + + " }\n" + + " default -> {\n" + + " int y = 1;\n" + + " break 3;\n" + + " }\n" + + " }\n" + + " }\n" + + "}\n").replaceAll("[\\s]+", " ")); + } + + @Override + protected List<Fix> computeFixes(CompilationInfo info, int pos, TreePath path) throws Exception { + return new DifferentCaseKindsFix().run(info, null, pos, path, null); + } + + @Override + protected Set<String> getSupportedErrorKeys() { + return new DifferentCaseKindsFix().getCodes(); + } + + @Override + protected String toDebugString(CompilationInfo info, Fix f) { + return f.getText(); + } +} diff --git a/java/java.source.base/apichanges.xml b/java/java.source.base/apichanges.xml index 6d4c376..bc3d971 100644 --- a/java/java.source.base/apichanges.xml +++ b/java/java.source.base/apichanges.xml @@ -25,6 +25,18 @@ <apidef name="javasource_base">Java Source API</apidef> </apidefs> <changes> + <change id="TreeMaker.SwitchExpression"> + <api name="javasource_base" /> + <summary>Creates a new SwitchExpressionTree</summary> + <version major="1" minor="2.41.0"/> + <date day="27" month="2" year="2019"/> + <author login="vikasprabhakar"/> + <compatibility addition="yes" binary="compatible" source="compatible"/> + <description> + Creates a new SwitchExpressionTree. + </description> + <class name="TreeMaker" package="org.netbeans.api.java.source"/> + </change> <change id="Matcher.setKeepSyntheticTrees"> <api name="javasource_base"/> <summary>Adding Matcher.setKeepSyntheticTrees method</summary> diff --git a/java/java.source.base/nbproject/project.properties b/java/java.source.base/nbproject/project.properties index 199f22b..6e036de 100644 --- a/java/java.source.base/nbproject/project.properties +++ b/java/java.source.base/nbproject/project.properties @@ -23,7 +23,7 @@ javadoc.name=Java Source Base javadoc.title=Java Source Base javadoc.arch=${basedir}/arch.xml javadoc.apichanges=${basedir}/apichanges.xml -spec.version.base=2.40.0 +spec.version.base=2.41.0 test.qa-functional.cp.extra=${refactoring.java.dir}/modules/ext/nb-javac-api.jar test.unit.run.cp.extra=${o.n.core.dir}/core/core.jar:\ ${o.n.core.dir}/lib/boot.jar:\ diff --git a/java/java.source.base/src/org/netbeans/api/java/source/TreeMaker.java b/java/java.source.base/src/org/netbeans/api/java/source/TreeMaker.java index 174a820..36592c8 100644 --- a/java/java.source.base/src/org/netbeans/api/java/source/TreeMaker.java +++ b/java/java.source.base/src/org/netbeans/api/java/source/TreeMaker.java @@ -1042,7 +1042,19 @@ public final class TreeMaker { public SwitchTree Switch(ExpressionTree expression, List<? extends CaseTree> cases) { return delegate.Switch(expression, cases); } - + + /** + * Creates a new SwitchExpressionTree. + * + * @param expression the expression which provides the value to be switched. + * @param cases the list of cases, or an empty list. + * @see com.sun.source.tree.SwitchExpressionTree + * @since 2.41 + */ + public Tree SwitchExpression(ExpressionTree expression, List<? extends CaseTree> cases) { + return delegate.SwitchExpression(expression, cases); + } + /** * Creates a new SynchronizedTree. * diff --git a/java/java.source.base/src/org/netbeans/api/java/source/WorkingCopy.java b/java/java.source.base/src/org/netbeans/api/java/source/WorkingCopy.java index ff8bc89..6f74af6 100644 --- a/java/java.source.base/src/org/netbeans/api/java/source/WorkingCopy.java +++ b/java/java.source.base/src/org/netbeans/api/java/source/WorkingCopy.java @@ -864,6 +864,8 @@ public class WorkingCopy extends CompilationController { Tree t; if (translated != null) { t = translate(translated); + } else if (tree != null && tree.getKind().toString().equals("SWITCH_EXPRESSION")) { + t = visitSwitchExpression(tree, null); } else { t = super.translate(tree); } @@ -886,6 +888,10 @@ public class WorkingCopy extends CompilationController { } return super.translate(tree); } + + public Tree visitSwitchExpression(Tree set, Object p) { + return rewriteChildren(set); + } }; Context c = impl.getJavacTask().getContext(); itt.attach(c, ia, tree2Tag); diff --git a/java/java.source.base/src/org/netbeans/modules/java/source/TreeShims.java b/java/java.source.base/src/org/netbeans/modules/java/source/TreeShims.java index 73d9c1c..9dbd309 100644 --- a/java/java.source.base/src/org/netbeans/modules/java/source/TreeShims.java +++ b/java/java.source.base/src/org/netbeans/modules/java/source/TreeShims.java @@ -25,6 +25,8 @@ import com.sun.source.tree.StatementTree; import com.sun.source.tree.SwitchTree; import com.sun.source.tree.Tree; import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.TreeMaker; +import com.sun.tools.javac.util.ListBuffer; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; @@ -112,6 +114,19 @@ public class TreeShims { } } + public static Tree SwitchExpression(TreeMaker make, ExpressionTree selector, List<? extends CaseTree> caseList) throws SecurityException { + ListBuffer<JCTree.JCCase> cases = new ListBuffer<JCTree.JCCase>(); + for (CaseTree t : caseList) { + cases.append((JCTree.JCCase) t); + } + try { + Method getMethod = TreeMaker.class.getDeclaredMethod("SwitchExpression", JCTree.JCExpression.class, com.sun.tools.javac.util.List.class); + return (Tree) getMethod.invoke(make, (JCTree.JCExpression) selector, cases.toList()); + } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + throw TreeShims.<RuntimeException>throwAny(ex); + } + } + @SuppressWarnings("unchecked") private static <T extends Throwable> RuntimeException throwAny(Throwable t) throws T { throw (T) t; diff --git a/java/java.source.base/src/org/netbeans/modules/java/source/builder/TreeFactory.java b/java/java.source.base/src/org/netbeans/modules/java/source/builder/TreeFactory.java index 8157cec..7fd32f9 100644 --- a/java/java.source.base/src/org/netbeans/modules/java/source/builder/TreeFactory.java +++ b/java/java.source.base/src/org/netbeans/modules/java/source/builder/TreeFactory.java @@ -80,6 +80,7 @@ import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import javax.tools.JavaFileObject; import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.modules.java.source.TreeShims; import static org.netbeans.modules.java.source.save.PositionEstimator.*; /** @@ -803,7 +804,11 @@ public class TreeFactory { cases.append((JCCase)t); return make.at(NOPOS).Switch((JCExpression)expression, cases.toList()); } - + + public Tree SwitchExpression(ExpressionTree expression, List<? extends CaseTree> caseList) { + return TreeShims.SwitchExpression(make.at(NOPOS), expression, caseList); + } + public SynchronizedTree Synchronized(ExpressionTree expression, BlockTree block) { return make.at(NOPOS).Synchronized((JCExpression)expression, (JCBlock)block); } diff --git a/java/java.source.base/src/org/netbeans/modules/java/source/matching/CopyFinder.java b/java/java.source.base/src/org/netbeans/modules/java/source/matching/CopyFinder.java index 303ff24..3e0b46b 100644 --- a/java/java.source.base/src/org/netbeans/modules/java/source/matching/CopyFinder.java +++ b/java/java.source.base/src/org/netbeans/modules/java/source/matching/CopyFinder.java @@ -71,6 +71,7 @@ import com.sun.source.tree.WhileLoopTree; import com.sun.source.tree.WildcardTree; import com.sun.source.util.SourcePositions; import com.sun.source.util.TreePath; +import com.sun.tools.javac.tree.JCTree; import org.netbeans.api.java.source.support.ErrorAwareTreeScanner; import java.util.ArrayList; import java.util.Arrays; @@ -100,6 +101,7 @@ import javax.lang.model.type.TypeMirror; import org.netbeans.api.annotations.common.CheckForNull; import org.netbeans.api.annotations.common.NonNull; import org.netbeans.api.java.source.CompilationInfo; +import org.netbeans.modules.java.source.TreeShims; /**TODO: tested by CopyFinderTest in java.hints module. * @@ -1789,7 +1791,14 @@ public class CopyFinder extends ErrorAwareTreeScanner<Boolean, TreePath> { case BLOCK: return ((BlockTree) firstLeaf.getParentPath().getLeaf()).getStatements(); case CASE: - return ((CaseTree) firstLeaf.getParentPath().getLeaf()).getStatements(); + CaseTree caseTree = (CaseTree) firstLeaf.getParentPath().getLeaf(); + if (caseTree.getStatements() != null) { + return caseTree.getStatements(); + } else if (TreeShims.getBody(caseTree) instanceof StatementTree) { + return Collections.singletonList((StatementTree) TreeShims.getBody(caseTree)); + } else { + return null; + } default: return Collections.singletonList((StatementTree) firstLeaf.getLeaf()); } diff --git a/java/java.source.base/src/org/netbeans/modules/java/source/pretty/VeryPretty.java b/java/java.source.base/src/org/netbeans/modules/java/source/pretty/VeryPretty.java index 667dbd6..793a388 100644 --- a/java/java.source.base/src/org/netbeans/modules/java/source/pretty/VeryPretty.java +++ b/java/java.source.base/src/org/netbeans/modules/java/source/pretty/VeryPretty.java @@ -103,6 +103,7 @@ import org.netbeans.api.java.source.Comment; import org.netbeans.api.java.source.Comment.Style; import org.netbeans.api.lexer.TokenHierarchy; import org.netbeans.api.lexer.TokenSequence; +import org.netbeans.modules.java.source.TreeShims; import org.netbeans.modules.java.source.builder.CommentHandlerService; import org.netbeans.modules.java.source.query.CommentHandler; import org.netbeans.modules.java.source.query.CommentSet; @@ -116,8 +117,6 @@ import org.netbeans.modules.java.source.save.Reformatter; import org.netbeans.modules.java.source.transform.FieldGroupTree; import org.netbeans.spi.java.classpath.support.ClassPathSupport; -import org.openide.util.Exceptions; - /** Prints out a tree as an indented Java source program. */ public final class VeryPretty extends JCTree.Visitor implements DocTreeVisitor<Void, Void>, TrimBufferObserver { @@ -1436,13 +1435,15 @@ public final class VeryPretty extends JCTree.Visitor implements DocTreeVisitor<V @Override public void visitBreak(JCBreak tree) { - print("break"); - //TODO: value breaks - if (tree.getLabel() != null) { - needSpace(); - print(tree.getLabel()); - } - print(';'); + print("break"); + if (TreeShims.getValue(tree) != null) { + needSpace(); + print((JCTree) TreeShims.getValue(tree)); + } else if (tree.getLabel() != null) { + needSpace(); + print(tree.getLabel()); + } + print(';'); } @Override diff --git a/java/java.source.base/src/org/netbeans/modules/java/source/save/CasualDiff.java b/java/java.source.base/src/org/netbeans/modules/java/source/save/CasualDiff.java index 7daa817..87377e3 100644 --- a/java/java.source.base/src/org/netbeans/modules/java/source/save/CasualDiff.java +++ b/java/java.source.base/src/org/netbeans/modules/java/source/save/CasualDiff.java @@ -125,6 +125,7 @@ import com.sun.tools.javac.tree.JCTree.TypeBoundKind; import com.sun.tools.javac.tree.Pretty; import com.sun.tools.javac.tree.TreeInfo; import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.ListBuffer; import com.sun.tools.javac.util.Name; import com.sun.tools.javac.util.Names; import com.sun.tools.javac.util.Position; @@ -167,6 +168,7 @@ import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.openide.util.NbCollections; import javax.lang.model.type.TypeKind; +import org.netbeans.modules.java.source.TreeShims; public class CasualDiff { @@ -1874,6 +1876,31 @@ public class CasualDiff { return bounds[1]; } + protected int diffSwitchExpression(Tree oldT, Tree newT, int[] bounds) { + int localPointer = bounds[0]; + + // rename in switch + int[] selectorBounds = getBounds((JCTree)TreeShims.getExpressions(oldT).get(0)); + copyTo(localPointer, selectorBounds[0]); + localPointer = diffTree((JCTree)TreeShims.getExpressions(oldT).get(0), (JCTree)TreeShims.getExpressions(newT).get(0), selectorBounds); + + tokenSequence.move(selectorBounds[1]); + do { } while (tokenSequence.moveNext() && JavaTokenId.LBRACE != tokenSequence.token().id()); + tokenSequence.moveNext(); + copyTo(localPointer, localPointer = tokenSequence.offset()); + PositionEstimator est = EstimatorFactory.cases(TreeShims.getCases(oldT), TreeShims.getCases(newT), diffContext); + ListBuffer<JCTree.JCCase> oldTcases = new ListBuffer<JCTree.JCCase>(); + for (CaseTree t : TreeShims.getCases(oldT)) + oldTcases.append((JCTree.JCCase)t); + ListBuffer<JCTree.JCCase> newTcases = new ListBuffer<JCTree.JCCase>(); + for (CaseTree t : TreeShims.getCases(newT)) + newTcases.append((JCTree.JCCase)t); + localPointer = diffList(oldTcases.toList(), newTcases.toList(), localPointer, est, Measure.MEMBER, printer); + + copyTo(localPointer, bounds[1]); + return bounds[1]; + } + protected int diffCase(JCCase oldT, JCCase newT, int[] bounds) { int localPointer = bounds[0]; List<JCExpression> oldPatterns = getCasePatterns(oldT); @@ -5585,6 +5612,10 @@ public class CasualDiff { } break; } + if(oldT.getKind().toString().equals("SWITCH_EXPRESSION")){ + retVal = diffSwitchExpression(oldT, newT, elementBounds); + break; + } String msg = "Diff not implemented: " + ((com.sun.source.tree.Tree)oldT).getKind().toString() + " " + oldT.getClass().getName(); diff --git a/java/java.source.base/src/org/netbeans/modules/java/source/transform/ImmutableTreeTranslator.java b/java/java.source.base/src/org/netbeans/modules/java/source/transform/ImmutableTreeTranslator.java index ce0648d..a88916a 100644 --- a/java/java.source.base/src/org/netbeans/modules/java/source/transform/ImmutableTreeTranslator.java +++ b/java/java.source.base/src/org/netbeans/modules/java/source/transform/ImmutableTreeTranslator.java @@ -805,6 +805,19 @@ public class ImmutableTreeTranslator implements TreeVisitor<Tree,Object> { return tree; } + protected final Tree rewriteChildren(Tree tree) { + ExpressionTree selector = (ExpressionTree)translate(TreeShims.getExpressions(tree).get(0)); + List<? extends CaseTree> cases = translateStable(TreeShims.getCases(tree)); + if (selector != TreeShims.getExpressions(tree).get(0) || !cases.equals(TreeShims.getCases(tree))) { + Tree switchExpression = make.SwitchExpression(selector, cases); + model.setType(switchExpression, model.getType(tree)); + copyCommentTo(tree,switchExpression); + copyPosTo(tree,switchExpression); + tree = switchExpression; + } + return tree; + } + protected final CaseTree rewriteChildren(CaseTree tree) { Tree body = TreeShims.getBody(tree); List<? extends ExpressionTree> expressions = TreeShims.getExpressions(tree); diff --git a/java/java.source.base/test/unit/src/org/netbeans/api/java/source/gen/SwitchExpressionTest.java b/java/java.source.base/test/unit/src/org/netbeans/api/java/source/gen/SwitchExpressionTest.java new file mode 100644 index 0000000..81a3897 --- /dev/null +++ b/java/java.source.base/test/unit/src/org/netbeans/api/java/source/gen/SwitchExpressionTest.java @@ -0,0 +1,215 @@ +/* + * 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.api.java.source.gen; + +import com.sun.source.tree.BlockTree; +import com.sun.source.tree.CaseTree; +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.StatementTree; +import com.sun.source.tree.SwitchTree; +import com.sun.source.tree.Tree; +import com.sun.source.tree.VariableTree; +import com.sun.tools.javac.tree.JCTree; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import javax.lang.model.element.Element; +import javax.swing.event.ChangeListener; +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertNotNull; +import org.netbeans.api.java.source.JavaSource; +import org.netbeans.api.java.source.Task; +import org.netbeans.api.java.source.TestUtilities; +import org.netbeans.api.java.source.TreeMaker; +import org.netbeans.api.java.source.WorkingCopy; +import org.netbeans.junit.NbTestSuite; +import org.netbeans.modules.java.source.TreeShims; +import org.netbeans.modules.java.source.parsing.JavacParser; +import org.netbeans.spi.java.queries.CompilerOptionsQueryImplementation; +import org.openide.filesystems.FileObject; +import org.openide.util.lookup.ServiceProvider; + +/** + * Test cases for SwitchExpression + * + */ +public class SwitchExpressionTest extends TreeRewriteTestBase { + + private static final List<String> EXTRA_OPTIONS = new ArrayList<>(); + + public SwitchExpressionTest(String testName) { + super(testName); + } + + public static NbTestSuite suite() { + NbTestSuite suite = new NbTestSuite(); + suite.addTestSuite(SwitchExpressionTest.class); + return suite; + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + sourceLevel = "1.12"; + JavacParser.DISABLE_SOURCE_LEVEL_DOWNGRADE = true; + EXTRA_OPTIONS.add("--enable-preview"); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + JavacParser.DISABLE_SOURCE_LEVEL_DOWNGRADE = false; + + } + + @ServiceProvider(service = CompilerOptionsQueryImplementation.class, position = 100) + public static class TestCompilerOptionsQueryImplementation implements CompilerOptionsQueryImplementation { + + @Override + public CompilerOptionsQueryImplementation.Result getOptions(FileObject file) { + return new CompilerOptionsQueryImplementation.Result() { + @Override + public List<? extends String> getArguments() { + return EXTRA_OPTIONS; + } + + @Override + public void addChangeListener(ChangeListener listener) { + } + + @Override + public void removeChangeListener(ChangeListener listener) { + } + }; + } + + } + + public void testSwitchExpression() throws Exception { + + String code = "package test; \n" + + "public class Test {\n" + + " private void test(int p) {\n" + + " var v = switch (p) {\n" + + " case 1: break 1;\n" + + " case 2 -> 2;\n" + + " default -> 3;\n" + + " }\n" + + " }\n" + + "}\n"; + String golden = "package test; \n" + + "public class Test {\n" + + " private void test(int p) {\n" + + " var v = switch (p) {\n" + + " case 1 -> {\n" + + " break 1;\n" + + " }\n" + + " case 2 -> {\n" + + " break 2;\n" + + " }\n" + + " default -> {\n" + + " break 3;\n" + + " }\n" + + " }\n" + + " }\n" + + "}\n"; + + prepareTest("Test", code); + + rewriteSwitchExpression(); + String res = TestUtilities.copyFileToString(getTestFile()); + System.err.println(res); + assertEquals(golden, res); + + } + + /** + * Rewrite Switch Expression cases. + * + * @throws IOException + */ + private void rewriteSwitchExpression() throws IOException { + + JavaSource js = getJavaSource(); + assertNotNull(js); + + Task<WorkingCopy> task = new Task<WorkingCopy>() { + + public void run(WorkingCopy workingCopy) throws IOException { + workingCopy.toPhase(JavaSource.Phase.RESOLVED); + CompilationUnitTree cut = workingCopy.getCompilationUnit(); + TreeMaker make = workingCopy.getTreeMaker(); + ClassTree clazz = (ClassTree) cut.getTypeDecls().get(0); + MethodTree method = (MethodTree) clazz.getMembers().get(1); + List<CaseTree> newCases = new ArrayList<>(); + VariableTree switcExpression = (VariableTree) ((BlockTree) method.getBody()).getStatements().get(0); + Tree switchBlock = switcExpression.getInitializer(); + List<? extends CaseTree> cases; + List<ExpressionTree> patterns = new ArrayList<>(); + boolean switchExpressionFlag = switchBlock.getKind().toString().equals("SWITCH_EXPRESSION"); + if (switchExpressionFlag) { + cases = TreeShims.getCases(switchBlock); + } else { + cases = ((SwitchTree) switchBlock).getCases(); + } + for (Iterator<? extends CaseTree> it = cases.iterator(); it.hasNext();) { + CaseTree ct = it.next(); + patterns.addAll(TreeShims.getExpressions(ct)); + List<StatementTree> statements; + if (ct.getStatements() == null) { + statements = new ArrayList<>(((JCTree.JCCase) ct).stats); + } else { + statements = new ArrayList<>(ct.getStatements()); + } + if (statements.isEmpty()) { + if (it.hasNext()) { + continue; + } + } + Set<Element> seenVariables = new HashSet<>(); + int idx = 0; + for (StatementTree statement : new ArrayList<>(statements)) { + 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<>(); + } + } + workingCopy.rewrite(switchBlock, make.SwitchExpression(TreeShims.getExpressions(switchBlock).get(0), newCases)); + } + + }; + + js.runModificationTask(task).commit(); + } + +} --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@netbeans.apache.org For additional commands, e-mail: commits-h...@netbeans.apache.org For further information about the NetBeans mailing lists, visit: https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists