leangen / geantyref

Advanced generic type reflection library with support for working with AnnotatedTypes (for Java 8+)
Apache License 2.0
99 stars 15 forks source link

GenericTypeReflector.transform visitor visitVariable cannot change type away from variable type #13

Closed stevenschlansker closed 2 years ago

stevenschlansker commented 2 years ago

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!

kaqqao commented 2 years ago

Oh, I see the problem now. I deleted the previous 2 messages I wrote, as they were just me being confused, nothing useful. But I think the provided test case doesn't trigger the described bug, there's got to be a wildcard somewhere to capture. But no matter, I understand the issue... I'll see what I can do.

kaqqao commented 2 years ago

The default TypeVisitor was trying to be too smart... I simplified its behavior in regards to capture types. Additionally, I implemented a method doing exactly (I think) what you need: GenericTypeReflector#reduceBounded. It reduces all bounded types (variables, wildcards & captures) to their first bound. I actually want to use this this exact same thing in GraphQL SPQR instead of the current (flaky) custom implementation not relying on transform 😅 Released v1.3.13 with this.

stevenschlansker commented 2 years ago

Thank you so much! The reduceBounded method seems to do exactly what I need. Jdbi now has much better type handling :smile_cat: