All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.inject.multibindings.MapBinder Maven / Gradle / Ivy

There is a newer version: 4.2.3
Show newest version
/**
 * 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 com.google.inject.Binder;
import com.google.inject.Binding;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.internal.util.ImmutableList;
import com.google.inject.internal.util.ImmutableMap;
import com.google.inject.internal.util.ImmutableSet;
import com.google.inject.internal.util.Lists;
import com.google.inject.multibindings.Multibinder.RealMultibinder;
import static com.google.inject.multibindings.Multibinder.checkConfiguration;
import static com.google.inject.multibindings.Multibinder.checkNotNull;
import static com.google.inject.multibindings.Multibinder.setOf;

import com.google.inject.spi.BindingTargetVisitor;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.ProviderInstanceBinding;
import com.google.inject.spi.ProviderLookup;
import com.google.inject.spi.ProviderWithDependencies;
import com.google.inject.spi.ProviderWithExtensionVisitor;
import com.google.inject.spi.Toolable;
import com.google.inject.util.Types;
import static com.google.inject.util.Types.newParameterizedTypeWithOwner;
import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * 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). * * @author [email protected] (David P. Baker) */ public abstract class MapBinder { private 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) { binder = binder.skipSources(MapBinder.class, RealMapBinder.class); return newMapBinder(binder, keyType, valueType, Key.get(mapOf(keyType, valueType)), Key.get(mapOfProviderOf(keyType, valueType)), Key.get(mapOf(keyType, setOf(valueType))), Key.get(mapOfSetOfProviderOf(keyType, valueType)), Multibinder.newSetBinder(binder, entryOfProviderOf(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) { binder = binder.skipSources(MapBinder.class, RealMapBinder.class); return newMapBinder(binder, keyType, valueType, Key.get(mapOf(keyType, valueType), annotation), Key.get(mapOfProviderOf(keyType, valueType), annotation), Key.get(mapOf(keyType, setOf(valueType)), annotation), Key.get(mapOfSetOfProviderOf(keyType, valueType), annotation), Multibinder.newSetBinder(binder, entryOfProviderOf(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 annotationType) { binder = binder.skipSources(MapBinder.class, RealMapBinder.class); return newMapBinder(binder, keyType, valueType, Key.get(mapOf(keyType, valueType), annotationType), Key.get(mapOfProviderOf(keyType, valueType), annotationType), Key.get(mapOf(keyType, setOf(valueType)), annotationType), Key.get(mapOfSetOfProviderOf(keyType, valueType), annotationType), Multibinder.newSetBinder(binder, entryOfProviderOf(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 annotationType) { return newMapBinder( binder, TypeLiteral.get(keyType), TypeLiteral.get(valueType), annotationType); } @SuppressWarnings("unchecked") // a map of is safely a Map static TypeLiteral> mapOf( TypeLiteral keyType, TypeLiteral valueType) { return (TypeLiteral>) TypeLiteral.get( Types.mapOf(keyType.getType(), valueType.getType())); } @SuppressWarnings("unchecked") // a provider map is safely a Map> static TypeLiteral>> mapOfProviderOf( TypeLiteral keyType, TypeLiteral valueType) { return (TypeLiteral>>) TypeLiteral.get( Types.mapOf(keyType.getType(), Types.providerOf(valueType.getType()))); } @SuppressWarnings("unchecked") // a provider map > is safely a Map>> static TypeLiteral>>> mapOfSetOfProviderOf( TypeLiteral keyType, TypeLiteral valueType) { return (TypeLiteral>>>) TypeLiteral.get( Types.mapOf(keyType.getType(), Types.setOf(Types.providerOf(valueType.getType())))); } @SuppressWarnings("unchecked") // a provider entry is safely a Map.Entry> static TypeLiteral>> entryOfProviderOf( TypeLiteral keyType, TypeLiteral valueType) { return (TypeLiteral>>) TypeLiteral.get(newParameterizedTypeWithOwner( Map.class, Entry.class, keyType.getType(), Types.providerOf(valueType.getType()))); } private static MapBinder newMapBinder(Binder binder, TypeLiteral keyType, TypeLiteral valueType, Key> mapKey, Key>> providerMapKey, Key>> multimapKey, Key>>> providerMultimapKey, Multibinder>> entrySetBinder) { RealMapBinder mapBinder = new RealMapBinder( binder, keyType, valueType, mapKey, providerMapKey, multimapKey, providerMultimapKey, entrySetBinder); binder.install(mapBinder); return mapBinder; } /** * 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 * @since 3.0 */ public abstract MapBinder permitDuplicates(); /** * 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 abstract LinkedBindingBuilder addBinding(K key); /** * The actual mapbinder plays several roles: * *

As a MapBinder, it acts as a factory for LinkedBindingBuilders for * each of the map's values. It delegates to a {@link Multibinder} of * entries (keys to value providers). * *

As a Module, it installs the binding to the map itself, as well as to * a corresponding map whose values are providers. It uses the entry set * multibinder to construct the map and the provider map. * *

As a module, this implements equals() and hashcode() in order to trick * Guice into executing its configure() method only once. That makes it so * that multiple mapbinders can be created for the same target map, but * only one is bound. Since the list of bindings is retrieved from the * injector itself (and not the mapbinder), each mapbinder has access to * all contributions from all equivalent mapbinders. * *

Rather than binding a single Map.Entry<K, V>, the map binder * binds keys and values independently. This allows the values to be properly * scoped. * *

We use a subclass to hide 'implements Module' from the public API. */ private static final class RealMapBinder extends MapBinder implements Module { private final TypeLiteral keyType; private final TypeLiteral valueType; private final Key> mapKey; private final Key>> providerMapKey; private final Key>> multimapKey; private final Key>>> providerMultimapKey; private final RealMultibinder>> entrySetBinder; /* the target injector's binder. non-null until initialization, null afterwards */ private Binder binder; private boolean permitDuplicates; private ImmutableList>> mapBindings; private RealMapBinder(Binder binder, TypeLiteral keyType, TypeLiteral valueType, Key> mapKey, Key>> providerMapKey, Key>> multimapKey, Key>>> providerMultimapKey, Multibinder>> entrySetBinder) { this.keyType = keyType; this.valueType = valueType; this.mapKey = mapKey; this.providerMapKey = providerMapKey; this.multimapKey = multimapKey; this.providerMultimapKey = providerMultimapKey; this.entrySetBinder = (RealMultibinder>>) entrySetBinder; this.binder = binder; } @Override public MapBinder permitDuplicates() { entrySetBinder.permitDuplicates(); binder.install(new MultimapBinder( multimapKey, providerMultimapKey, entrySetBinder.getSetKey())); return this; } /** * This creates two bindings. One for the {@code Map.Entry>} * and another for {@code V}. */ @Override public LinkedBindingBuilder addBinding(K key) { checkNotNull(key, "key"); checkConfiguration(!isInitialized(), "MapBinder was already initialized"); Key valueKey = Key.get(valueType, new RealElement(entrySetBinder.getSetName())); entrySetBinder.addBinding().toInstance(new MapEntry>(key, binder.getProvider(valueKey), valueKey)); return binder.bind(valueKey); } public void configure(Binder binder) { checkConfiguration(!isInitialized(), "MapBinder was already initialized"); final ImmutableSet> dependencies = ImmutableSet.>of(Dependency.get(entrySetBinder.getSetKey())); // Binds a Map> from a collection of Set>. final Provider>>> entrySetProvider = binder .getProvider(entrySetBinder.getSetKey()); binder.bind(providerMapKey).toProvider(new RealMapBinderProviderWithDependencies>>(mapKey) { private Map> providerMap; @SuppressWarnings({ "unused", "unchecked" }) @Toolable @Inject void initialize(Injector injector) { RealMapBinder.this.binder = null; permitDuplicates = entrySetBinder.permitsDuplicates(injector); Map> providerMapMutable = new LinkedHashMap>(); List>> bindingsMutable = Lists.newArrayList(); for (Entry> entry : entrySetProvider.get()) { Provider previous = providerMapMutable.put(entry.getKey(), entry.getValue()); checkConfiguration(previous == null || permitDuplicates, "Map injection failed due to duplicated key \"%s\"", entry.getKey()); Key valueKey = (Key)((MapEntry)entry).getValueKey(); bindingsMutable.add(new MapEntry(entry.getKey(), injector.getBinding(valueKey), valueKey)); } providerMap = ImmutableMap.copyOf(providerMapMutable); mapBindings = ImmutableList.copyOf(bindingsMutable); } public Map> get() { return providerMap; } public Set> getDependencies() { return dependencies; } }); final Provider>> mapProvider = binder.getProvider(providerMapKey); binder.bind(mapKey).toProvider(new RealMapWithExtensionProvider>(mapKey) { public Map get() { Map map = new LinkedHashMap(); for (Entry> entry : mapProvider.get().entrySet()) { V value = entry.getValue().get(); K key = entry.getKey(); checkConfiguration(value != null, "Map injection failed due to null value for key \"%s\"", key); map.put(key, value); } return Collections.unmodifiableMap(map); } public Set> getDependencies() { return dependencies; } @SuppressWarnings("unchecked") public R acceptExtensionVisitor(BindingTargetVisitor visitor, ProviderInstanceBinding binding) { if (visitor instanceof MultibindingsTargetVisitor) { return ((MultibindingsTargetVisitor, R>)visitor).visit(this); } else { return visitor.visit(binding); } } public Key> getMapKey() { return mapKey; } public TypeLiteral getKeyTypeLiteral() { return keyType; } public TypeLiteral getValueTypeLiteral() { return valueType; } @SuppressWarnings("unchecked") public List>> getEntries() { if (isInitialized()) { return (List)mapBindings; // safe because mapBindings is immutable } else { throw new UnsupportedOperationException("getElements() not supported for module bindings"); } } public boolean permitsDuplicates() { if (isInitialized()) { return permitDuplicates; } else { throw new UnsupportedOperationException("permitsDuplicates() not supported for module bindings"); } } public boolean containsElement(com.google.inject.spi.Element element) { if (entrySetBinder.containsElement(element)) { return true; } else { Key key; if (element instanceof Binding) { key = ((Binding)element).getKey(); } else if (element instanceof ProviderLookup) { key = ((ProviderLookup)element).getKey(); } else { return false; // cannot match; } return key.equals(mapKey) || key.equals(providerMapKey) || key.equals(multimapKey) || key.equals(providerMultimapKey) || key.equals(entrySetBinder.getSetKey()) || matchesValueKey(key); } } }); } /** Returns true if the key indicates this is a value in the map. */ private boolean matchesValueKey(Key key) { return key.getAnnotation() instanceof Element && ((Element) key.getAnnotation()).setName().equals(entrySetBinder.getSetName()) && key.getTypeLiteral().equals(valueType); } private boolean isInitialized() { return binder == null; } @Override public boolean equals(Object o) { return o instanceof RealMapBinder && ((RealMapBinder) o).mapKey.equals(mapKey); } @Override public int hashCode() { return mapKey.hashCode(); } /** * Binds {@code Map>} and {{@code Map>>}. */ private static final class MultimapBinder implements Module { private final Key>> multimapKey; private final Key>>> providerMultimapKey; private final Key>>> entrySetKey; public MultimapBinder( Key>> multimapKey, Key>>> providerMultimapKey, Key>>> entrySetKey) { this.multimapKey = multimapKey; this.providerMultimapKey = providerMultimapKey; this.entrySetKey = entrySetKey; } public void configure(Binder binder) { final ImmutableSet> dependencies = ImmutableSet.>of(Dependency.get(entrySetKey)); final Provider>>> entrySetProvider = binder.getProvider(entrySetKey); // Binds a Map>> from a collection of Map> if // permitDuplicates was called. binder.bind(providerMultimapKey).toProvider( new RealMapBinderProviderWithDependencies>>>(multimapKey) { private Map>> providerMultimap; @SuppressWarnings("unused") @Inject void initialize(Injector injector) { Map>> providerMultimapMutable = new LinkedHashMap>>(); for (Entry> entry : entrySetProvider.get()) { if (!providerMultimapMutable.containsKey(entry.getKey())) { providerMultimapMutable.put( entry.getKey(), ImmutableSet.>builder()); } providerMultimapMutable.get(entry.getKey()).add(entry.getValue()); } ImmutableMap.Builder>> providerMultimapBuilder = ImmutableMap.builder(); for (Entry>> entry : providerMultimapMutable.entrySet()) { providerMultimapBuilder.put(entry.getKey(), entry.getValue().build()); } providerMultimap = providerMultimapBuilder.build(); } public Map>> get() { return providerMultimap; } public Set> getDependencies() { return dependencies; } }); final Provider>>> multimapProvider = binder.getProvider(providerMultimapKey); binder.bind(multimapKey).toProvider(new RealMapBinderProviderWithDependencies>>(multimapKey) { public Map> get() { ImmutableMap.Builder> multimapBuilder = ImmutableMap.builder(); for (Entry>> entry : multimapProvider.get().entrySet()) { K key = entry.getKey(); ImmutableSet.Builder valuesBuilder = ImmutableSet.builder(); for (Provider valueProvider : entry.getValue()) { V value = valueProvider.get(); checkConfiguration(value != null, "Multimap injection failed due to null value for key \"%s\"", key); valuesBuilder.add(value); } multimapBuilder.put(key, valuesBuilder.build()); } return multimapBuilder.build(); } public Set> getDependencies() { return dependencies; } }); } } private static final class MapEntry implements Map.Entry { private final K key; private final V value; private final Key valueKey; private MapEntry(K key, V value, Key valueKey) { this.key = key; this.value = value; this.valueKey = valueKey; } public Key getValueKey() { return valueKey; } public K getKey() { return key; } public V getValue() { return value; } public V setValue(V value) { throw new UnsupportedOperationException(); } @Override public boolean equals(Object obj) { return obj instanceof Map.Entry && key.equals(((Map.Entry) obj).getKey()) && value.equals(((Map.Entry) obj).getValue()); } @Override public int hashCode() { return 127 * ("key".hashCode() ^ key.hashCode()) + 127 * ("value".hashCode() ^ value.hashCode()); } @Override public String toString() { return "MapEntry(" + key + ", " + value + ")"; } } private static abstract class RealMapWithExtensionProvider extends RealMapBinderProviderWithDependencies implements ProviderWithExtensionVisitor, MapBinderBinding { public RealMapWithExtensionProvider(Object equality) { super(equality); } } /** * A base class for ProviderWithDependencies that need equality * based on a specific object. */ private static abstract class RealMapBinderProviderWithDependencies implements ProviderWithDependencies { private final Object equality; public RealMapBinderProviderWithDependencies(Object equality) { this.equality = equality; } @Override public boolean equals(Object obj) { return this.getClass() == obj.getClass() && equality.equals(((RealMapBinderProviderWithDependencies)obj).equality); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy