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.annotate adds invalid annotations #7

Open thecodewarrior opened 5 years ago

thecodewarrior commented 5 years ago

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.

kaqqao commented 5 years ago

Class is a Type, so annotations on the class are type annotations (AnnotationType.TYPE). This is why they're taken into account when wrapping a class into AnnotatedType. Maybe you are referring to TYPE_USE annotations only?

Anyway, for the moment you can use GenericTypeReflector.replaceAnnotations to set the exact annotations. As long as you're simply wrapping a Class this should suffice. And I'll provide a more performant method for wrapping Types with the specified annotations only in the next release (which can be soon, if it's blocking you).

Btw, out of curiosity, does GeAnTyRef have the features you're looking for and, if not, which ones do you lack? Asking because I find it a pretty complete solution for working with (annotated) types and advanced reflection, but I of course don't know everyone's use-case.

thecodewarrior commented 5 years ago

I don't use it for the "find exact type based on generics" functionality, as that's what Mirror is actually for (Mirror is kinda like GeAnTyRef in that way, but on steroids). I use it primarily for creating Type/AnnotatedType objects. For example, when I call Mirror.reflect(List.class).specialize(Mirror.reflect(String.class)).getCoreType() I need to dynamically create a ParameterizedType representing List<String>.

Currently I created a fork that kinda gets the job done, so there isn't much time pressure at the moment. Along with the GenericTypeReflector.annotate issue, I ran into problems creating AnnotatedParameterizedTypes, but that was remedied with a small patch. Another issue I ran into was the fact that you have a special condition on your ParameterizedType creation code that requires that the varargs be null, which is syntactically impossible to pass from Kotlin code.

Other than these few hitches though your library has been immensely useful.

kaqqao commented 5 years ago

Ah, I'll see to fix the null varargs thing as well.

I ran into problems creating AnnotatedParameterizedTypes, but that was remedied with a small patch.

What was wrong there? The same varargs issue or something else?

Mirror is kinda like GeAnTyRef in that way, but on steroids

I'm now curious :) What features are you making?

I use it primarily for creating Type/AnnotatedType objects

So I presume you're mainly making use of TypeFactory then, rather than GenericTypeReflector, right?

thecodewarrior commented 5 years ago

The existing methods didn’t support owner types. It was as simple as adding a Type parameter to the annotatedParameterizedClass method (I believe that was the one) and passing that on to the parameterizedClass call within it.

Yeah, I’m primarily using those two classes.

From what I can see GeAnTyRef allows you to directly query the resolved types of methods/fields based upon a subclass. In the readme it also only shows getting it from a non-generic type, so I’m not sure if you could, say, get the return value of ArrayList<String>.get.

What Mirror does is create a whole separate family of objects, mirrors, which closely match the Java Core Reflection classes. These objects can be “specialized”, which means that ArrayList<String> is a separate object from ArrayList<Integer>, and the former implements List<String> and the latter List<Integer>

This allows stuff like this:

class CoolObject<T> extends BoringObject<Set<T>> {}
class BoringObject<T> {
    public HashMap<String, ArrayList<T>> thing;
}

ClassMirror coolNumber = Mirror.reflect(new TypeToken<CoolObject<Number>>() {});
// asClassMirror just casts to ClassMirror and is there to avoid the nested casts from hell
TypeMirror listValue = 
    coolNumber.getField("thing").getType().asClassMirror() // HashMap<String, ArrayList<Set<String>>>
    .getTypeParameters()[1].asClassMirror()                // ArrayList<Set<String>>
    .getTypeParameters()[0].asClassMirror();               // Set<String
assert(listValue == Mirror.reflect(new TypeToken<Set<Number>>() {}));
kaqqao commented 5 years ago

The existing methods didn’t support owner types

Aah, I see. Will fix! Thanks for reporting these!

I’m not sure if you could, say, get the return value of ArrayList<String>.get.

Yes, of course! This is what GeAnTyRef is all about.

Type listOfString = new TypeToken<ArrayList<String>>() {}.getType();
Method get = List.class.getMethod("get", Integer.TYPE);
Type returnType = GenericTypeReflector.getReturnType(get, listOfString);
assertEquals(String.class, returnType);

This allows stuff like this...

Since GeAnTyRef only exposes (and expects) the regular Java interfaces (Type, AnnotatedType etc) the API isn't as fluent as in your example, but it can certainly achieve the same result:

Type coolNumber = new TypeToken<CoolObject<Number>>(){}.getType();
Field thing = CoolObject.class.getField("thing");
Type list = ((ParameterizedType) GenericTypeReflector.getFieldType(thing, coolNumber)) // HashMap<String, ArrayList<Set<String>>>
            .getActualTypeArguments()[1];                                // ArrayList<Set<String>>
Type listValue = ((ParameterizedType) list).getActualTypeArguments()[0]; // Set<String
assertEquals(listValue, new TypeToken<Set<Number>>() {}.getType());

(Same thing also works for AnnotatedTypes in place of Types) I hope this makes your life easier, as you should be able to use GeAnTyRef for more under the hood stuff in your lib, while exposing a nice fluent API. I think that approach sort of combines the strengths of GeAnTyRef and libraries like ClassMate.

kaqqao commented 5 years ago

ArrayList<String> is a separate object from ArrayList<Integer>, and the former implements List<String> and the latter List<Integer>

To check if one type extends another GeAnTyRef provides GenericTypeReflector.isSuperType. So for your example:

Type listOfIntegers = TypeFactory.parameterizedClass(List.class, Integer.class);
Type listOfStrings = TypeFactory.parameterizedClass(List.class, String.class);
Type arrayListOfIntegers = TypeFactory.parameterizedClass(ArrayList.class, Integer.class);
Type arrayListOfStrings = TypeFactory.parameterizedClass(ArrayList.class, String.class);

// ArrayList<X> extends List<X>
assertTrue(GenericTypeReflector.isSuperType(listOfIntegers, arrayListOfIntegers));
assertTrue(GenericTypeReflector.isSuperType(listOfStrings, arrayListOfStrings));

//ArrayList<X> does not extend List<Y>
assertFalse(GenericTypeReflector.isSuperType(listOfStrings, arrayListOfIntegers));
assertFalse(GenericTypeReflector.isSuperType(listOfIntegers, arrayListOfStrings));

Your approach got me thinking... I should maybe provide a nicer API to construct types than what TypeFactory currently offers. A builder pattern would be more ergonomic. Something like:

//Concept only, does not exist
TypeBuilder.of(List.class)
    .annotations(...)
    .parameters(...)
    .owner(...)
    .build();