Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get the actual type arguments to an indirectly implemented generic interface?

I have a parameterized interface that is implemented in many different ways. At run time I need to figure out, given an arbitrary object that implements that interface, what the actual type parameters to the interface is.

Here's a snippet to illustrate the problem, and a halfway attempt to solve it (also on ideone.com):

import java.util.*;
import java.lang.reflect.*;

interface Awesome<X> { }
class Base<E> implements Awesome<Set<E>> { }
class Child extends Base<List<Integer>> { }

class AwesomeExample {      
    public static void main(String[] args) {
        Awesome<Set<List<Integer>>> x = new Child();

        System.out.println(
            ((ParameterizedType)
                Child.class.getGenericSuperclass()
            ).getActualTypeArguments()[0]
        );
        // prints "java.util.List<java.lang.Integer>"

        System.out.println(
            ((ParameterizedType)
                Base.class.getGenericInterfaces()[0]
            ).getActualTypeArguments()[0]
        );
        // prints "java.util.Set<E>"        

        investigate(x);
        // we want this to print "Set<List<Integer>>"
    }

    static void investigate(Awesome<?> somethingAwesome) {
        // how to do this?
    }
}

It looks like there's enough generic type information at runtime to deduce that:

  • Child extends Base<List<Integer>>
  • Base<E> implements Awesome<Set<E>>

And therefore we can put all the bits and pieces together to conclude that:

  • Child implements Awesome<Set<List<Integer>>>

So it looks like the problem is solvable, but it's not that simple, since we'd have to work with an arbitrary class/interface hierarchy. Is this the only way to do this? Is there a simpler way? Has someone written a library to do this already?

like image 540
polygenelubricants Avatar asked Dec 23 '10 08:12

polygenelubricants


2 Answers

Edit: You may just want to look into using: http://code.google.com/p/gentyref/

If you can guarantee that all implementations of Awesome<?> will not have type arguments, the following code should get you started [1]:

static void investigate(Object o) {
    final Class<?> c = o.getClass();
    System.out.println("\n" + c.getName() + " implements: ");
    investigate(c, (Type[])null);
}

static void investigate(Type t, Type...typeArgs) {
    if(t == null) return;

    if(t instanceof Class<?>) {
        investigate((Class<?>)t, typeArgs);
    } else if(t instanceof ParameterizedType) {
        investigate((ParameterizedType)t, typeArgs);
    }
}

static void investigate(Class<?> c, Type...typeArgs) {
    investigate(c.getGenericSuperclass(), typeArgs);

    for(Type i : c.getGenericInterfaces()) {
        investigate(i, typeArgs);
    }
}

static void investigate(ParameterizedType p, Type...typeArgs) {
    final Class<?> c = (Class<?>)p.getRawType();
    final StringBuilder b = new StringBuilder(c.getName());
    b.append('<');
    Type[] localArgs = p.getActualTypeArguments();
    if(typeArgs != null && typeArgs.length > 0) {
        int i = 0, nextTypeArg = 0;
        for(Type local : localArgs) {
            if(local instanceof ParameterizedType) {
                ParameterizedType localP = (ParameterizedType) local;
                b.append(localP.getRawType()).append('<');
                b.append(typeArgs[nextTypeArg++]);
                b.append('>');
            } else if(local instanceof TypeVariable) {
                // reify local type arg to instantiated one.
                localArgs[nextTypeArg] = typeArgs[nextTypeArg];
                b.append(localArgs[nextTypeArg]);
                nextTypeArg++;
            } else {
                b.append(local.toString());
            }
            b.append(", ");
            i++;
        }
        if(typeArgs.length > 0) {
            b.delete(b.length() - 2, b.length());
        }
        b.append('>');
    } else {
        String args = Arrays.toString(localArgs);
        b.append(args.substring(1, args.length()-1)).append('>');
    }
    System.out.println(b);
    investigate(c, localArgs);
}

If, however, instantiations of Awesome<?> or Base<E> will be made, that type information will be lost due to erasure. This can be worked around, as a convention, with something like this:

Awesome<?> awesome = new Base<Double>() {};

Notice the {}, this creates a new anonymous class that implements (or here extends) Base<E>. This class will have its type parameters available for reflection.

If you're afraid enforcing this convention will be an issue, you can hide the constructors & only expose factory methods:

class Base<E> implements Awesome<Set<E>> {

    public static Base<Number> newNumberInstance() {
        return new Base<Number> () {};
    }

    protected Base() {}
}

As the above code has not been completely tested, you may want to do that. The point here is that you can find the actual type parameters given your requirements are stringent enough. Whether or not that applies to your situation is up to you to determine.

[1] It will print out all of the interfaces a class implements & not just the type parameters of Awesome. This can be changed, but I figured I'd go for more general & let you work out the specifics. For example, you'll want to test these to see what I mean:

investigate(new ArrayList<Integer>());
investigate(new ArrayList<String>() {}); // new anonymous ArrayList class
investigate("");
investigate(new Awesome<Comparable<?>> () {}); // new anonymous implementation of Awesome
like image 51
oconnor0 Avatar answered Nov 15 '22 13:11

oconnor0


Following proposal of @oconnor0, here is how to do it with gentyref:

static Type investigate(Awesome<?> somethingAwesome) {
    return GenericTypeReflector.getTypeParameter(somethingAwesome.getClass(), Awesome.class.getTypeParameters()[0]);
}

In case somethingAwesome.getClass() could be also generic, it might be useful to pass it through GenericTypeReflector.addWildcardParameters first.

Spring has also a GenericTypeResolver.resolveTypeArgument(Class,Class) which can achieve the same result.

like image 1
Didier L Avatar answered Nov 15 '22 12:11

Didier L