8312418: Add Elements.getEnumConstantBody
Co-authored-by: Jan Lahoda <jlahoda@openjdk.org> Reviewed-by: vromero
This commit is contained in:
parent
dccf670492
commit
578ded4645
src
java.compiler/share/classes/javax/lang/model/util
jdk.compiler/share/classes/com/sun/tools/javac
test/langtools/tools/javac/processing/model/util/elements
@ -758,6 +758,29 @@ public interface Elements {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the class body of an {@code enum} constant if the
|
||||
* argument is an {@code enum} constant declared with an optional
|
||||
* class body, {@code null} otherwise}
|
||||
*
|
||||
* @implSpec
|
||||
* The default implementation of this method throws {@code
|
||||
* UnsupportedOperationException} if the argument is an {@code
|
||||
* enum} constant and throws an {@code IllegalArgumentException}
|
||||
* if it is not.
|
||||
*
|
||||
* @param enumConstant an enum constant
|
||||
* @throws IllegalArgumentException if the argument is not an {@code enum} constant
|
||||
* @jls 8.9.1 Enum Constants
|
||||
* @since 22
|
||||
*/
|
||||
default TypeElement getEnumConstantBody(VariableElement enumConstant) {
|
||||
switch(enumConstant.getKind()) {
|
||||
case ENUM_CONSTANT -> throw new UnsupportedOperationException();
|
||||
default -> throw new IllegalArgumentException("Argument not an enum constant");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the record component for the given accessor. Returns
|
||||
* {@code null} if the given method is not a record component
|
||||
|
@ -71,6 +71,7 @@ import com.sun.tools.javac.util.Name;
|
||||
import static com.sun.tools.javac.code.Kinds.Kind.*;
|
||||
import static com.sun.tools.javac.code.Scope.LookupKind.NON_RECURSIVE;
|
||||
import static com.sun.tools.javac.code.TypeTag.CLASS;
|
||||
import com.sun.tools.javac.comp.Attr;
|
||||
import com.sun.tools.javac.comp.Modules;
|
||||
import com.sun.tools.javac.comp.Resolve;
|
||||
import com.sun.tools.javac.comp.Resolve.RecoveryLoadClass;
|
||||
@ -93,6 +94,7 @@ public class JavacElements implements Elements {
|
||||
private final Names names;
|
||||
private final Types types;
|
||||
private final Enter enter;
|
||||
private final Attr attr;
|
||||
private final Resolve resolve;
|
||||
private final JavacTaskImpl javacTaskImpl;
|
||||
private final Log log;
|
||||
@ -114,6 +116,7 @@ public class JavacElements implements Elements {
|
||||
names = Names.instance(context);
|
||||
types = Types.instance(context);
|
||||
enter = Enter.instance(context);
|
||||
attr = Attr.instance(context);
|
||||
resolve = Resolve.instance(context);
|
||||
JavacTask t = context.get(JavacTask.class);
|
||||
javacTaskImpl = t instanceof JavacTaskImpl taskImpl ? taskImpl : null;
|
||||
@ -725,6 +728,37 @@ public class JavacElements implements Elements {
|
||||
return (msym.flags() & Flags.AUTOMATIC_MODULE) != 0;
|
||||
}
|
||||
|
||||
@Override @DefinedBy(Api.LANGUAGE_MODEL)
|
||||
public TypeElement getEnumConstantBody(VariableElement enumConstant) {
|
||||
if (enumConstant.getKind() == ElementKind.ENUM_CONSTANT) {
|
||||
JCTree enumBodyTree = getTreeAlt(enumConstant);
|
||||
JCTree enclosingEnumTree = getTreeAlt(enumConstant.getEnclosingElement());
|
||||
|
||||
if (enumBodyTree instanceof JCVariableDecl decl
|
||||
&& enclosingEnumTree instanceof JCClassDecl clazz
|
||||
&& decl.init instanceof JCNewClass nc
|
||||
&& nc.def != null) {
|
||||
if ((clazz.sym.flags_field & Flags.UNATTRIBUTED) != 0) {
|
||||
attr.attribClass(clazz.pos(), clazz.sym);
|
||||
}
|
||||
return nc.def.sym; // ClassSymbol for enum constant body
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Argument not an enum constant");
|
||||
}
|
||||
}
|
||||
|
||||
private JCTree getTreeAlt(Element e) {
|
||||
Symbol sym = cast(Symbol.class, e);
|
||||
Env<AttrContext> enterEnv = getEnterEnv(sym);
|
||||
if (enterEnv == null)
|
||||
return null;
|
||||
JCTree tree = TreeInfo.declarationFor(sym, enterEnv.tree);
|
||||
return tree;
|
||||
}
|
||||
|
||||
@Override @DefinedBy(Api.LANGUAGE_MODEL)
|
||||
public boolean isCompactConstructor(ExecutableElement e) {
|
||||
return (((MethodSymbol)e).flags() & Flags.COMPACT_RECORD_CONSTRUCTOR) != 0;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -184,6 +184,16 @@ public class PrintingProcessor extends AbstractProcessor {
|
||||
NestingKind nestingKind = e.getNestingKind();
|
||||
|
||||
if (NestingKind.ANONYMOUS == nestingKind) {
|
||||
// Print nothing for an anonymous class used for an
|
||||
// enum constant body.
|
||||
TypeMirror supertype = e.getSuperclass();
|
||||
if (supertype.getKind() != TypeKind.NONE) {
|
||||
TypeElement superClass = (TypeElement)(((DeclaredType)supertype).asElement());
|
||||
if (superClass.getKind() == ENUM) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
// Print out an anonymous class in the style of a
|
||||
// class instance creation expression rather than a
|
||||
// class declaration.
|
||||
@ -693,6 +703,12 @@ public class PrintingProcessor extends AbstractProcessor {
|
||||
}
|
||||
|
||||
private void printPermittedSubclasses(TypeElement e) {
|
||||
if (e.getKind() == ENUM) {
|
||||
// any permitted classes on an enum are anonymous
|
||||
// classes for enum bodies, elide.
|
||||
return;
|
||||
}
|
||||
|
||||
List<? extends TypeMirror> subtypes = e.getPermittedSubclasses();
|
||||
if (!subtypes.isEmpty()) { // could remove this check with more complicated joining call
|
||||
writer.print(" permits ");
|
||||
|
266
test/langtools/tools/javac/processing/model/util/elements/TestGetEnumConstantBody.java
Normal file
266
test/langtools/tools/javac/processing/model/util/elements/TestGetEnumConstantBody.java
Normal file
@ -0,0 +1,266 @@
|
||||
/*
|
||||
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8312418
|
||||
* @summary Test Elements.getEnumConstantBody
|
||||
* @library /tools/javac/lib
|
||||
* @build JavacTestingAbstractProcessor TestGetEnumConstantBody
|
||||
* @compile -processor TestGetEnumConstantBody -XDshould.stop-at=FLOW TestGetEnumConstantBody.java
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.*;
|
||||
import java.util.function.*;
|
||||
import javax.annotation.processing.*;
|
||||
import javax.lang.model.element.*;
|
||||
import javax.lang.model.util.*;
|
||||
import javax.lang.model.type.*;
|
||||
|
||||
/**
|
||||
* Test basic workings of Elements.getEnumConstantBody
|
||||
*/
|
||||
public class TestGetEnumConstantBody extends JavacTestingAbstractProcessor {
|
||||
private Elements vacuousElements = new VacuousElements();
|
||||
private Set<Element> allElements = new HashSet<>();
|
||||
private int round;
|
||||
|
||||
public boolean process(Set<? extends TypeElement> annotations,
|
||||
RoundEnvironment roundEnv) {
|
||||
|
||||
allElements.addAll(roundEnv.getRootElements());
|
||||
|
||||
// In the innermost loop, examine the fields defined by the the nested classes
|
||||
for (TypeElement typeRoot : ElementFilter.typesIn(allElements) ) {
|
||||
if (typeRoot.getQualifiedName().contentEquals("Gen")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean elementSeen = false;
|
||||
|
||||
for (TypeElement typeElt : ElementFilter.typesIn(typeRoot.getEnclosedElements()) ) {
|
||||
System.out.println("Testing type " + typeElt);
|
||||
|
||||
for (VariableElement field : ElementFilter.fieldsIn(typeElt.getEnclosedElements()) ) {
|
||||
elementSeen = true;
|
||||
System.out.println(field);
|
||||
switch (field.getKind()) {
|
||||
case FIELD -> expectIAE(field);
|
||||
case ENUM_CONSTANT -> testEnumConstant(field, typeElt);
|
||||
default -> throw new RuntimeException("Unexpected field kind seen");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!elementSeen) {
|
||||
throw new RuntimeException("No elements seen.");
|
||||
}
|
||||
}
|
||||
switch (round++) {
|
||||
case 0:
|
||||
try (Writer w = processingEnv.getFiler().createSourceFile("Cleaned").openWriter()) {
|
||||
w.write("""
|
||||
class Enclosing {
|
||||
enum Cleaned {
|
||||
@TestGetEnumConstantBody.ExpectedBinaryName("Enclosing$Cleaned$2")
|
||||
A(new Object() {}) {
|
||||
void test(Gen g) {
|
||||
g.run();
|
||||
}
|
||||
},
|
||||
B,
|
||||
@TestGetEnumConstantBody.ExpectedBinaryName("Enclosing$Cleaned$4")
|
||||
C(new Object() {}) {
|
||||
};
|
||||
|
||||
private Cleaned() {}
|
||||
|
||||
private Cleaned(Object o) {}
|
||||
}
|
||||
}
|
||||
""");
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
try (Writer w = processingEnv.getFiler().createSourceFile("Gen").openWriter()) {
|
||||
w.write("""
|
||||
public class Gen {
|
||||
public void run() {}
|
||||
}
|
||||
""");
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private String computeExpectedBinaryName(VariableElement e) {
|
||||
ExpectedBinaryName ebn = e.getAnnotation(ExpectedBinaryName.class);
|
||||
return (ebn == null) ? null : ebn.value();
|
||||
}
|
||||
|
||||
private void expectIAE(VariableElement variable) {
|
||||
expectException0(() -> elements.getEnumConstantBody(variable),
|
||||
"Expected exception not thrown");
|
||||
|
||||
expectException0(() -> vacuousElements.getEnumConstantBody(variable),
|
||||
"Expected vacuous exception not thrown");
|
||||
}
|
||||
|
||||
private void expectException0(Supplier<TypeElement> supplier, String message) {
|
||||
try {
|
||||
var typeElement = supplier.get();
|
||||
messager.printError(message, typeElement);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
; // Expected
|
||||
}
|
||||
}
|
||||
|
||||
void expectUOE(VariableElement field) {
|
||||
try {
|
||||
var result = vacuousElements.getEnumConstantBody(field);
|
||||
messager.printError("Unexpected non-exceptional result returned", field);
|
||||
|
||||
} catch(UnsupportedOperationException uoe) {
|
||||
; // Expected
|
||||
}
|
||||
}
|
||||
|
||||
private void testEnumConstant(VariableElement field,
|
||||
TypeElement enclosingClass) {
|
||||
String expectedBinaryName = computeExpectedBinaryName(field);
|
||||
boolean expectEnumConstantBody = expectedBinaryName != null;
|
||||
|
||||
System.out.println("\tTesting enum constant " + field + " expected " + expectEnumConstantBody);
|
||||
expectUOE(field);
|
||||
|
||||
TypeElement enumConstantBody = elements.getEnumConstantBody(field);
|
||||
|
||||
if (Objects.nonNull(enumConstantBody) != expectEnumConstantBody) {
|
||||
messager.printError("Unexpected body value", field);
|
||||
}
|
||||
|
||||
if (enumConstantBody != null) {
|
||||
testEnumConstantBody(enumConstantBody, expectedBinaryName, enclosingClass);
|
||||
}
|
||||
|
||||
System.out.println("\t constant body " + enumConstantBody);
|
||||
}
|
||||
|
||||
/*
|
||||
* From JLS 8.9.1:
|
||||
*
|
||||
* "The optional class body of an enum constant implicitly
|
||||
* declares an anonymous class (15.9.5) that (i) is a direct
|
||||
* subclass of the immediately enclosing enum class (8.1.4), and
|
||||
* (ii) is final (8.1.1.2). The class body is governed by the
|
||||
* usual rules of anonymous classes; in particular it cannot
|
||||
* contain any constructors. Instance methods declared in these
|
||||
* class bodies may be invoked outside the enclosing enum class
|
||||
* only if they override accessible methods in the enclosing enum
|
||||
* class (8.4.8)."
|
||||
*/
|
||||
private void testEnumConstantBody(TypeElement enumConstBody, String expectedBinaryName, TypeElement enumClass) {
|
||||
if (enumConstBody.getNestingKind() != NestingKind.ANONYMOUS) {
|
||||
messager.printError("Class body not an anonymous class", enumConstBody);
|
||||
}
|
||||
|
||||
// Get the TypeElement for the direct superclass.
|
||||
TypeElement superClass =
|
||||
(TypeElement)(((DeclaredType)enumConstBody.getSuperclass()).asElement());
|
||||
|
||||
if (!superClass.equals(enumClass)) {
|
||||
messager.printError("Class body is not a direct subclass of the enum", enumConstBody);
|
||||
}
|
||||
|
||||
if (!enumConstBody.getModifiers().contains(Modifier.FINAL)) {
|
||||
messager.printError("Modifier final missing on class body", enumConstBody);
|
||||
}
|
||||
|
||||
if (!elements.getBinaryName(enumConstBody).contentEquals(expectedBinaryName)) {
|
||||
messager.printError("Unexpected binary name, expected: " + expectedBinaryName +
|
||||
", but was: " + elements.getBinaryName(enumConstBody), enumConstBody);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@interface ExpectedBinaryName {
|
||||
String value();
|
||||
}
|
||||
|
||||
// Nested classes hosting a variety of different kinds of fields.
|
||||
|
||||
private static enum Body {
|
||||
@ExpectedBinaryName("TestGetEnumConstantBody$Body$1")
|
||||
GOLGI(true) {
|
||||
public boolean isOrganelle() {return true;}
|
||||
},
|
||||
|
||||
@ExpectedBinaryName("TestGetEnumConstantBody$Body$2")
|
||||
HEAVENLY(true) {
|
||||
public boolean isCelestial() {return true;}
|
||||
};
|
||||
|
||||
private Body(boolean predicate) {
|
||||
this.predicate = predicate;
|
||||
}
|
||||
|
||||
private boolean predicate;
|
||||
|
||||
public static int field = 42;
|
||||
|
||||
public void method() {return;}
|
||||
}
|
||||
|
||||
private static enum MetaSyntaxVar {
|
||||
FOO("foo"),
|
||||
BAR("bar");
|
||||
|
||||
private String lower;
|
||||
private MetaSyntaxVar(String lower) {
|
||||
this.lower = lower;
|
||||
}
|
||||
|
||||
int BAZ = 0;
|
||||
float QUUX = 0.1f;
|
||||
}
|
||||
|
||||
// Instance and static fields.
|
||||
public static class FieldHolder {
|
||||
public static final int f1 = 1;
|
||||
public static final String s = "s";
|
||||
|
||||
private Object data;
|
||||
public FieldHolder(Object data) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user