/** * 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.multibindings.Element.Type.MAPBINDER; 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 static com.google.inject.util.Types.newParameterizedType; import static com.google.inject.util.Types.newParameterizedTypeWithOwner; import com.google.common.base.Joiner; import com.google.common.base.Objects; import com.google.common.base.Supplier; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; import com.google.common.collect.Sets; 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.Errors; import com.google.inject.multibindings.Indexer.IndexedBinding; import com.google.inject.multibindings.Multibinder.RealMultibinder; import com.google.inject.spi.BindingTargetVisitor; import com.google.inject.spi.Dependency; import com.google.inject.spi.Element; import com.google.inject.spi.HasDependencies; 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 java.lang.annotation.Annotation; import java.util.Collection; 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: * <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 abstract class MapBinder<K, V> { 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 <K, V> MapBinder<K, V> newMapBinder(Binder binder, TypeLiteral<K> keyType, TypeLiteral<V> 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 <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 {@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) { 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 <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 {@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) { 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 <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); } @SuppressWarnings("unchecked") // a map of <K, V> is safely a Map<K, V> static <K, V> TypeLiteral<Map<K, V>> mapOf( TypeLiteral<K> keyType, TypeLiteral<V> valueType) { return (TypeLiteral<Map<K, V>>) TypeLiteral.get( Types.mapOf(keyType.getType(), valueType.getType())); } @SuppressWarnings("unchecked") // a provider map <K, V> is safely a Map<K, Provider<V>> static <K, V> TypeLiteral<Map<K, Provider<V>>> mapOfProviderOf( TypeLiteral<K> keyType, TypeLiteral<V> valueType) { return (TypeLiteral<Map<K, Provider<V>>>) TypeLiteral.get( Types.mapOf(keyType.getType(), Types.providerOf(valueType.getType()))); } // provider map <K, V> is safely a Map<K, javax.inject.Provider<V>>> @SuppressWarnings("unchecked") static <K, V> TypeLiteral<Map<K, javax.inject.Provider<V>>> mapOfJavaxProviderOf( TypeLiteral<K> keyType, TypeLiteral<V> valueType) { return (TypeLiteral<Map<K, javax.inject.Provider<V>>>) TypeLiteral.get( Types.mapOf(keyType.getType(), newParameterizedType(javax.inject.Provider.class, valueType.getType()))); } @SuppressWarnings("unchecked") // a provider map <K, Set<V>> is safely a Map<K, Set<Provider<V>>> static <K, V> TypeLiteral<Map<K, Set<Provider<V>>>> mapOfSetOfProviderOf( TypeLiteral<K> keyType, TypeLiteral<V> valueType) { return (TypeLiteral<Map<K, Set<Provider<V>>>>) TypeLiteral.get( Types.mapOf(keyType.getType(), Types.setOf(Types.providerOf(valueType.getType())))); } @SuppressWarnings("unchecked") // a provider entry <K, V> is safely a Map.Entry<K, Provider<V>> static <K, V> TypeLiteral<Map.Entry<K, Provider<V>>> entryOfProviderOf( TypeLiteral<K> keyType, TypeLiteral<V> valueType) { return (TypeLiteral<Entry<K, Provider<V>>>) TypeLiteral.get(newParameterizedTypeWithOwner( Map.class, Entry.class, keyType.getType(), Types.providerOf(valueType.getType()))); } private static <K, V> MapBinder<K, V> newMapBinder(Binder binder, TypeLiteral<K> keyType, TypeLiteral<V> valueType, Key<Map<K, V>> mapKey, Key<Map<K, Provider<V>>> providerMapKey, Key<Map<K, Set<V>>> multimapKey, Key<Map<K, Set<Provider<V>>>> providerMultimapKey, Multibinder<Entry<K, Provider<V>>> entrySetBinder) { RealMapBinder<K, V> mapBinder = new RealMapBinder<K, V>( binder, keyType, valueType, mapKey, providerMapKey, multimapKey, providerMultimapKey, entrySetBinder); binder.install(mapBinder); return mapBinder; } /** * 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 abstract MapBinder<K, V> 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. * * <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 abstract LinkedBindingBuilder<V> addBinding(K key); /** * The actual mapbinder plays several roles: * * <p>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). * * <p>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. * * <p>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. * * <p>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. * * <p>We use a subclass to hide 'implements Module' from the public API. */ static final class RealMapBinder<K, V> extends MapBinder<K, V> implements Module { private final TypeLiteral<K> keyType; private final TypeLiteral<V> valueType; private final Key<Map<K, V>> mapKey; private final Key<Map<K, javax.inject.Provider<V>>> javaxProviderMapKey; private final Key<Map<K, Provider<V>>> providerMapKey; private final Key<Map<K, Set<V>>> multimapKey; private final Key<Map<K, Set<Provider<V>>>> providerMultimapKey; private final RealMultibinder<Map.Entry<K, Provider<V>>> entrySetBinder; private final Map<K, String> duplicateKeyErrorMessages; /* the target injector's binder. non-null until initialization, null afterwards */ private Binder binder; private boolean permitDuplicates; private ImmutableList<Map.Entry<K, Binding<V>>> mapBindings; private RealMapBinder(Binder binder, TypeLiteral<K> keyType, TypeLiteral<V> valueType, Key<Map<K, V>> mapKey, Key<Map<K, Provider<V>>> providerMapKey, Key<Map<K, Set<V>>> multimapKey, Key<Map<K, Set<Provider<V>>>> providerMultimapKey, Multibinder<Map.Entry<K, Provider<V>>> entrySetBinder) { this.keyType = keyType; this.valueType = valueType; this.mapKey = mapKey; this.providerMapKey = providerMapKey; this.javaxProviderMapKey = providerMapKey.ofType(mapOfJavaxProviderOf(keyType, valueType)); this.multimapKey = multimapKey; this.providerMultimapKey = providerMultimapKey; this.entrySetBinder = (RealMultibinder<Entry<K, Provider<V>>>) entrySetBinder; this.binder = binder; this.duplicateKeyErrorMessages = Maps.newHashMap(); } /** Sets the error message to be shown if the key had duplicate non-equal bindings. */ void updateDuplicateKeyMessage(K k, String errMsg) { duplicateKeyErrorMessages.put(k, errMsg); } @Override public MapBinder<K, V> permitDuplicates() { entrySetBinder.permitDuplicates(); binder.install(new MultimapBinder<K, V>( multimapKey, providerMultimapKey, entrySetBinder.getSetKey())); return this; } /** * This creates two bindings. One for the {@code Map.Entry<K, Provider<V>>} * and another for {@code V}. */ @Override public LinkedBindingBuilder<V> addBinding(K key) { checkNotNull(key, "key"); checkConfiguration(!isInitialized(), "MapBinder was already initialized"); Key<V> valueKey = Key.get(valueType, new RealElement(entrySetBinder.getSetName(), MAPBINDER, keyType.toString())); entrySetBinder.addBinding().toProvider(new ProviderMapEntry<K, V>( key, binder.getProvider(valueKey), valueKey)); return binder.bind(valueKey); } @Override public void configure(Binder binder) { checkConfiguration(!isInitialized(), "MapBinder was already initialized"); ImmutableSet<Dependency<?>> dependencies = ImmutableSet.<Dependency<?>>of(Dependency.get(entrySetBinder.getSetKey())); // Binds a Map<K, Provider<V>> from a collection of Set<Entry<K, Provider<V>>. Provider<Set<Entry<K, Provider<V>>>> entrySetProvider = binder .getProvider(entrySetBinder.getSetKey()); binder.bind(providerMapKey).toProvider( new RealProviderMapProvider(dependencies, entrySetProvider)); // The map this exposes is internally an ImmutableMap, so it's OK to massage // the guice Provider to javax Provider in the value (since Guice provider // implements javax Provider). @SuppressWarnings("unchecked") Key massagedProviderMapKey = (Key)providerMapKey; binder.bind(javaxProviderMapKey).to(massagedProviderMapKey); Provider<Map<K, Provider<V>>> mapProvider = binder.getProvider(providerMapKey); binder.bind(mapKey).toProvider(new RealMapProvider(dependencies, mapProvider)); } boolean containsElement(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(javaxProviderMapKey) || 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 RealElement && ((RealElement) key.getAnnotation()).setName().equals(entrySetBinder.getSetName()) && ((RealElement) key.getAnnotation()).type() == MAPBINDER && ((RealElement) key.getAnnotation()).keyType().equals(keyType.toString()) && 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(); } final class RealProviderMapProvider extends RealMapBinderProviderWithDependencies<Map<K, Provider<V>>> { private final ImmutableSet<Dependency<?>> dependencies; private final Provider<Set<Entry<K, Provider<V>>>> entrySetProvider; private Map<K, Provider<V>> providerMap; private RealProviderMapProvider( ImmutableSet<Dependency<?>> dependencies, Provider<Set<Entry<K, Provider<V>>>> entrySetProvider) { super(mapKey); this.dependencies = dependencies; this.entrySetProvider = entrySetProvider; } @Toolable @Inject void initialize(Injector injector) { RealMapBinder.this.binder = null; permitDuplicates = entrySetBinder.permitsDuplicates(injector); Map<K, Provider<V>> providerMapMutable = new LinkedHashMap<K, Provider<V>>(); List<Map.Entry<K, Binding<V>>> bindingsMutable = Lists.newArrayList(); Indexer indexer = new Indexer(injector); Multimap<K, IndexedBinding> index = HashMultimap.create(); Set<K> duplicateKeys = null; for (Entry<K, Provider<V>> entry : entrySetProvider.get()) { ProviderMapEntry<K, V> providerEntry = (ProviderMapEntry<K, V>) entry; Key<V> valueKey = providerEntry.getValueKey(); Binding<V> valueBinding = injector.getBinding(valueKey); // If this isn't a dup due to an exact same binding, add it. if (index.put(entry.getKey(), valueBinding.acceptTargetVisitor(indexer))) { Provider<V> previous = providerMapMutable.put(entry.getKey(), entry.getValue()); if (previous != null && !permitDuplicates) { if (duplicateKeys == null) { duplicateKeys = Sets.newHashSet(); } duplicateKeys.add(entry.getKey()); } bindingsMutable.add(Maps.immutableEntry(entry.getKey(), valueBinding)); } } if (duplicateKeys != null) { // Must use a ListMultimap in case more than one binding has the same source // and is listed multiple times. Multimap<K, String> dups = newLinkedKeyArrayValueMultimap(); for (Map.Entry<K, Binding<V>> entry : bindingsMutable) { if (duplicateKeys.contains(entry.getKey())) { dups.put(entry.getKey(), "\t at " + Errors.convert(entry.getValue().getSource())); } } StringBuilder sb = new StringBuilder("Map injection failed due to duplicated key "); boolean first = true; for (K key : dups.keySet()) { if (first) { first = false; if (duplicateKeyErrorMessages.containsKey(key)) { sb.setLength(0); sb.append(duplicateKeyErrorMessages.get(key)); } else { sb.append("\"" + key + "\", from bindings:\n"); } } else { if (duplicateKeyErrorMessages.containsKey(key)) { sb.append("\n and " + duplicateKeyErrorMessages.get(key)); } else { sb.append("\n and key: \"" + key + "\", from bindings:\n"); } } Joiner.on('\n').appendTo(sb, dups.get(key)).append("\n"); } checkConfiguration(false, sb.toString()); } providerMap = ImmutableMap.copyOf(providerMapMutable); mapBindings = ImmutableList.copyOf(bindingsMutable); } @Override public Map<K, Provider<V>> get() { return providerMap; } @Override public Set<Dependency<?>> getDependencies() { return dependencies; } } final class RealMapProvider extends RealMapWithExtensionProvider<Map<K, V>> { private final ImmutableSet<Dependency<?>> dependencies; private final Provider<Map<K, Provider<V>>> mapProvider; private RealMapProvider( ImmutableSet<Dependency<?>> dependencies, Provider<Map<K, Provider<V>>> mapProvider) { super(mapKey); this.dependencies = dependencies; this.mapProvider = mapProvider; } @Override public Map<K, V> get() { // We can initialize the internal table efficiently this way and then swap the values // one by one. Map<K, Object> map = new LinkedHashMap<K, Object>(mapProvider.get()); for (Entry<K, Object> entry : map.entrySet()) { @SuppressWarnings("unchecked") // we initialized the entries with providers V value = ((Provider<V>) entry.getValue()).get(); checkConfiguration(value != null, "Map injection failed due to null value for key \"%s\"", entry.getKey()); entry.setValue(value); } @SuppressWarnings("unchecked") // if we exited the loop then we replaced all Providers Map<K, V> typedMap = (Map<K, V>) map; return Collections.unmodifiableMap(typedMap); } @Override public Set<Dependency<?>> getDependencies() { return dependencies; } @SuppressWarnings("unchecked") @Override public <B, R> R acceptExtensionVisitor(BindingTargetVisitor<B, R> visitor, ProviderInstanceBinding<? extends B> binding) { if (visitor instanceof MultibindingsTargetVisitor) { return ((MultibindingsTargetVisitor<Map<K, V>, R>)visitor).visit(this); } else { return visitor.visit(binding); } } @Override public Key<Map<K, V>> getMapKey() { return mapKey; } @Override public TypeLiteral<?> getKeyTypeLiteral() { return keyType; } @Override public TypeLiteral<?> getValueTypeLiteral() { return valueType; } @SuppressWarnings("unchecked") @Override public List<Entry<?, Binding<?>>> getEntries() { if (isInitialized()) { return (List)mapBindings; // safe because mapBindings is immutable } else { throw new UnsupportedOperationException( "getElements() not supported for module bindings"); } } @Override public boolean permitsDuplicates() { if (isInitialized()) { return permitDuplicates; } else { throw new UnsupportedOperationException( "permitsDuplicates() not supported for module bindings"); } } @Override public boolean containsElement(Element element) { return RealMapBinder.this.containsElement(element); } } /** * Binds {@code Map<K, Set<V>>} and {{@code Map<K, Set<Provider<V>>>}. */ static final class MultimapBinder<K, V> implements Module { private final Key<Map<K, Set<V>>> multimapKey; private final Key<Map<K, Set<Provider<V>>>> providerMultimapKey; private final Key<Set<Entry<K,Provider<V>>>> entrySetKey; public MultimapBinder( Key<Map<K, Set<V>>> multimapKey, Key<Map<K, Set<Provider<V>>>> providerMultimapKey, Key<Set<Entry<K,Provider<V>>>> entrySetKey) { this.multimapKey = multimapKey; this.providerMultimapKey = providerMultimapKey; this.entrySetKey = entrySetKey; } @Override public void configure(Binder binder) { ImmutableSet<Dependency<?>> dependencies = ImmutableSet.<Dependency<?>>of(Dependency.get(entrySetKey)); Provider<Set<Entry<K, Provider<V>>>> entrySetProvider = binder.getProvider(entrySetKey); // Binds a Map<K, Set<Provider<V>>> from a collection of Map<Entry<K, Provider<V>> if // permitDuplicates was called. binder.bind(providerMultimapKey).toProvider( new RealProviderMultimapProvider(dependencies, entrySetProvider)); Provider<Map<K, Set<Provider<V>>>> multimapProvider = binder.getProvider(providerMultimapKey); binder.bind(multimapKey).toProvider( new RealMultimapProvider(dependencies, multimapProvider)); } @Override public int hashCode() { return multimapKey.hashCode(); } @Override public boolean equals(Object o) { return o instanceof MultimapBinder && ((MultimapBinder<?, ?>) o).multimapKey.equals(multimapKey); } final class RealProviderMultimapProvider extends RealMapBinderProviderWithDependencies<Map<K, Set<Provider<V>>>> { private final ImmutableSet<Dependency<?>> dependencies; private final Provider<Set<Entry<K, Provider<V>>>> entrySetProvider; private Map<K, Set<Provider<V>>> providerMultimap; private RealProviderMultimapProvider(ImmutableSet<Dependency<?>> dependencies, Provider<Set<Entry<K, Provider<V>>>> entrySetProvider) { super(multimapKey); this.dependencies = dependencies; this.entrySetProvider = entrySetProvider; } @SuppressWarnings("unused") @Inject void initialize(Injector injector) { Map<K, ImmutableSet.Builder<Provider<V>>> providerMultimapMutable = new LinkedHashMap<K, ImmutableSet.Builder<Provider<V>>>(); for (Entry<K, Provider<V>> entry : entrySetProvider.get()) { if (!providerMultimapMutable.containsKey(entry.getKey())) { providerMultimapMutable.put( entry.getKey(), ImmutableSet.<Provider<V>>builder()); } providerMultimapMutable.get(entry.getKey()).add(entry.getValue()); } ImmutableMap.Builder<K, Set<Provider<V>>> providerMultimapBuilder = ImmutableMap.builder(); for (Entry<K, ImmutableSet.Builder<Provider<V>>> entry : providerMultimapMutable.entrySet()) { providerMultimapBuilder.put(entry.getKey(), entry.getValue().build()); } providerMultimap = providerMultimapBuilder.build(); } @Override public Map<K, Set<Provider<V>>> get() { return providerMultimap; } @Override public Set<Dependency<?>> getDependencies() { return dependencies; } } final class RealMultimapProvider extends RealMapBinderProviderWithDependencies<Map<K, Set<V>>> { private final ImmutableSet<Dependency<?>> dependencies; private final Provider<Map<K, Set<Provider<V>>>> multimapProvider; RealMultimapProvider( ImmutableSet<Dependency<?>> dependencies, Provider<Map<K, Set<Provider<V>>>> multimapProvider) { super(multimapKey); this.dependencies = dependencies; this.multimapProvider = multimapProvider; } @Override public Map<K, Set<V>> get() { ImmutableMap.Builder<K, Set<V>> multimapBuilder = ImmutableMap.builder(); for (Entry<K, Set<Provider<V>>> entry : multimapProvider.get().entrySet()) { K key = entry.getKey(); ImmutableSet.Builder<V> valuesBuilder = ImmutableSet.builder(); for (Provider<V> 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(); } @Override public Set<Dependency<?>> getDependencies() { return dependencies; } } } /** * A Provider that Map.Entry that is also a Provider. The key is the entry in the * map this corresponds to and the value is the provider of the user's binding. * This returns itself as the Provider.get value. */ static final class ProviderMapEntry<K, V> implements ProviderWithDependencies<Map.Entry<K, Provider<V>>>, Map.Entry<K, Provider<V>> { private final K key; private final Provider<V> provider; private final Key<V> valueKey; private ProviderMapEntry(K key, Provider<V> provider, Key<V> valueKey) { this.key = key; this.provider = provider; this.valueKey = valueKey; } @Override public Entry<K, Provider<V>> get() { return this; } @Override public Set<Dependency<?>> getDependencies() { return ((HasDependencies) provider).getDependencies(); } public Key<V> getValueKey() { return valueKey; } @Override public K getKey() { return key; } @Override public Provider<V> getValue() { return provider; } @Override public Provider<V> setValue(Provider<V> value) { throw new UnsupportedOperationException(); } @Override public boolean equals(Object obj) { if (obj instanceof Map.Entry) { Map.Entry o = (Map.Entry)obj; return Objects.equal(key, o.getKey()) && Objects.equal(provider, o.getValue()); } return false; } @Override public int hashCode() { return key.hashCode() ^ provider.hashCode(); } @Override public String toString() { return "ProviderMapEntry(" + key + ", " + provider + ")"; } } private static abstract class RealMapWithExtensionProvider<T> extends RealMapBinderProviderWithDependencies<T> implements ProviderWithExtensionVisitor<T>, MapBinderBinding<T> { public RealMapWithExtensionProvider(Object equality) { super(equality); } } /** * A base class for ProviderWithDependencies that need equality * based on a specific object. */ private static abstract class RealMapBinderProviderWithDependencies<T> implements ProviderWithDependencies<T> { 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); } @Override public int hashCode() { return equality.hashCode(); } } private Multimap<K, String> newLinkedKeyArrayValueMultimap() { return Multimaps.newListMultimap( new LinkedHashMap<K, Collection<String>>(), new Supplier<List<String>>() { @Override public List<String> get() { return Lists.newArrayList(); } }); } } }