diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a9721518..c881c0be 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,12 +23,12 @@ jobs: metadata-file-path: '.github/project.yml' - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: token: ${{secrets.RELEASE_TOKEN}} - name: Set up JDK 8 - uses: actions/setup-java@v2 + uses: actions/setup-java@v3 with: distribution: temurin java-version: 8 diff --git a/core/src/main/java/org/jboss/jandex/JandexReflection.java b/core/src/main/java/org/jboss/jandex/JandexReflection.java new file mode 100644 index 00000000..f3b53c3e --- /dev/null +++ b/core/src/main/java/org/jboss/jandex/JandexReflection.java @@ -0,0 +1,114 @@ +package org.jboss.jandex; + +import java.lang.reflect.Array; + +/** + * Utilities that allow moving from the Jandex world to the runtime world using reflection. + * To maintain stratification, these methods are intentionally not present + * on the respective Jandex classes. + */ +public class JandexReflection { + /** + * Loads a class corresponding to the raw type of given {@link Type} from the thread context classloader. + * If there is no TCCL, the classloader that loaded {@code JandexReflection} is used. + * Returns {@code null} when {@code type} is {@code null}. + *

+ * Specifically: + *

+ * + * @param type a Jandex {@link Type} + * @return the corresponding {@link Class} + */ + public static Class loadRawType(Type type) { + if (type == null) { + return null; + } + + switch (type.kind()) { + case VOID: + return void.class; + case PRIMITIVE: + switch (type.asPrimitiveType().primitive()) { + case BOOLEAN: + return boolean.class; + case BYTE: + return byte.class; + case SHORT: + return short.class; + case INT: + return int.class; + case LONG: + return long.class; + case FLOAT: + return float.class; + case DOUBLE: + return double.class; + case CHAR: + return char.class; + default: + throw new IllegalArgumentException("Unknown primitive type: " + type); + } + case CLASS: + return load(type.asClassType().name()); + case PARAMETERIZED_TYPE: + return load(type.asParameterizedType().name()); + case ARRAY: + // this handles nested array types correctly, see Array.newInstance + // it is unfortunate that we have to instantiate the array type, + // but I don't know a better way + Class component = loadRawType(type.asArrayType().component()); + int dimensions = type.asArrayType().dimensions(); + return Array.newInstance(component, new int[dimensions]).getClass(); + case WILDCARD_TYPE: + return loadRawType(type.asWildcardType().extendsBound()); + case TYPE_VARIABLE: + return load(type.asTypeVariable().name()); + case UNRESOLVED_TYPE_VARIABLE: + return Object.class; // can't do better here + default: + throw new IllegalArgumentException("Unknown type: " + type); + } + } + + /** + * Loads a class corresponding to given {@link ClassInfo} from the thread context classloader. + * If there is no TCCL, the classloader that loaded {@code JandexReflection} is used. + * Returns {@code null} when {@code clazz} is {@code null}. + * + * @param clazz a Jandex {@link ClassInfo} + * @return the corresponding {@link Class} + */ + public static Class loadClass(ClassInfo clazz) { + if (clazz == null) { + return null; + } + + return load(clazz.name()); + } + + private static Class load(DotName name) { + try { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + if (cl == null) { + cl = JandexReflection.class.getClassLoader(); + } + return cl.loadClass(name.toString()); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } +} diff --git a/core/src/test/java/org/jboss/jandex/test/JandexReflectionTest.java b/core/src/test/java/org/jboss/jandex/test/JandexReflectionTest.java new file mode 100644 index 00000000..3ed502e3 --- /dev/null +++ b/core/src/test/java/org/jboss/jandex/test/JandexReflectionTest.java @@ -0,0 +1,108 @@ +package org.jboss.jandex.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.io.Serializable; +import java.util.List; + +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.Index; +import org.jboss.jandex.JandexReflection; +import org.jboss.jandex.Type; +import org.jboss.jandex.TypeVariable; +import org.jboss.jandex.UnresolvedTypeVariable; +import org.junit.jupiter.api.Test; + +public class JandexReflectionTest { + interface TestMethods { + void nothing(); + + int primitive(); + + String clazz(); + + List parameterized(); + + String[][] array(); + + // annotations are present to make sure the `ArrayType` has multiple levels of nesting + @MyAnnotation("1") + String[] @MyAnnotation("2") [][] @MyAnnotation("3") [] annotatedArray(); + + X typeParameter(); + + Y typeParameterWithSingleBound(); + + > Y typeParameterWithMultipleBounds(); + + > Y typeParameterWithSingleParameterizedBound(); + + & Serializable> Y typeParameterWithMultipleBoundsFirstParameterized(); + + > Y typeParameterWithMultipleBoundsSecondParameterized(); + + List unboundedWildcard(); + + List wildcardWithUpperBound(); + + List wildcardWithLowerBound(); + + List wildcardWithUnboundedTypeParameterAsUpperBound(); + + List wildcardWithBoundedTypeParameterAsUpperBound(); + + List wildcardWithBoundedTypeParameterAsLowerBound(); + } + + static class NestedClass { + class InnerClass { + } + } + + static class SimpleClass { + } + + @Test + public void test() throws IOException { + // intentionally _not_ indexing `NestedClass`, so that the type parameter bound of `InnerClass` is unresolved + Index index = Index.of(TestMethods.class, NestedClass.InnerClass.class, SimpleClass.class); + + ClassInfo clazz = index.getClassByName(TestMethods.class); + assertEquals(void.class, type(clazz, "nothing")); + assertEquals(int.class, type(clazz, "primitive")); + assertEquals(String.class, type(clazz, "clazz")); + assertEquals(List.class, type(clazz, "parameterized")); + assertEquals(String[][].class, type(clazz, "array")); + assertEquals(String[][][][].class, type(clazz, "annotatedArray")); + assertEquals(Object.class, type(clazz, "typeParameter")); + assertEquals(Number.class, type(clazz, "typeParameterWithSingleBound")); + assertEquals(Number.class, type(clazz, "typeParameterWithMultipleBounds")); + assertEquals(Comparable.class, type(clazz, "typeParameterWithSingleParameterizedBound")); + assertEquals(Comparable.class, type(clazz, "typeParameterWithMultipleBoundsFirstParameterized")); + assertEquals(Serializable.class, type(clazz, "typeParameterWithMultipleBoundsSecondParameterized")); + assertEquals(Object.class, firstTypeArgument(clazz, "unboundedWildcard")); + assertEquals(Number.class, firstTypeArgument(clazz, "wildcardWithUpperBound")); + assertEquals(Object.class, firstTypeArgument(clazz, "wildcardWithLowerBound")); + assertEquals(Object.class, firstTypeArgument(clazz, "wildcardWithUnboundedTypeParameterAsUpperBound")); + assertEquals(Number.class, firstTypeArgument(clazz, "wildcardWithBoundedTypeParameterAsUpperBound")); + assertEquals(Object.class, firstTypeArgument(clazz, "wildcardWithBoundedTypeParameterAsLowerBound")); + + TypeVariable u = index.getClassByName(NestedClass.InnerClass.class).typeParameters().get(0); + assertEquals(Type.Kind.UNRESOLVED_TYPE_VARIABLE, u.bounds().get(0).kind()); + UnresolvedTypeVariable t = u.bounds().get(0).asUnresolvedTypeVariable(); + assertEquals(Object.class, JandexReflection.loadRawType(t)); + + assertEquals(SimpleClass.class, JandexReflection.loadClass(index.getClassByName(SimpleClass.class))); + } + + private Class type(ClassInfo clazz, String method) { + Type jandexType = clazz.firstMethod(method).returnType(); + return JandexReflection.loadRawType(jandexType); + } + + private Class firstTypeArgument(ClassInfo clazz, String method) { + Type jandexType = clazz.firstMethod(method).returnType().asParameterizedType().arguments().get(0); + return JandexReflection.loadRawType(jandexType); + } +}