Hakanai
Hakanai

Reputation: 12670

How can I resolve a ParametrizedType / TypeVariable when I know the ParametrizedType of the class it's in?

I am using reflection proxies to perform additional checking on a public API. Essentially I want to wrap every object that comes back from it so that any object the caller gets their hands on is a proxy to the real object.

Java still has the whole erasure problem, so I am passing the type of the wrapped object around with it. I should know what type everything is because the entry into the API is a single, non-generic interface.

public class ProxyInvocationHandler implements InvocationHandler {
    private final Object delegate;
    private final Type delegateType;

    public ProxyInvocationHandler(Object delegate, Type delegateType) {
        this.delegate = delegate;
        this.delegateType = delegateType;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        // Omitted: additional checks performed here.

        Object result = method.invoke(delegate, args);

        Type returnType = method.getGenericReturnType();

        // e.g. if delegateType is List<Cat> and the method is the get method,
        // returnType would be E but resultType should be Cat.
        Type resultType = ???

        // Utility method I will omit, it just creates another proxy instance
        // using its own invocation handler.
        return ProxyUtils.wrap(result, resultType);
    }
}

I have looked around the Type / ParametrizedType API and can't seem to find a way to get resultType, even though delegateType and returnType should be enough information to compute this.

What is the "proper" way to do this?

Upvotes: 1

Views: 1338

Answers (2)

Hakanai
Hakanai

Reputation: 12670

Here's the Guava way, for people who come by in the future:

...
Type returnType = method.getGenericReturnType();
TypeToken<?> resultType = TypeToken.of(delegateType).resolveType(returnType);

I changed the type of delegateType to TypeToken<?> to make things a little easier. And of course, I added quite a bit of caching (using a LoadingCache) to get the performance down to a sensible speed. Their resolution code was slower than my initial hacked-up code which did the same thing. The difference is that I now have the confidence that it's being done correctly.

Upvotes: 1

Saintali
Saintali

Reputation: 4571

You can use Java ClassMate for that purpose. You'll have to use com.fasterxml.classmate.GenericType for type tokens:

GenericType<?> delegateType = new GenericType<List<Cat>>() {};

Note the empty {} that's called the "Super-type Token" pattern.

TypeResolver typeResolver = new TypeResolver();
MemberResolver memberResolver = new MemberResolver(

ResolvedType type = typeResolver.resolve(delegateType);
ResolvedTypeWithMembers members = memberResolver.resolve(type, null, null);
ResolvedMethod[] methods = members.getMemberMethods();

Cache the results in a Map:

Map<Method, ResolvedMethod> resolved = new HashMap<>();
for (ResolvedMethod method: methods) {
    resolved.put(method.getRawMember(), method);
}

Now, when you have a method declared by the delegateType, i.e. List, you can get its resolved return type:

Method method = List.class.getMethod("get", int.class);
ResolvedType resultType = resolved.get(method).getReturnType();
System.out.println("resultType = " + resultType);              // prints resultType = Cat

Upvotes: 1

Related Questions