From f5f156b3f676f9dbffe63be4f48da129648846c5 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Fri, 9 Sep 2022 17:25:02 +0200 Subject: [PATCH] Add methods to access enum constants This commit also adds missing javadoc to the `ClassInfo.unsorted*` methods, describing their assumptions and limitations. --- .../main/java/org/jboss/jandex/ClassInfo.java | 88 +++++++++++ .../main/java/org/jboss/jandex/FieldInfo.java | 22 ++- .../java/org/jboss/jandex/FieldInternal.java | 4 + .../jboss/jandex/test/EnumConstantsTest.java | 139 ++++++++++++++++++ 4 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 core/src/test/java/org/jboss/jandex/test/EnumConstantsTest.java diff --git a/core/src/main/java/org/jboss/jandex/ClassInfo.java b/core/src/main/java/org/jboss/jandex/ClassInfo.java index 45041615..67e33c0b 100644 --- a/core/src/main/java/org/jboss/jandex/ClassInfo.java +++ b/core/src/main/java/org/jboss/jandex/ClassInfo.java @@ -686,6 +686,18 @@ public final List methods() { return new MethodInfoGenerator(this, methods, EMPTY_POSITIONS); } + /** + * Returns a list of all methods declared in this class, in the declaration order. + * See {@link #methods()} for more information. + *

+ * Note that for the result to actually be in declaration order, the index must be produced + * by at least Jandex 2.4. Previous Jandex versions do not store method positions. At most 256 + * methods may be present; if there's more, order is undefined. This also assumes that the bytecode + * order corresponds to declaration order, which is not guaranteed, but practically always holds. + * + * @return a list of methods + * @since 2.4 + */ public final List unsortedMethods() { return new MethodInfoGenerator(this, methods, methodPositions); } @@ -786,6 +798,18 @@ public final List fields() { return new FieldInfoGenerator(this, fields, EMPTY_POSITIONS); } + /** + * Returns a list of all fields declared in this class, in the declaration order. + * See {@link #fields()} for more information. + *

+ * Note that for the result to actually be in declaration order, the index must be produced + * by at least Jandex 2.4. Previous Jandex versions do not store field positions. At most 256 + * fields may be present; if there's more, order is undefined. This also assumes that the bytecode + * order corresponds to declaration order, which is not guaranteed, but practically always holds. + * + * @return a list of fields + * @since 2.4 + */ public final List unsortedFields() { return new FieldInfoGenerator(this, fields, fieldPositions); } @@ -823,6 +847,19 @@ public final List recordComponents() { return new RecordComponentInfoGenerator(this, recordComponents, EMPTY_POSITIONS); } + /** + * Returns a list of all record components declared in this class, in the declaration order. + * See {@link #recordComponents()} for more information. + *

+ * Note that for the result to actually be in declaration order, the index must be produced + * by at least Jandex 2.4. Previous Jandex versions do not store record component positions. + * At most 256 record components may be present; if there's more, order is undefined. This also + * assumes that the bytecode order corresponds to declaration order, which is not guaranteed, + * but practically always holds. + * + * @return a list of record components + * @since 2.4 + */ public final List unsortedRecordComponents() { return new RecordComponentInfoGenerator(this, recordComponents, recordComponentPositions); } @@ -835,6 +872,57 @@ final byte[] recordComponentPositionArray() { return recordComponentPositions; } + /** + * Returns a list of enum constants declared by this enum class, represented as {@link FieldInfo}. + * Enum constants are returned in declaration order. + *

+ * If this class is not an enum, returns an empty list. + *

+ * Note that for the result to actually be in declaration order, the index must be produced + * by at least Jandex 2.4. Previous Jandex versions do not store field positions. At most 256 + * fields may be present in the class; if there's more, order is undefined. This also assumes + * that the bytecode order corresponds to declaration order, which is not guaranteed, + * but practically always holds. + * + * @return immutable list of enum constants, never {@code null} + * @since 3.0.1 + */ + public final List enumConstants() { + if (!isEnum()) { + return Collections.emptyList(); + } + + List result = new ArrayList<>(); + byte[] positions = fieldPositions; + for (int i = 0; i < fields.length; i++) { + FieldInternal field = (positions.length > 0) ? fields[positions[i] & 0xFF] : fields[i]; + if (field.isEnumConstant()) { + result.add(new FieldInfo(this, field)); + } + } + return Collections.unmodifiableList(result); + } + + final int enumConstantOrdinal(FieldInternal enumConstant) { + if (!isEnum()) { + return -1; + } + + int counter = 0; + byte[] positions = fieldPositions; + for (int i = 0; i < fields.length; i++) { + FieldInternal field = (positions.length > 0) ? fields[positions[i] & 0xFF] : fields[i]; + if (field.isEnumConstant()) { + if (field.equals(enumConstant)) { + return counter; + } else { + counter++; + } + } + } + return -1; + } + /** * Returns a list of names for all interfaces this class implements. This list may be empty, but never null. *

diff --git a/core/src/main/java/org/jboss/jandex/FieldInfo.java b/core/src/main/java/org/jboss/jandex/FieldInfo.java index c1457885..340bcf40 100644 --- a/core/src/main/java/org/jboss/jandex/FieldInfo.java +++ b/core/src/main/java/org/jboss/jandex/FieldInfo.java @@ -343,7 +343,7 @@ public final List declaredAnnotations() { * @see java.lang.reflect.Field#isEnumConstant() */ public final boolean isEnumConstant() { - return (flags() & Modifiers.ENUM) != 0; + return internal.isEnumConstant(); } /** @@ -363,6 +363,26 @@ public final boolean isSynthetic() { return Modifiers.isSynthetic(internal.flags()); } + /** + * Returns an ordinal of this enum constant, that is, the zero-based position in the enum declaration. + * This is currently very inefficient (requires traversing fields of the declaring class), + * but may be improved in the future. + *

+ * If this field is not an enum constant, returns -1. + *

+ * Note that for the result to actually be the ordinal value, the index must be produced + * by at least Jandex 2.4. Previous Jandex versions do not store field positions. At most 256 + * fields may be present in the class; if there's more, outcome is undefined. This also assumes + * that the bytecode order corresponds to declaration order, which is not guaranteed, + * but practically always holds. + * + * @return ordinal of this enum constant, or -1 if this field is not an enum constant + * @since 3.0.1 + */ + public int enumConstantOrdinal() { + return clazz.enumConstantOrdinal(internal); + } + /** * Returns a string representation describing this field. It is similar although not necessarily equivalent * to a Java source code expression representing this field. diff --git a/core/src/main/java/org/jboss/jandex/FieldInternal.java b/core/src/main/java/org/jboss/jandex/FieldInternal.java index a59b564b..bcea4d34 100644 --- a/core/src/main/java/org/jboss/jandex/FieldInternal.java +++ b/core/src/main/java/org/jboss/jandex/FieldInternal.java @@ -134,6 +134,10 @@ final short flags() { return flags; } + final boolean isEnumConstant() { + return (flags & Modifiers.ENUM) != 0; + } + @Override public String toString() { return type.toString(true) + " " + name(); diff --git a/core/src/test/java/org/jboss/jandex/test/EnumConstantsTest.java b/core/src/test/java/org/jboss/jandex/test/EnumConstantsTest.java new file mode 100644 index 00000000..91b7ab98 --- /dev/null +++ b/core/src/test/java/org/jboss/jandex/test/EnumConstantsTest.java @@ -0,0 +1,139 @@ +package org.jboss.jandex.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.util.List; + +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.FieldInfo; +import org.jboss.jandex.Index; +import org.jboss.jandex.test.util.IndexingUtil; +import org.junit.jupiter.api.Test; + +public class EnumConstantsTest { + enum SimpleEnum { + FOO, + BAZ, + QUUX, + BAR, + } + + enum ComplexEnum { + ONE(1) { + @Override + int munge() { + return value + 1; + } + }, + TWO(2) { + @Override + int munge() { + return value * 2; + } + }, + THREE(3) { + @Override + int munge() { + return value / 3; + } + }, + FOUR(4) { + @Override + int munge() { + return value - 4; + } + }, + ; + + final int value; + + ComplexEnum(int value) { + this.value = value; + } + + abstract int munge(); + } + + enum EnumSingleton { + INSTANCE + } + + enum EmptyEnum { + } + + static class NotAnEnum { + static final NotAnEnum INSTANCE = new NotAnEnum(0); + + final int value; + + NotAnEnum(int value) { + this.value = value; + } + } + + @Test + public void test() throws IOException { + Index index = Index.of(SimpleEnum.class, ComplexEnum.class, EnumSingleton.class, EmptyEnum.class, NotAnEnum.class); + doTest(index); + doTest(IndexingUtil.roundtrip(index)); + } + + private void doTest(Index index) { + ClassInfo simpleEnum = index.getClassByName(SimpleEnum.class); + assertTrue(simpleEnum.isEnum()); + List simpleEnumConstants = simpleEnum.enumConstants(); + assertEquals(4, simpleEnumConstants.size()); + assertEquals("FOO", simpleEnumConstants.get(0).name()); + assertEquals(0, simpleEnumConstants.get(0).enumConstantOrdinal()); + assertEquals("BAZ", simpleEnumConstants.get(1).name()); + assertEquals(1, simpleEnumConstants.get(1).enumConstantOrdinal()); + assertEquals("QUUX", simpleEnumConstants.get(2).name()); + assertEquals(2, simpleEnumConstants.get(2).enumConstantOrdinal()); + assertEquals("BAR", simpleEnumConstants.get(3).name()); + assertEquals(3, simpleEnumConstants.get(3).enumConstantOrdinal()); + for (FieldInfo enumConstant : simpleEnumConstants) { + assertTrue(enumConstant.isEnumConstant()); + } + + ClassInfo complexEnum = index.getClassByName(ComplexEnum.class); + assertTrue(complexEnum.isEnum()); + List complexEnumConstants = complexEnum.enumConstants(); + assertEquals(4, complexEnumConstants.size()); + assertEquals("ONE", complexEnumConstants.get(0).name()); + assertEquals(0, complexEnumConstants.get(0).enumConstantOrdinal()); + assertEquals("TWO", complexEnumConstants.get(1).name()); + assertEquals(1, complexEnumConstants.get(1).enumConstantOrdinal()); + assertEquals("THREE", complexEnumConstants.get(2).name()); + assertEquals(2, complexEnumConstants.get(2).enumConstantOrdinal()); + assertEquals("FOUR", complexEnumConstants.get(3).name()); + assertEquals(3, complexEnumConstants.get(3).enumConstantOrdinal()); + for (FieldInfo enumConstant : complexEnumConstants) { + assertTrue(enumConstant.isEnumConstant()); + } + assertFalse(complexEnum.field("value").isEnumConstant()); + assertEquals(-1, complexEnum.field("value").enumConstantOrdinal()); + + ClassInfo enumSingleton = index.getClassByName(EnumSingleton.class); + assertTrue(enumSingleton.isEnum()); + List enumSingletonConstants = enumSingleton.enumConstants(); + assertEquals(1, enumSingletonConstants.size()); + assertEquals("INSTANCE", enumSingletonConstants.get(0).name()); + assertEquals(0, enumSingletonConstants.get(0).enumConstantOrdinal()); + for (FieldInfo enumConstant : enumSingletonConstants) { + assertTrue(enumConstant.isEnumConstant()); + } + + ClassInfo emptyEnum = index.getClassByName(EmptyEnum.class); + assertTrue(emptyEnum.isEnum()); + assertEquals(0, emptyEnum.enumConstants().size()); + + ClassInfo notAnEnum = index.getClassByName(NotAnEnum.class); + assertFalse(notAnEnum.isEnum()); + assertTrue(notAnEnum.enumConstants().isEmpty()); + assertEquals(-1, notAnEnum.field("INSTANCE").enumConstantOrdinal()); + assertEquals(-1, notAnEnum.field("value").enumConstantOrdinal()); + } +}