Selena Selena - 3 months ago 19
Java Question

Guava TypeToken isn't able to resolve generic parameter

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.

Answer

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: