-
Notifications
You must be signed in to change notification settings - Fork 93
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #200 from Ladicek/jandex-reflection
add JandexReflection for runtime loading of classes corresponding to Jandex objects
- Loading branch information
Showing
3 changed files
with
224 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
114 changes: 114 additions & 0 deletions
114
core/src/main/java/org/jboss/jandex/JandexReflection.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
108
core/src/test/java/org/jboss/jandex/test/JandexReflectionTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |