From 35cc5abdf0fa3810e815b8dcfb1aba16eec1b903 Mon Sep 17 00:00:00 2001 From: Thibaud Date: Fri, 28 Dec 2018 19:18:47 +0100 Subject: [PATCH] Add some reflection with generics experiments --- lang/pom.xml | 2 +- pom.xml | 2 +- .../{dynamicload => }/DynamicLoadDemo.java | 4 +- .../fr/gasser/reflection/GenericClass.java | 26 +++++ .../fr/gasser/reflection/GenericsTest.java | 26 +++++ .../java/fr/gasser/reflection/TypeToken.java | 102 ++++++++++++++++++ 6 files changed, 158 insertions(+), 4 deletions(-) rename reflection/src/main/java/fr/gasser/reflection/{dynamicload => }/DynamicLoadDemo.java (95%) create mode 100644 reflection/src/main/java/fr/gasser/reflection/GenericClass.java create mode 100644 reflection/src/main/java/fr/gasser/reflection/GenericsTest.java create mode 100644 reflection/src/main/java/fr/gasser/reflection/TypeToken.java diff --git a/lang/pom.xml b/lang/pom.xml index 1fa88c0..f52cbd7 100644 --- a/lang/pom.xml +++ b/lang/pom.xml @@ -9,7 +9,7 @@ 4.0.0 - core + lang junit diff --git a/pom.xml b/pom.xml index 4b17a1e..80892d1 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ reflection gui async - core + lang diff --git a/reflection/src/main/java/fr/gasser/reflection/dynamicload/DynamicLoadDemo.java b/reflection/src/main/java/fr/gasser/reflection/DynamicLoadDemo.java similarity index 95% rename from reflection/src/main/java/fr/gasser/reflection/dynamicload/DynamicLoadDemo.java rename to reflection/src/main/java/fr/gasser/reflection/DynamicLoadDemo.java index 4126841..eaa4a18 100644 --- a/reflection/src/main/java/fr/gasser/reflection/dynamicload/DynamicLoadDemo.java +++ b/reflection/src/main/java/fr/gasser/reflection/DynamicLoadDemo.java @@ -1,4 +1,4 @@ -package fr.gasser.reflection.dynamicload; +package fr.gasser.reflection; import javax.tools.JavaCompiler; import javax.tools.ToolProvider; @@ -18,7 +18,7 @@ public class DynamicLoadDemo { */ private static final String SOURCE = "package test;" - + "import static fr.gasser.reflection.dynamicload.DynamicLoadDemo.*;" + + "import static DynamicLoadDemo.*;" + "public class Test implements IDynamicLoad {" + " static { System.out.println(\"Test\"); }" + " @Override public String doSomething() {" diff --git a/reflection/src/main/java/fr/gasser/reflection/GenericClass.java b/reflection/src/main/java/fr/gasser/reflection/GenericClass.java new file mode 100644 index 0000000..8ea9901 --- /dev/null +++ b/reflection/src/main/java/fr/gasser/reflection/GenericClass.java @@ -0,0 +1,26 @@ +package fr.gasser.reflection; + +import java.lang.reflect.ParameterizedType; + +/** + * Shows how to access generic type parameter type at runtime. + */ +public abstract class GenericClass { + + private Class clazz; + + @SuppressWarnings("unchecked") + private GenericClass() { + ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass(); + this.clazz = (Class) genericSuperclass.getActualTypeArguments()[0]; + } + + Class getClazz() { + return clazz; + } + + public static void main(String[] args) { + System.out.println(new GenericClass() {}.getClazz()); + System.out.println(new GenericClass() {}.getClazz()); + } +} diff --git a/reflection/src/main/java/fr/gasser/reflection/GenericsTest.java b/reflection/src/main/java/fr/gasser/reflection/GenericsTest.java new file mode 100644 index 0000000..64aaf03 --- /dev/null +++ b/reflection/src/main/java/fr/gasser/reflection/GenericsTest.java @@ -0,0 +1,26 @@ +package fr.gasser.reflection; + +public class GenericsTest { + + private TypeToken typeToken; + private T data; + + + public GenericsTest(T data) { + this.data = data; + this.typeToken = new TypeToken() {}; + } + + @Override + public String toString() { + return "GenericsTest{" + + "type=" + typeToken.type + + ", data=" + data + + '}'; + } + + public static void main(String[] args) { + GenericsTest g = new GenericsTest<>(5L); + System.out.println(g); + } +} diff --git a/reflection/src/main/java/fr/gasser/reflection/TypeToken.java b/reflection/src/main/java/fr/gasser/reflection/TypeToken.java new file mode 100644 index 0000000..59a3121 --- /dev/null +++ b/reflection/src/main/java/fr/gasser/reflection/TypeToken.java @@ -0,0 +1,102 @@ +package fr.gasser.reflection; + +import java.lang.reflect.*; +import java.util.List; + +/* + * {@code TypeToken> list = new TypeToken>() {};} + */ +public class TypeToken { + private final Class rawType; + final Type type; + private final int hashCode; + + @SuppressWarnings("unchecked") + TypeToken() { + this.type = getSuperclassTypeParameter(getClass()); + this.rawType = (Class) getRawType(type); + this.hashCode = type.hashCode(); + } + + + /** + * Unsafe. Constructs a type literal manually. + */ + @SuppressWarnings("unchecked") + private TypeToken(Type type) { + this.type = checkNotNull(type); + this.rawType = (Class) getRawType(this.type); + this.hashCode = this.type.hashCode(); + } + + /** + * Returns the type from super class's type parameter + */ + private static Type getSuperclassTypeParameter(Class subclass) { + Type superclass = subclass.getGenericSuperclass(); + if (superclass instanceof Class) { + throw new RuntimeException("Missing type parameter."); + } + ParameterizedType parameterized = (ParameterizedType) superclass; + return parameterized.getActualTypeArguments()[0]; + } + + public static Class getRawType(Type type) { + if (type instanceof Class) { + // type is a normal class. + return (Class) type; + + } else if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + + // I'm not exactly sure why getRawType() returns Type instead of Class. + // Neal isn't either but suspects some pathological case related + // to nested classes exists. + Type rawType = parameterizedType.getRawType(); + checkArgument(rawType instanceof Class); + return (Class) rawType; + + } else if (type instanceof GenericArrayType) { + Type componentType = ((GenericArrayType)type).getGenericComponentType(); + return Array.newInstance(getRawType(componentType), 0).getClass(); + + } else if (type instanceof TypeVariable) { + // we could use the variable's bounds, but that won't work if there are multiple. + // having a raw type that's more general than necessary is okay + return Object.class; + + } else if (type instanceof WildcardType) { + return getRawType(((WildcardType) type).getUpperBounds()[0]); + + } else { + String className = type == null ? "null" : type.getClass().getName(); + throw new IllegalArgumentException("Expected a Class, ParameterizedType, or " + + "GenericArrayType, but <" + type + "> is of type " + className); + } + } + + private static void checkArgument(boolean condition) { + if (!condition) { + throw new IllegalArgumentException(); + } + } + + private static T checkNotNull(T obj) { + if (obj == null) { + throw new NullPointerException(); + } + return obj; + } + + /** + * Gets type literal for the given {@code Class} instance. + */ + public static TypeToken get(Class type) { + return new TypeToken(type); + } + + public static void main(String[] args) { + TypeToken> token = new TypeToken>() {}; + System.out.println(token); + } +}