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

com.google.inject.internal.RealMapBinder Maven / Gradle / Ivy

There is a newer version: 7.0.0
Show newest version

package com.google.inject.internal;

import static com.google.inject.internal.Element.Type.MAPBINDER;
import static com.google.inject.internal.Errors.checkConfiguration;
import static com.google.inject.internal.Errors.checkNotNull;
import static com.google.inject.internal.RealMultibinder.setOf;
import static com.google.inject.util.Types.newParameterizedType;
import static com.google.inject.util.Types.newParameterizedTypeWithOwner;

import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.inject.Binder;
import com.google.inject.Binding;
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.InternalProviderInstanceBindingImpl.InitializationTiming;
import com.google.inject.multibindings.MapBinderBinding;
import com.google.inject.multibindings.Multibinder;
import com.google.inject.multibindings.MultibindingsTargetVisitor;
import com.google.inject.spi.BindingTargetVisitor;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.Element;
import com.google.inject.spi.ProviderInstanceBinding;
import com.google.inject.spi.ProviderWithExtensionVisitor;
import com.google.inject.util.Types;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

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

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. */ public final class RealMapBinder implements Module { /** * 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 RealMapBinder newMapRealBinder( Binder binder, TypeLiteral keyType, TypeLiteral valueType) { binder = binder.skipSources(RealMapBinder.class); return newRealMapBinder( binder, keyType, valueType, Key.get(mapOf(keyType, valueType)), RealMultibinder.newRealSetBinder(binder, Key.get(entryOfProviderOf(keyType, 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 RealMapBinder newRealMapBinder( Binder binder, TypeLiteral keyType, TypeLiteral valueType, Annotation annotation) { binder = binder.skipSources(RealMapBinder.class); return newRealMapBinder( binder, keyType, valueType, Key.get(mapOf(keyType, valueType), annotation), RealMultibinder.newRealSetBinder( binder, Key.get(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 annotationType}. */ public static RealMapBinder newRealMapBinder( Binder binder, TypeLiteral keyType, TypeLiteral valueType, Class annotationType) { binder = binder.skipSources(RealMapBinder.class); return newRealMapBinder( binder, keyType, valueType, Key.get(mapOf(keyType, valueType), annotationType), RealMultibinder.newRealSetBinder( binder, Key.get(entryOfProviderOf(keyType, 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 map > is safely a Map>> static TypeLiteral>>> mapOfSetOfJavaxProviderOf( TypeLiteral keyType, TypeLiteral valueType) { return (TypeLiteral>>>) TypeLiteral.get( Types.mapOf( keyType.getType(), Types.setOf(Types.javaxProviderOf(valueType.getType())))); } @SuppressWarnings("unchecked") // a provider map > is safely a Map>> static TypeLiteral>>> mapOfCollectionOfProviderOf( TypeLiteral keyType, TypeLiteral valueType) { return (TypeLiteral>>>) TypeLiteral.get( Types.mapOf( keyType.getType(), Types.collectionOf(Types.providerOf(valueType.getType())))); } @SuppressWarnings("unchecked") // a provider map > is safely a Map>> static TypeLiteral>>> mapOfCollectionOfJavaxProviderOf( TypeLiteral keyType, TypeLiteral valueType) { return (TypeLiteral>>>) TypeLiteral.get( Types.mapOf( keyType.getType(), Types.collectionOf(Types.javaxProviderOf(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, Map.Entry.class, keyType.getType(), Types.providerOf(valueType.getType()))); } @SuppressWarnings("unchecked") // a provider entry is safely a Map.Entry> static TypeLiteral>> entryOfJavaxProviderOf( TypeLiteral keyType, TypeLiteral valueType) { return (TypeLiteral>>) TypeLiteral.get( newParameterizedTypeWithOwner( Map.class, Map.Entry.class, keyType.getType(), Types.javaxProviderOf(valueType.getType()))); } @SuppressWarnings("unchecked") // a provider entry is safely a Map.Entry> static TypeLiteral>>> setOfEntryOfJavaxProviderOf( TypeLiteral keyType, TypeLiteral valueType) { return (TypeLiteral>>>) TypeLiteral.get(Types.setOf(entryOfJavaxProviderOf(keyType, valueType).getType())); } /** Given a Key will return a Key> */ @SuppressWarnings("unchecked") private static Key> getKeyOfProvider(Key valueKey) { return (Key>) valueKey.ofType(Types.providerOf(valueKey.getTypeLiteral().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(RealMapBinder.class); TypeLiteral valueType = valueTypeAndAnnotation.getTypeLiteral(); return newRealMapBinder( binder, keyType, valueType, valueTypeAndAnnotation.ofType(mapOf(keyType, valueType)), RealMultibinder.newRealSetBinder( binder, valueTypeAndAnnotation.ofType(entryOfProviderOf(keyType, valueType)))); } private static RealMapBinder newRealMapBinder( Binder binder, TypeLiteral keyType, TypeLiteral valueType, Key> mapKey, RealMultibinder>> entrySetBinder) { RealMapBinder mapBinder = new RealMapBinder(binder, keyType, valueType, mapKey, entrySetBinder); binder.install(mapBinder); return mapBinder; } // Until the injector initializes us, we don't know what our dependencies are, // so initialize to the whole Injector. private static final ImmutableSet> MODULE_DEPENDENCIES = ImmutableSet.>of(Dependency.get(Key.get(Injector.class))); private final BindingSelection bindingSelection; private final Binder binder; private final RealMultibinder>> entrySetBinder; private RealMapBinder( Binder binder, TypeLiteral keyType, TypeLiteral valueType, Key> mapKey, RealMultibinder>> entrySetBinder) { this.bindingSelection = new BindingSelection<>(keyType, valueType, mapKey, entrySetBinder); this.binder = binder; this.entrySetBinder = entrySetBinder; } public void permitDuplicates() { checkConfiguration(!bindingSelection.isInitialized(), "MapBinder was already initialized"); entrySetBinder.permitDuplicates(); binder.install(new MultimapBinder(bindingSelection)); } /** Adds a binding to the map for the given key. */ Key getKeyForNewValue(K key) { checkNotNull(key, "key"); checkConfiguration(!bindingSelection.isInitialized(), "MapBinder was already initialized"); RealMultibinder>> entrySetBinder = bindingSelection.getEntrySetBinder(); Key valueKey = Key.get( bindingSelection.getValueType(), new RealElement( entrySetBinder.getSetName(), MAPBINDER, bindingSelection.getKeyType().toString())); entrySetBinder.addBinding().toProvider(new ProviderMapEntry(key, valueKey)); return valueKey; } /** * This creates two bindings. One for the {@code Map.Entry>} and another for {@code * V}. */ public LinkedBindingBuilder addBinding(K key) { return binder.bind(getKeyForNewValue(key)); } @Override public void configure(Binder binder) { checkConfiguration(!bindingSelection.isInitialized(), "MapBinder was already initialized"); // Binds a Map> RealProviderMapProvider providerMapProvider = new RealProviderMapProvider(bindingSelection); binder.bind(bindingSelection.getProviderMapKey()).toProvider(providerMapProvider); // 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", "rawtypes"}) Provider>> javaxProviderMapProvider = (Provider) providerMapProvider; binder.bind(bindingSelection.getJavaxProviderMapKey()).toProvider(javaxProviderMapProvider); RealMapProvider mapProvider = new RealMapProvider<>(bindingSelection); binder.bind(bindingSelection.getMapKey()).toProvider(mapProvider); // The Map.Entries are all ProviderMapEntry instances which do not allow setValue, so it is // safe to massage the return type like this @SuppressWarnings({"unchecked", "rawtypes"}) Key>>> massagedEntrySetProviderKey = (Key) bindingSelection.getEntrySetBinder().getSetKey(); binder.bind(bindingSelection.getEntrySetJavaxProviderKey()).to(massagedEntrySetProviderKey); } @Override public boolean equals(Object o) { return o instanceof RealMapBinder && ((RealMapBinder) o).bindingSelection.equals(bindingSelection); } @Override public int hashCode() { return bindingSelection.hashCode(); } /** * The BindingSelection contains some of the core state and logic for the MapBinder. * *

It lazily computes the value for keys for various permutations of Maps that are provided by * this module. It also builds up maps from {@code K} to {@code Binding}, which is used by all * of the internal factories to actually provide the desired maps. * *

During initialization time there is only one BindingSelection. It is possible that multiple * different BindingSelections are constructed. Specifically, in the case of two different modules * each adding bindings to the same MapBinder. If that happens, we define the BindingSelection * held by the {@link RealMapProvider} to be the authoritative one. The logic for this exists in * {@link RealMultimapBinderProviderWithDependencies}. This is done to avoid confusion because the * BindingSelection contains mutable state. */ private static final class BindingSelection { private enum InitializationState { UNINITIALIZED, INITIALIZED, HAS_ERRORS; } private final TypeLiteral keyType; private final TypeLiteral valueType; private final Key> mapKey; // Lazily computed private Key>> javaxProviderMapKey; private Key>> providerMapKey; private Key>> multimapKey; private Key>>> providerSetMultimapKey; private Key>>> javaxProviderSetMultimapKey; private Key>>> providerCollectionMultimapKey; private Key>>> javaxProviderCollectionMultimapKey; private Key>>> entrySetJavaxProviderKey; private final RealMultibinder>> entrySetBinder; private InitializationState initializationState; /** * These are built during initialization and used by all factories to actually provide the * relevant maps. These contain all of the necessary information about the map binder. */ private ImmutableMap> mapBindings; private ImmutableMap>> multimapBindings; private ImmutableList>> entries; /** * Indicates if this Map permits duplicates. It is initialized during initialization by querying * the injector. This is done because multiple different modules can contribute to a MapBinder, * and any one could set permitDuplicates. */ private boolean permitsDuplicates; private BindingSelection( TypeLiteral keyType, TypeLiteral valueType, Key> mapKey, RealMultibinder>> entrySetBinder) { this.keyType = keyType; this.valueType = valueType; this.mapKey = mapKey; this.entrySetBinder = entrySetBinder; this.initializationState = InitializationState.UNINITIALIZED; } /** * Will initialize internal data structures. * * @return {@code true} if initialization was successful, {@code false} if there were errors */ private boolean tryInitialize(InjectorImpl injector, Errors errors) { // Every one of our providers will call this method, so only execute the logic once. if (initializationState != InitializationState.UNINITIALIZED) { return initializationState != InitializationState.HAS_ERRORS; } // Multiple different modules can all contribute to the same MapBinder, and if any // one of them permits duplicates, then the map binder as a whole will permit duplicates. // Since permitDuplicates() may not have been called on this instance, we need to go // to the injector to see if permitDuplicates was set. permitsDuplicates = entrySetBinder.permitsDuplicates(injector); // We now build the Map>> from the entrySetBinder. // The entrySetBinder contains all of the ProviderMapEntrys, and once // we have those, it's easy to iterate through them to organize them by K. Map>> bindingMultimapMutable = new LinkedHashMap>>(); Map> bindingMapMutable = new LinkedHashMap<>(); Multimap index = HashMultimap.create(); Indexer indexer = new Indexer(injector); Multimap> duplicates = null; ImmutableList.Builder>> entriesBuilder = ImmutableList.builder(); // We get all of the Bindings that were put into the entrySetBinder for (Binding>> binding : injector.findBindingsByType(entrySetBinder.getElementTypeLiteral())) { if (entrySetBinder.containsElement(binding)) { // Protected by findBindingByType() and the fact that all providers are added by us // in addBinding(). It would theoretically be possible for someone to directly // add their own binding to the entrySetBinder, but they shouldn't do that. @SuppressWarnings({"unchecked", "rawtypes"}) ProviderInstanceBinding> entryBinding = (ProviderInstanceBinding) binding; // We added all these bindings initially, so we know they are ProviderMapEntrys @SuppressWarnings({"unchecked", "rawtypes"}) ProviderMapEntry entry = (ProviderMapEntry) entryBinding.getUserSuppliedProvider(); K key = entry.getKey(); Key valueKey = entry.getValueKey(); Binding valueBinding = injector.getExistingBinding(valueKey); // Use the indexer to de-dupe user bindings. This is needed because of the // uniqueId in RealElement. The uniqueId intentionally circumvents the regular // Guice deduplication, so we need to re-implement our own here, ignoring // uniqueId. if (index.put(key, valueBinding.acceptTargetVisitor(indexer))) { entriesBuilder.add(Maps.immutableEntry(key, valueBinding)); Binding previous = bindingMapMutable.put(key, valueBinding); // Check if this is a duplicate binding if (previous != null && !permitsDuplicates) { if (duplicates == null) { // This is linked for both keys and values to maintain order duplicates = LinkedHashMultimap.create(); } // We add both the previous and the current value to the duplicates map. // This is because if there are three duplicates, we will only execute this code // for the second and third, but we want all three values to display a helpful // error message. We rely on the multimap to dedupe repeated values. duplicates.put(key, previous); duplicates.put(key, valueBinding); } // Don't do extra work unless we need to if (permitsDuplicates) { // Create a set builder for this key if it's the first time we've seen it if (!bindingMultimapMutable.containsKey(key)) { bindingMultimapMutable.put(key, ImmutableSet.>builder()); } // Add the Binding bindingMultimapMutable.get(key).add(valueBinding); } } } } // It is safe to check if duplicates is non-null because if duplicates are allowed, // we don't build up this data structure if (duplicates != null) { initializationState = InitializationState.HAS_ERRORS; reportDuplicateKeysError(duplicates, errors); return false; } // Build all of the ImmutableSet.Builders, // transforming from Map>> to // ImmutableMap>> ImmutableMap.Builder>> bindingsMultimapBuilder = ImmutableMap.builder(); for (Map.Entry>> entry : bindingMultimapMutable.entrySet()) { bindingsMultimapBuilder.put(entry.getKey(), entry.getValue().build()); } mapBindings = ImmutableMap.copyOf(bindingMapMutable); multimapBindings = bindingsMultimapBuilder.build(); entries = entriesBuilder.build(); initializationState = InitializationState.INITIALIZED; return true; } private static void reportDuplicateKeysError( Multimap> duplicates, Errors errors) { StringBuilder sb = new StringBuilder("Map injection failed due to duplicated key "); boolean first = true; for (Map.Entry>> entry : duplicates.asMap().entrySet()) { K dupKey = entry.getKey(); if (first) { first = false; sb.append("\"" + dupKey + "\", from bindings:\n"); } else { sb.append("\n and key: \"" + dupKey + "\", from bindings:\n"); } for (Binding dup : entry.getValue()) { sb.append("\t at " + Errors.convert(dup.getSource()) + "\n"); } } // TODO(user): Add a different error for every duplicated key errors.addMessage(sb.toString()); } private boolean containsElement(Element element) { if (entrySetBinder.containsElement(element)) { return true; } Key key; if (element instanceof Binding) { key = ((Binding) element).getKey(); } else { return false; // cannot match; } return key.equals(getMapKey()) || key.equals(getProviderMapKey()) || key.equals(getJavaxProviderMapKey()) || key.equals(getMultimapKey()) || key.equals(getProviderSetMultimapKey()) || key.equals(getJavaxProviderSetMultimapKey()) || key.equals(getProviderCollectionMultimapKey()) || key.equals(getJavaxProviderCollectionMultimapKey()) || key.equals(entrySetBinder.getSetKey()) || key.equals(getEntrySetJavaxProviderKey()) || 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 Key>> getProviderMapKey() { Key>> local = providerMapKey; if (local == null) { local = providerMapKey = mapKey.ofType(mapOfProviderOf(keyType, valueType)); } return local; } private Key>> getJavaxProviderMapKey() { Key>> local = javaxProviderMapKey; if (local == null) { local = javaxProviderMapKey = mapKey.ofType(mapOfJavaxProviderOf(keyType, valueType)); } return local; } private Key>> getMultimapKey() { Key>> local = multimapKey; if (local == null) { local = multimapKey = mapKey.ofType(mapOf(keyType, setOf(valueType))); } return local; } private Key>>> getProviderSetMultimapKey() { Key>>> local = providerSetMultimapKey; if (local == null) { local = providerSetMultimapKey = mapKey.ofType(mapOfSetOfProviderOf(keyType, valueType)); } return local; } private Key>>> getJavaxProviderSetMultimapKey() { Key>>> local = javaxProviderSetMultimapKey; if (local == null) { local = javaxProviderSetMultimapKey = mapKey.ofType(mapOfSetOfJavaxProviderOf(keyType, valueType)); } return local; } private Key>>> getProviderCollectionMultimapKey() { Key>>> local = providerCollectionMultimapKey; if (local == null) { local = providerCollectionMultimapKey = mapKey.ofType(mapOfCollectionOfProviderOf(keyType, valueType)); } return local; } private Key>>> getJavaxProviderCollectionMultimapKey() { Key>>> local = javaxProviderCollectionMultimapKey; if (local == null) { local = javaxProviderCollectionMultimapKey = mapKey.ofType(mapOfCollectionOfJavaxProviderOf(keyType, valueType)); } return local; } private Key>>> getEntrySetJavaxProviderKey() { Key>>> local = entrySetJavaxProviderKey; if (local == null) { local = entrySetJavaxProviderKey = mapKey.ofType(setOfEntryOfJavaxProviderOf(keyType, valueType)); } return local; } private ImmutableMap> getMapBindings() { checkConfiguration(isInitialized(), "MapBinder has not yet been initialized"); return mapBindings; } private ImmutableMap>> getMultimapBindings() { checkConfiguration(isInitialized(), "MapBinder has not yet been initialized"); return multimapBindings; } private ImmutableList>> getEntries() { checkConfiguration(isInitialized(), "MapBinder has not yet been initialized"); return entries; } private boolean isInitialized() { return initializationState == InitializationState.INITIALIZED; } private TypeLiteral getKeyType() { return keyType; } private TypeLiteral getValueType() { return valueType; } private Key> getMapKey() { return mapKey; } private RealMultibinder>> getEntrySetBinder() { return entrySetBinder; } private boolean permitsDuplicates() { if (isInitialized()) { return permitsDuplicates; } else { throw new UnsupportedOperationException( "permitsDuplicates() not supported for module bindings"); } } @Override public boolean equals(Object o) { return o instanceof BindingSelection && ((BindingSelection) o).mapKey.equals(mapKey); } @Override public int hashCode() { return mapKey.hashCode(); } } private static final class RealProviderMapProvider extends RealMapBinderProviderWithDependencies>> { private Map> mapOfProviders; private Set> dependencies = RealMapBinder.MODULE_DEPENDENCIES; private RealProviderMapProvider(BindingSelection bindingSelection) { super(bindingSelection); } @Override public Set> getDependencies() { return dependencies; } @Override protected void doInitialize(InjectorImpl injector, Errors errors) { ImmutableMap.Builder> mapOfProvidersBuilder = ImmutableMap.builder(); ImmutableSet.Builder> dependenciesBuilder = ImmutableSet.builder(); for (Map.Entry> entry : bindingSelection.getMapBindings().entrySet()) { mapOfProvidersBuilder.put(entry.getKey(), entry.getValue().getProvider()); dependenciesBuilder.add(Dependency.get(getKeyOfProvider(entry.getValue().getKey()))); } mapOfProviders = mapOfProvidersBuilder.build(); dependencies = dependenciesBuilder.build(); } @Override protected Map> doProvision(InternalContext context, Dependency dependency) { return mapOfProviders; } } private static final class RealMapProvider extends RealMapBinderProviderWithDependencies> implements ProviderWithExtensionVisitor>, MapBinderBinding> { private Set> dependencies = RealMapBinder.MODULE_DEPENDENCIES; /** * An array of all the injectors. * *

This is parallel to array of keys below */ private SingleParameterInjector[] injectors; private K[] keys; private RealMapProvider(BindingSelection bindingSelection) { super(bindingSelection); } private BindingSelection getBindingSelection() { return bindingSelection; } @Override protected void doInitialize(InjectorImpl injector, Errors errors) throws ErrorsException { @SuppressWarnings("unchecked") K[] keysArray = (K[]) new Object[bindingSelection.getMapBindings().size()]; keys = keysArray; ImmutableSet.Builder> dependenciesBuilder = ImmutableSet.builder(); int i = 0; for (Map.Entry> entry : bindingSelection.getMapBindings().entrySet()) { dependenciesBuilder.add(Dependency.get(entry.getValue().getKey())); keys[i] = entry.getKey(); i++; } ImmutableSet> localDependencies = dependenciesBuilder.build(); dependencies = localDependencies; List> dependenciesList = localDependencies.asList(); // We know the type because we built up our own sets of dependencies, it's just // that the interface uses a "?" generic @SuppressWarnings("unchecked") SingleParameterInjector[] typedInjectors = (SingleParameterInjector[]) injector.getParametersInjectors(dependenciesList, errors); injectors = typedInjectors; } @Override protected Map doProvision(InternalContext context, Dependency dependency) throws InternalProvisionException { SingleParameterInjector[] localInjectors = injectors; if (localInjectors == null) { // if injectors == null, then we have no bindings so return the empty map. return ImmutableMap.of(); } ImmutableMap.Builder resultBuilder = ImmutableMap.builder(); K[] localKeys = keys; for (int i = 0; i < localInjectors.length; i++) { SingleParameterInjector injector = localInjectors[i]; K key = localKeys[i]; V value = injector.inject(context); if (value == null) { throw createNullValueException(key, bindingSelection.getMapBindings().get(key)); } resultBuilder.put(key, value); } return resultBuilder.build(); } @Override public Set> getDependencies() { return dependencies; } @Override @SuppressWarnings("unchecked") public W acceptExtensionVisitor( BindingTargetVisitor visitor, ProviderInstanceBinding binding) { if (visitor instanceof MultibindingsTargetVisitor) { return ((MultibindingsTargetVisitor, W>) visitor).visit(this); } else { return visitor.visit(binding); } } @Override public Key> getMapKey() { return bindingSelection.getMapKey(); } @Override public TypeLiteral getKeyTypeLiteral() { return bindingSelection.getKeyType(); } @Override public TypeLiteral getValueTypeLiteral() { return bindingSelection.getValueType(); } @Override @SuppressWarnings("unchecked") public List>> getEntries() { if (bindingSelection.isInitialized()) { return (List>>) (List) bindingSelection.getEntries(); } else { throw new UnsupportedOperationException("getEntries() not supported for module bindings"); } } @Override public List>> getEntries(Iterable elements) { // Iterate over the elements, building up the below maps // This is a preprocessing step allowing us to only iterate over elements // once and have O(n) runtime ImmutableMultimap.Builder> keyToValueKeyBuilder = ImmutableMultimap.builder(); ImmutableMap.Builder, Binding> valueKeyToBindingBuilder = ImmutableMap.builder(); ImmutableMap.Builder, K> valueKeyToKeyBuilder = ImmutableMap.builder(); ImmutableMap.Builder, Binding>>> valueKeyToEntryBindingBuilder = ImmutableMap.builder(); for (Element element : elements) { if (element instanceof Binding) { Binding binding = (Binding) element; if (bindingSelection.matchesValueKey(binding.getKey()) && binding.getKey().getTypeLiteral().equals(bindingSelection.valueType)) { // Safe because of the check on the type literal above @SuppressWarnings("unchecked") Binding typedBinding = (Binding) binding; Key typedKey = typedBinding.getKey(); valueKeyToBindingBuilder.put(typedKey, typedBinding); } } if (element instanceof ProviderInstanceBinding && bindingSelection.getEntrySetBinder().containsElement(element)) { // Safe because of the instanceof check, and containsElement() check @SuppressWarnings({"unchecked", "rawtypes"}) ProviderInstanceBinding>> entryBinding = (ProviderInstanceBinding) element; // Safe because of the check for containsElement() above @SuppressWarnings("unchecked") Provider>> typedProvider = (Provider>>) entryBinding.getUserSuppliedProvider(); Provider>> userSuppliedProvider = typedProvider; if (userSuppliedProvider instanceof ProviderMapEntry) { // Safe because of the instanceof check @SuppressWarnings("unchecked") ProviderMapEntry typedUserSuppliedProvider = (ProviderMapEntry) userSuppliedProvider; ProviderMapEntry entry = typedUserSuppliedProvider; keyToValueKeyBuilder.put(entry.getKey(), entry.getValueKey()); valueKeyToEntryBindingBuilder.put(entry.getValueKey(), entryBinding); valueKeyToKeyBuilder.put(entry.getValueKey(), entry.getKey()); } } } ImmutableMultimap> keyToValueKey = keyToValueKeyBuilder.build(); ImmutableMap, K> valueKeyToKey = valueKeyToKeyBuilder.build(); ImmutableMap, Binding> valueKeyToBinding = valueKeyToBindingBuilder.build(); ImmutableMap, Binding>>> valueKeyToEntryBinding = valueKeyToEntryBindingBuilder.build(); // Check that there is a 1:1 mapping from keys from the ProviderMapEntrys to the // keys from the Bindings. Set> keysFromProviderMapEntrys = Sets.newHashSet(keyToValueKey.values()); Set> keysFromBindings = valueKeyToBinding.keySet(); if (!keysFromProviderMapEntrys.equals(keysFromBindings)) { Set> keysOnlyFromProviderMapEntrys = Sets.difference(keysFromProviderMapEntrys, keysFromBindings); Set> keysOnlyFromBindings = Sets.difference(keysFromBindings, keysFromProviderMapEntrys); StringBuilder sb = new StringBuilder("Expected a 1:1 mapping from map keys to values."); if (!keysOnlyFromBindings.isEmpty()) { sb.append( Errors.format("%nFound these Bindings that were missing an associated entry:%n")); for (Key key : keysOnlyFromBindings) { sb.append( Errors.format(" %s bound at: %s%n", key, valueKeyToBinding.get(key).getSource())); } } if (!keysOnlyFromProviderMapEntrys.isEmpty()) { sb.append(Errors.format("%nFound these map keys without a corresponding value:%n")); for (Key key : keysOnlyFromProviderMapEntrys) { sb.append( Errors.format( " '%s' bound at: %s%n", valueKeyToKey.get(key), valueKeyToEntryBinding.get(key).getSource())); } } throw new IllegalArgumentException(sb.toString()); } // Now that we have the two maps, generate the result map ImmutableList.Builder>> resultBuilder = ImmutableList.builder(); for (Map.Entry> entry : keyToValueKey.entries()) { Binding binding = valueKeyToBinding.get(entry.getValue()); // No null check for binding needed because of the above check to make sure all the // values in keyToValueKey are present as keys in valueKeyToBinding @SuppressWarnings({"unchecked", "rawtypes"}) Map.Entry> newEntry = (Map.Entry) Maps.immutableEntry(entry.getKey(), binding); resultBuilder.add(newEntry); } return resultBuilder.build(); } @Override public boolean permitsDuplicates() { if (bindingSelection.isInitialized()) { return bindingSelection.permitsDuplicates(); } else { throw new UnsupportedOperationException( "permitsDuplicates() not supported for module bindings"); } } @Override public boolean containsElement(Element element) { return bindingSelection.containsElement(element); } } /** * Binds {@code Map>} and {{@code Map>>}. * *

This will only exist if permitDuplicates() is called. */ private static final class MultimapBinder implements Module { private final BindingSelection bindingSelection; private MultimapBinder(BindingSelection bindingSelection) { this.bindingSelection = bindingSelection; } @Override public void configure(Binder binder) { // Binds a Map>> Provider>>> multimapProvider = new RealProviderMultimapProvider(bindingSelection.getMapKey()); binder.bind(bindingSelection.getProviderSetMultimapKey()).toProvider(multimapProvider); // Provide links from a few different public keys to the providerMultimapKey. // The collection this exposes is internally an ImmutableMap, so it's OK to massage // the guice Provider to javax Provider in the value (since the guice Provider implements // javax Provider). @SuppressWarnings({"unchecked", "rawtypes"}) Provider>>> javaxProvider = (Provider) multimapProvider; binder.bind(bindingSelection.getJavaxProviderSetMultimapKey()).toProvider(javaxProvider); @SuppressWarnings({"unchecked", "rawtypes"}) Provider>>> collectionProvider = (Provider) multimapProvider; binder .bind(bindingSelection.getProviderCollectionMultimapKey()) .toProvider(collectionProvider); @SuppressWarnings({"unchecked", "rawtypes"}) Provider>>> collectionJavaxProvider = (Provider) multimapProvider; binder .bind(bindingSelection.getJavaxProviderCollectionMultimapKey()) .toProvider(collectionJavaxProvider); // Binds a Map> @SuppressWarnings({"unchecked", "rawtypes"}) Provider>> realMultimapProvider = new RealMultimapProvider(bindingSelection.getMapKey()); binder.bind(bindingSelection.getMultimapKey()).toProvider(realMultimapProvider); } @Override public int hashCode() { return bindingSelection.hashCode(); } @Override public boolean equals(Object o) { return o instanceof MultimapBinder && ((MultimapBinder) o).bindingSelection.equals(bindingSelection); } private static final class RealProviderMultimapProvider extends RealMultimapBinderProviderWithDependencies>>> { private Map>> multimapOfProviders; private Set> dependencies = RealMapBinder.MODULE_DEPENDENCIES; private RealProviderMultimapProvider(Key> mapKey) { super(mapKey); } @Override public Set> getDependencies() { return dependencies; } @Override protected void doInitialize(InjectorImpl injector, Errors errors) { ImmutableMap.Builder>> multimapOfProvidersBuilder = ImmutableMap.builder(); ImmutableSet.Builder> dependenciesBuilder = ImmutableSet.builder(); for (Map.Entry>> entry : bindingSelection.getMultimapBindings().entrySet()) { ImmutableSet.Builder> providersBuilder = ImmutableSet.builder(); for (Binding binding : entry.getValue()) { providersBuilder.add(binding.getProvider()); dependenciesBuilder.add(Dependency.get(getKeyOfProvider(binding.getKey()))); } multimapOfProvidersBuilder.put(entry.getKey(), providersBuilder.build()); } multimapOfProviders = multimapOfProvidersBuilder.build(); dependencies = dependenciesBuilder.build(); } @Override protected Map>> doProvision( InternalContext context, Dependency dependency) { return multimapOfProviders; } } private static final class RealMultimapProvider extends RealMultimapBinderProviderWithDependencies>> { /** * A simple class to hold a key and the associated bindings as an array. * *

Arrays are used for performance. */ private static final class PerKeyData { private final K key; private final Binding[] bindings; private final SingleParameterInjector[] injectors; private PerKeyData(K key, Binding[] bindings, SingleParameterInjector[] injectors) { Preconditions.checkArgument(bindings.length == injectors.length); this.key = key; this.bindings = bindings; this.injectors = injectors; } } private Set> dependencies = RealMapBinder.MODULE_DEPENDENCIES; private PerKeyData[] perKeyDatas; private RealMultimapProvider(Key> mapKey) { super(mapKey); } @Override public Set> getDependencies() { return dependencies; } @Override protected void doInitialize(InjectorImpl injector, Errors errors) throws ErrorsException { @SuppressWarnings({"unchecked", "rawtypes"}) PerKeyData[] typedPerKeyData = new PerKeyData[bindingSelection.getMapBindings().size()]; perKeyDatas = typedPerKeyData; ImmutableSet.Builder> dependenciesBuilder = ImmutableSet.builder(); List> dependenciesForKey = Lists.newArrayList(); int i = 0; for (Map.Entry>> entry : bindingSelection.getMultimapBindings().entrySet()) { // Clear the list of dependencies because we're reusing it for each different key dependenciesForKey.clear(); Set> bindings = entry.getValue(); @SuppressWarnings({"unchecked", "rawtypes"}) Binding[] typedBindings = new Binding[bindings.size()]; Binding[] bindingsArray = typedBindings; int j = 0; for (Binding binding : bindings) { Dependency dependency = Dependency.get(binding.getKey()); dependenciesBuilder.add(dependency); dependenciesForKey.add(dependency); bindingsArray[j] = binding; j++; } @SuppressWarnings("unchecked") SingleParameterInjector[] injectors = (SingleParameterInjector[]) injector.getParametersInjectors(dependenciesForKey, errors); perKeyDatas[i] = new PerKeyData<>(entry.getKey(), bindingsArray, injectors); i++; } dependencies = dependenciesBuilder.build(); } @Override protected Map> doProvision(InternalContext context, Dependency dependency) throws InternalProvisionException { ImmutableMap.Builder> resultBuilder = ImmutableMap.builder(); for (PerKeyData perKeyData : perKeyDatas) { ImmutableSet.Builder bindingsBuilder = ImmutableSet.builder(); SingleParameterInjector[] injectors = perKeyData.injectors; for (int i = 0; i < injectors.length; i++) { SingleParameterInjector injector = injectors[i]; V value = injector.inject(context); if (value == null) { throw createNullValueException(perKeyData.key, perKeyData.bindings[i]); } bindingsBuilder.add(value); } resultBuilder.put(perKeyData.key, bindingsBuilder.build()); } return resultBuilder.build(); } } } /** A factory for a {@code Map.Entry>}. */ //VisibleForTesting static final class ProviderMapEntry extends InternalProviderInstanceBindingImpl.Factory>> { private final K key; private final Key valueKey; private Map.Entry> entry; ProviderMapEntry(K key, Key valueKey) { super(InitializationTiming.EAGER); this.key = key; this.valueKey = valueKey; } @Override public Set> getDependencies() { // The dependencies are Key> return ImmutableSet.>of(Dependency.get(getKeyOfProvider(valueKey))); } @Override void initialize(InjectorImpl injector, Errors errors) { Binding valueBinding = injector.getExistingBinding(valueKey); entry = Maps.immutableEntry(key, valueBinding.getProvider()); } @Override protected Map.Entry> doProvision( InternalContext context, Dependency dependency) { return entry; } K getKey() { return key; } Key getValueKey() { return valueKey; } @Override public boolean equals(Object obj) { if (obj instanceof ProviderMapEntry) { ProviderMapEntry o = (ProviderMapEntry) obj; return key.equals(o.key) && valueKey.equals(o.valueKey); } return false; } @Override public int hashCode() { return Objects.hashCode(key, valueKey); } @Override public String toString() { return "ProviderMapEntry(" + key + ", " + valueKey + ")"; } } /** A base class for ProviderWithDependencies that need equality based on a specific object. */ private abstract static class RealMapBinderProviderWithDependencies extends InternalProviderInstanceBindingImpl.Factory

{ final BindingSelection bindingSelection; private RealMapBinderProviderWithDependencies(BindingSelection bindingSelection) { // While MapBinders only depend on bindings created in modules so we could theoretically // initialize eagerly, they also depend on // 1. findBindingsByType returning results // 2. being able to call BindingImpl.acceptTargetVisitor // neither of those is available during eager initialization, so we use DELAYED super(InitializationTiming.DELAYED); this.bindingSelection = bindingSelection; } @Override final void initialize(InjectorImpl injector, Errors errors) throws ErrorsException { if (bindingSelection.tryInitialize(injector, errors)) { doInitialize(injector, errors); } } /** * Initialize the factory. BindingSelection is guaranteed to be initialized at this point and * this will be called prior to any provisioning. */ protected abstract void doInitialize(InjectorImpl injector, Errors errors) throws ErrorsException; @Override public boolean equals(Object obj) { return obj != null && this.getClass() == obj.getClass() && bindingSelection.equals( ((RealMapBinderProviderWithDependencies) obj).bindingSelection); } @Override public int hashCode() { return bindingSelection.hashCode(); } } /** * A base class for ProviderWithDependencies that need equality based on a specific object. * *

This differs from {@link RealMapBinderProviderWithDependencies} in that it gets the {@code * bindingSelection} from the injector at initialization time, rather than in the constructor. * This is done to allow all the providers to operate on the same instance of the {@link * BindingSelection}. */ private abstract static class RealMultimapBinderProviderWithDependencies extends InternalProviderInstanceBindingImpl.Factory

{ final Key> mapKey; BindingSelection bindingSelection; private RealMultimapBinderProviderWithDependencies(Key> mapKey) { // While MapBinders only depend on bindings created in modules so we could theoretically // initialize eagerly, they also depend on // 1. findBindingsByType returning results // 2. being able to call BindingImpl.acceptTargetVisitor // neither of those is available during eager initialization, so we use DELAYED super(InitializationTiming.DELAYED); this.mapKey = mapKey; } /** * This will get the authoritative {@link BindingSelection} from the map provider. This * guarantees that everyone has the same instance of the bindingSelection and sees consistent * state. */ @Override final void initialize(InjectorImpl injector, Errors errors) throws ErrorsException { Binding> mapBinding = injector.getExistingBinding(mapKey); ProviderInstanceBinding> providerInstanceBinding = (ProviderInstanceBinding>) mapBinding; @SuppressWarnings("unchecked") RealMapProvider mapProvider = (RealMapProvider) providerInstanceBinding.getUserSuppliedProvider(); this.bindingSelection = mapProvider.getBindingSelection(); if (bindingSelection.tryInitialize(injector, errors)) { doInitialize(injector, errors); } } /** * Initialize the factory. BindingSelection is guaranteed to be initialized at this point and * this will be called prior to any provisioning. */ abstract void doInitialize(InjectorImpl injector, Errors errors) throws ErrorsException; @Override public boolean equals(Object obj) { return obj != null && this.getClass() == obj.getClass() && mapKey.equals(((RealMultimapBinderProviderWithDependencies) obj).mapKey); } @Override public int hashCode() { return mapKey.hashCode(); } } private static InternalProvisionException createNullValueException( K key, Binding binding) { return InternalProvisionException.create( "Map injection failed due to null value for key \"%s\", bound at: %s", key, binding.getSource()); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy