com.google.inject.multibindings.MapBinder Maven / Gradle / Ivy
package com.google.inject.multibindings;
import static com.google.inject.internal.RealMapBinder.newMapRealBinder;
import static com.google.inject.internal.RealMapBinder.newRealMapBinder;
import com.google.inject.Binder;
import com.google.inject.TypeLiteral;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.internal.RealMapBinder;
import java.lang.annotation.Annotation;
import java.util.Map;
/**
* An API to bind multiple map entries separately, only to later inject them as
* a complete map. MapBinder is intended for use in your application's module:
*
* public class SnacksModule extends AbstractModule {
* protected void configure() {
* MapBinder<String, Snack> mapbinder
* = MapBinder.newMapBinder(binder(), String.class, Snack.class);
* mapbinder.addBinding("twix").toInstance(new Twix());
* mapbinder.addBinding("snickers").toProvider(SnickersProvider.class);
* mapbinder.addBinding("skittles").to(Skittles.class);
* }
* }
*
* With this binding, a {@link Map}{@code } can now be
* injected:
*
* class SnackMachine {
* {@literal @}Inject
* public SnackMachine(Map<String, Snack> snacks) { ... }
* }
*
* In addition to binding {@code Map}, a mapbinder will also bind
* {@code Map>} for lazy value provision:
*
* class SnackMachine {
* {@literal @}Inject
* public SnackMachine(Map<String, Provider<Snack>> snackProviders) { ... }
* }
*
* Contributing mapbindings from different modules is supported. For example,
* it is okay to have both {@code CandyModule} and {@code ChipsModule} both
* create their own {@code MapBinder}, and to each contribute
* bindings to the snacks map. When that map is injected, it will contain
* entries from both modules.
*
* The map's iteration order is consistent with the binding order. This is
* convenient when multiple elements are contributed by the same module because
* that module can order its bindings appropriately. Avoid relying on the
* iteration order of elements contributed by different modules, since there is
* no equivalent mechanism to order modules.
*
*
The map is unmodifiable. Elements can only be added to the map by
* configuring the MapBinder. Elements can never be removed from the map.
*
*
Values are resolved at map injection time. If a value is bound to a
* provider, that provider's get method will be called each time the map is
* injected (unless the binding is also scoped, or a map of providers is injected).
*
*
Annotations are used to create different maps of the same key/value
* type. Each distinct annotation gets its own independent map.
*
*
Keys must be distinct. If the same key is bound more than
* once, map injection will fail. However, use {@link #permitDuplicates()} in
* order to allow duplicate keys; extra bindings to {@code Map>} and
* {@code Map>} will be added.
*
* Keys must be non-null. {@code addBinding(null)} will
* throw an unchecked exception.
*
*
Values must be non-null to use map injection. If any
* value is null, map injection will fail (although injecting a map of providers
* will not).
*/
public class MapBinder {
/**
* Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a
* {@link Map} that is itself bound with no binding annotation.
*/
public static MapBinder newMapBinder(Binder binder,
TypeLiteral keyType,
TypeLiteral valueType) {
return new MapBinder(
newMapRealBinder(binder.skipSources(MapBinder.class), keyType, valueType));
}
/**
* Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a
* {@link Map} that is itself bound with no binding annotation.
*/
public static MapBinder newMapBinder(Binder binder,
Class keyType, Class valueType) {
return newMapBinder(binder, TypeLiteral.get(keyType), TypeLiteral.get(valueType));
}
/**
* Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a
* {@link Map} that is itself bound with {@code annotation}.
*/
public static MapBinder newMapBinder(Binder binder,
TypeLiteral keyType,
TypeLiteral valueType,
Annotation annotation) {
return new MapBinder(
newRealMapBinder(binder.skipSources(MapBinder.class), keyType, valueType, annotation));
}
/**
* Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a
* {@link Map} that is itself bound with {@code annotation}.
*/
public static MapBinder newMapBinder(Binder binder,
Class keyType,
Class valueType,
Annotation annotation) {
return newMapBinder(binder, TypeLiteral.get(keyType), TypeLiteral.get(valueType), annotation);
}
/**
* Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a
* {@link Map} that is itself bound with {@code annotationType}.
*/
public static MapBinder newMapBinder(Binder binder,
TypeLiteral keyType,
TypeLiteral valueType,
Class extends Annotation> annotationType) {
return new MapBinder(
newRealMapBinder(binder.skipSources(MapBinder.class), keyType, valueType, annotationType));
}
/**
* Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a
* {@link Map} that is itself bound with {@code annotationType}.
*/
public static MapBinder newMapBinder(Binder binder,
Class keyType,
Class valueType,
Class extends Annotation> annotationType) {
return newMapBinder(binder, TypeLiteral.get(keyType), TypeLiteral.get(valueType), annotationType);
}
private final RealMapBinder delegate;
private MapBinder(RealMapBinder delegate) {
this.delegate = delegate;
}
/**
* Configures the {@code MapBinder} to handle duplicate entries.
* When multiple equal keys are bound, the value that gets included in the map is
* arbitrary.
*
In addition to the {@code Map} and {@code Map>}
* maps that are normally bound, a {@code Map>} and
* {@code Map>>} are also bound, which contain
* all values bound to each key.
*
* When multiple modules contribute elements to the map, this configuration
* option impacts all of them.
*
* @return this map binder
*/
public MapBinder permitDuplicates() {
delegate.permitDuplicates();
return this;
}
/**
* Returns a binding builder used to add a new entry in the map. Each
* key must be distinct (and non-null). Bound providers will be evaluated each
* time the map is injected.
*
* It is an error to call this method without also calling one of the
* {@code to} methods on the returned binding builder.
*
*
Scoping elements independently is supported. Use the {@code in} method
* to specify a binding scope.
*/
public LinkedBindingBuilder addBinding(K key) {
return delegate.addBinding(key);
}
// Some tests rely on MapBinder implementing equals/hashCode
@Override
public boolean equals(Object obj) {
if (obj instanceof MapBinder) {
return delegate.equals(((MapBinder, ?>) obj).delegate);
}
return false;
}
@Override
public int hashCode() {
return delegate.hashCode();
}
}