3
votes

Is it possible to automate bindings of generic classes? Consider this:

Generic interface:

public interface IAction<T> {
     T echo(T inst);
}

Long subtype:

public class LongAction implements IAction<Long> {    
    @Override
    public Long echo(Long inst) {return inst;}
}

String subtype:

public class StringAction implements IAction<String> {
    @Override
    public String echo(String inst) {return inst;}
}

Custom module: public class CustomModule extends AbstractModule {

@Override
protected void configure() {
  //do this automagically?
  bind(new TypeLiteral<IAction<String>>() {}).to(StringAction.class);
  bind(new TypeLiteral<IAction<Long>>() {}).to(LongAction.class);
  //
}

}

Main

// i know i can obtain the correct instance
IAction<String> is = injector.getInstance(new Key<IAction<String>>(){});

Is it possible to auto bind, in some way(eg: base abstract class, reflection or whatever) the binding of the StringAction and LongAction classes? I've tried using reflection to no avail.

2
How do you want to automatically discover LongAction, StringAction, etc? Are they in a particular package or something? What if two classes implement IAction<String>? - Tavian Barnes
@TavianBarnes they are in a particular package. I don't mind assuming there's no two impls for the same T for now, so there won't be two IAction<String> - Miguel Ping

2 Answers

0
votes

You'll have to do some sort of classpath scanning if you don't want to explicitly list the implementations. Guava has some support with its ClassPath class, for example.

Note that classpath scanning is somewhat antithetical to the design philosophy of Guice. Is it really so much extra effort to add one line to a module for every implementation you write?

0
votes

I somehow managed to trick the compiler.

class CustomModule extends AbstractModule {

static class Holder<T> {
    Class<T> param;
    Class<IAction<T>> klass;

    Holder(Class<?> p, Class<IAction<T>> k) {
        param = (Class<T>) p;
        klass = k;
    }
}

// this would be similar to the result of classpath scanning
List<IAction<?>> stuff;

public <T> void bindSome(Class<T> typeParameter, Class<IAction<T>> implementation) {
    Type parameterizedType = Types.newParameterizedType(IAction.class, typeParameter);
    bind((TypeLiteral<IAction<T>>) TypeLiteral.get(parameterizedType)).to(implementation);
}

public CustomModule() {
    stuff = new ArrayList<>();
    stuff.add(new StringAction());
    stuff.add(new LongAction());
}

@Override
protected void configure() {
    for (IAction<?> act : stuff) {
        System.out.printf("Configuring %s for %s\n", act.getTypeArguments(), act.getClass());
        //the following doesn't work because the compiler cannot understand that 
        //the 1st argument T is the same T in 2nd argument Class<T>
        //bindSome(act.getTypeArguments(), act.getClass());

        //w00t? compiler is tricked?? <String> is erased, but the holder preserves proper class?
        Holder<String> holder = new Holder(act.getTypeArguments(), (Class<IAction<String>>) act.getClass());
        bindSome(holder.param, holder.klass);

        //this is what I want to avoid doing manually
        //bind(new TypeLiteral<IAction<String>>() {}).to(StringAction.class);
    }
}
}