2

I have several classes implementing interface Provider<Communication> and I'm using Guice with a @Named annotation to bind them as required, e.g.:

@Singleton
public class Verizon implements Provider<Call> {
  ...
}

@Singleton
public class TMobile implements Provider<Call> {
  ...
}

bind (new TypeLiteral<Provider<Call>>() {}).annotatedWith(
  Names.named("Verizon")).to(Verizon.class);

bind (new TypeLiteral<Provider<Call>>() {}).annotatedWith(
  Names.named("TMobile")).to(TMobile.class);

Is there a clean way to implement a factory that takes the name as a parameter, e.g.:

public static <C extends Communication> Provider<C> getCallProvider(C communication) {
  String providerName = communication.getProviderName();

  return [Guice's matching object for type Provider<?> and @Named = providerName];
}

I've attempted using Injector but Guice won't take a generic as parameter to TypeLiteral:

public <C extends Communication> Provider<C> getCommunicationProvider(C communication) {
  return injector.getInstance(Key.get(new TypeLiteral<CommunicationProvider<C>>() {},
    Names.named(communication.getProvider().getId())));
}

This throws:

com.google.inject.ConfigurationException: Guice configuration errors:
  1) Provider<C> cannot be used as a key; It is not fully specified.
REAPP3R
  • 206
  • 2
  • 7

2 Answers2

3

Providers are managed by Guice; when you bind a Foo or Provider<Foo> correctly, you should be able to ask for a Foo or Provider<Foo> without any additional work. Therefore, you probably don't want this:

bind (new TypeLiteral<Provider<Call>>() {}).annotatedWith(
  Names.named("Verizon")).to(Verizon.class);

Instead you probably want this:

bind(Call.class).annotatedWith(Names.named("Verizon")).toProvider(Verizon.class);

...which will let you inject @Named("Verizon") Provider<Call> but also @Named("Verizon") call. At that point your original request is as easy as this:

/**
 * Because of erasure, at compile time the injector can only guarantee that it
 * returns something that extends Communication, not necessarily C. The cast and
 * @SuppressWarnings will help with that.
 */
@SuppressWarnings("unchecked")
public static <C extends Communication> Provider<C> getCommunicationProvider(
    C communication) {
  return (Provider<C>) injector.getProvider(Key.get(communication.getClass(),
      Names.named(communication.toString())));
}

Note also that because of erasure, there's no other way to get a class literal of type C, so using a mock or dynamic proxy of Call will fail.

If you wanted to bind SomeOtherInterface<Call> instead of Provider<Call>, you could still do so, but you would need to dynamically create a ParameterizedType using Guice's Types util class and use that as the input to Key#get(Type, Annotation). For a little more context on creating a ParameterizedType implementation, read up at this SO answer.

Community
  • 1
  • 1
Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
0

I think it is not possible. You could write a factory yourself and change your code from using the interface to using the factory. Or you can bind your Interface to a Provder (but this will lead to more code not to less).

bind (new TypeLiteral<Provider<Call>>() {}).annotatedWith(
    Names.named("Verizon")).toProvider(new Provider<Provider<Call>>(){public Provider get(){return new Verizon();}});

(Or is YOUR Provider the Guice-Provider?)

Christian Kuetbach
  • 15,850
  • 5
  • 43
  • 79
  • The problem with that approach is that the Factory would have to be aware of all possible types - I am on the other hand trying to keep things as generic as possible. – REAPP3R Sep 23 '13 at 22:00
  • A factory should know, what to create ... If not, use Reflections-API and use a naming-pattern to find a concrete implementation. But I think this is more ugly, than a factory knowing all classes. – Christian Kuetbach Sep 23 '13 at 22:21