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:
+ *
+ * - for the {@link VoidType void} pseudo-type, returns {@code void.class};
+ * - for {@linkplain PrimitiveType primitive} types, returns the corresponding class object (e.g. {@code int.class});
+ * - for {@linkplain ClassType class} types, returns the corresponding class object (e.g. {@code String.class});
+ * - for {@linkplain ArrayType array} types, returns the corresponding class object (e.g. {@code String[][].class});
+ * - for {@linkplain ParameterizedType parameterized} types, returns the class object of the generic class
+ * (e.g. {@code List.class} for {@code List});
+ * - for {@linkplain WildcardType wildcard} types, returns the class object of the upper bound type
+ * (e.g. {@code Number.class} for {@code ? extends Number}), or {@code Object.class} if the wildcard type
+ * has no upper bound (e.g. {@code ? super Integer});
+ * - for {@linkplain TypeVariable type variables}, returns the class object of the first bound
+ * (e.g. {@code Number.class} for {@code T extends Number & Comparable}), or {@code Object.class}
+ * if the type variable has no bounds (e.g. just {@code T});
+ * - for {@linkplain UnresolvedTypeVariable unresolved type variables}, returns {@code Object.class}.
+ *
+ *
+ * @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 extends Number> wildcardWithUpperBound();
+
+ List super String> wildcardWithLowerBound();
+
+ List extends X> wildcardWithUnboundedTypeParameterAsUpperBound();
+
+ List extends Y> wildcardWithBoundedTypeParameterAsUpperBound();
+
+ List super Y> 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);
+ }
+}