This is an automated email from the ASF dual-hosted git repository. akhileshsingh pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/netbeans.git
The following commit(s) were added to refs/heads/master by this push: new 6beddee262 Implementation of auto completion, formatting adjustment and providing hints for JEP 405 record patterns (#4533) 6beddee262 is described below commit 6beddee262e8bdf6f2d0f94b2085a797d85dc23f Author: Meghna Jayan <meghna.ja...@oracle.com> AuthorDate: Wed Oct 19 12:48:13 2022 +0530 Implementation of auto completion, formatting adjustment and providing hints for JEP 405 record patterns (#4533) * Add autocompletion for Record Patterns --- .../java/completion/JavaCompletionTask.java | 26 +- .../1.8/AutoCompletion_RecordPattern_1.pass | 1 + .../1.8/AutoCompletion_RecordPattern_2.pass | 1 + .../1.8/AutoCompletion_RecordPattern_3.pass | 1 + .../19/AutoCompletion_RecordPattern_1.pass | 1 + .../19/AutoCompletion_RecordPattern_2.pass | 1 + .../19/AutoCompletion_RecordPattern_3.pass | 1 + .../java/completion/data/RecordPattern.java | 44 ++++ .../java/completion/CompletionTestBase.java | 30 ++- .../JavaCompletionTask119FeaturesTest.java | 26 +- .../modules/editor/java/JavaCompletionItem.java | 55 +++- .../editor/java/JavaCompletionItemFactory.java | 8 +- .../hints/jdk/ConvertToNestedRecordPattern.java | 278 +++++++++++++++++++++ .../java/hints/jdk/ConvertToRecordPattern.java | 194 ++++++++++++++ .../jdk/ConvertToNestedRecordPatternTest.java | 159 ++++++++++++ .../java/hints/jdk/ConvertToRecordPatternTest.java | 145 +++++++++++ .../org/netbeans/api/java/source/TreeMaker.java | 13 + .../netbeans/modules/java/source/TreeShims.java | 58 +++++ .../modules/java/source/pretty/VeryPretty.java | 23 +- .../modules/java/source/save/CasualDiff.java | 26 ++ .../modules/java/source/save/Reformatter.java | 4 +- .../modules/java/source/save/FormatingTest.java | 177 +++++++++++++ 22 files changed, 1259 insertions(+), 13 deletions(-) diff --git a/java/java.completion/src/org/netbeans/modules/java/completion/JavaCompletionTask.java b/java/java.completion/src/org/netbeans/modules/java/completion/JavaCompletionTask.java index 422efaa3c1..d4298e774f 100644 --- a/java/java.completion/src/org/netbeans/modules/java/completion/JavaCompletionTask.java +++ b/java/java.completion/src/org/netbeans/modules/java/completion/JavaCompletionTask.java @@ -130,6 +130,10 @@ public final class JavaCompletionTask<T> extends BaseTask { T createModuleItem(String moduleName, int substitutionOffset); } + public static interface RecordPatternItemFactory<T> extends ItemFactory<T> { + T createRecordPatternItem(CompilationInfo info, TypeElement elem, DeclaredType type, int substitutionOffset, ReferencesCount referencesCount, boolean isDeprecated, boolean insideNew, boolean addTypeVars); + } + public static enum Options { ALL_COMPLETION, @@ -493,6 +497,9 @@ public final class JavaCompletionTask<T> extends BaseTask { localResult(env); addKeywordsForBlock(env); break; + case DECONSTRUCTION_PATTERN: + insideDeconstructionRecordPattern(env); + break; case PARENTHESIZED_PATTERN: insideParenthesizedPattern(env); break; @@ -3250,6 +3257,16 @@ public final class JavaCompletionTask<T> extends BaseTask { } + private void insideDeconstructionRecordPattern(final Env env) throws IOException { + final CompilationController controller = env.getController(); + final Elements elements = controller.getElements(); + TypeMirror tm = controller.getTreeUtilities().parseType(env.getPrefix(), env.getScope().getEnclosingClass()); + TypeElement e = (TypeElement) ((DeclaredType) tm).asElement(); + if (e.getSimpleName().toString().contentEquals(env.getPrefix()) && (e.getKind().equals(ElementKind.RECORD))) { + results.add(((RecordPatternItemFactory<T>) itemFactory).createRecordPatternItem(env.getController(), e, (DeclaredType) e.asType(), anchorOffset, null, elements.isDeprecated(e), env.isInsideNew(), env.isInsideNew() || env.isInsideClass())); + } + } + private void insideParenthesizedPattern(Env env) { final int offset = env.getOffset(); final CompilationController controller = env.getController(); @@ -4234,7 +4251,14 @@ public final class JavaCompletionTask<T> extends BaseTask { } }; for (TypeElement e : controller.getElementUtilities().getGlobalTypes(acceptor)) { - results.add(itemFactory.createTypeItem(env.getController(), e, (DeclaredType) e.asType(), anchorOffset, null, elements.isDeprecated(e), env.isInsideNew(), env.isInsideNew() || env.isInsideClass(), false, false, false)); + Tree iot = env.getPath().getLeaf(); + TokenSequence<JavaTokenId> ts = findLastNonWhitespaceToken(env, iot, env.getOffset()); + if (env.getPrefix() != null && e.getSimpleName().toString().contentEquals(env.getPrefix()) && (e.getKind().equals(ElementKind.RECORD)) + && ts != null && ts.token().id() == JavaTokenId.INSTANCEOF) { + results.add(((RecordPatternItemFactory<T>) itemFactory).createRecordPatternItem(controller, e, (DeclaredType) e.asType(), anchorOffset, null, controller.getElements().isDeprecated(e), env.isInsideNew(), env.isInsideNew() || env.isInsideClass())); + } else { + results.add(itemFactory.createTypeItem(env.getController(), e, (DeclaredType) e.asType(), anchorOffset, null, elements.isDeprecated(e), env.isInsideNew(), env.isInsideNew() || env.isInsideClass(), false, false, false)); + } } } diff --git a/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/1.8/AutoCompletion_RecordPattern_1.pass b/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/1.8/AutoCompletion_RecordPattern_1.pass new file mode 100644 index 0000000000..6daf41cb8c --- /dev/null +++ b/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/1.8/AutoCompletion_RecordPattern_1.pass @@ -0,0 +1 @@ +Person(String name, int a) \ No newline at end of file diff --git a/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/1.8/AutoCompletion_RecordPattern_2.pass b/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/1.8/AutoCompletion_RecordPattern_2.pass new file mode 100644 index 0000000000..a1346a9156 --- /dev/null +++ b/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/1.8/AutoCompletion_RecordPattern_2.pass @@ -0,0 +1 @@ +Rect(ColoredPoint upperLeft, ColoredPoint lr) \ No newline at end of file diff --git a/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/1.8/AutoCompletion_RecordPattern_3.pass b/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/1.8/AutoCompletion_RecordPattern_3.pass new file mode 100644 index 0000000000..3cd138f22b --- /dev/null +++ b/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/1.8/AutoCompletion_RecordPattern_3.pass @@ -0,0 +1 @@ +ColoredPoint(Point p, Color c) \ No newline at end of file diff --git a/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/19/AutoCompletion_RecordPattern_1.pass b/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/19/AutoCompletion_RecordPattern_1.pass new file mode 100644 index 0000000000..6daf41cb8c --- /dev/null +++ b/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/19/AutoCompletion_RecordPattern_1.pass @@ -0,0 +1 @@ +Person(String name, int a) \ No newline at end of file diff --git a/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/19/AutoCompletion_RecordPattern_2.pass b/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/19/AutoCompletion_RecordPattern_2.pass new file mode 100644 index 0000000000..a1346a9156 --- /dev/null +++ b/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/19/AutoCompletion_RecordPattern_2.pass @@ -0,0 +1 @@ +Rect(ColoredPoint upperLeft, ColoredPoint lr) \ No newline at end of file diff --git a/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/19/AutoCompletion_RecordPattern_3.pass b/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/19/AutoCompletion_RecordPattern_3.pass new file mode 100644 index 0000000000..3cd138f22b --- /dev/null +++ b/java/java.completion/test/unit/data/goldenfiles/org/netbeans/modules/java/completion/JavaCompletionTaskTest/19/AutoCompletion_RecordPattern_3.pass @@ -0,0 +1 @@ +ColoredPoint(Point p, Color c) \ No newline at end of file diff --git a/java/java.completion/test/unit/data/org/netbeans/modules/java/completion/data/RecordPattern.java b/java/java.completion/test/unit/data/org/netbeans/modules/java/completion/data/RecordPattern.java new file mode 100644 index 0000000000..1cc92ac252 --- /dev/null +++ b/java/java.completion/test/unit/data/org/netbeans/modules/java/completion/data/RecordPattern.java @@ -0,0 +1,44 @@ +/* + * 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. + */ + +/** + * + * @author mjayan + */ +public class RecordPattern { + public void op(Object o) { + if(o instanceof Person) { + System.out.println("Hello"); + } + if(o instanceof Rect r){ + System.out.println("Hy"); + } + if(o instanceof Rect(ColoredPoint upperLeft,ColoredPoint lr)){ + System.out.println("Hy"); + } + } +} +record Person(String name, int a) {} +record Point(int x, int y) {} +record Check(int x, String y) {} +record Rect(ColoredPoint upperLeft,ColoredPoint lr) {} +record ColoredPoint(Point p, Color c) {} +enum Color { + RED,GREEN,BLUE + } diff --git a/java/java.completion/test/unit/src/org/netbeans/modules/java/completion/CompletionTestBase.java b/java/java.completion/test/unit/src/org/netbeans/modules/java/completion/CompletionTestBase.java index 73247b09bd..fdba908c90 100644 --- a/java/java.completion/test/unit/src/org/netbeans/modules/java/completion/CompletionTestBase.java +++ b/java/java.completion/test/unit/src/org/netbeans/modules/java/completion/CompletionTestBase.java @@ -112,7 +112,7 @@ public class CompletionTestBase extends CompletionTestBaseBase { LifecycleManager.getDefault().saveAll(); } - private static class CIFactory implements JavaCompletionTask.ModuleItemFactory<CI> { + private static class CIFactory implements JavaCompletionTask.ModuleItemFactory<CI>, JavaCompletionTask.RecordPatternItemFactory<CI> { private static final int SMART_TYPE = 1000; @Override @@ -438,6 +438,34 @@ public class CompletionTestBase extends CompletionTestBaseBase { String simpleName = elem.getSimpleName().toString(); return new CI(simpleName + "()", smartType ? 650 - SMART_TYPE : 650, simpleName + "#0#"); } + + @Override + public CI createRecordPatternItem(CompilationInfo info, TypeElement elem, DeclaredType type, int substitutionOffset, ReferencesCount referencesCount, boolean isDeprecated, boolean insideNew, boolean addTypeVars) { + String simpleName = elem.getSimpleName().toString(); + StringBuilder sb = new StringBuilder(); + StringBuilder sortParams = new StringBuilder(); + sb.append(simpleName); + sb.append('('); + sortParams.append('('); + Iterator<? extends RecordComponentElement> it = elem.getRecordComponents().iterator(); + RecordComponentElement recordComponent; + String name; + int cnt = 0; + while (it.hasNext()) { + recordComponent = it.next(); + name = recordComponent.getAccessor().getReturnType().toString(); + sortParams.append(name); + sb.append(name.substring(name.lastIndexOf(".") + 1)); + sb.append(" "); + sb.append(recordComponent.getSimpleName().toString()); + if (it.hasNext()) { + sb.append(", "); //NOI18N + } + cnt++; + } + sb.append(")"); + return new CI(sb.toString(), 650, "#" + ((cnt < 10 ? "0" : "") + cnt) + "#" + sortParams.toString()); + } @Override public CI createParametersItem(CompilationInfo info, ExecutableElement elem, ExecutableType type, int substitutionOffset, boolean isDeprecated, int activeParamIndex, String name) { diff --git a/java/java.completion/test/unit/src/org/netbeans/modules/java/completion/JavaCompletionTask119FeaturesTest.java b/java/java.completion/test/unit/src/org/netbeans/modules/java/completion/JavaCompletionTask119FeaturesTest.java index 231bee5eff..5cb9fecfa4 100644 --- a/java/java.completion/test/unit/src/org/netbeans/modules/java/completion/JavaCompletionTask119FeaturesTest.java +++ b/java/java.completion/test/unit/src/org/netbeans/modules/java/completion/JavaCompletionTask119FeaturesTest.java @@ -1,4 +1,3 @@ -package org.netbeans.modules.java.completion; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file @@ -17,7 +16,7 @@ package org.netbeans.modules.java.completion; * specific language governing permissions and limitations * under the License. */ - +package org.netbeans.modules.java.completion; import java.util.ArrayList; import java.util.List; @@ -51,6 +50,24 @@ public class JavaCompletionTask119FeaturesTest extends CompletionTestBase { return suite; } + public void testRecordPatternCompletion_1() throws Exception { + TestCompilerOptionsQueryImplementation.EXTRA_OPTIONS.add("--enable-preview"); + performTest("RecordPattern", 930, null, "AutoCompletion_RecordPattern_1.pass", getLatestSource()); + } + + public void testRecordPatternCompletion_2() throws Exception { + TestCompilerOptionsQueryImplementation.EXTRA_OPTIONS.add("--enable-preview"); + performTest("RecordPattern", 1013, null, "AutoCompletion_RecordPattern_2.pass", getLatestSource()); + } + + public void testRecordPatternCompletion_3() throws Exception { + TestCompilerOptionsQueryImplementation.EXTRA_OPTIONS.add("--enable-preview"); + performTest("RecordPattern", 1107, null, "AutoCompletion_RecordPattern_3.pass", getLatestSource()); + } + + private String getLatestSource() { + return SourceVersion.latest().name().substring(SourceVersion.latest().name().indexOf("_") + 1); + } public void testCasePatternGuard_1() throws Exception { TestCompilerOptionsQueryImplementation.EXTRA_OPTIONS.add("--enable-preview"); @@ -69,12 +86,9 @@ public class JavaCompletionTask119FeaturesTest extends CompletionTestBase { JavacParser.DISABLE_SOURCE_LEVEL_DOWNGRADE = true; } - private String getLatestSource() { - return SourceVersion.latest().name().substring(SourceVersion.latest().name().indexOf("_")+1); - } - @ServiceProvider(service = CompilerOptionsQueryImplementation.class, position = 100) public static class TestCompilerOptionsQueryImplementation implements CompilerOptionsQueryImplementation { + private static final List<String> EXTRA_OPTIONS = new ArrayList<>(); @Override diff --git a/java/java.editor/src/org/netbeans/modules/editor/java/JavaCompletionItem.java b/java/java.editor/src/org/netbeans/modules/editor/java/JavaCompletionItem.java index 1b7f7d3015..aa52558e64 100644 --- a/java/java.editor/src/org/netbeans/modules/editor/java/JavaCompletionItem.java +++ b/java/java.editor/src/org/netbeans/modules/editor/java/JavaCompletionItem.java @@ -128,6 +128,15 @@ public abstract class JavaCompletionItem implements CompletionItem { } } + public static JavaCompletionItem createRecordPatternItem(CompilationInfo info, TypeElement elem, DeclaredType type, int substitutionOffset, ReferencesCount referencesCount, boolean isDeprecated, boolean insideNew, boolean addTypeVars) { + if(elem.getKind().equals(ElementKind.RECORD)) { + return new RecordPatternItem(info, elem, type, 0, substitutionOffset, referencesCount, isDeprecated, insideNew); + } + else { + throw new IllegalArgumentException("kind=" + elem.getKind()); + } + } + public static JavaCompletionItem createArrayItem(CompilationInfo info, ArrayType type, int substitutionOffset, ReferencesCount referencesCount, Elements elements, WhiteListQuery.WhiteList whiteList) { int dim = 0; TypeMirror tm = type; @@ -1336,7 +1345,51 @@ public abstract class JavaCompletionItem implements CompletionItem { return icon; } } - + + static class RecordPatternItem extends ClassItem { + + private String simpleName; + private String recordParams; + + private RecordPatternItem(CompilationInfo info, TypeElement elem, DeclaredType type, int dim, int substitutionOffset, ReferencesCount referencesCount, boolean isDeprecated, boolean insideNew) { + super(info, elem, type, dim, substitutionOffset, referencesCount, isDeprecated, insideNew, false, false, false, false, null); + simpleName = elem.getSimpleName().toString(); + Iterator<? extends RecordComponentElement> it = elem.getRecordComponents().iterator(); + StringBuilder sb = new StringBuilder(); + RecordComponentElement recordComponent; + String name; + sb.append("("); + while (it.hasNext()) { + recordComponent = it.next(); + name = recordComponent.getAccessor().getReturnType().toString(); + name = name.substring(name.lastIndexOf(".") + 1); + sb.append(name); + sb.append(" "); + sb.append(recordComponent.getSimpleName().toString()); + if (it.hasNext()) { + sb.append(", "); //NOI18N + } + } + sb.append(")"); + recordParams = sb.toString(); + } + + @Override + protected CharSequence substituteText(final JTextComponent c, final int offset, final int length, final CharSequence text, final CharSequence toAdd) { + return recordParams; + } + + @Override + protected String getLeftHtmlText() { + return simpleName + recordParams; + } + + @Override + public int getSortPriority() { + return 650; + } + } + static class AnnotationTypeItem extends ClassItem { private static final String ANNOTATION = "org/netbeans/modules/editor/resources/completion/annotation_type.png"; // NOI18N diff --git a/java/java.editor/src/org/netbeans/modules/editor/java/JavaCompletionItemFactory.java b/java/java.editor/src/org/netbeans/modules/editor/java/JavaCompletionItemFactory.java index 535a555dfb..41836d865f 100644 --- a/java/java.editor/src/org/netbeans/modules/editor/java/JavaCompletionItemFactory.java +++ b/java/java.editor/src/org/netbeans/modules/editor/java/JavaCompletionItemFactory.java @@ -48,7 +48,8 @@ import org.openide.filesystems.FileObject; */ public final class JavaCompletionItemFactory implements JavaCompletionTask.TypeCastableItemFactory<JavaCompletionItem>, JavaCompletionTask.LambdaItemFactory<JavaCompletionItem>, - JavaCompletionTask.ModuleItemFactory<JavaCompletionItem> { + JavaCompletionTask.ModuleItemFactory<JavaCompletionItem>, + JavaCompletionTask.RecordPatternItemFactory<JavaCompletionItem> { private final WhiteListQuery.WhiteList whiteList; @@ -180,4 +181,9 @@ public final class JavaCompletionItemFactory implements JavaCompletionTask.TypeC public JavaCompletionItem createLambdaItem(CompilationInfo info, TypeElement elem, DeclaredType type, int substitutionOffset, boolean expression, boolean addSemicolon) { return JavaCompletionItem.createLambdaItem(info, elem, type, substitutionOffset, expression, addSemicolon); } + + @Override + public JavaCompletionItem createRecordPatternItem(CompilationInfo info, TypeElement elem, DeclaredType type, int substitutionOffset, ReferencesCount referencesCount, boolean isDeprecated, boolean insideNew, boolean addTypeVars) { + return JavaCompletionItem.createRecordPatternItem(info, elem, type, substitutionOffset, referencesCount, isDeprecated, insideNew, addTypeVars); + } } diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/jdk/ConvertToNestedRecordPattern.java b/java/java.hints/src/org/netbeans/modules/java/hints/jdk/ConvertToNestedRecordPattern.java new file mode 100644 index 0000000000..cc2d539e02 --- /dev/null +++ b/java/java.hints/src/org/netbeans/modules/java/hints/jdk/ConvertToNestedRecordPattern.java @@ -0,0 +1,278 @@ +/* + * 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.jdk; + +import com.sun.source.tree.BindingPatternTree; +import com.sun.source.tree.BlockTree; +import com.sun.source.tree.DeconstructionPatternTree; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.ParenthesizedPatternTree; +import com.sun.source.tree.PatternTree; +import com.sun.source.tree.StatementTree; +import com.sun.source.tree.Tree; +import com.sun.source.util.TreePath; +import com.sun.source.tree.VariableTree; +import com.sun.source.tree.CaseTree; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.EnumSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.RecordComponentElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; +import org.netbeans.api.java.source.CompilationInfo; +import org.netbeans.api.java.source.TreePathHandle; +import org.netbeans.api.java.source.CodeStyleUtils; +import org.netbeans.api.java.source.WorkingCopy; +import org.netbeans.api.java.queries.CompilerOptionsQuery; +import org.netbeans.api.java.source.support.CancellableTreePathScanner; +import org.netbeans.modules.java.hints.errors.Utilities; +import org.netbeans.spi.editor.hints.ErrorDescription; +import org.netbeans.spi.editor.hints.Fix; +import org.netbeans.spi.java.hints.ErrorDescriptionFactory; +import org.netbeans.spi.java.hints.Hint; +import org.netbeans.spi.java.hints.HintContext; +import org.netbeans.spi.java.hints.JavaFix; +import org.netbeans.spi.java.hints.MatcherUtilities; +import org.netbeans.spi.java.hints.TriggerTreeKind; +import org.openide.util.NbBundle; + +@NbBundle.Messages({ + "DN_ConvertToNestedRecordPattern=Convert to nested record pattern", + "DESC_ConvertToNestedRecordPattern=Convert to nested record pattern", + "ERR_ConvertToNestedRecordPattern=Convert to nested record pattern", + "FIX_ConvertToNestedRecordPattern=Convert to nested record pattern" +}) +@Hint(displayName = "#DN_ConvertToNestedRecordPattern", description = "#DESC_ConvertToNestedRecordPattern", category = "rules15", + minSourceVersion = "19") +/** + * + * @author mjayan + */ +public class ConvertToNestedRecordPattern { + + private static final int RECORD_PATTERN_PREVIEW_JDK_VERSION = 19; + + @TriggerTreeKind(Tree.Kind.DECONSTRUCTION_PATTERN) + public static ErrorDescription convertToNestedRecordPattern(HintContext ctx) { + if (Utilities.isJDKVersionLower(RECORD_PATTERN_PREVIEW_JDK_VERSION) && !CompilerOptionsQuery.getOptions(ctx.getInfo().getFileObject()).getArguments().contains("--enable-preview")) { + return null; + } + TreePath t = ctx.getPath(); + if (!t.getParentPath().getLeaf().getKind().equals(Tree.Kind.INSTANCE_OF)) { + return null; + } + Set<String> recordPatternVarSet = new HashSet<>(); + Map<PatternTree, List<PatternTree>> recordComponentMap = new LinkedHashMap<>(); + DeconstructionPatternTree recordPattern = (DeconstructionPatternTree) t.getLeaf(); + recordComponentMap = findNested(recordPattern, recordComponentMap); + + for (PatternTree p : recordComponentMap.keySet()) { + BindingPatternTree bTree = (BindingPatternTree) p; + recordPatternVarSet.add(bTree.getVariable().getName().toString()); + } + while (t != null && t.getLeaf().getKind() != Tree.Kind.BLOCK) { + t = t.getParentPath(); + } + Set<TreePath> convertPath = new HashSet<>(); + List<String> localVarList = new ArrayList<>(); + Map<String, List<UserVariables>> userVars = new HashMap<>(); + new CancellableTreePathScanner<Void, Void>() { + + @Override + public Void visitVariable(VariableTree node, Void p) { + localVarList.add(node.getName().toString()); + Map<String, TreePath> outerVariables = new HashMap<>(); + Map<String, String> innerVariables = new HashMap<>(); + List<UserVariables> nList = new ArrayList<>(); + boolean match = MatcherUtilities.matches(ctx, getCurrentPath(), "$type $var1 = $expr3.$meth1()", outerVariables, new HashMap<>(), innerVariables); + + if (match && recordPatternVarSet.contains(outerVariables.get("$expr3").getLeaf().toString())) { + String expr3 = outerVariables.get("$expr3").getLeaf().toString(); + nList.clear(); + if (userVars.get(expr3) != null) { + nList = userVars.get(expr3); + } + nList.add(new UserVariables(innerVariables.get("$var1"), innerVariables.get("$meth1"))); + userVars.put(expr3, nList); + convertPath.add(getCurrentPath()); + } + return super.visitVariable(node, p); + } + + @Override + protected boolean isCanceled() { + return ctx.isCanceled(); + } + }.scan(t, null); + if (!convertPath.isEmpty()) { + Fix fix = new FixImpl(ctx.getInfo(), ctx.getPath(), convertPath, localVarList, userVars).toEditorFix(); + return ErrorDescriptionFactory.forName(ctx, ctx.getPath(), Bundle.ERR_ConvertToNestedRecordPattern(), fix); + } + return null; + } + + private static Map<PatternTree, List<PatternTree>> findNested(PatternTree pTree, Map<PatternTree, List<PatternTree>> recordComponentMap) { + switch (pTree.getKind()) { + case BINDING_PATTERN: + recordComponentMap.put(pTree, new ArrayList<>()); + return recordComponentMap; + case DECONSTRUCTION_PATTERN: + DeconstructionPatternTree bTree = (DeconstructionPatternTree) pTree; + for (PatternTree p : bTree.getNestedPatterns()) { + findNested(p, recordComponentMap); + } + break; + case PARENTHESIZED_PATTERN: + ParenthesizedPatternTree parenthTree = (ParenthesizedPatternTree) pTree; + findNested(parenthTree.getPattern(), recordComponentMap); + break; + default: + return recordComponentMap; + } + return recordComponentMap; + } + + private static class UserVariables { + + String methodName; + String variable; + + UserVariables(String variable, String methodName) { + this.variable = variable; + this.methodName = methodName; + } + + public String getMethodName() { + return methodName; + } + + public String getVariable() { + return variable; + } + } + + private static final class FixImpl extends JavaFix { + + private final Map<String, List<UserVariables>> userVars; + private final Set<TreePathHandle> replaceOccurrences; + List<String> localVarList; + + public FixImpl(CompilationInfo info, TreePath main, Set<TreePath> replaceOccurrences, List<String> localVarList, Map<String, List<UserVariables>> userVars) { + super(info, main); + this.replaceOccurrences = replaceOccurrences.stream().map(tp -> TreePathHandle.create(tp, info)).collect(Collectors.toSet()); + this.userVars = userVars; + this.localVarList = localVarList; + } + + @Override + protected String getText() { + return Bundle.ERR_ConvertToNestedRecordPattern(); + } + + @Override + protected void performRewrite(JavaFix.TransformationContext ctx) { + WorkingCopy wc = ctx.getWorkingCopy(); + TreePath t = ctx.getPath(); + TypeElement type = null; + Map<PatternTree, List<PatternTree>> recordComponentMap = new LinkedHashMap<>(); + DeconstructionPatternTree recordPattern = (DeconstructionPatternTree) t.getLeaf(); + recordComponentMap = findNested(recordPattern, recordComponentMap); + + Set<String> localVars = new HashSet<>(localVarList); + for (PatternTree p : recordComponentMap.keySet()) { + List<PatternTree> bindTree = new ArrayList<>(); + BindingPatternTree bTree = (BindingPatternTree) p; + VariableTree v = bTree.getVariable(); + type = (TypeElement) wc.getTrees().getElement(TreePath.getPath(t, v.getType())); + if (type == null || type.getRecordComponents().size() == 0) { + continue; + } + outer: + for (RecordComponentElement recordComponent : type.getRecordComponents()) { + String name = recordComponent.getSimpleName().toString(); + TypeMirror returnType = recordComponent.getAccessor().getReturnType(); + if (userVars.get(v.getName().toString()) != null) { + for (UserVariables var : userVars.get(v.getName().toString())) { + if (var.getMethodName().equals(name)) { + bindTree.add((BindingPatternTree) wc.getTreeMaker().BindingPattern(wc.getTreeMaker().Variable(wc.getTreeMaker(). + Modifiers(EnumSet.noneOf(Modifier.class)), var.getVariable(), wc.getTreeMaker().Type(returnType), null))); + continue outer; + } + } + } + String baseName = name; + int cnt = 1; + while (SourceVersion.isKeyword(name) || localVars.contains(name)) { + name = CodeStyleUtils.addPrefixSuffix(baseName + cnt++, "", ""); + } + localVars.add(name); + bindTree.add((BindingPatternTree) wc.getTreeMaker().BindingPattern(wc.getTreeMaker().Variable(wc.getTreeMaker(). + Modifiers(EnumSet.noneOf(Modifier.class)), name, wc.getTreeMaker().Type(returnType), null))); + } + recordComponentMap.put(p, bindTree); + } + + DeconstructionPatternTree d = (DeconstructionPatternTree) createNestedPattern((PatternTree) t.getLeaf(), wc, recordComponentMap); + while (t != null && t.getLeaf().getKind() != Tree.Kind.BLOCK) { + t = t.getParentPath(); + } + + List<Tree> removeList = replaceOccurrences.stream().map(tph -> tph.resolve(wc).getLeaf()).collect(Collectors.toList()); + for (Tree tree : removeList) { + StatementTree statementTree = (StatementTree) tree; + Utilities.removeStatements(wc, TreePath.getPath(t, statementTree), null); + } + wc.rewrite(ctx.getPath().getLeaf(), d); + } + } + + private static PatternTree createNestedPattern(PatternTree pTree, WorkingCopy wc, Map<PatternTree, List<PatternTree>> map) { + switch (pTree.getKind()) { + case BINDING_PATTERN: + if (map.containsKey(pTree) && !map.get(pTree).isEmpty()) { + BindingPatternTree p = (BindingPatternTree) pTree; + VariableTree v = (VariableTree) p.getVariable(); + return (DeconstructionPatternTree) wc.getTreeMaker().RecordPattern((ExpressionTree) v.getType(), map.get(pTree), v); + } else { + return pTree; + } + case DECONSTRUCTION_PATTERN: + DeconstructionPatternTree bTree = (DeconstructionPatternTree) pTree; + List<PatternTree> list = new ArrayList<>(); + for (PatternTree p : bTree.getNestedPatterns()) { + PatternTree val = createNestedPattern(p, wc, map); + list.add(val); + } + return (DeconstructionPatternTree) wc.getTreeMaker().RecordPattern(bTree.getDeconstructor(), list, bTree.getVariable()); + case PARENTHESIZED_PATTERN: + ParenthesizedPatternTree parenthTree = (ParenthesizedPatternTree) pTree; + return createNestedPattern(parenthTree.getPattern(), wc, map); + default: + return pTree; + } + } +} diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/jdk/ConvertToRecordPattern.java b/java/java.hints/src/org/netbeans/modules/java/hints/jdk/ConvertToRecordPattern.java new file mode 100644 index 0000000000..5ead0e6785 --- /dev/null +++ b/java/java.hints/src/org/netbeans/modules/java/hints/jdk/ConvertToRecordPattern.java @@ -0,0 +1,194 @@ +/* + * 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.jdk; + +import com.sun.source.tree.BindingPatternTree; +import com.sun.source.tree.BlockTree; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.IfTree; +import com.sun.source.tree.InstanceOfTree; +import com.sun.source.tree.ParenthesizedTree; +import com.sun.source.tree.PatternTree; +import com.sun.source.tree.StatementTree; +import com.sun.source.tree.Tree; +import com.sun.source.util.TreePath; +import com.sun.source.tree.VariableTree; +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.RecordComponentElement; +import javax.lang.model.element.TypeElement; +import org.netbeans.api.java.source.CodeStyleUtils; +import org.netbeans.api.java.source.CompilationInfo; +import org.netbeans.api.java.source.TreePathHandle; +import org.netbeans.api.java.source.ElementHandle; +import org.netbeans.api.java.source.WorkingCopy; +import org.netbeans.api.java.queries.CompilerOptionsQuery; +import org.netbeans.api.java.source.support.CancellableTreePathScanner; +import org.netbeans.modules.java.hints.errors.Utilities; +import org.netbeans.spi.editor.hints.ErrorDescription; +import org.netbeans.spi.editor.hints.Fix; +import org.netbeans.spi.java.hints.ErrorDescriptionFactory; +import org.netbeans.spi.java.hints.Hint; +import org.netbeans.spi.java.hints.HintContext; +import org.netbeans.spi.java.hints.JavaFix; +import org.netbeans.spi.java.hints.MatcherUtilities; +import org.netbeans.spi.java.hints.TriggerPattern; +import org.netbeans.spi.java.hints.TriggerPatterns; +import org.openide.util.NbBundle; + +/** + * + * @author mjayan + */ +@NbBundle.Messages({ + "DN_ConvertToRecordPattern=Convert to instanceof <record pattern>", + "DESC_ConvertToRecordPattern=Convert to instanceof <record pattern>", + "ERR_ConvertToRecordPattern=instanceof <record pattern> can be used here", + "FIX_ConvertToRecordPattern=Use instanceof record pattern" +}) +@Hint(displayName = "#DN_ConvertToRecordPattern", description = "#DESC_ConvertToRecordPattern", category = "rules15", + minSourceVersion = "19") +public class ConvertToRecordPattern { + + private static final int RECORD_PATTERN_PREVIEW_JDK_VERSION = 19; + + @TriggerPatterns({ + @TriggerPattern(value = "if ($expr instanceof $typeI0 $var0 ) { $statements$;} else $else$;") + }) + public static ErrorDescription trivial(HintContext ctx) { + if (Utilities.isJDKVersionLower(RECORD_PATTERN_PREVIEW_JDK_VERSION) && !CompilerOptionsQuery.getOptions(ctx.getInfo().getFileObject()).getArguments().contains("--enable-preview")) { + return null; + } + ElementKind kind = ctx.getInfo().getTrees().getElement(ctx.getVariables().get("$typeI0")).getKind(); + if (kind.equals(ElementKind.RECORD)) { + Set<TreePath> convertPath = new HashSet<>(); + Set<String> localVarList = new HashSet<>(); + localVarList.add(ctx.getInfo().getTrees().getElement(ctx.getVariables().get("$expr")).getSimpleName().toString()); + Map<String, String> varNames = new HashMap<>(); + new CancellableTreePathScanner<Void, Void>() { + String variableName = null; + + @Override + public Void visitVariable(VariableTree node, Void p) { + if (variableName == null) { + variableName = node.getName().toString(); + } + localVarList.add(node.getName().toString()); + Map<String, TreePath> outerVariables = new HashMap<>(); + Map<String, String> innerVariables = new HashMap<>(); + boolean match = MatcherUtilities.matches(ctx, getCurrentPath(), "$type $var1 = $expr3.$meth1()", outerVariables, new HashMap<String, Collection<? extends TreePath>>(), innerVariables); + + if (match && outerVariables.get("$expr3").getLeaf().toString().equals(variableName)) { + varNames.put(innerVariables.get("$meth1"), innerVariables.get("$var1")); + convertPath.add(getCurrentPath()); + } + return super.visitVariable(node, p); + } + + @Override + protected boolean isCanceled() { + return ctx.isCanceled(); + } + }.scan(ctx.getPath(), null); + TypeElement type = (TypeElement) ctx.getInfo().getTrees().getElement(ctx.getVariables().get("$typeI0")); + List<? extends RecordComponentElement> recordSig = type.getRecordComponents(); + if (!convertPath.isEmpty()) { + Fix fix = new FixImpl(ctx.getInfo(), ctx.getPath(), convertPath, recordSig, varNames, localVarList).toEditorFix(); + return ErrorDescriptionFactory.forName(ctx, ctx.getPath(), Bundle.ERR_ConvertToRecordPattern(), fix); + } + } + return null; + } + + private static final class FixImpl extends JavaFix { + + private final Set<TreePathHandle> replaceOccurrences; + private final List<? extends ElementHandle> recordSig; + private final Map<String, String> varNames; + private final Set<String> localVarList; + + public FixImpl(CompilationInfo info, TreePath main, Set<TreePath> replaceOccurrences, List<? extends RecordComponentElement> recordSig, Map<String, String> varNames, Set<String> localVarList) { + super(info, main); + this.recordSig = recordSig.stream().map(elem -> ElementHandle.create(elem)).collect(Collectors.toList()); + this.varNames = varNames; + this.replaceOccurrences = replaceOccurrences.stream().map(tp -> TreePathHandle.create(tp, info)).collect(Collectors.toSet()); + this.localVarList = new HashSet<>(localVarList); + } + + @Override + protected String getText() { + return Bundle.FIX_ConvertToRecordPattern(); + } + + @Override + protected void performRewrite(JavaFix.TransformationContext ctx) { + WorkingCopy wc = ctx.getWorkingCopy(); + TreePath main = ctx.getPath(); + IfTree it = (IfTree) main.getLeaf(); + InstanceOfTree iot = (InstanceOfTree) ((ParenthesizedTree) it.getCondition()).getExpression(); + BindingPatternTree pattern = (BindingPatternTree) iot.getPattern(); + StatementTree bt = it.getThenStatement(); + + List<PatternTree> bindTree = new ArrayList<>(); + + List<RecordComponentElement> recordSignature = new ArrayList<>(); + recordSig.stream().map(elem -> elem.resolve(wc)).forEach(elem -> { + recordSignature.add((RecordComponentElement) elem); + }); + Set<String> localVars = new HashSet<>(localVarList); + for (RecordComponentElement recordComponent : recordSignature) { + String compName = recordComponent.getSimpleName().toString(); + String name = null; + String returnType = null; + if (varNames.containsKey(compName)) { + name = varNames.get(compName); + } else { + int cnt = 1; + name = compName; + while (SourceVersion.isKeyword(name) || localVars.contains(name)) { + name = CodeStyleUtils.addPrefixSuffix(compName + cnt++, "", ""); + } + localVars.add(name); + } + + returnType = recordComponent.getAccessor().getReturnType().toString(); + returnType = returnType.substring(returnType.lastIndexOf(".") + 1); + bindTree.add((BindingPatternTree) wc.getTreeMaker().BindingPattern(wc.getTreeMaker().Variable(wc.getTreeMaker(). + Modifiers(EnumSet.noneOf(Modifier.class)), name, wc.getTreeMaker().Identifier(returnType), null))); + } + InstanceOfTree cond = wc.getTreeMaker().InstanceOf(iot.getExpression(), wc.getTreeMaker().RecordPattern((ExpressionTree) pattern. + getVariable().getType(), bindTree, pattern.getVariable())); + List<Tree> removeList = replaceOccurrences.stream().map(tph -> tph.resolve(wc).getLeaf()).collect(Collectors.toList()); + for (Tree t : removeList) { + bt = wc.getTreeMaker().removeBlockStatement((BlockTree) bt, (StatementTree) t); + } + wc.rewrite(it, wc.getTreeMaker().If(wc.getTreeMaker().Parenthesized(cond), bt, it.getElseStatement())); + } + } +} diff --git a/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/jdk/ConvertToNestedRecordPatternTest.java b/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/jdk/ConvertToNestedRecordPatternTest.java new file mode 100644 index 0000000000..d985c5929f --- /dev/null +++ b/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/jdk/ConvertToNestedRecordPatternTest.java @@ -0,0 +1,159 @@ +/* + * 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.jdk; + +import org.netbeans.junit.NbTestCase; +import org.netbeans.modules.java.hints.test.api.HintTest; +import javax.lang.model.SourceVersion; + +/** + * + * @author mjayan + */ +public class ConvertToNestedRecordPatternTest extends NbTestCase { + + public ConvertToNestedRecordPatternTest(String name) { + super(name); + } + + public void testSimple() throws Exception { + if (!isRecordClassPresent()) { + return; + } + HintTest.create() + .input("package test;\n" + + "record Rect(ColoredPoint upperLeft,ColoredPoint lr) {}\n" + + "record ColoredPoint(Point p, Color c) {}\n" + + "record Point(int x, int y){}\n" + + "enum Color {RED,GREEN,BLUE}\n" + + "public class Test {\n" + + " private void test(Object o) {\n" + + " if (o instanceof Rect(ColoredPoint ul, ColoredPoint lr)) {\n" + + " Point p = ul.p();\n" + + " System.out.println(\"Hello\");\n" + + " }\n" + + " }\n" + + "}\n") + .sourceLevel(SourceVersion.latest().name()) + .options("--enable-preview") + .run(ConvertToNestedRecordPattern.class) + .findWarning("7:25-7:63:verifier:" + Bundle.ERR_ConvertToNestedRecordPattern()) + .applyFix() + .assertCompilable() + .assertOutput("package test;\n" + + "record Rect(ColoredPoint upperLeft,ColoredPoint lr) {}\n" + + "record ColoredPoint(Point p, Color c) {}\n" + + "record Point(int x, int y){}\n" + + "enum Color {RED,GREEN,BLUE}\n" + + "public class Test {\n" + + " private void test(Object o) {\n" + + " if (o instanceof Rect(ColoredPoint(Point p, Color c) ul, ColoredPoint(Point p1, Color c1) lr)) {\n" + + " System.out.println(\"Hello\");\n" + + " }\n" + + " }\n" + + "}\n"); + } + + public void testMultipleNested() throws Exception { + if (!isRecordClassPresent()) { + return; + } + HintTest.create() + .input("package test;\n" + + "record Rect(ColoredPoint upperLeft) {}\n" + + "record ColoredPoint(Point p, Color c) {}\n" + + "record Point(int x, int y){}\n" + + "enum Color {RED,GREEN,BLUE}\n" + + "public class Test {\n" + + " private void test(Object o) {\n" + + " if (o instanceof Rect(ColoredPoint(Point p, Color c) ul)) {\n" + + " int x = p.x();\n" + + " System.out.println(\"Hello\");\n" + + " }\n" + + " }\n" + + "}\n") + .sourceLevel(SourceVersion.latest().name()) + .options("--enable-preview") + .run(ConvertToNestedRecordPattern.class) + .findWarning("7:25-7:64:verifier:" + Bundle.ERR_ConvertToNestedRecordPattern()) + .applyFix() + .assertCompilable() + .assertOutput("package test;\n" + + "record Rect(ColoredPoint upperLeft) {}\n" + + "record ColoredPoint(Point p, Color c) {}\n" + + "record Point(int x, int y){}\n" + + "enum Color {RED,GREEN,BLUE}\n" + + "public class Test {\n" + + " private void test(Object o) {\n" + + " if (o instanceof Rect(ColoredPoint(Point(int x, int y) p, Color c) ul)) {\n" + + " System.out.println(\"Hello\");\n" + + " }\n" + + " }\n" + + "}\n"); + } + + public void testUserVar() throws Exception { + if (!isRecordClassPresent()) { + return; + } + HintTest.create() + .input("package test;\n" + + "record Rect(ColoredPoint upperLeft,ColoredPoint lr,ColoredPoint ur) {}\n" + + "record ColoredPoint(Point p, Color c) {}\n" + + "record Point(int x, int y){}\n" + + "enum Color {RED,GREEN,BLUE}\n" + + "public class Test {\n" + + " private void test(Object o) {\n" + + " if (o instanceof Rect(ColoredPoint(Point p, Color c) ul, ColoredPoint lr, ColoredPoint(Point p1, Color c1) ur)) {\n" + + " int xVal = p.x();\n" + + " int y1 = p.y();\n" + + " Point p2 = lr.p();\n" + + " System.out.println(\"Hello\");\n" + + " }\n" + + " }\n" + + "}\n") + .sourceLevel(SourceVersion.latest().name()) + .options("--enable-preview") + .run(ConvertToNestedRecordPattern.class) + .findWarning("7:25-7:118:verifier:" + Bundle.ERR_ConvertToNestedRecordPattern()) + .applyFix() + .assertCompilable() + .assertOutput("package test;\n" + + "record Rect(ColoredPoint upperLeft,ColoredPoint lr,ColoredPoint ur) {}\n" + + "record ColoredPoint(Point p, Color c) {}\n" + + "record Point(int x, int y){}\n" + + "enum Color {RED,GREEN,BLUE}\n" + + "public class Test {\n" + + " private void test(Object o) {\n" + + " if (o instanceof Rect(ColoredPoint(Point(int xVal, int y1) p, Color c) ul, ColoredPoint(Point p2, Color c2) lr, ColoredPoint(Point(int x, int y) p1, Color c1) ur)) {\n" + + " System.out.println(\"Hello\");\n" + + " }\n" + + " }\n" + + "}\n"); + } + + private boolean isRecordClassPresent() { + try { + Class.forName("java.lang.Record"); + return true; + } catch (ClassNotFoundException ex) { + return false; + } + } +} diff --git a/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/jdk/ConvertToRecordPatternTest.java b/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/jdk/ConvertToRecordPatternTest.java new file mode 100644 index 0000000000..72cd50c080 --- /dev/null +++ b/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/jdk/ConvertToRecordPatternTest.java @@ -0,0 +1,145 @@ +/* + * 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.jdk; + +import org.netbeans.junit.NbTestCase; +import org.netbeans.modules.java.hints.test.api.HintTest; +import javax.lang.model.SourceVersion; + +/** + * + * @author mjayan + */ +public class ConvertToRecordPatternTest extends NbTestCase { + + public ConvertToRecordPatternTest(String name) { + super(name); + } + + public void testSimple() throws Exception { + if (!isRecordClassPresent()) { + return; + } + HintTest.create() + .input("package test;\n" + + "record Person(String name, String place){}\n" + + "public class Test {\n" + + " private int test(Object o) {\n" + + " if (o instanceof Person p) {\n" + + " String name = p.name();\n" + + " return name.length();\n" + + " }\n" + + " return -1;\n" + + " }\n" + + "}\n") + .sourceLevel(SourceVersion.latest().name()) + .options("--enable-preview") + .run(ConvertToRecordPattern.class) + .findWarning("4:8-4:10:verifier:" + Bundle.ERR_ConvertToRecordPattern()) + .applyFix() + .assertCompilable() + .assertOutput("package test;\n" + + "record Person(String name, String place){}\n" + + "public class Test {\n" + + " private int test(Object o) {\n" + + " if (o instanceof Person(String name, String place) p) {\n" + + " return name.length();\n" + + " }\n" + + " return -1;\n" + + " }\n" + + "}\n"); + } + + public void testDuplicateVarName() throws Exception { + if (!isRecordClassPresent()) { + return; + } + HintTest.create() + .input("package test;\n" + + "record Person(String name, int s){}\n" + + "public class Test {\n" + + " private int test(Object s) {\n" + + " if (s instanceof Person p) {\n" + + " String name = p.name();\n" + + " return name.length();\n" + + " }\n" + + " return -1;\n" + + " }\n" + + "}\n") + .sourceLevel(SourceVersion.latest().name()) + .options("--enable-preview") + .run(ConvertToRecordPattern.class) + .findWarning("4:8-4:10:verifier:" + Bundle.ERR_ConvertToRecordPattern()) + .applyFix() + .assertCompilable() + .assertOutput("package test;\n" + + "record Person(String name, int s){}\n" + + "public class Test {\n" + + " private int test(Object s) {\n" + + " if (s instanceof Person(String name, int s1) p) {\n" + + " return name.length();\n" + + " }\n" + + " return -1;\n" + + " }\n" + + "}\n"); + } + + public void testUsingUserVar() throws Exception { + if (!isRecordClassPresent()) { + return; + } + HintTest.create() + .input("package test;\n" + + "record Person(String name, int s){}\n" + + "public class Test {\n" + + " private int test(Object s) {\n" + + " if (s instanceof Person p) {\n" + + " String userName = p.name();\n" + + " return userName.length();\n" + + " }\n" + + " return -1;\n" + + " }\n" + + "}\n") + .sourceLevel(SourceVersion.latest().name()) + .options("--enable-preview") + .run(ConvertToRecordPattern.class) + .findWarning("4:8-4:10:verifier:" + Bundle.ERR_ConvertToRecordPattern()) + .applyFix() + .assertCompilable() + .assertOutput("package test;\n" + + "record Person(String name, int s){}\n" + + "public class Test {\n" + + " private int test(Object s) {\n" + + " if (s instanceof Person(String userName, int s1) p) {\n" + + " return userName.length();\n" + + " }\n" + + " return -1;\n" + + " }\n" + + "}\n"); + } + + private boolean isRecordClassPresent() { + try { + Class.forName("java.lang.Record"); + return true; + } catch (ClassNotFoundException ex) { + return false; + } + } +} 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 cb830c6249..ac708a8fe8 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 @@ -1278,6 +1278,19 @@ public final class TreeMaker { return delegate.BindingPattern(vt); } + /** + * Creates a new Tree for a given DeconstructionPatternTree + * @param deconstructor deconstructor of record pattern + * @param nested list of nested patterns + * @param vt the variable of record pattern + * @see com.sun.source.tree.DeconstructionPatternTree + * @return the newly created RecordPatternTree + * @since 19 + */ + public DeconstructionPatternTree RecordPattern(ExpressionTree deconstructor, List<PatternTree> nested, VariableTree vt) { + return delegate.DeconstructionPattern(deconstructor, nested, vt); + } + /** * Creates a new VariableTree from a VariableElement. * 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 46389a0a19..1702191252 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 @@ -60,6 +60,8 @@ public class TreeShims { // public static final String NULL_LITERAL = "NULL_LITERAL"; //NOI18N // public static final String PARENTHESIZED_PATTERN = "PARENTHESIZED_PATTERN"; //NOI18N // public static final String GUARDED_PATTERN = "GUARDED_PATTERN"; //NOI18N +// public static final String DECONSTRUCTION_PATTERN = "DECONSTRUCTION_PATTERN"; +// public static final String RECORDPATTERN = "RECORDPATTERN"; // // public static List<? extends ExpressionTree> getExpressions(CaseTree node) { // try { @@ -390,6 +392,58 @@ public class TreeShims { // } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { // throw TreeShims.<RuntimeException>throwAny(ex); // } +// } + +// public static ExpressionTree getDeconstructor(Tree node) { +// try { +// Class gpt = Class.forName("com.sun.source.tree.DeconstructionPatternTree"); //NOI18N +// return isJDKVersionRelease19_Or_Above() +// ? (ExpressionTree) gpt.getDeclaredMethod("getDeconstructor").invoke(node) //NOI18N +// : null; +// } catch (NoSuchMethodException | ClassNotFoundException ex) { +// return null; +// } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { +// throw TreeShims.<RuntimeException>throwAny(ex); +// } +// } + +// public static List<? extends PatternTree> getNestedPatterns(Tree node) { +// try { +// Class gpt = Class.forName("com.sun.source.tree.DeconstructionPatternTree"); //NOI18N +// return isJDKVersionRelease19_Or_Above() +// ? (List<? extends PatternTree>) gpt.getDeclaredMethod("getNestedPatterns").invoke(node) //NOI18N +// : null; +// } catch (NoSuchMethodException | ClassNotFoundException ex) { +// return null; +// } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { +// throw TreeShims.<RuntimeException>throwAny(ex); +// } +// } + +// public static VariableTree getVariable(Tree node) { +// try { +// Class gpt = Class.forName("com.sun.source.tree.DeconstructionPatternTree"); //NOI18N +// return isJDKVersionRelease19_Or_Above() +// ? (VariableTree) gpt.getDeclaredMethod("getVariable").invoke(node) //NOI18N +// : null; +// } catch (NoSuchMethodException | ClassNotFoundException ex) { +// return null; +// } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { +// throw TreeShims.<RuntimeException>throwAny(ex); +// } +// } + +// public static Tree RecordPattern(TreeMaker make, ExpressionTree deconstructor, List<PatternTree> nested, VariableTree var) { +// ListBuffer<JCTree.JCPattern> nestedVar = new ListBuffer<>(); +// for (PatternTree t : nested) { +// nestedVar.append((JCTree.JCPattern) t); +// } +// try { +// Method getMethod = TreeMaker.class.getDeclaredMethod("RecordPattern", JCTree.JCExpression.class, com.sun.tools.javac.util.List.class, JCTree.JCVariableDecl.class); +// return (Tree) getMethod.invoke(make, (JCTree.JCExpression) deconstructor, nestedVar.toList(), (JCTree.JCVariableDecl) var); +// } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { +// throw TreeShims.<RuntimeException>throwAny(ex); +// } // } public static Element toRecordComponent(Element el) { @@ -425,6 +479,10 @@ public class TreeShims { return Integer.valueOf(SourceVersion.latest().name().split("_")[1]).compareTo(17) >= 0; } +// public static boolean isJDKVersionRelease19_Or_Above(){ +// return Integer.valueOf(SourceVersion.latest().name().split("_")[1]).compareTo(19) >= 0; +// } + // public static boolean isJDKVersionRelease18_Or_Above() { // return Integer.valueOf(SourceVersion.latest().name().split("_")[1]).compareTo(18) >= 0; // } 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 5a76b60586..efb1458744 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 @@ -26,6 +26,7 @@ import com.sun.source.tree.LambdaExpressionTree.BodyKind; import com.sun.source.tree.MemberReferenceTree.ReferenceMode; import com.sun.source.tree.MethodTree; import com.sun.source.tree.ModuleTree; +import com.sun.source.tree.PatternTree; import com.sun.source.tree.StatementTree; import com.sun.source.tree.Tree; import static com.sun.source.tree.Tree.*; @@ -2091,8 +2092,26 @@ public final class VeryPretty extends JCTree.Visitor implements DocTreeVisitor<V @Override public void visitTree(JCTree tree) { - print("(UNKNOWN: " + tree + ")"); - newline(); + print("(UNKNOWN: " + tree + ")"); + newline(); + } + + @Override + public void visitRecordPattern(JCRecordPattern tree) { + print(tree.deconstructor); + print("("); + Iterator<JCPattern> it = tree.nested.iterator(); + while (it.hasNext()) { + JCPattern pattern = it.next(); + doAccept(pattern, true); + if (it.hasNext()) { + print(", "); + } + } + print(") "); + if (tree.var != null) { + print(tree.var.name.toString()); + } } /************************************************************************** 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 3bc2d5dd28..f13b66e1ac 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 @@ -105,6 +105,7 @@ import com.sun.tools.javac.tree.JCTree.JCPackageDecl; import com.sun.tools.javac.tree.JCTree.JCParens; import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree; import com.sun.tools.javac.tree.JCTree.JCProvides; +import com.sun.tools.javac.tree.JCTree.JCRecordPattern; import com.sun.tools.javac.tree.JCTree.JCRequires; import com.sun.tools.javac.tree.JCTree.JCReturn; import com.sun.tools.javac.tree.JCTree.JCStatement; @@ -1966,6 +1967,28 @@ public class CasualDiff { return diffTree((JCTree) oldT.var, (JCTree) newT.var, bounds); } + protected int diffRecordPattern(JCRecordPattern oldT, JCRecordPattern newT, int[] bounds) { + int localPointer = bounds[0]; + + // deconstructor + int[] exprBounds = getBounds(oldT.deconstructor); + copyTo(localPointer, exprBounds[0]); + localPointer = diffTree(oldT.deconstructor, newT.deconstructor, exprBounds); + + // Nested Patterns + Iterator<? extends JCTree> oldTIter = oldT.nested.iterator(); + Iterator<? extends JCTree> newTIter = newT.nested.iterator(); + while (oldTIter.hasNext()) { + JCTree oldP = oldTIter.next(); + JCTree newP = newTIter.next(); + int[] patternBounds = getBounds(oldP); + copyTo(localPointer, patternBounds[0]); + localPointer = diffTree(oldP, newP, patternBounds); + } + copyTo(localPointer, bounds[1]); + return bounds[1]; + } + protected int diffConstantCaseLabel(JCConstantCaseLabel oldT, JCConstantCaseLabel newT, int[] bounds) { return diffTree((JCTree) oldT.expr, (JCTree) newT.expr, bounds); } @@ -5720,6 +5743,9 @@ public class CasualDiff { case CONSTANTCASELABEL: retVal = diffConstantCaseLabel((JCConstantCaseLabel) oldT, (JCConstantCaseLabel) newT, elementBounds); break; + case RECORDPATTERN: + retVal = diffRecordPattern((JCRecordPattern) oldT, (JCRecordPattern) newT, elementBounds); + break; default: // handle special cases like field groups and enum constants if (oldT.getKind() == Kind.OTHER) { diff --git a/java/java.source.base/src/org/netbeans/modules/java/source/save/Reformatter.java b/java/java.source.base/src/org/netbeans/modules/java/source/save/Reformatter.java index 941da1c039..e04f054f03 100644 --- a/java/java.source.base/src/org/netbeans/modules/java/source/save/Reformatter.java +++ b/java/java.source.base/src/org/netbeans/modules/java/source/save/Reformatter.java @@ -2825,8 +2825,10 @@ public class Reformatter implements ReformatTask { @Override public Boolean visitDeconstructionPattern(DeconstructionPatternTree node, Void p) { scan(node.getDeconstructor(), p); + spaces(0); accept(LPAREN); - scan(node.getNestedPatterns(), p); + spaces(cs.spaceWithinMethodDeclParens() ? 1 : 0, true); + wrapList(cs.wrapMethodParams(), cs.alignMultilineMethodParams(), false, COMMA, node.getNestedPatterns()); accept(RPAREN); if (node.getVariable() != null) { space(); diff --git a/java/java.source.base/test/unit/src/org/netbeans/modules/java/source/save/FormatingTest.java b/java/java.source.base/test/unit/src/org/netbeans/modules/java/source/save/FormatingTest.java index a7b245a078..c4afa398dd 100644 --- a/java/java.source.base/test/unit/src/org/netbeans/modules/java/source/save/FormatingTest.java +++ b/java/java.source.base/test/unit/src/org/netbeans/modules/java/source/save/FormatingTest.java @@ -3322,6 +3322,183 @@ public class FormatingTest extends NbTestCase { reformat(doc, content, golden); } + public void testNormalRecordPattern() throws Exception { + try { + SourceVersion.valueOf("RELEASE_19"); //NOI18N + } catch (IllegalArgumentException ex) { + return; + } + testFile = new File(getWorkDir(), "Test.java"); + TestUtilities.copyStringToFile(testFile, ""); + FileObject testSourceFO = FileUtil.toFileObject(testFile); + DataObject testSourceDO = DataObject.find(testSourceFO); + EditorCookie ec = (EditorCookie) testSourceDO.getCookie(EditorCookie.class); + final Document doc = ec.openDocument(); + doc.putProperty(Language.class, JavaTokenId.language()); + String content = "package test;\n" + + "record Point(int x, int y){}\n" + + "public class Test {\n" + + " private void test(Object o) {\n" + + " if (o instanceof Point\n" + + " (int x, int \n" + + " y) p) {\n" + + " System.out.println(\"Hello\");\n" + + " }\n" + + " }\n" + + "}"; + + String golden = "package test;\n" + + "\n" + + "record Point(int x, int y) {\n" + + "\n" + + "}\n" + + "\n" + + "public class Test {\n" + + "\n" + + " private void test(Object o) {\n" + + " if (o instanceof Point(int x, int y) p) {\n" + + " System.out.println(\"Hello\");\n" + + " }\n" + + " }\n" + + "}" + + "\n"; + reformat(doc, content, golden); + } + + public void testNestedRecordPattern() throws Exception { + try { + SourceVersion.valueOf("RELEASE_19"); //NOI18N + } catch (IllegalArgumentException ex) { + //OK, no RELEASE_19, skip test + return; + } + testFile = new File(getWorkDir(), "Test.java"); + TestUtilities.copyStringToFile(testFile, ""); + FileObject testSourceFO = FileUtil.toFileObject(testFile); + DataObject testSourceDO = DataObject.find(testSourceFO); + EditorCookie ec = (EditorCookie) testSourceDO.getCookie(EditorCookie.class); + final Document doc = ec.openDocument(); + doc.putProperty(Language.class, JavaTokenId.language()); + String content = "package test;\n" + + "\n" + + "record Rect(ColoredPoint ul,ColoredPoint lr) {}\n" + + "enum Color {RED,GREEN,BLUE}\n" + + "record ColoredPoint(Point p, Color c) {}\n" + + "record Point(int x, int y) {}\n" + + "\n" + + "public class Test {\n" + + "\n" + + " private void test(Object o) {\n" + + " if (o instanceof\n" + + " Rect\n" + + " ( ColoredPoint ul, ColoredPoint\n" + + " lr) r) {\n" + + " Point p = ul.p();\n" + + " System.out.println(\"Hello\");\n" + + " }\n" + + " }\n" + + "}"; + + String golden = "package test;\n" + + "\n" + + "record Rect(ColoredPoint ul, ColoredPoint lr) {\n" + + "\n" + + "}\n" + + "\n" + + "enum Color {\n" + + " RED, GREEN, BLUE\n" + + "}\n" + + "\n" + + "record ColoredPoint(Point p, Color c) {\n" + + "\n" + + "}\n" + + "\n" + + "record Point(int x, int y) {\n" + + "\n" + + "}\n" + + "\n" + + "public class Test {\n" + + "\n" + + " private void test(Object o) {\n" + + " if (o instanceof Rect(ColoredPoint ul, ColoredPoint lr) r) {\n" + + " Point p = ul.p();\n" + + " System.out.println(\"Hello\");\n" + + " }\n" + + " }\n" + + "}" + + "\n"; + reformat(doc, content, golden); + } + + public void testMultipleNestingRecordPattern() throws Exception { + try { + SourceVersion.valueOf("RELEASE_19"); //NOI18N + } catch (IllegalArgumentException ex) { + //OK, no RELEASE_19, skip test + return; + } + testFile = new File(getWorkDir(), "Test.java"); + TestUtilities.copyStringToFile(testFile, ""); + FileObject testSourceFO = FileUtil.toFileObject(testFile); + DataObject testSourceDO = DataObject.find(testSourceFO); + EditorCookie ec = (EditorCookie) testSourceDO.getCookie(EditorCookie.class); + final Document doc = ec.openDocument(); + doc.putProperty(Language.class, JavaTokenId.language()); + String content = "package test;\n" + + "\n" + + "record Rect(ColoredPoint ul,ColoredPoint lr) {}\n" + + "enum Color {RED,GREEN,BLUE}\n" + + "record ColoredPoint(Point p, Color c) {}\n" + + "record Point(int x, int y) {}\n" + + "\n" + + "public class Test {\n" + + "\n" + + " private void test(Object o) {\n" + + " if (o instanceof\n" + + " Rect\n" + + " ( ColoredPoint(Point \n" + + " p, Color \n" + + " c\n" + + " ) ul, ColoredPoint\n" + + " lr) r\n" + + " ) {\n" + + " int x = p.x();\n" + + " System.out.println(\"Hello\");\n" + + " }\n" + + " }\n" + + "}"; + + String golden = "package test;\n" + + "\n" + + "record Rect(ColoredPoint ul, ColoredPoint lr) {\n" + + "\n" + + "}\n" + + "\n" + + "enum Color {\n" + + " RED, GREEN, BLUE\n" + + "}\n" + + "\n" + + "record ColoredPoint(Point p, Color c) {\n" + + "\n" + + "}\n" + + "\n" + + "record Point(int x, int y) {\n" + + "\n" + + "}\n" + + "\n" + + "public class Test {\n" + + "\n" + + " private void test(Object o) {\n" + + " if (o instanceof Rect(ColoredPoint(Point p, Color c) ul, ColoredPoint lr) r) {\n" + + " int x = p.x();\n" + + " System.out.println(\"Hello\");\n" + + " }\n" + + " }\n" + + "}" + + "\n"; + reformat(doc, content, golden); + } + public void testDoWhile() throws Exception { testFile = new File(getWorkDir(), "Test.java"); TestUtilities.copyStringToFile(testFile, --------------------------------------------------------------------- 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