From 7928dc1f4cf91c038ce6a8755b3513d991c31a96 Mon Sep 17 00:00:00 2001 From: Michael Bien Date: Wed, 1 May 2024 23:23:11 +0200 Subject: [PATCH] Code generator record updates. record accessors: - javac generates final methods for records unless they are already in the code - code can't override them, but it can replace them using the abstract method impl generator record other generators: - made equals, hashcode and toString generators record aware - disabled getter/setter and property generator for records enums: - toString support for enums --- .../addproperty/AddPropertyCodeGenerator.java | 13 ++- .../codegen/EqualsHashCodeGenerator.java | 22 +++-- .../java/editor/codegen/GeneratorUtils.java | 3 - .../editor/codegen/GetterSetterGenerator.java | 10 ++- .../ImplementOverrideMethodGenerator.java | 2 +- .../editor/codegen/ToStringGenerator.java | 50 ++++++++--- .../java/editor/codegen/ui/ElementNode.java | 4 +- .../editor/overridden/ComputeOverriders.java | 37 +------- .../editor/codegen/ToStringGeneratorTest.java | 84 +++++++++++++++++-- .../api/java/source/ElementUtilities.java | 14 +++- .../api/java/source/GeneratorUtilities.java | 31 +++---- .../resources/GeneratedMethodBody.template | 7 +- .../resources/OverriddenMethodBody.template | 3 +- 13 files changed, 191 insertions(+), 89 deletions(-) diff --git a/java/beans/src/org/netbeans/modules/beans/addproperty/AddPropertyCodeGenerator.java b/java/beans/src/org/netbeans/modules/beans/addproperty/AddPropertyCodeGenerator.java index 64dcd804fac7..be0c4f77ba8b 100644 --- a/java/beans/src/org/netbeans/modules/beans/addproperty/AddPropertyCodeGenerator.java +++ b/java/beans/src/org/netbeans/modules/beans/addproperty/AddPropertyCodeGenerator.java @@ -29,6 +29,7 @@ import com.sun.source.util.Trees; import java.io.IOException; import java.util.Collections; +import java.util.EnumSet; import java.util.LinkedList; import java.util.List; import javax.lang.model.element.Element; @@ -92,10 +93,12 @@ public AddPropertyCodeGenerator(JTextComponent component, String className, List this.vcsName = vcsName; } + @Override public String getDisplayName() { return NbBundle.getMessage(AddPropertyCodeGenerator.class, "DN_AddProperty"); } + @Override public void invoke() { Object o = component.getDocument().getProperty(Document.StreamDescriptionProperty); @@ -216,6 +219,7 @@ public void run(CompilationController parameter) throws Exception { r.lock(); try { NbDocument.runAtomicAsUser((StyledDocument) doc, new Runnable() { + @Override public void run() { try { GuardedSectionManager manager = GuardedSectionManager.getInstance((StyledDocument) doc); @@ -256,6 +260,7 @@ public void run() { // code insertion to document passed try { JavaSource.forFileObject(file).runModificationTask(new Task() { + @Override public void run(WorkingCopy workingCopy) throws Exception { workingCopy.toPhase(Phase.RESOLVED); @@ -352,11 +357,17 @@ public Void visitClass(ClassTree node, Void p) { public static final class Factory implements CodeGenerator.Factory { + private static final EnumSet TREE_KINDS = EnumSet.copyOf(TreeUtilities.CLASS_TREE_KINDS); + static { + TREE_KINDS.remove(Tree.Kind.RECORD); // no fields in records + } + + @Override public List create(Lookup context) { JTextComponent component = context.lookup(JTextComponent.class); CompilationController cc = context.lookup(CompilationController.class); TreePath path = context.lookup(TreePath.class); - while (path != null && !TreeUtilities.CLASS_TREE_KINDS.contains(path.getLeaf().getKind())) { + while (path != null && !TREE_KINDS.contains(path.getLeaf().getKind())) { path = path.getParentPath(); } diff --git a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/EqualsHashCodeGenerator.java b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/EqualsHashCodeGenerator.java index 51cec2cd09f7..ddcd188c490d 100644 --- a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/EqualsHashCodeGenerator.java +++ b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/EqualsHashCodeGenerator.java @@ -67,12 +67,14 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.type.WildcardType; import javax.lang.model.util.ElementFilter; +import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import javax.swing.text.JTextComponent; import org.netbeans.api.java.source.Task; import org.netbeans.api.java.source.CompilationController; import org.netbeans.api.java.source.CompilationInfo; import org.netbeans.api.java.source.ElementHandle; +import org.netbeans.api.java.source.ElementUtilities; import org.netbeans.api.java.source.GeneratorUtilities; import org.netbeans.api.java.source.JavaSource; import org.netbeans.api.java.source.ModificationResult; @@ -95,6 +97,7 @@ public class EqualsHashCodeGenerator implements CodeGenerator { private static final String ERROR = ""; //NOI18N + private static final Set TREE_KINDS = EnumSet.of(ElementKind.CLASS, ElementKind.RECORD); public static class Factory implements CodeGenerator.Factory { @@ -152,7 +155,7 @@ public String getDisplayName() { } static EqualsHashCodeGenerator createEqualsHashCodeGenerator(JTextComponent component, CompilationController cc, Element el) throws IOException { - if (el.getKind() != ElementKind.CLASS) { + if (!TREE_KINDS.contains(el.getKind())) { return null; } //#125114: ignore anonymous innerclasses: @@ -203,12 +206,15 @@ public Void visitMemberSelect(MemberSelectTree sel, VariableElement what) { return super.visitMemberSelect(sel, what); } } + Trees tree = cc.getTrees(); for (ExecutableElement e : methods) { if (e == null) { continue; } - Trees tree = cc.getTrees(); TreePath path = tree.getPath(e); + if (path == null) { + continue; + } Used used = new Used(); used.scan(path, field); if (used.found) { @@ -232,7 +238,7 @@ public static ExecutableElement[] overridesHashCodeAndEquals(CompilationInfo com if (el == null) { return ret; } - if (type == null || type.getKind() != ElementKind.CLASS) { + if (type == null || !TREE_KINDS.contains(type.getKind())) { return ret; } @@ -266,6 +272,8 @@ public static ExecutableElement[] overridesHashCodeAndEquals(CompilationInfo com return ret; } + Elements elements = compilationInfo.getElements(); + ElementUtilities elementUtils = compilationInfo.getElementUtilities(); TypeElement clazz = (TypeElement)type; for (Element ee : type.getEnclosedElements()) { if (stop != null && stop.isCanceled()) { @@ -276,11 +284,11 @@ public static ExecutableElement[] overridesHashCodeAndEquals(CompilationInfo com } ExecutableElement method = (ExecutableElement)ee; - if (compilationInfo.getElements().overrides(method, hashCode, clazz)) { + if (!elementUtils.isSynthetic(method) && elements.overrides(method, hashCode, clazz)) { ret[1] = method; } - if (compilationInfo.getElements().overrides(method, equals, clazz)) { + if (!elementUtils.isSynthetic(method) && elements.overrides(method, equals, clazz)) { ret[0] = method; } } @@ -410,9 +418,7 @@ public static void generateEqualsAndHashCode(WorkingCopy wc, TreePath path, Iter if (!dt.getTypeArguments().isEmpty()) { WildcardType wt = wc.getTypes().getWildcardType(null, null); TypeMirror[] typeArgs = new TypeMirror[dt.getTypeArguments().size()]; - for (int i = 0; i < typeArgs.length; i++) { - typeArgs[i] = wt; - } + Arrays.fill(typeArgs, wt); dt = dt.getEnclosingType().getKind() == TypeKind.DECLARED ? wc.getTypes().getDeclaredType((DeclaredType)dt.getEnclosingType(), te, typeArgs) : wc.getTypes().getDeclaredType(te, typeArgs); diff --git a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/GeneratorUtils.java b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/GeneratorUtils.java index a6cb7fd50a03..423bd41af052 100644 --- a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/GeneratorUtils.java +++ b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/GeneratorUtils.java @@ -43,11 +43,9 @@ import javax.lang.model.type.TypeMirror; import javax.swing.JButton; import javax.swing.JComponent; -import javax.swing.text.Document; import javax.swing.text.JTextComponent; import org.netbeans.api.annotations.common.NonNull; -import org.netbeans.api.java.source.CodeStyle; import org.netbeans.api.java.source.CompilationInfo; import org.netbeans.api.java.source.ElementUtilities; import org.netbeans.api.java.source.GeneratorUtilities; @@ -58,7 +56,6 @@ import org.netbeans.editor.Utilities; import org.openide.DialogDescriptor; import org.openide.ErrorManager; -import org.openide.filesystems.FileObject; import org.openide.util.NbBundle; /** diff --git a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/GetterSetterGenerator.java b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/GetterSetterGenerator.java index 34f9b97dbcd7..1f2f68d4d462 100644 --- a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/GetterSetterGenerator.java +++ b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/GetterSetterGenerator.java @@ -18,6 +18,7 @@ */ package org.netbeans.modules.java.editor.codegen; +import com.sun.source.tree.Tree; import java.awt.Dialog; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; @@ -62,6 +63,11 @@ */ public class GetterSetterGenerator implements CodeGenerator { + private static final EnumSet TREE_KINDS = EnumSet.copyOf(TreeUtilities.CLASS_TREE_KINDS); + static { + TREE_KINDS.remove(Tree.Kind.RECORD); // no getters/setters for records + } + public static class Factory implements CodeGenerator.Factory { private static final String ERROR = ""; //NOI18N @@ -76,7 +82,7 @@ public List create(Lookup context) { } CodeStyle codeStyle = CodeStyle.getDefault(component.getDocument()); TreePath path = context.lookup(TreePath.class); - path = controller.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, path); + path = controller.getTreeUtilities().getPathElementOfKind(TREE_KINDS, path); if (path == null) { return ret; } @@ -214,7 +220,7 @@ public void run(WorkingCopy copy) throws IOException { copy.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED); Element e = description.getElementHandle().resolve(copy); TreePath path = e != null ? copy.getTrees().getPath(e) : copy.getTreeUtilities().pathFor(caretOffset); - path = copy.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, path); + path = copy.getTreeUtilities().getPathElementOfKind(TREE_KINDS, path); if (path == null) { String message = NbBundle.getMessage(GetterSetterGenerator.class, "ERR_CannotFindOriginalClass"); //NOI18N org.netbeans.editor.Utilities.setStatusBoldText(component, message); diff --git a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/ImplementOverrideMethodGenerator.java b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/ImplementOverrideMethodGenerator.java index 9b0460bb0929..435cf1e08d3d 100644 --- a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/ImplementOverrideMethodGenerator.java +++ b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/ImplementOverrideMethodGenerator.java @@ -35,7 +35,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.BiFunction; -import java.util.function.Function; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; @@ -193,6 +192,7 @@ public List> get() throws InterruptedExceptio } } + @Override public void run() { List> tmp; if (testOverrideMethodsSelection != null) { diff --git a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/ToStringGenerator.java b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/ToStringGenerator.java index a0dbf39793d1..2a7392b387af 100644 --- a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/ToStringGenerator.java +++ b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/ToStringGenerator.java @@ -42,10 +42,12 @@ import java.util.Set; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; +import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; +import javax.lang.model.util.ElementFilter; import javax.swing.text.JTextComponent; import org.netbeans.api.annotations.common.CheckForNull; import org.netbeans.api.java.source.CompilationController; @@ -111,16 +113,32 @@ public List create(Lookup context) { private final boolean supportsStringBuilder; @CheckForNull - static ToStringGenerator createToStringGenerator(JTextComponent component, CompilationController controller, Element typeElement, boolean useStringBuilder) { + static ToStringGenerator createToStringGenerator(JTextComponent component, CompilationController controller, TypeElement typeElement, boolean useStringBuilder) { List descriptions = new ArrayList<>(); + + // add ordinal() and name() for enums + if (typeElement.getKind() == ElementKind.ENUM) { + Element enumElement = controller.getTypes().asElement(typeElement.getSuperclass()); + for (Element element : ElementFilter.methodsIn(enumElement.getEnclosedElements())) { + Name name = element.getSimpleName(); + if (name.contentEquals("ordinal")) { //NOI18N + descriptions.add(0, ElementNode.Description.create(controller, element, null, true, true)); + } else if (name.contentEquals("name")) { //NOI18N + descriptions.add(ElementNode.Description.create(controller, element, null, true, true)); + } + } + } + for (Element element : typeElement.getEnclosedElements()) { switch (element.getKind()) { case METHOD: - if (element.getSimpleName().contentEquals("toString") && ((ExecutableElement) element).getParameters().isEmpty()) { //NOI18N + if (element.getSimpleName().contentEquals("toString") && ((ExecutableElement) element).getParameters().isEmpty() //NOI18N + && !controller.getElementUtilities().isSynthetic(element)) { // e.g record return null; } break; case FIELD: +// case RECORD_COMPONENT: // record components will show up as fields for some reason if (!ERROR.contentEquals(element.getSimpleName()) && !element.getModifiers().contains(Modifier.STATIC)) { descriptions.add(ElementNode.Description.create(controller, element, null, true, true)); } @@ -189,9 +207,9 @@ public void run(WorkingCopy copy) throws IOException { org.netbeans.editor.Utilities.setStatusBoldText(component, message); } else { ClassTree cls = (ClassTree) path.getLeaf(); - ArrayList fields = new ArrayList<>(); + List fields = new ArrayList<>(); for (ElementHandle elementHandle : panel.getVariables()) { - VariableElement field = (VariableElement) elementHandle.resolve(copy); + Element field = elementHandle.resolve(copy); if (field == null) { return; } @@ -209,7 +227,7 @@ public void run(WorkingCopy copy) throws IOException { } } - public static MethodTree createToStringMethod(WorkingCopy wc, Iterable fields, String typeName, boolean useStringBuilder) { + public static MethodTree createToStringMethod(WorkingCopy wc, Iterable fields, String typeName, boolean useStringBuilder) { TreeMaker make = wc.getTreeMaker(); Set mods = EnumSet.of(Modifier.PUBLIC); List annotations = new LinkedList<>(); @@ -224,7 +242,7 @@ public static MethodTree createToStringMethod(WorkingCopy wc, IterableemptyList(), Collections.emptyList(), Collections.emptyList(), body, null); //NOI18N } - private static BlockTree createToStringMethodBody(TreeMaker make, String typeName, Iterable fields, boolean useStringBuilder) { + private static BlockTree createToStringMethodBody(TreeMaker make, String typeName, Iterable fields, boolean useStringBuilder) { List statements; if (useStringBuilder) { statements = createToStringMethodBodyWithStringBuilder(make, typeName, fields); @@ -235,24 +253,24 @@ private static BlockTree createToStringMethodBody(TreeMaker make, String typeNam return body; } - private static List createToStringMethodBodyWithPlusOperator(TreeMaker make, String typeName, Iterable fields) { + private static List createToStringMethodBodyWithPlusOperator(TreeMaker make, String typeName, Iterable fields) { ExpressionTree exp = make.Literal(typeName + '{'); boolean first = true; - for (VariableElement variableElement : fields) { + for (Element variableElement : fields) { StringBuilder sb = new StringBuilder(); if (!first) { sb.append(", "); } sb.append(variableElement.getSimpleName().toString()).append('='); exp = make.Binary(Tree.Kind.PLUS, exp, make.Literal(sb.toString())); - exp = make.Binary(Tree.Kind.PLUS, exp, make.Identifier(variableElement.getSimpleName())); + exp = make.Binary(Tree.Kind.PLUS, exp, makeExpression(make, variableElement)); first = false; } StatementTree stat = make.Return(make.Binary(Tree.Kind.PLUS, exp, make.Literal('}'))); //NOI18N return Collections.singletonList(stat); } - private static List createToStringMethodBodyWithStringBuilder(TreeMaker make, String typeName, Iterable fields) { + private static List createToStringMethodBodyWithStringBuilder(TreeMaker make, String typeName, Iterable fields) { List statements = new ArrayList<>(); final ExpressionTree stringBuilder = make.QualIdent(StringBuilder.class.getName()); NewClassTree newStringBuilder = make.NewClass(null, Collections.emptyList(), stringBuilder, Collections.emptyList(), null); @@ -265,7 +283,7 @@ private static List createToStringMethodBodyWithStringBuilder(Tre Collections.singletonList(make.Literal(typeName + '{')) ))); boolean first = true; - for (VariableElement variableElement : fields) { + for (Element variableElement : fields) { StringBuilder sb = new StringBuilder(); if (!first) { sb.append(", "); // NOI18N @@ -278,7 +296,7 @@ private static List createToStringMethodBodyWithStringBuilder(Tre make, varName, Collections.singletonList(make.Literal(sb.toString()))), - Collections.singletonList(make.Identifier(variableElement.getSimpleName()))) + Collections.singletonList(makeExpression(make, variableElement))) )); first = false; } @@ -303,4 +321,10 @@ private static MethodInvocationTree createAppendInvocation(TreeMaker make, Expre ); } + private static ExpressionTree makeExpression(TreeMaker make, Element element) { + return element.getKind() == ElementKind.METHOD + ? make.MethodInvocation(Collections.emptyList(), make.Identifier(element.getSimpleName()), Collections.emptyList()) + : make.Identifier(element.getSimpleName()); + } + } diff --git a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/ui/ElementNode.java b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/ui/ElementNode.java index 8f8ff4df3bda..522f505ed4dd 100644 --- a/java/java.editor/src/org/netbeans/modules/java/editor/codegen/ui/ElementNode.java +++ b/java/java.editor/src/org/netbeans/modules/java/editor/codegen/ui/ElementNode.java @@ -155,7 +155,7 @@ public static class Description { private List subs; private String htmlHeader; private boolean isSelected; - private boolean isSelectable; + private final boolean isSelectable; public static Description create(List subs) { return new Description("", null, null, subs, null, false, false); // NOI18N @@ -168,6 +168,7 @@ public static Description create(CompilationInfo info, Element element, List { + @Override public int compare(Description d1, Description d2) { if ( k2i(d1.elementHandle.getKind()) != k2i(d2.elementHandle.getKind()) ) { diff --git a/java/java.editor/src/org/netbeans/modules/java/editor/overridden/ComputeOverriders.java b/java/java.editor/src/org/netbeans/modules/java/editor/overridden/ComputeOverriders.java index df60a474422c..97e274d1e2dc 100644 --- a/java/java.editor/src/org/netbeans/modules/java/editor/overridden/ComputeOverriders.java +++ b/java/java.editor/src/org/netbeans/modules/java/editor/overridden/ComputeOverriders.java @@ -19,8 +19,6 @@ package org.netbeans.modules.java.editor.overridden; -import java.awt.Point; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URL; import java.util.ArrayList; @@ -58,7 +56,6 @@ import org.openide.DialogDisplayer; import org.openide.NotifyDescriptor; import org.openide.filesystems.FileObject; -import org.openide.filesystems.FileStateInvalidException; import org.openide.filesystems.URLMapper; import org.openide.util.Exceptions; import org.openide.util.Lookup; @@ -553,22 +550,7 @@ private static Map> getDependencies(boolean binary) { Method dependenciesMethod = clazz.getDeclaredMethod(method); return (Map>) dependenciesMethod.invoke(instance); - } catch (IllegalAccessException ex) { - Logger.getLogger(GoToImplementation.class.getName()).log(Level.FINE, null, ex); - return null; - } catch (IllegalArgumentException ex) { - Logger.getLogger(GoToImplementation.class.getName()).log(Level.FINE, null, ex); - return null; - } catch (InvocationTargetException ex) { - Logger.getLogger(GoToImplementation.class.getName()).log(Level.FINE, null, ex); - return null; - } catch (NoSuchMethodException ex) { - Logger.getLogger(GoToImplementation.class.getName()).log(Level.FINE, null, ex); - return null; - } catch (SecurityException ex) { - Logger.getLogger(GoToImplementation.class.getName()).log(Level.FINE, null, ex); - return null; - } catch (ClassCastException ex) { + } catch (ReflectiveOperationException | IllegalArgumentException | SecurityException | ClassCastException ex) { Logger.getLogger(GoToImplementation.class.getName()).log(Level.FINE, null, ex); return null; } @@ -604,22 +586,7 @@ private static Map> getRootPeers() { Method peersMethod = clazz.getDeclaredMethod(method); return (Map>) peersMethod.invoke(instance); - } catch (IllegalAccessException ex) { - Logger.getLogger(GoToImplementation.class.getName()).log(Level.FINE, null, ex); - return null; - } catch (IllegalArgumentException ex) { - Logger.getLogger(GoToImplementation.class.getName()).log(Level.FINE, null, ex); - return null; - } catch (InvocationTargetException ex) { - Logger.getLogger(GoToImplementation.class.getName()).log(Level.FINE, null, ex); - return null; - } catch (NoSuchMethodException ex) { - Logger.getLogger(GoToImplementation.class.getName()).log(Level.FINE, null, ex); - return null; - } catch (SecurityException ex) { - Logger.getLogger(GoToImplementation.class.getName()).log(Level.FINE, null, ex); - return null; - } catch (ClassCastException ex) { + } catch (ReflectiveOperationException | IllegalArgumentException | SecurityException | ClassCastException ex) { Logger.getLogger(GoToImplementation.class.getName()).log(Level.FINE, null, ex); return null; } diff --git a/java/java.editor/test/unit/src/org/netbeans/modules/java/editor/codegen/ToStringGeneratorTest.java b/java/java.editor/test/unit/src/org/netbeans/modules/java/editor/codegen/ToStringGeneratorTest.java index fc5db31cd49a..4ea50d635636 100644 --- a/java/java.editor/test/unit/src/org/netbeans/modules/java/editor/codegen/ToStringGeneratorTest.java +++ b/java/java.editor/test/unit/src/org/netbeans/modules/java/editor/codegen/ToStringGeneratorTest.java @@ -20,7 +20,7 @@ import java.awt.Dialog; import java.io.IOException; -import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; import javax.swing.JDialog; import javax.swing.JTextArea; import javax.swing.SwingUtilities; @@ -111,7 +111,7 @@ class Task implements org.netbeans.api.java.source.Task { @Override public void run(CompilationController controller) throws Exception { controller.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED); - Element typeElement = controller.getElements().getTypeElement("NewClass"); + TypeElement typeElement = controller.getElements().getTypeElement("NewClass"); generator = ToStringGenerator.createToStringGenerator(component, controller, typeElement, false); } @@ -178,7 +178,7 @@ class Task implements org.netbeans.api.java.source.Task { @Override public void run(CompilationController controller) throws Exception { controller.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED); - Element typeElement = controller.getElements().getTypeElement("NewClass"); + TypeElement typeElement = controller.getElements().getTypeElement("NewClass"); generator = ToStringGenerator.createToStringGenerator(component, controller, typeElement, true); } @@ -222,6 +222,48 @@ public void post() throws Exception { assertEquals(expected, text); } + public void testEnumToString() throws Exception { + String name = "NewEnum"; + String code = "" + + "public enum NewEnum {\n" + + " A, B;\n" + + " private final String test1 = \"test\";\n" + + "|\n" + + "}"; + String expected = "" + + "public enum NewEnum {\n" + + " A, B;\n" + + " private final String test1 = \"test\";\n" + + "\n" + + " @Override\n" + + " public String toString() {\n" + + " return \"NewEnum{\" + \"ordinal=\" + ordinal() + \", name=\" + name() + \", test1=\" + test1 + '}';\n" + + " }\n" + + "\n" + + "}"; + + runTest(name, code, expected); + } + + public void testRecordToString() throws Exception { + String name = "NewRecord"; + String code = "" + + "public record NewRecord(int x, int y) {\n" + + "|\n" + + "}"; + String expected = "" + + "public record NewRecord(int x, int y) {\n" + + "\n" + + " @Override\n" + + " public String toString() {\n" + + " return \"NewRecord{\" + \"x=\" + x + \", y=\" + y + '}';\n" + + " }\n" + + "\n" + + "}"; + + runTest(name, code, expected); + } + public void testToStringExists() throws Exception { FileObject javaFile = FileUtil.createData(fo, "NewClass.java"); String what1 = "" @@ -264,7 +306,7 @@ class Task implements org.netbeans.api.java.source.Task { @Override public void run(CompilationController controller) throws Exception { controller.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED); - Element typeElement = controller.getElements().getTypeElement("NewClass"); + TypeElement typeElement = controller.getElements().getTypeElement("NewClass"); generator = ToStringGenerator.createToStringGenerator(component, controller, typeElement, true); } @@ -308,7 +350,7 @@ class Task implements org.netbeans.api.java.source.Task { @Override public void run(CompilationController controller) throws Exception { controller.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED); - Element typeElement = controller.getElements().getTypeElement("NewClass"); + TypeElement typeElement = controller.getElements().getTypeElement("NewClass"); generator = ToStringGenerator.createToStringGenerator(component, controller, typeElement, true); } @@ -345,6 +387,38 @@ public void post() throws Exception { assertEquals(expected, text); } + private void runTest(String name, String code, String expected) throws Exception { + int caret = code.indexOf("|"); + FileObject javaFile = FileUtil.createData(fo, name+".java"); + GeneratorUtilsTest.writeIntoFile(javaFile, code.replace("|", "")); + + JavaSource javaSource = JavaSource.forFileObject(javaFile); + assertNotNull("Created", javaSource); + + Document doc = getDocuemnt(javaFile); + + JTextArea component = new JTextArea(doc); + component.setCaretPosition(caret); + + class Task implements org.netbeans.api.java.source.Task { + private ToStringGenerator generator; + @Override + public void run(CompilationController controller) throws Exception { + controller.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED); + TypeElement typeElement = controller.getElements().getTypeElement(name); + generator = ToStringGenerator.createToStringGenerator(component, controller, typeElement, false); + } + + } + Task task = new Task(); + javaSource.runUserActionTask(task, false); + SwingUtilities.invokeAndWait(() -> task.generator.invoke()); + + Document document = component.getDocument(); + String text = document.getText(0, document.getLength()); + assertEquals(expected, text); + } + private static Document getDocuemnt(FileObject fileObject) throws DataObjectNotFoundException, IOException { DataObject dataObject = DataObject.find(fileObject); EditorCookie ec = dataObject.getLookup().lookup(org.openide.cookies.EditorCookie.class); diff --git a/java/java.source.base/src/org/netbeans/api/java/source/ElementUtilities.java b/java/java.source.base/src/org/netbeans/api/java/source/ElementUtilities.java index 52cb14bef2fa..11001a9037d6 100644 --- a/java/java.source.base/src/org/netbeans/api/java/source/ElementUtilities.java +++ b/java/java.source.base/src/org/netbeans/api/java/source/ElementUtilities.java @@ -20,6 +20,7 @@ import com.sun.source.tree.CompilationUnitTree; import com.sun.source.tree.Scope; +import com.sun.source.tree.Tree; import com.sun.source.util.TreePath; import com.sun.source.util.Trees; import com.sun.tools.javac.api.JavacScope; @@ -169,7 +170,9 @@ public Element getImplementationOf(ExecutableElement method, TypeElement origin) * @return true if and only if the given element is synthetic, false otherwise */ public boolean isSynthetic(Element element) { - return (((Symbol) element).flags() & Flags.SYNTHETIC) != 0 || (((Symbol) element).flags() & Flags.GENERATEDCONSTR) != 0; + return (((Symbol) element).flags() & Flags.SYNTHETIC) != 0 + || (((Symbol) element).flags() & Flags.GENERATEDCONSTR) != 0 + || (((Symbol) element).flags() & Flags.GENERATED_MEMBER) != 0; } /**Returns true if the given module is open. @@ -709,6 +712,8 @@ public List findOverridableMethods(TypeElement type DeclaredType dt = (DeclaredType)type.asType(); Types types = JavacTypes.instance(ctx); Set typeStrings = new HashSet<>(); + Tree.Kind kind = info.getTrees().getTree(type).getKind(); + for (ExecutableElement ee : ElementFilter.methodsIn(info.getElements().getAllMembers(type))) { TypeMirror methodType = types.erasure(types.asMemberOf(dt, ee)); @@ -716,11 +721,14 @@ public List findOverridableMethods(TypeElement type if (typeStrings.contains(methodTypeString)) { continue; } + // javac generated record members disappear if overridden + boolean replaceable = kind == Tree.Kind.RECORD && isSynthetic(ee); + Set set = EnumSet.copyOf(notOverridable); - set.removeAll(ee.getModifiers()); + set.removeAll(ee.getModifiers()); if (set.size() == notOverridable.size() && !overridesPackagePrivateOutsidePackage(ee, type) //do not offer package private methods in case they're from different package - && !isOverridden(ee, type)) { + && (replaceable || !isOverridden(ee, type))) { overridable.add(ee); if (ee.getModifiers().contains(Modifier.ABSTRACT)) { typeStrings.add(methodTypeString); diff --git a/java/java.source.base/src/org/netbeans/api/java/source/GeneratorUtilities.java b/java/java.source.base/src/org/netbeans/api/java/source/GeneratorUtilities.java index 7aac23e9ae57..aed6e6110c13 100644 --- a/java/java.source.base/src/org/netbeans/api/java/source/GeneratorUtilities.java +++ b/java/java.source.base/src/org/netbeans/api/java/source/GeneratorUtilities.java @@ -141,7 +141,7 @@ */ public final class GeneratorUtilities { - private WorkingCopy copy; + private final WorkingCopy copy; private GeneratorUtilities(WorkingCopy copy) { this.copy = copy; @@ -1738,7 +1738,8 @@ private MethodTree createMethod(final ExecutableElement element, final TypeEleme if (isImplement && clazz.getKind().isInterface()) { mt = make.addModifiersModifier(mt, Modifier.DEFAULT); } - boolean isAbstract = element.getModifiers().contains(Modifier.ABSTRACT); + boolean replaceable = copy.getTrees().getTree(clazz).getKind() == Kind.RECORD && copy.getElementUtilities().isSynthetic(element); + boolean isAbstract = element.getModifiers().contains(Modifier.ABSTRACT) || replaceable; if (isImplement || clazz.getKind().isClass() && (!isAbstract || !clazz.getModifiers().contains(Modifier.ABSTRACT))) { try { bodyTemplate = "{" + readFromTemplate(isAbstract ? GENERATED_METHOD_BODY : OVERRIDDEN_METHOD_BODY, createBindings(clazz, element)) + "\n}"; //NOI18N @@ -1938,6 +1939,7 @@ private Map createBindings(TypeElement clazz, ExecutableElement if (clazz != null) { bindings.put(CLASS_NAME, clazz.getQualifiedName().toString()); bindings.put(SIMPLE_CLASS_NAME, clazz.getSimpleName().toString()); + bindings.put(CLASS_KIND, clazz.getKind().toString()); } if (element != null) { bindings.put(METHOD_NAME, element.getSimpleName().toString()); @@ -1959,7 +1961,11 @@ private Map createBindings(TypeElement clazz, ExecutableElement default: value = "null"; //NOI18N } - bindings.put(DEFAULT_RETURN_TYPE_VALUE, value); + if (clazz != null && clazz.getKind() == ElementKind.RECORD) { + bindings.put(DEFAULT_RETURN_TYPE_VALUE, element.getSimpleName()); + } else { + bindings.put(DEFAULT_RETURN_TYPE_VALUE, value); + } } if (clazz != null && element != null) { StringBuilder sb = new StringBuilder(); @@ -2205,6 +2211,7 @@ static boolean checkPackagesForStarImport(String pkgName, CodeStyle cs) { private static final String METHOD_NAME = "method_name"; //NOI18N private static final String CLASS_NAME = "class_name"; //NOI18N private static final String SIMPLE_CLASS_NAME = "simple_class_name"; //NOI18N + private static final String CLASS_KIND = "class_kind"; //NOI18N private static final String SCRIPT_ENGINE_ATTR = "javax.script.ScriptEngine"; //NOI18N private static final String STRING_OUTPUT_MODE_ATTR = "com.sun.script.freemarker.stringOut"; //NOI18N private static ScriptEngineManager manager; @@ -2214,20 +2221,14 @@ private static String readFromTemplate(String pathToTemplate, Map +<#if method_return_type?? && method_return_type != "void" && class_kind == "RECORD"> +return ${default_return_value}; // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody +<#else> throw new java.lang.UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody + diff --git a/java/java.source/src/org/netbeans/modules/java/source/resources/OverriddenMethodBody.template b/java/java.source/src/org/netbeans/modules/java/source/resources/OverriddenMethodBody.template index f6eacad1c6b0..18bebc5c02fb 100644 --- a/java/java.source/src/org/netbeans/modules/java/source/resources/OverriddenMethodBody.template +++ b/java/java.source/src/org/netbeans/modules/java/source/resources/OverriddenMethodBody.template @@ -1,5 +1,5 @@ <#-- -A built-in Freemarker template (see http://freemarker.sourceforge.net) used for +A built-in Freemarker template (see https://freemarker.apache.org) used for filling the body of overridden methods generated by the IDE. When editing the template, the following predefined variables, that will be then expanded into the corresponding values, could be used together with Java expressions and @@ -9,6 +9,7 @@ ${method_return_type} a return type of a created method ${default_return_value} a value returned by the method by default ${method_name} name of the created method ${class_name} qualified name of the enclosing class +${class_kind} kind of the enclosing class, such as CLASS, INTERFACE, RECORD or ENUM ${simple_class_name} simple name of the enclosing class --> <#if method_return_type?? && method_return_type != "void">