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
- 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
This library aims to provide a simple way to analyse generic type information and dynamically create
(Annotated)Type
instances, all at runtime.
All functionality of the library is exposed via a handful of classes:
GenericTypeReflector
: contains static methods used for generic type analysisTypeFactory
: contains static methods used forType/AnnotatedType
instance creationTypeToken
: Used to createType/AnnotatedType
literals (using THC pattern)
<dependency>
<groupId>io.leangen.geantyref</groupId>
<artifactId>geantyref</artifactId>
<version>2.0.0</version>
</dependency>
You can find instructions at maven.org
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.
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.
GenericTypeReflector.getExactParameterTypes(methodOrConstructor, aType)
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>
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
.
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.
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
.
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();
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);
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);
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);
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.
More info can be found at the project Wiki.