/** * 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: *

 * 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 dpb@google.com (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 newRealMapBinder(binder, keyType, valueType, Key.get(mapOf(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 newRealMapBinder(binder, keyType, valueType, Key.get(mapOf(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 newRealMapBinder(binder, keyType, valueType, Key.get(mapOf(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()))); } // provider map is safely a Map>> @SuppressWarnings("unchecked") static TypeLiteral>> mapOfJavaxProviderOf( TypeLiteral keyType, TypeLiteral valueType) { return (TypeLiteral>>) TypeLiteral.get( Types.mapOf(keyType.getType(), newParameterizedType(javax.inject.Provider.class, 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()))); } // Note: We use valueTypeAndAnnotation effectively as a Pair // since it's an easy way to group a type and an optional annotation type or instance. static RealMapBinder newRealMapBinder(Binder binder, TypeLiteral keyType, Key valueTypeAndAnnotation) { binder = binder.skipSources(MapBinder.class, RealMapBinder.class); TypeLiteral valueType = valueTypeAndAnnotation.getTypeLiteral(); return newRealMapBinder(binder, keyType, valueType, valueTypeAndAnnotation.ofType(mapOf(keyType, valueType)), Multibinder.newSetBinder(binder, valueTypeAndAnnotation.ofType(entryOfProviderOf(keyType, valueType)))); } private static RealMapBinder newRealMapBinder(Binder binder, TypeLiteral keyType, TypeLiteral valueType, Key> mapKey, Multibinder>> entrySetBinder) { RealMapBinder mapBinder = new RealMapBinder(binder, keyType, valueType, mapKey, 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. */ static final class RealMapBinder extends MapBinder implements Module { private final TypeLiteral keyType; private final TypeLiteral valueType; private final Key> mapKey; private final Key>> javaxProviderMapKey; private final Key>> providerMapKey; private final Key>> multimapKey; private final Key>>> providerMultimapKey; private final RealMultibinder>> entrySetBinder; private final Map duplicateKeyErrorMessages; /* 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, Multibinder>> entrySetBinder) { this.keyType = keyType; this.valueType = valueType; this.mapKey = mapKey; this.providerMapKey = mapKey.ofType(mapOfProviderOf(keyType, valueType)); this.javaxProviderMapKey = mapKey.ofType(mapOfJavaxProviderOf(keyType, valueType)); this.multimapKey = mapKey.ofType(mapOf(keyType, setOf(valueType))); this.providerMultimapKey = mapKey.ofType(mapOfSetOfProviderOf(keyType, valueType)); this.entrySetBinder = (RealMultibinder>>) 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 permitDuplicates() { entrySetBinder.permitDuplicates(); binder.install(new MultimapBinder( multimapKey, providerMultimapKey, entrySetBinder.getSetKey())); return this; } Key getKeyForNewValue(K key) { checkNotNull(key, "key"); checkConfiguration(!isInitialized(), "MapBinder was already initialized"); Key valueKey = Key.get(valueType, new RealElement(entrySetBinder.getSetName(), MAPBINDER, keyType.toString())); entrySetBinder.addBinding().toProvider(new ProviderMapEntry( key, binder.getProvider(valueKey), valueKey)); return valueKey; } /** * This creates two bindings. One for the {@code Map.Entry>} * and another for {@code V}. */ @Override public LinkedBindingBuilder addBinding(K key) { return binder.bind(getKeyForNewValue(key)); } @Override public void configure(Binder binder) { checkConfiguration(!isInitialized(), "MapBinder was already initialized"); ImmutableSet> dependencies = ImmutableSet.>of(Dependency.get(entrySetBinder.getSetKey())); // Binds a Map> from a collection of Set>. Provider>>> 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>> 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>> { private final ImmutableSet> dependencies; private final Provider>>> entrySetProvider; private Map> providerMap; private RealProviderMapProvider( ImmutableSet> dependencies, Provider>>> entrySetProvider) { super(mapKey); this.dependencies = dependencies; this.entrySetProvider = entrySetProvider; } @Toolable @Inject void initialize(Injector injector) { RealMapBinder.this.binder = null; permitDuplicates = entrySetBinder.permitsDuplicates(injector); Map> providerMapMutable = new LinkedHashMap>(); List>> bindingsMutable = Lists.newArrayList(); Indexer indexer = new Indexer(injector); Multimap index = HashMultimap.create(); Set duplicateKeys = null; for (Entry> entry : entrySetProvider.get()) { ProviderMapEntry providerEntry = (ProviderMapEntry) entry; Key valueKey = providerEntry.getValueKey(); Binding valueBinding = injector.getBinding(valueKey); // If this isn't a dup due to an exact same binding, add it. if (index.put(providerEntry.getKey(), valueBinding.acceptTargetVisitor(indexer))) { Provider previous = providerMapMutable.put(providerEntry.getKey(), new ValueProvider(providerEntry.getValue(), valueBinding)); if (previous != null && !permitDuplicates) { if (duplicateKeys == null) { duplicateKeys = Sets.newHashSet(); } duplicateKeys.add(providerEntry.getKey()); } bindingsMutable.add(Maps.immutableEntry(providerEntry.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 dups = newLinkedKeyArrayValueMultimap(); for (Map.Entry> 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> get() { return providerMap; } @Override public Set> getDependencies() { return dependencies; } } final class RealMapProvider extends RealMapWithExtensionProvider> { private final ImmutableSet> dependencies; private final Provider>> mapProvider; private RealMapProvider( ImmutableSet> dependencies, Provider>> mapProvider) { super(mapKey); this.dependencies = dependencies; this.mapProvider = mapProvider; } @Override public Map get() { // We can initialize the internal table efficiently this way and then swap the values // one by one. Map map = new LinkedHashMap(mapProvider.get()); for (Entry entry : map.entrySet()) { @SuppressWarnings("unchecked") // we initialized the entries with providers ValueProvider provider = (ValueProvider)entry.getValue(); V value = provider.get(); checkConfiguration(value != null, "Map injection failed due to null value for key \"%s\", bound at: %s", entry.getKey(), provider.getValueBinding().getSource()); entry.setValue(value); } @SuppressWarnings("unchecked") // if we exited the loop then we replaced all Providers Map typedMap = (Map) map; return Collections.unmodifiableMap(typedMap); } @Override public Set> getDependencies() { return dependencies; } @SuppressWarnings("unchecked") @Override public R acceptExtensionVisitor(BindingTargetVisitor visitor, ProviderInstanceBinding binding) { if (visitor instanceof MultibindingsTargetVisitor) { return ((MultibindingsTargetVisitor, R>)visitor).visit(this); } else { return visitor.visit(binding); } } @Override public Key> getMapKey() { return mapKey; } @Override public TypeLiteral getKeyTypeLiteral() { return keyType; } @Override public TypeLiteral getValueTypeLiteral() { return valueType; } @SuppressWarnings("unchecked") @Override public List>> 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>} and {{@code Map>>}. */ 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; } @Override public void configure(Binder binder) { ImmutableSet> dependencies = ImmutableSet.>of(Dependency.get(entrySetKey)); Provider>>> entrySetProvider = binder.getProvider(entrySetKey); // Binds a Map>> from a collection of Map> if // permitDuplicates was called. binder.bind(providerMultimapKey).toProvider( new RealProviderMultimapProvider(dependencies, entrySetProvider)); Provider>>> 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>>> { private final ImmutableSet> dependencies; private final Provider>>> entrySetProvider; private Map>> providerMultimap; private RealProviderMultimapProvider(ImmutableSet> dependencies, Provider>>> entrySetProvider) { super(multimapKey); this.dependencies = dependencies; this.entrySetProvider = entrySetProvider; } @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(); } @Override public Map>> get() { return providerMultimap; } @Override public Set> getDependencies() { return dependencies; } } final class RealMultimapProvider extends RealMapBinderProviderWithDependencies>> { private final ImmutableSet> dependencies; private final Provider>>> multimapProvider; RealMultimapProvider( ImmutableSet> dependencies, Provider>>> multimapProvider) { super(multimapKey); this.dependencies = dependencies; this.multimapProvider = multimapProvider; } @Override 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(); } @Override public Set> getDependencies() { return dependencies; } } } static final class ValueProvider implements Provider { private final Provider delegate; private final Binding binding; ValueProvider(Provider delegate, Binding binding) { this.delegate = delegate; this.binding = binding; } @Override public V get() { return delegate.get(); } public Binding getValueBinding() { return binding; } } /** * 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 implements ProviderWithDependencies>>, Map.Entry> { private final K key; private final Provider provider; private final Key valueKey; private ProviderMapEntry(K key, Provider provider, Key valueKey) { this.key = key; this.provider = provider; this.valueKey = valueKey; } @Override public Entry> get() { return this; } @Override public Set> getDependencies() { return ((HasDependencies) provider).getDependencies(); } public Key getValueKey() { return valueKey; } @Override public K getKey() { return key; } @Override public Provider getValue() { return provider; } @Override public Provider setValue(Provider 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 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); } @Override public int hashCode() { return equality.hashCode(); } } private Multimap newLinkedKeyArrayValueMultimap() { return Multimaps.newListMultimap( new LinkedHashMap>(), new Supplier>() { @Override public List get() { return Lists.newArrayList(); } }); } } }