Skip to content

Commit 578ded4

Browse files
jddarcylahodaj
andcommitted
8312418: Add Elements.getEnumConstantBody
Co-authored-by: Jan Lahoda <jlahoda@openjdk.org> Reviewed-by: vromero
1 parent dccf670 commit 578ded4

File tree

4 files changed

+340
-1
lines changed

4 files changed

+340
-1
lines changed

src/java.compiler/share/classes/javax/lang/model/util/Elements.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,29 @@ default boolean isAutomaticModule(ModuleElement module) {
758758
return false;
759759
}
760760

761+
/**
762+
* {@return the class body of an {@code enum} constant if the
763+
* argument is an {@code enum} constant declared with an optional
764+
* class body, {@code null} otherwise}
765+
*
766+
* @implSpec
767+
* The default implementation of this method throws {@code
768+
* UnsupportedOperationException} if the argument is an {@code
769+
* enum} constant and throws an {@code IllegalArgumentException}
770+
* if it is not.
771+
*
772+
* @param enumConstant an enum constant
773+
* @throws IllegalArgumentException if the argument is not an {@code enum} constant
774+
* @jls 8.9.1 Enum Constants
775+
* @since 22
776+
*/
777+
default TypeElement getEnumConstantBody(VariableElement enumConstant) {
778+
switch(enumConstant.getKind()) {
779+
case ENUM_CONSTANT -> throw new UnsupportedOperationException();
780+
default -> throw new IllegalArgumentException("Argument not an enum constant");
781+
}
782+
}
783+
761784
/**
762785
* Returns the record component for the given accessor. Returns
763786
* {@code null} if the given method is not a record component

src/jdk.compiler/share/classes/com/sun/tools/javac/model/JavacElements.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
import static com.sun.tools.javac.code.Kinds.Kind.*;
7272
import static com.sun.tools.javac.code.Scope.LookupKind.NON_RECURSIVE;
7373
import static com.sun.tools.javac.code.TypeTag.CLASS;
74+
import com.sun.tools.javac.comp.Attr;
7475
import com.sun.tools.javac.comp.Modules;
7576
import com.sun.tools.javac.comp.Resolve;
7677
import com.sun.tools.javac.comp.Resolve.RecoveryLoadClass;
@@ -93,6 +94,7 @@ public class JavacElements implements Elements {
9394
private final Names names;
9495
private final Types types;
9596
private final Enter enter;
97+
private final Attr attr;
9698
private final Resolve resolve;
9799
private final JavacTaskImpl javacTaskImpl;
98100
private final Log log;
@@ -114,6 +116,7 @@ protected JavacElements(Context context) {
114116
names = Names.instance(context);
115117
types = Types.instance(context);
116118
enter = Enter.instance(context);
119+
attr = Attr.instance(context);
117120
resolve = Resolve.instance(context);
118121
JavacTask t = context.get(JavacTask.class);
119122
javacTaskImpl = t instanceof JavacTaskImpl taskImpl ? taskImpl : null;
@@ -725,6 +728,37 @@ public boolean isAutomaticModule(ModuleElement module) {
725728
return (msym.flags() & Flags.AUTOMATIC_MODULE) != 0;
726729
}
727730

731+
@Override @DefinedBy(Api.LANGUAGE_MODEL)
732+
public TypeElement getEnumConstantBody(VariableElement enumConstant) {
733+
if (enumConstant.getKind() == ElementKind.ENUM_CONSTANT) {
734+
JCTree enumBodyTree = getTreeAlt(enumConstant);
735+
JCTree enclosingEnumTree = getTreeAlt(enumConstant.getEnclosingElement());
736+
737+
if (enumBodyTree instanceof JCVariableDecl decl
738+
&& enclosingEnumTree instanceof JCClassDecl clazz
739+
&& decl.init instanceof JCNewClass nc
740+
&& nc.def != null) {
741+
if ((clazz.sym.flags_field & Flags.UNATTRIBUTED) != 0) {
742+
attr.attribClass(clazz.pos(), clazz.sym);
743+
}
744+
return nc.def.sym; // ClassSymbol for enum constant body
745+
} else {
746+
return null;
747+
}
748+
} else {
749+
throw new IllegalArgumentException("Argument not an enum constant");
750+
}
751+
}
752+
753+
private JCTree getTreeAlt(Element e) {
754+
Symbol sym = cast(Symbol.class, e);
755+
Env<AttrContext> enterEnv = getEnterEnv(sym);
756+
if (enterEnv == null)
757+
return null;
758+
JCTree tree = TreeInfo.declarationFor(sym, enterEnv.tree);
759+
return tree;
760+
}
761+
728762
@Override @DefinedBy(Api.LANGUAGE_MODEL)
729763
public boolean isCompactConstructor(ExecutableElement e) {
730764
return (((MethodSymbol)e).flags() & Flags.COMPACT_RECORD_CONSTRUCTOR) != 0;

src/jdk.compiler/share/classes/com/sun/tools/javac/processing/PrintingProcessor.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -184,6 +184,16 @@ public PrintingElementVisitor visitType(TypeElement e, Boolean p) {
184184
NestingKind nestingKind = e.getNestingKind();
185185

186186
if (NestingKind.ANONYMOUS == nestingKind) {
187+
// Print nothing for an anonymous class used for an
188+
// enum constant body.
189+
TypeMirror supertype = e.getSuperclass();
190+
if (supertype.getKind() != TypeKind.NONE) {
191+
TypeElement superClass = (TypeElement)(((DeclaredType)supertype).asElement());
192+
if (superClass.getKind() == ENUM) {
193+
return this;
194+
}
195+
}
196+
187197
// Print out an anonymous class in the style of a
188198
// class instance creation expression rather than a
189199
// class declaration.
@@ -693,6 +703,12 @@ private void printInterfaces(TypeElement e) {
693703
}
694704

695705
private void printPermittedSubclasses(TypeElement e) {
706+
if (e.getKind() == ENUM) {
707+
// any permitted classes on an enum are anonymous
708+
// classes for enum bodies, elide.
709+
return;
710+
}
711+
696712
List<? extends TypeMirror> subtypes = e.getPermittedSubclasses();
697713
if (!subtypes.isEmpty()) { // could remove this check with more complicated joining call
698714
writer.print(" permits ");
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
/*
2+
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/*
25+
* @test
26+
* @bug 8312418
27+
* @summary Test Elements.getEnumConstantBody
28+
* @library /tools/javac/lib
29+
* @build JavacTestingAbstractProcessor TestGetEnumConstantBody
30+
* @compile -processor TestGetEnumConstantBody -XDshould.stop-at=FLOW TestGetEnumConstantBody.java
31+
*/
32+
33+
import java.io.IOException;
34+
import java.io.Writer;
35+
import java.util.*;
36+
import java.util.function.*;
37+
import javax.annotation.processing.*;
38+
import javax.lang.model.element.*;
39+
import javax.lang.model.util.*;
40+
import javax.lang.model.type.*;
41+
42+
/**
43+
* Test basic workings of Elements.getEnumConstantBody
44+
*/
45+
public class TestGetEnumConstantBody extends JavacTestingAbstractProcessor {
46+
private Elements vacuousElements = new VacuousElements();
47+
private Set<Element> allElements = new HashSet<>();
48+
private int round;
49+
50+
public boolean process(Set<? extends TypeElement> annotations,
51+
RoundEnvironment roundEnv) {
52+
53+
allElements.addAll(roundEnv.getRootElements());
54+
55+
// In the innermost loop, examine the fields defined by the the nested classes
56+
for (TypeElement typeRoot : ElementFilter.typesIn(allElements) ) {
57+
if (typeRoot.getQualifiedName().contentEquals("Gen")) {
58+
continue;
59+
}
60+
61+
boolean elementSeen = false;
62+
63+
for (TypeElement typeElt : ElementFilter.typesIn(typeRoot.getEnclosedElements()) ) {
64+
System.out.println("Testing type " + typeElt);
65+
66+
for (VariableElement field : ElementFilter.fieldsIn(typeElt.getEnclosedElements()) ) {
67+
elementSeen = true;
68+
System.out.println(field);
69+
switch (field.getKind()) {
70+
case FIELD -> expectIAE(field);
71+
case ENUM_CONSTANT -> testEnumConstant(field, typeElt);
72+
default -> throw new RuntimeException("Unexpected field kind seen");
73+
}
74+
}
75+
}
76+
77+
if (!elementSeen) {
78+
throw new RuntimeException("No elements seen.");
79+
}
80+
}
81+
switch (round++) {
82+
case 0:
83+
try (Writer w = processingEnv.getFiler().createSourceFile("Cleaned").openWriter()) {
84+
w.write("""
85+
class Enclosing {
86+
enum Cleaned {
87+
@TestGetEnumConstantBody.ExpectedBinaryName("Enclosing$Cleaned$2")
88+
A(new Object() {}) {
89+
void test(Gen g) {
90+
g.run();
91+
}
92+
},
93+
B,
94+
@TestGetEnumConstantBody.ExpectedBinaryName("Enclosing$Cleaned$4")
95+
C(new Object() {}) {
96+
};
97+
98+
private Cleaned() {}
99+
100+
private Cleaned(Object o) {}
101+
}
102+
}
103+
""");
104+
} catch (IOException ex) {
105+
throw new RuntimeException(ex);
106+
}
107+
break;
108+
case 1:
109+
try (Writer w = processingEnv.getFiler().createSourceFile("Gen").openWriter()) {
110+
w.write("""
111+
public class Gen {
112+
public void run() {}
113+
}
114+
""");
115+
} catch (IOException ex) {
116+
throw new RuntimeException(ex);
117+
}
118+
break;
119+
}
120+
return true;
121+
}
122+
123+
private String computeExpectedBinaryName(VariableElement e) {
124+
ExpectedBinaryName ebn = e.getAnnotation(ExpectedBinaryName.class);
125+
return (ebn == null) ? null : ebn.value();
126+
}
127+
128+
private void expectIAE(VariableElement variable) {
129+
expectException0(() -> elements.getEnumConstantBody(variable),
130+
"Expected exception not thrown");
131+
132+
expectException0(() -> vacuousElements.getEnumConstantBody(variable),
133+
"Expected vacuous exception not thrown");
134+
}
135+
136+
private void expectException0(Supplier<TypeElement> supplier, String message) {
137+
try {
138+
var typeElement = supplier.get();
139+
messager.printError(message, typeElement);
140+
} catch (IllegalArgumentException iae) {
141+
; // Expected
142+
}
143+
}
144+
145+
void expectUOE(VariableElement field) {
146+
try {
147+
var result = vacuousElements.getEnumConstantBody(field);
148+
messager.printError("Unexpected non-exceptional result returned", field);
149+
150+
} catch(UnsupportedOperationException uoe) {
151+
; // Expected
152+
}
153+
}
154+
155+
private void testEnumConstant(VariableElement field,
156+
TypeElement enclosingClass) {
157+
String expectedBinaryName = computeExpectedBinaryName(field);
158+
boolean expectEnumConstantBody = expectedBinaryName != null;
159+
160+
System.out.println("\tTesting enum constant " + field + " expected " + expectEnumConstantBody);
161+
expectUOE(field);
162+
163+
TypeElement enumConstantBody = elements.getEnumConstantBody(field);
164+
165+
if (Objects.nonNull(enumConstantBody) != expectEnumConstantBody) {
166+
messager.printError("Unexpected body value", field);
167+
}
168+
169+
if (enumConstantBody != null) {
170+
testEnumConstantBody(enumConstantBody, expectedBinaryName, enclosingClass);
171+
}
172+
173+
System.out.println("\t constant body " + enumConstantBody);
174+
}
175+
176+
/*
177+
* From JLS 8.9.1:
178+
*
179+
* "The optional class body of an enum constant implicitly
180+
* declares an anonymous class (15.9.5) that (i) is a direct
181+
* subclass of the immediately enclosing enum class (8.1.4), and
182+
* (ii) is final (8.1.1.2). The class body is governed by the
183+
* usual rules of anonymous classes; in particular it cannot
184+
* contain any constructors. Instance methods declared in these
185+
* class bodies may be invoked outside the enclosing enum class
186+
* only if they override accessible methods in the enclosing enum
187+
* class (8.4.8)."
188+
*/
189+
private void testEnumConstantBody(TypeElement enumConstBody, String expectedBinaryName, TypeElement enumClass) {
190+
if (enumConstBody.getNestingKind() != NestingKind.ANONYMOUS) {
191+
messager.printError("Class body not an anonymous class", enumConstBody);
192+
}
193+
194+
// Get the TypeElement for the direct superclass.
195+
TypeElement superClass =
196+
(TypeElement)(((DeclaredType)enumConstBody.getSuperclass()).asElement());
197+
198+
if (!superClass.equals(enumClass)) {
199+
messager.printError("Class body is not a direct subclass of the enum", enumConstBody);
200+
}
201+
202+
if (!enumConstBody.getModifiers().contains(Modifier.FINAL)) {
203+
messager.printError("Modifier final missing on class body", enumConstBody);
204+
}
205+
206+
if (!elements.getBinaryName(enumConstBody).contentEquals(expectedBinaryName)) {
207+
messager.printError("Unexpected binary name, expected: " + expectedBinaryName +
208+
", but was: " + elements.getBinaryName(enumConstBody), enumConstBody);
209+
}
210+
211+
return;
212+
}
213+
214+
215+
@interface ExpectedBinaryName {
216+
String value();
217+
}
218+
219+
// Nested classes hosting a variety of different kinds of fields.
220+
221+
private static enum Body {
222+
@ExpectedBinaryName("TestGetEnumConstantBody$Body$1")
223+
GOLGI(true) {
224+
public boolean isOrganelle() {return true;}
225+
},
226+
227+
@ExpectedBinaryName("TestGetEnumConstantBody$Body$2")
228+
HEAVENLY(true) {
229+
public boolean isCelestial() {return true;}
230+
};
231+
232+
private Body(boolean predicate) {
233+
this.predicate = predicate;
234+
}
235+
236+
private boolean predicate;
237+
238+
public static int field = 42;
239+
240+
public void method() {return;}
241+
}
242+
243+
private static enum MetaSyntaxVar {
244+
FOO("foo"),
245+
BAR("bar");
246+
247+
private String lower;
248+
private MetaSyntaxVar(String lower) {
249+
this.lower = lower;
250+
}
251+
252+
int BAZ = 0;
253+
float QUUX = 0.1f;
254+
}
255+
256+
// Instance and static fields.
257+
public static class FieldHolder {
258+
public static final int f1 = 1;
259+
public static final String s = "s";
260+
261+
private Object data;
262+
public FieldHolder(Object data) {
263+
this.data = data;
264+
}
265+
}
266+
}

0 commit comments

Comments
 (0)