| /** |
| * 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)), |
| 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), |
| 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), |
| 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, |
| Multibinder<Entry<K, Provider<V>>> entrySetBinder) { |
| RealMapBinder<K, V> mapBinder = |
| new RealMapBinder<K, V>(binder, keyType, valueType, mapKey, 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, Multibinder<Map.Entry<K, Provider<V>>> 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<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(); |
| } |
| }); |
| } |
| } |
| } |