geantyref
geantyref copied to clipboard
Advanced generic type reflection library with support for working with AnnotatedTypes (for Java 8+)
GeantyRef
A fork of the excellent GenTyRef library, adding support for working with AnnotatedTypes introduced in Java 8 plus many nifty features.
Table of Contents
-
GeantyRef
- Goal
- Overview
-
Usage
- Maven
- Other build tools
-
Examples
- Getting the exact return type of a method
- Getting the exact type of a field
- Getting the exact types of method parameters
- Getting the exact super type
- Getting the exact sub type
- Getting annotated return/parameter/field/sub/super types
- Creating type literals using TypeToken
- Creating annotated type literals using TypeToken
- Creating types dynamically using TypeFactory
- Creating annotated types dynamically using TypeFactory
- Turning any Type into an AnnotatedType
- More
- Wiki
- License
Goal
This library aims to provide a simple way to analyse generic type information and dynamically create
(Annotated)Type
instances, all at runtime.
Overview
All functionality of the library is exposed via a handful of classes:
-
GenericTypeReflector
: contains static methods used for generic type analysis -
TypeFactory
: contains static methods used forType/AnnotatedType
instance creation -
TypeToken
: Used to createType/AnnotatedType
literals (using THC pattern)
Usage
Maven:
<dependency>
<groupId>io.leangen.geantyref</groupId>
<artifactId>geantyref</artifactId>
<version>1.3.15</version>
</dependency>
Other build tools:
You can find instructions at maven.org
Examples
Getting the exact return type of a method
The simplest example would be having a class similar to this:
class StringList extends ArrayList<String> {
...
}
Getting the exact return type of StringList
's get
method is rather difficult:
Method get = StringList.class.getMethod("get", int.class);
get.getGenericReturnType() //yields T, which is not very useful information
On the other hand, running GenericTypeReflector.getExactReturnType(get, StringClass.class)
would yield String
which is what we were looking for.
Getting the exact type of a field
Presume we have two simple classes:
class Container<T> {
public T item;
}
class NumberContainer extends Container<Number> {}
We again face issues when trying to discover the exact type of the item
field:
Field item = NumberContainer.class.getField("item");
item.getGenericType(); //yields T again
Instead, GenericTypeReflector.getExactFieldType(item, NumberContainer.class)
returns Number
as desired.
Getting the exact types of method parameters
GenericTypeReflector.getExactParameterTypes(methodOrConstructor, aType)
Getting the exact super type
If we had the classes defined as follows:
class Container<T> {
public T item;
}
class NumberContainer<T extends Number> extends Container<T> {}
class LongContainer extends NumberContainer<Long> {}
If we'd call LongContainer.class.getGenericSuperclass()
it would correctly return NumberContainer<Long>
but getting from there to Container<Long>
is much more difficult, as there's no direct way.
GeantyRef allows us to simply call
GenericTypeReflector.getExactSuperType(LongContainer.class, Container.class)
to get Container<Long>
Getting the exact sub type
Even more interestingly, it is sometimes possible to get the exact sub type of a type.
For example, if we had List<String>
and we wanted ArrayList<String>
if would be possible,
as the ArrayList
's sole type parameter is coming from List
, i.e. ArrayList
does not define
any type parameters itself, List<String>
already contains all the needed information.
This is rather difficult to calculate using standard reflection, but if we already had a reference to
List<String>
called listOfString
, it is enough to call:
GenericTypeReflector.getExactSubType(listOfString, ArrayList.class)
to get ArrayList<String>
Still, how to get to listOfString
? Probably by calling one of the methods described above.
But, it's also possible to create a type literal directly via TypeToken
, or construct the desired
Type
(List<String>
) dynamically using TypeFactory
.
Getting annotated return/parameter/field/sub/super types
It is worth noting that all the basic methods on the GenericTypeReflector
listed above have
overloads that work with AnnotatedType
s.
class Person {
public List<@NonNull String> nicknames;
}
AnnotatedType listOfNonNullStrings = Person.class.getField("nicknames").getAnnotatedType();
Method get = List.class.getMethod("get", int.class);
//returns an AnnotatedType representing: @NonNull String
AnnotatedType nonNullString = GenericTypeReflector.getExactReturnType(get, listOfNonNullStrings);
Similarly, getExactFieldType
, getExactParameterTypes
, getExactSuperType
, getExactSubType
work with AnnotatedType
s.
Creating type literals using TypeToken
This approach, known as Typesafe Heterogenous Container (THC) or type token, is widely used in libraries like Jackson or Gson that need to work with generic types. There are various sources describing the intricacies of this approach, Neal Gafter's blog being a classic one.
To obtain a Type
instance representing a know generic type, such as List<String>
it is enough to
do the following:
Type listOfString = new TypeToken<List<String>>(){}.getType();
What we're doing here is creating an anonymous subclass of a parameterized type (TypeToken
) and
getting it's generic super class via getGenericSuperclass
.
Creating annotated type literals using TypeToken
If instead we wanted an instance of an annotated type, such as List<@NonNull String>
, the same
principle would apply:
AnnotatedType listOfNonNullString = new TypeToken<List<@NonNull String>>(){}.getAnnotatedType();
Creating types dynamically using TypeFactory
TypeToken
is only useful if all the generic type information is known ahead of time. It will not
allow us to create a Type
or AnnotatedType
dynamically. For such a task, TypeFactory
provides
adequate methods.
Class<List> listType = List.class;
Class<String> typeParameter = String.class;
//returns a Type representing List<String>
Type listOfStrings = TypeFactory.parameterizedClass(listType, typeParameter);
Class<Map> mapType = Map.class;
Class<String> keyTypeParameter = String.class;
Class<Number> valueTypeParameter = Number.class;
//returns a Type representing Map<String, Number>
Type mapOfStringNumber = TypeFactory.parameterizedClass(mapType, keyTypeParameter, valueTypeParameter);
Creating annotated types dynamically using TypeFactory
TypeFactory
can also produce AnnotatedType
instances, but this means we need to somehow obtain
instances of the annotations themselves (instances of Annotation
).
An obvious way of getting them would be from an existing AnnotatedElement
(annotatedElement.getAnnotations()
).
Class
, Parameter
, Method
, Constructor
, Field
all implement AnnotatedElement
.
But, TypeFactory
also allows you to instantiate an Annotation
dynamically:
Map<String, Object> annotationParameters = new HashMap<>();
annotationParameters.put("name", "someName");
MyAnnotation myAnnotation = TypeFactory.annotation(MyAnnotation.class, annotationParameters);
This produces an Annotation
instance as if @MyAnnotation(name = "someName")
was found in the sources.
Armed with this knowledge, it's easy to produce an AnnotatedType
:
Class<List> listType = List.class;
Class<String> typeParameter = String.class;
Annotation[] annotations = { myAnnotation };
//returns an AnnotatedType representing: @MyAnnotation(name = "someName") List<String>
AnnotatedType annotatedListOfString = TypeFactory.parameterizedClass(listType, annotations, typeParameter);
Turning any Type
into an AnnotatedType
GenericTypeReflector
also allows you to wrap any Type
into an AnnotatedType
by collecting its
direct annotations. For example:
@SuppressWarnings("unchecked")
class Something {}
//returns an AnnotatedType representing: @SuppressWarnings("unchecked") Something
AnnotatedType something = GenericTypeReflector.annotate(Something.class);
This method will correctly turn a ParameterizedType
into an AnnotatedParameterizedType
,
WildcardType
into an AnnotatedWildcardType
etc.
To turn a ParameterizedType
into an AnnotatedParameterizedType
with customized annotations:
Type listOfStrings = TypeFactory.parameterizedClass(List.class, String.class);
Annotation[] typeAnnotations = { myAnnotation };
Annotation[] argumentAnnotations = { anotherAnnotation };
//Get an AnnotatedParameterizedType representing: @MyAnnotation List<@AnotherAnnotation String>
AnnotatedParameterizedType annotatedListOfAnnotatedStrings =
parameterizedAnnotatedType(listOfStrings, typeAnnotations, argumentAnnotations);
More
There are more features provided by GenericTypeReflector
that were not described in depth here,
like the possibility to replace annotations on an AnnotatedType
via replaceAnnotations
,
or update them via updateAnnotations
, calculate hash codes and check equality of AnnoatedType
s
(as equals
and hasCode
are not overridden in Java's AnnotatedType
implementations) etc, so
feel free to explore a bit on your own.
Wiki
More info can be found at the project Wiki.