leangen / geantyref Goto Github PK
View Code? Open in Web Editor NEWThis project forked from coekie/gentyref
Advanced generic type reflection library with support for working with AnnotatedTypes (for Java 8+)
License: Apache License 2.0
This project forked from coekie/gentyref
Advanced generic type reflection library with support for working with AnnotatedTypes (for Java 8+)
License: Apache License 2.0
TypeVisitor
has a protected visitCaptureType
as if you are supposed to be able to override it.
However, the parameter is a AnnotatedCaptureType
which is package-private, so to override it you must be in the geantyref package.
It seems that either the type should be visible, or the method itself should be package private not protected
.
Consider this:
While the ParameterizedTypeImpl (which is part of geantryref) returns io.leangen.geantyref.Issue20Test.UnitX<io.leangen.geantyref.Issue20Test$QuantityX>
, the actual class name is io.leangen.geantyref.Issue20Test$UnitX
as UnitX
is a nested class within the Issue20Test
class. But the toString() method omits the $
as a marker for a nested class and uses a .
Coming from coekie#5 (comment)
I gave it a spin and came up with this:
public static Type getResolvedReturnType(Method m, Type declaringType) {
return resolveTypeVariableIfNecessary(GenericTypeReflector.getReturnType(m, declaringType));
}
public static Type getResolvedFieldType(Field f, Type declaringType) {
return resolveTypeVariableIfNecessary(GenericTypeReflector.getFieldType(f, declaringType));
}
public static List<Type> getResolvedParameterTypes(Method m, Type declaringType) {
return Arrays.stream(GenericTypeReflector.getParameterTypes(m, declaringType))
.map(ReflectionUtil::resolveTypeVariableIfNecessary)
.collect(Collectors.toList());
}
private static Type resolveTypeVariableIfNecessary(Type returnType) {
if (returnType instanceof TypeVariable) {
AnnotatedType annotatedType = GenericTypeReflector.annotate(returnType);
return GenericTypeReflector.reduceBounded(annotatedType).getType();
}
return returnType;
}
it seems to work, but I'm not sure its the best way. You can see the full change here https://github.com/leonard84/spock/tree/replace_gentyref_with_geantyref
I think that it might have value to have something like this in GenericTypeReflector
directly, this way it could be optimized as well, the current implementation has to re-annotate the type that was discarded in GenericTypeReflector
internally.
It would be cool to be able to use Geantyref from Modular Java, i.e. with tools like jlink
. But right now, it is not possible, because Geantyref ships an Automatic-Module-Name
, instead of a module-info.java
.
I would be useful to support getExactParameterTypes()
not only for methods but also for constructors.
Given that geantyref already requires Java 1.8+, the easiest way to accomplish this is to change the method signature
public static AnnotatedType[] getExactParameterTypes(Method m, AnnotatedType declaringType)
to
public static AnnotatedType[] getExactParameterTypes(Executable m, AnnotatedType declaringType)
Likewise for the Type
overload. No other changes required.
PS: Since Type
overloads are implemented in terms of AnnotatedType
overloads but discard annotation information, would it be more efficient to lift Type
to AnnotatedType
using new AnnotatedTypeImpl(type)
rather than annotate(type)
?
Hi @kaqqao , we ran into some behavior that we don't understand over at jdbi/jdbi#2306
@Test
void testGenericTypeWorksInParameterizedClasses() {
abstract class TypeCapture<T> {
private final Type type;
protected TypeCapture() {
this.type = GenericTypeReflector.getTypeParameter(getClass(), TypeCapture.class.getTypeParameters()[0]);
}
public final Type getType() {
return type;
}
}
class Foo<T> {
void checkType() {
assertThat(new TypeCapture<List<String>>() {}.getType()).isEqualTo(TypeFactory.parameterizedClass(List.class, String.class));
}
}
new Foo<>().checkType();
}
This code works as you'd expect when the enclosing class Foo
has no type parameter - but when you introduce a parameter Foo<T>
, suddenly the type parameter is lost in the TypeCapture class and this test fails.
Is this a bug in GeAnTyRef, or are we perhaps using it incorrectly? Thank you!
mfriedenhagen reported an issue in Spock spockframework/spock#1909 where geantyref throws a StackOverflowError
.
I have removed the Spock dependency from the reproducer attached to the Spock issue, and the error is still there:
import java.lang.reflect.Method;
import java.util.Objects;
public class StackOverflowReproducer {
static abstract class Status<StatusT, StatusBuilderT> {
static class Builder<StatusT, StatusBuilderT> {
}
}
static abstract class Resource<StatusT, StatusBuilderT> {
}
static class ProjectValidator {
public <
StatusT extends Status<StatusT, StatusBuilderT>,
StatusBuilderT extends Status.Builder<StatusT, StatusBuilderT>,
T extends Resource<StatusT, StatusBuilderT>>
void validate(long id, Class<T> klass) {
throw new UnsupportedOperationException("Just for testing");
}
}
public static void main(String[] args) throws NoSuchMethodException {
Class<?> cls = ProjectValidator.class;
Method method = Objects.requireNonNull(cls.getMethod("validate", long.class, Class.class));
io.leangen.geantyref.GenericTypeReflector.getParameterTypes(method, cls);
}
}
The exception stack looks like:
java.lang.StackOverflowError
at java.base/sun.reflect.generics.reflectiveObjects.TypeVariableImpl.getGenericDeclaration(TypeVariableImpl.java:144)
at java.base/sun.reflect.generics.reflectiveObjects.TypeVariableImpl.typeVarIndex(TypeVariableImpl.java:227)
at java.base/sun.reflect.generics.reflectiveObjects.TypeVariableImpl.getAnnotatedBounds(TypeVariableImpl.java:220)
at java.base/sun.reflect.annotation.AnnotatedTypeFactory$AnnotatedTypeVariableImpl.getAnnotatedBounds(AnnotatedTypeFactory.java:383)
at io.leangen.geantyref.VarMap.map(VarMap.java:98)
at io.leangen.geantyref.VarMap.map(VarMap.java:115)
at io.leangen.geantyref.VarMap.map(VarMap.java:148)
at io.leangen.geantyref.VarMap.map(VarMap.java:98)
The used geantyref version is io.leangen.geantyref:geantyref:1.3.15
Tested for Java 8
and 17
In its current state, GenericTypeReflector.annotate
adds class annotations as type annotations. I'm not entirely sure where this would be useful, but it is a serious problem for almost anything that wants to create annotated classes. See, I'm making a library and I need to create AnnotatedTypes
given a Type and a set of given annotations, however I need those exact annotations, and don't want any of the random @SuppressWarnings
etc. annotations from the class being added to the type on my behalf.
getAnnotatedOwnerType()
was added to the AnnotatedType
interface in JDK9. It should be properly implemented, effectively bumping the version requirement.
Functions should be changed to work with AnnotatedTypes, where sensible and applicable.
1.8
package com.zyc;
import io.leangen.geantyref.TypeToken;
import java.lang.annotation.*;
import java.lang.reflect.AnnotatedParameterizedType;
import java.util.Arrays;
import java.util.List;
/**
* @author zyc
*/
public class Test {
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@interface Example {
}
public static void main(String[] args) {
// Direct execution works
AnnotatedParameterizedType annotatedParameterizedType = (AnnotatedParameterizedType) new TypeToken<List<@Example String>>() {
}.getAnnotatedType();
System.out.println(Arrays.toString(annotatedParameterizedType.getAnnotatedActualTypeArguments()[0].getDeclaredAnnotations()));
// The static method works
staticMethod();
// The instance method does not work
new Test().instanceMethod();
}
/**
* static method
*/
private static void staticMethod() {
AnnotatedParameterizedType annotatedParameterizedType = (AnnotatedParameterizedType) new TypeToken<List<@Example String>>() {
}.getAnnotatedType();
System.out.println(Arrays.toString(annotatedParameterizedType.getAnnotatedActualTypeArguments()[0].getDeclaredAnnotations()));
}
/**
* instance method
*/
private void instanceMethod() {
AnnotatedParameterizedType annotatedParameterizedType = (AnnotatedParameterizedType) new TypeToken<List<@Example String>>() {
}.getAnnotatedType();
System.out.println(Arrays.toString(annotatedParameterizedType.getAnnotatedActualTypeArguments()[0].getDeclaredAnnotations()));
}
@org.junit.Test
public void test() {
AnnotatedParameterizedType annotatedParameterizedType = (AnnotatedParameterizedType) new TypeToken<List<@Example String>>() {
}.getAnnotatedType();
System.out.println(Arrays.toString(annotatedParameterizedType.getAnnotatedActualTypeArguments()[0].getDeclaredAnnotations()));
}
}
[@com.zyc.Test$Example()]
[@com.zyc.Test$Example()]
[@com.zyc.Test$Example()]
[@com.zyc.Test$Example()]
[@com.zyc.Test$Example()]
[]
[]
The test results are unexpected. Do you know where the problem is? thanks.
Hi @kaqqao , thank you again for this great library. I think I might have found a bug:
@Test
public void testErase() throws Exception {
GenericTypeReflector.erase(
GenericTypeReflector.getExactReturnType(
E2.class.getMethod("getVal"),
GenericTypeReflector.addWildcardParameters(E2.class)));
}
public static class E2<T extends String> {
private final T val;
E2(final T val) {
this.val = val;
}
public T getVal() {
return val;
}
}
Expected result: String.class
Actual result: RuntimeException
java.lang.RuntimeException: not supported: class io.leangen.geantyref.CaptureTypeImpl
at io.leangen.geantyref.GenericTypeReflector.erase(GenericTypeReflector.java:87)
at org.jdbi.v3.core.mapper.TestEnums.testErase(TestEnums.java:111)
Hi @kaqqao happy new year,
We received a bug report in Jdbi: jdbi/jdbi#2582
It looks to actually be a bug in geantyref.
Here's the code to reproduce:
public interface UnitX<Q extends QuantityX<Q>> {
}
public interface QuantityX<Q extends QuantityX<Q>> {
}
class TestClass {
public UnitX<?> returnType() {
return null;
}
}
GenericTypeReflector.reduceBounded(GenericTypeReflector.annotate(
GenericTypeReflector.getExactReturnType(TestClass.class.getMethod("returnType"), TestClass.class)));
This results in a StackOverflowError
. Thank you for any insight you can provide!
According to the javadocs for java.lang.Object#hashCode
,
If two objects are equal according to the equals method, then calling the hashCode method on each of the two objects must produce the same integer result.
Currently, TypeToken
does not abide by this.
It is possible to get two type tokens where first.equals(second)
is true
, but first.hashCode() == second.hashCode()
is false.
This violates the contract of hashCode
and breaks any attempt to use TypeToken
as the key to a HashMap
.
Here is some example code that can reproduce the issue:
import io.leangen.geantyref.TypeToken;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class Test {
public static void main(String[] args) throws NoSuchMethodException {
TypeToken<Set<? extends Test>> testTypeToken = new TypeToken<>() {};
Method method = Test.class.getMethod("testFunction", Set.class);
TypeToken<?> otherTypeToken = TypeToken.get(method.getParameters()[0].getParameterizedType());
Map<TypeToken<?>, String> testMap = new HashMap<>();
testMap.put(otherTypeToken, "test");
System.out.println("testTypeToken: " + testTypeToken);
System.out.println("testTypeToken.getType(): " + testTypeToken.getType());
System.out.println("testTypeToken.hashCode(): " + testTypeToken.hashCode());
System.out.println();
System.out.println("otherTypeToken: " + otherTypeToken);
System.out.println("otherTypeToken.getType(): " + otherTypeToken.getType());
System.out.println("otherTypeToken.hashCode(): " + otherTypeToken.hashCode());
System.out.println();
System.out.println("testTypeToken.equals(otherTypeToken): " + testTypeToken.equals(otherTypeToken));
System.out.println("otherTypeToken.equals(testTypeToken): " + otherTypeToken.equals(testTypeToken));
System.out.println();
System.out.println("testMap: " + testMap);
System.out.println("testMap.get(testTypeToken): " + testMap.get(testTypeToken));
}
public static void testFunction(Set<? extends Test> testSet) {}
}
The current output of this program is:
testTypeToken: Test$1@60f4877b
testTypeToken.getType(): java.util.Set<? extends Test>
testTypeToken.hashCode(): 1626638203
otherTypeToken: io.leangen.geantyref.TypeToken$2@17a57871
otherTypeToken.getType(): java.util.Set<? extends Test>
otherTypeToken.hashCode(): 396720241
testTypeToken.equals(otherTypeToken): true
otherTypeToken.equals(testTypeToken): true
testMap: {io.leangen.geantyref.TypeToken$2@17a57871=test}
testMap.get(testTypeToken): null
The expected output is:
testTypeToken: Test$1@60f4877b
testTypeToken.getType(): java.util.Set<? extends Test>
-testTypeToken.hashCode(): 1626638203
+testTypeToken.hashCode(): 396720241
otherTypeToken: io.leangen.geantyref.TypeToken$2@17a57871
otherTypeToken.getType(): java.util.Set<? extends Test>
otherTypeToken.hashCode(): 396720241
testTypeToken.equals(otherTypeToken): true
otherTypeToken.equals(testTypeToken): true
testMap: {io.leangen.geantyref.TypeToken$2@17a57871=test}
-testMap.get(testTypeToken): null
+testMap.get(testTypeToken): "test"
As you can see, if reflection is used to get the parameterized type of a method, and that type is then added to a hash map, you cannot access that parameterized type using one constructed via new TypeToken<Set<? extends Test>>() {}
.
Java versions before 11 (?) have multiple bugs in the annotation parser (e.g. this one) causing it to lose annotations on nested types.
It is in some cases possible to recover the lost class-level annotations when transforming a type to its canonical form, and GenericTypeReflector#toCanonical
should take advantage of these mitigations where applicable.
With JDK 11:
[ERROR] /Users/henning/oss/geantyref/src/main/java/io/leangen/geantyref/AnnotatedArrayTypeImpl.java:[13,1] io.leangen.geantyref.AnnotatedArrayTypeImpl is not abstract and does not override abstract method getAnnotatedOwnerType() in java.lang.reflect.AnnotatedArrayType
[ERROR] /Users/henning/oss/geantyref/src/main/java/io/leangen/geantyref/AnnotatedTypeVariableImpl.java:[13,1] io.leangen.geantyref.AnnotatedTypeVariableImpl is not abstract and does not override abstract method getAnnotatedOwnerType() in java.lang.reflect.AnnotatedTypeVariable
[ERROR] /Users/henning/oss/geantyref/src/main/java/io/leangen/geantyref/AnnotatedParameterizedTypeImpl.java:[15,1] io.leangen.geantyref.AnnotatedParameterizedTypeImpl is not abstract and does not override abstract method getAnnotatedOwnerType() in java.lang.reflect.AnnotatedParameterizedType
[ERROR] /Users/henning/oss/geantyref/src/main/java/io/leangen/geantyref/AnnotatedWildcardTypeImpl.java:[16,1] io.leangen.geantyref.AnnotatedWildcardTypeImpl is not abstract and does not override abstract method getAnnotatedOwnerType() in java.lang.reflect.AnnotatedWildcardType
With JDK 8, the current code compiles but shows errors:
Provide a way to fully or partially resolve a type in the context of another.
E.g. given:
class A<T> {
Set<T> get();
}
class B extends A<String> {}
there should be a way to resolve Set<T>
in the context of B
(without having a reference to the get
method itself).
This aims to solve the issue outlined in #5
Hi @kaqqao thank you for your continued help, I wonder what you think of this.
Given the class,
public class GenericBean<T extends String> {
public List<T> getProperty() {
return Collections.emptyList();
}
}
I want to figure out the most specific type for the return value of getProperty
given that I don't have the parameterized GenericBean<T>
, only GenericBean.class
- replacing T
with String
(the bound) and then figure out List<String>
as the property type.
To do this, I am trying to use GenericTypeReflector.transform
and override visitVariable
to replace variables with their bound.
private AnnotatedType resolveBounds(final Type type) {
return GenericTypeReflector.transform(GenericTypeReflector.annotate(type), new TypeVisitor() {
@Override
protected AnnotatedType visitVariable(final AnnotatedTypeVariable type) {
return type.getAnnotatedBounds()[0]; // T extends String -> String
}
});
}
assertThat(Arrays.stream(GenericBean.class.getTypeParameters())
.map(this::resolveBounds)
.toArray(AnnotatedType[]::new))
.containsExactly(GenericTypeReflector.annotate(String.class));
Unfortunately, despite this being statically safe, it does not actually work:
java.lang.ClassCastException: class sun.reflect.annotation.AnnotatedTypeFactory$AnnotatedTypeBaseImpl cannot be cast to class java.lang.reflect.AnnotatedTypeVariable (sun.reflect.annotation.AnnotatedTypeFactory$AnnotatedTypeBaseImpl and java.lang.reflect.AnnotatedTypeVariable are in module java.base of loader 'bootstrap')
at io.leangen.geantyref.TypeVisitor.visitCaptureType(TypeVisitor.java:75)
at io.leangen.geantyref.GenericTypeReflector.transform(GenericTypeReflector.java:1129)
at io.leangen.geantyref.TypeVisitor.lambda$visitParameterizedType$0(TypeVisitor.java:27)
...
at io.leangen.geantyref.TypeVisitor.visitParameterizedType(TypeVisitor.java:28)
at io.leangen.geantyref.GenericTypeReflector.transform(GenericTypeReflector.java:1117)
at org.jdbi.v3.core.argument.TestBeanArguments.resolveBounds(TestBeanArguments.java:292)
at org.jdbi.v3.core.argument.TestBeanArguments.wut(TestBeanArguments.java:284)
It looks like the visitor assumes it can cast the result:
AnnotatedCaptureType annotatedCapture = new AnnotatedCaptureTypeImpl((CaptureType) type.getType(),
(AnnotatedWildcardType) transform(type.getAnnotatedWildcardType(), this),
(AnnotatedTypeVariable) transform(type.getAnnotatedTypeVariable(), this),
null, type.getAnnotations());
Please let me know if I'm on the wrong track, or if there's something I'm missing. Thank you!
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.