Selena
Selena

Reputation: 2268

Cannot resolve generic parameter TypeToken from Guava

I am developing a framework for building generified menus for a Selenium testing framework, and I've been using Guava TypeToken to resolve the types of generic parameters, but now I've run into a problem where the type token doesn't resolve a parameter:

I have an abstract base class for a builder that generates a menu option:

public abstract class AbstractMenuOptionBuilder<O extends IClickable>  {

    protected final TypeToken<AbstractMenuOptionBuilder<O>> typeToken = new
            TypeToken<AbstractMenuOptionBuilder<O>>(getClass()) { };

    public abstract O create();
}

This is a concrete class for a builder:

public class MenuOptionBuilder<O extends IClickable> extends AbstractMenuOptionBuilder<O> {

    public O create() {
        TypeToken<?> genericOptionParam = typeToken.resolveType(AbstractMenuOptionBuilder.class.getTypeParameters()[0]);

        Class<O> optionClass;

        try {

            optionClass = (Class<O>) Class.forName(genericOptionParam.getType().getTypeName());

             <.... snip ....>

        } catch(ClassNotFoundException e) {
            log.catching(e);
            return null;
        }
    }
}

I have an abstract base class for menus which has a method to return a list of menu options:

public abstract class AbstractMenu<O extends IClickable> {

    public final List<O> getOptions() {

        //This is where my plan doesn't work. The runtime type is given by
        //a concrete menu class which extends AbstractMenu, but that runtime
        //type doesn't seem to pass through to the abstract base class for the builder.
        MenuOptionBuilder<O> builder = new MenuOptionBuilder<O>(new MenuOptionBean()){};

             <.... snip ....>
    }

}

And I have a concrete menu class that extends it:

   //The runtime type of 'Link' is not known by the type token that is supposed to
   //resolve it in the abstract builder base class.
   public SimpleMenu extends AbstractMenu<Link> {
       <.... snip ....>
   }

I was expected that the variable genericOptionParam in MenuOptionBuilder would resolve to Link, but it doesn't Instead, it resolved to O, the name of the generic type parameter instead of its runtime type of Link. If I create an additional base class like this, the generic parameter resolves correctly:

public abstract class AbstractSimpleLinkedMenu extends AbstractMenu<Link> {

    public final List<Link> getOptions() {

        MenuOptionBuilder<Link> builder = new MenuOptionBuilder<Link>(new MenuOptionBean()){};
        <.... snip ....>
    }
} 

I would prefer not to have to add additional base classes like AbstractSimpleLinkedMenu, so is there something I have missed or done incorrectly here? I thought that the anonymous inner class for the abstract builder would know the runtime type, expect that it doesn't if the builder is declared with a generic parameter. The runtime type is specified by the concrete menu class, SimpleMenu, but it doesn't seem to filter through to the abstract builder class for menu options.

Upvotes: 0

Views: 1909

Answers (1)

Sotirios Delimanolis
Sotirios Delimanolis

Reputation: 280132

That's how the TypeToken "hack" works. It uses Class#getGenericSuperclass() (or getGenericSuperInterface). Its javadoc states

If the superclass is a parameterized type, the Type object returned must accurately reflect the actual type parameters used in the source code.

In this case, that is O, here

public abstract class AbstractMenuOptionBuilder<O extends IClickable>

You get what is hard coded in the source code. If you hard code Link as the type argument, as you do here

MenuOptionBuilder<Link> builder = 
    new MenuOptionBuilder<Link>(new MenuOptionBean()) {};

then you will get Link.

In this case

MenuOptionBuilder<O> builder = 
    new MenuOptionBuilder<O>(new MenuOptionBean()){};

you've hard coded O, so that's what you will get.

Here are some more things I've written on the subject of type tokens:

Upvotes: 4

Related Questions