Skip to content

Commit

Permalink
Merge pull request #200 from Ladicek/jandex-reflection
Browse files Browse the repository at this point in the history
add JandexReflection for runtime loading of classes corresponding to Jandex objects
  • Loading branch information
Ladicek authored May 16, 2022
2 parents 18212ea + c4ac60b commit 95177d5
Show file tree
Hide file tree
Showing 3 changed files with 224 additions and 2 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
114 changes: 114 additions & 0 deletions core/src/main/java/org/jboss/jandex/JandexReflection.java
Original file line number Diff line number Diff line change
@@ -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 <em>not</em> 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}.
* <p>
* Specifically:
* <ul>
* <li>for the {@link VoidType void} pseudo-type, returns {@code void.class};</li>
* <li>for {@linkplain PrimitiveType primitive} types, returns the corresponding class object (e.g. {@code int.class});</li>
* <li>for {@linkplain ClassType class} types, returns the corresponding class object (e.g. {@code String.class});</li>
* <li>for {@linkplain ArrayType array} types, returns the corresponding class object (e.g. {@code String[][].class});</li>
* <li>for {@linkplain ParameterizedType parameterized} types, returns the class object of the generic class
* (e.g. {@code List.class} for {@code List<String>});</li>
* <li>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});</li>
* <li>for {@linkplain TypeVariable type variables}, returns the class object of the first bound
* (e.g. {@code Number.class} for {@code T extends Number & Comparable<T>}), or {@code Object.class}
* if the type variable has no bounds (e.g. just {@code T});</li>
* <li>for {@linkplain UnresolvedTypeVariable unresolved type variables}, returns {@code Object.class}.</li>
* </ul>
*
* @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);
}
}
}
108 changes: 108 additions & 0 deletions core/src/test/java/org/jboss/jandex/test/JandexReflectionTest.java
Original file line number Diff line number Diff line change
@@ -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<X> {
void nothing();

int primitive();

String clazz();

List<String> 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 extends Number> Y typeParameterWithSingleBound();

<Y extends Number & Comparable<Y>> Y typeParameterWithMultipleBounds();

<Y extends Comparable<Y>> Y typeParameterWithSingleParameterizedBound();

<Y extends Comparable<Y> & Serializable> Y typeParameterWithMultipleBoundsFirstParameterized();

<Y extends Serializable & Comparable<Y>> Y typeParameterWithMultipleBoundsSecondParameterized();

List<?> unboundedWildcard();

List<? extends Number> wildcardWithUpperBound();

List<? super String> wildcardWithLowerBound();

List<? extends X> wildcardWithUnboundedTypeParameterAsUpperBound();

<Y extends Number> List<? extends Y> wildcardWithBoundedTypeParameterAsUpperBound();

<Y extends Number> List<? super Y> wildcardWithBoundedTypeParameterAsLowerBound();
}

static class NestedClass<T> {
class InnerClass<U extends T> {
}
}

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);
}
}

0 comments on commit 95177d5

Please sign in to comment.