/*
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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 Map
<String, Snack>
can now be injected:
class SnackMachine { @Inject public SnackMachine(Map<String, Snack> snacks) { ... } }
In addition to binding Map<K, V>
, a mapbinder will also bind Map<K,
Provider<V>>
for lazy value provision:
class SnackMachine { @Inject public SnackMachine(Map<String, Provider<Snack>> snackProviders) { ... } }
Contributing mapbindings from different modules is supported. For example, it is okay to have both CandyModule
and ChipsModule
both create their own MapBinder<String,
Snack>
, 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 permitDuplicates()
in order to allow duplicate keys; extra bindings to Map<K, Set<V>>
and Map<K, Set<Provider<V>>
will be added.
Keys must be non-null. 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).
Author: dpb@google.com (David P. Baker)
/**
* 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:
*
* <pre><code>
* 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);
* }
* }</code></pre>
*
* <p>With this binding, a {@link Map}{@code <String, Snack>} can now be injected:
*
* <pre><code>
* class SnackMachine {
* {@literal @}Inject
* public SnackMachine(Map<String, Snack> snacks) { ... }
* }</code></pre>
*
* <p>In addition to binding {@code Map<K, V>}, a mapbinder will also bind {@code Map<K,
* Provider<V>>} for lazy value provision:
*
* <pre><code>
* class SnackMachine {
* {@literal @}Inject
* public SnackMachine(Map<String, Provider<Snack>> snackProviders) { ... }
* }</code></pre>
*
* <p>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<String,
* Snack>}, and to each contribute bindings to the snacks map. When that map is injected, it will
* contain entries from both modules.
*
* <p>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.
*
* <p>The map is unmodifiable. Elements can only be added to the map by configuring the MapBinder.
* Elements can never be removed from the map.
*
* <p>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).
*
* <p>Annotations are used to create different maps of the same key/value type. Each distinct
* annotation gets its own independent map.
*
* <p><strong>Keys must be distinct.</strong> 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<K, Set<V>>} and {@code Map<K, Set<Provider<V>>} will be added.
*
* <p><strong>Keys must be non-null.</strong> {@code addBinding(null)} will throw an unchecked
* exception.
*
* <p><strong>Values must be non-null to use map injection.</strong> If any value is null, map
* injection will fail (although injecting a map of providers will not).
*
* @author dpb@google.com (David P. Baker)
*/
public class MapBinder<K, V> {
// This class is non-final due to users mocking this in tests :(
Returns a new mapbinder that collects entries of keyType
/valueType
in a Map
that is itself bound with no binding annotation. /**
* 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 <K, V> MapBinder<K, V> newMapBinder(
Binder binder, TypeLiteral<K> keyType, TypeLiteral<V> valueType) {
return new MapBinder<K, V>(
newMapRealBinder(binder.skipSources(MapBinder.class), keyType, valueType));
}
Returns a new mapbinder that collects entries of keyType
/valueType
in a Map
that is itself bound with no binding annotation. /**
* 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 <K, V> MapBinder<K, V> newMapBinder(
Binder binder, Class<K> keyType, Class<V> valueType) {
return newMapBinder(binder, TypeLiteral.get(keyType), TypeLiteral.get(valueType));
}
Returns a new mapbinder that collects entries of keyType
/valueType
in a Map
that is itself bound with 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 <K, V> MapBinder<K, V> newMapBinder(
Binder binder, TypeLiteral<K> keyType, TypeLiteral<V> valueType, Annotation annotation) {
return new MapBinder<K, V>(
newRealMapBinder(binder.skipSources(MapBinder.class), keyType, valueType, annotation));
}
Returns a new mapbinder that collects entries of keyType
/valueType
in a Map
that is itself bound with 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 <K, V> MapBinder<K, V> newMapBinder(
Binder binder, Class<K> keyType, Class<V> valueType, Annotation annotation) {
return newMapBinder(binder, TypeLiteral.get(keyType), TypeLiteral.get(valueType), annotation);
}
Returns a new mapbinder that collects entries of keyType
/valueType
in a Map
that is itself bound with 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 <K, V> MapBinder<K, V> newMapBinder(
Binder binder,
TypeLiteral<K> keyType,
TypeLiteral<V> valueType,
Class<? extends Annotation> annotationType) {
return new MapBinder<K, V>(
newRealMapBinder(binder.skipSources(MapBinder.class), keyType, valueType, annotationType));
}
Returns a new mapbinder that collects entries of keyType
/valueType
in a Map
that is itself bound with 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 <K, V> MapBinder<K, V> newMapBinder(
Binder binder,
Class<K> keyType,
Class<V> valueType,
Class<? extends Annotation> annotationType) {
return newMapBinder(
binder, TypeLiteral.get(keyType), TypeLiteral.get(valueType), annotationType);
}
private final RealMapBinder<K, V> delegate;
private MapBinder(RealMapBinder<K, V> delegate) {
this.delegate = delegate;
}
Configures the 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 Map<K, V>
and Map<K, Provider<V>>
maps that are normally bound, a Map<K, Set<V>>
and Map<K, Set<Provider<V>>>
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.
Returns: this map binder Since: 3.0
/**
* Configures the {@code MapBinder} to handle duplicate entries.
*
* <p>When multiple equal keys are bound, the value that gets included in the map is arbitrary.
*
* <p>In addition to the {@code Map<K, V>} and {@code Map<K, Provider<V>>} maps that are normally
* bound, a {@code Map<K, Set<V>>} and {@code Map<K, Set<Provider<V>>>} are <em>also</em> bound,
* which contain all values bound to each key.
*
* <p>When multiple modules contribute elements to the map, this configuration option impacts all
* of them.
*
* @return this map binder
* @since 3.0
*/
public MapBinder<K, V> 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 to
methods on the returned binding builder.
Scoping elements independently is supported. Use the in
method to specify a binding scope.
/**
* 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.
*
* <p>It is an error to call this method without also calling one of the {@code to} methods on the
* returned binding builder.
*
* <p>Scoping elements independently is supported. Use the {@code in} method to specify a binding
* scope.
*/
public LinkedBindingBuilder<V> 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();
}
}