/** * 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.common.base.Predicates.equalTo; import static com.google.common.primitives.Ints.MAX_POWER_OF_TWO; import static com.google.common.collect.Iterables.filter; import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.inject.multibindings.Element.Type.MULTIBINDER; import static com.google.inject.name.Names.named; import com.google.common.base.Objects; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.inject.AbstractModule; import com.google.inject.Binder; import com.google.inject.Binding; import com.google.inject.ConfigurationException; 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.spi.BindingTargetVisitor; import com.google.inject.spi.Dependency; import com.google.inject.spi.HasDependencies; import com.google.inject.spi.Message; import com.google.inject.spi.ProviderInstanceBinding; 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.lang.reflect.Type; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * An API to bind multiple values separately, only to later inject them as a * complete collection. Multibinder is intended for use in your application's * module: * <pre><code> * public class SnacksModule extends AbstractModule { * protected void configure() { * Multibinder<Snack> multibinder * = Multibinder.newSetBinder(binder(), Snack.class); * multibinder.addBinding().toInstance(new Twix()); * multibinder.addBinding().toProvider(SnickersProvider.class); * multibinder.addBinding().to(Skittles.class); * } * }</code></pre> * * <p>With this binding, a {@link Set}{@code <Snack>} can now be injected: * <pre><code> * class SnackMachine { * {@literal @}Inject * public SnackMachine(Set<Snack> snacks) { ... } * }</code></pre> * * If desired, {@link Collection}{@code <Provider<Snack>>} can also be injected. * * <p>Contributing multibindings from different modules is supported. For * example, it is okay for both {@code CandyModule} and {@code ChipsModule} * to create their own {@code Multibinder<Snack>}, and to each contribute * bindings to the set of snacks. When that set is injected, it will contain * elements from both modules. * * <p>The set'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 set is unmodifiable. Elements can only be added to the set by * configuring the multibinder. Elements can never be removed from the set. * * <p>Elements are resolved at set injection time. If an element is bound to a * provider, that provider's get method will be called each time the set is * injected (unless the binding is also scoped). * * <p>Annotations are be used to create different sets of the same element * type. Each distinct annotation gets its own independent collection of * elements. * * <p><strong>Elements must be distinct.</strong> If multiple bound elements * have the same value, set injection will fail. * * <p><strong>Elements must be non-null.</strong> If any set element is null, * set injection will fail. * * @author jessewilson@google.com (Jesse Wilson) */ public abstract class Multibinder<T> { private Multibinder() {} /** * Returns a new multibinder that collects instances of {@code type} in a {@link Set} that is * itself bound with no binding annotation. */ public static <T> Multibinder<T> newSetBinder(Binder binder, TypeLiteral<T> type) { binder = binder.skipSources(RealMultibinder.class, Multibinder.class); RealMultibinder<T> result = new RealMultibinder<T>(binder, type, Key.get(Multibinder.<T>setOf(type)), Key.get(Multibinder.<T>collectionOfProvidersOf(type))); binder.install(result); return result; } /** * Returns a new multibinder that collects instances of {@code type} in a {@link Set} that is * itself bound with no binding annotation. */ public static <T> Multibinder<T> newSetBinder(Binder binder, Class<T> type) { return newSetBinder(binder, TypeLiteral.get(type)); } /** * Returns a new multibinder that collects instances of {@code type} in a {@link Set} that is * itself bound with {@code annotation}. */ public static <T> Multibinder<T> newSetBinder( Binder binder, TypeLiteral<T> type, Annotation annotation) { binder = binder.skipSources(RealMultibinder.class, Multibinder.class); RealMultibinder<T> result = new RealMultibinder<T>(binder, type, Key.get(Multibinder.<T>setOf(type), annotation), Key.get(Multibinder.<T>collectionOfProvidersOf(type), annotation)); binder.install(result); return result; } /** * Returns a new multibinder that collects instances of {@code type} in a {@link Set} that is * itself bound with {@code annotation}. */ public static <T> Multibinder<T> newSetBinder( Binder binder, Class<T> type, Annotation annotation) { return newSetBinder(binder, TypeLiteral.get(type), annotation); } /** * Returns a new multibinder that collects instances of {@code type} in a {@link Set} that is * itself bound with {@code annotationType}. */ public static <T> Multibinder<T> newSetBinder(Binder binder, TypeLiteral<T> type, Class<? extends Annotation> annotationType) { binder = binder.skipSources(RealMultibinder.class, Multibinder.class); RealMultibinder<T> result = new RealMultibinder<T>(binder, type, Key.get(Multibinder.<T>setOf(type), annotationType), Key.get(Multibinder.<T>collectionOfProvidersOf(type), annotationType)); binder.install(result); return result; } /** * Returns a new multibinder that collects instances of {@code type} in a {@link Set} that is * itself bound with {@code annotationType}. */ public static <T> Multibinder<T> newSetBinder(Binder binder, Class<T> type, Class<? extends Annotation> annotationType) { return newSetBinder(binder, TypeLiteral.get(type), annotationType); } @SuppressWarnings("unchecked") // wrapping a T in a Set safely returns a Set<T> static <T> TypeLiteral<Set<T>> setOf(TypeLiteral<T> elementType) { Type type = Types.setOf(elementType.getType()); return (TypeLiteral<Set<T>>) TypeLiteral.get(type); } @SuppressWarnings("unchecked") static <T> TypeLiteral<Collection<Provider<T>>> collectionOfProvidersOf( TypeLiteral<T> elementType) { Type providerType = Types.providerOf(elementType.getType()); Type type = Types.newParameterizedType(Collection.class, providerType); return (TypeLiteral<Collection<Provider<T>>>) TypeLiteral.get(type); } /** * Configures the bound set to silently discard duplicate elements. When multiple equal values are * bound, the one that gets included is arbitrary. When multiple modules contribute elements to * the set, this configuration option impacts all of them. * * @return this multibinder * @since 3.0 */ public abstract Multibinder<T> permitDuplicates(); /** * Returns a binding builder used to add a new element in the set. Each * bound element must have a distinct value. Bound providers will be * evaluated each time the set 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<T> addBinding(); /** * The actual multibinder plays several roles: * * <p>As a Multibinder, it acts as a factory for LinkedBindingBuilders for * each of the set's elements. Each binding is given an annotation that * identifies it as a part of this set. * * <p>As a Module, it installs the binding to the set itself. 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 multibinders can be created for the same target collection, but * only one is bound. Since the list of bindings is retrieved from the * injector itself (and not the multibinder), each multibinder has access to * all contributions from all multibinders. * * <p>As a Provider, this constructs the set instances. * * <p>We use a subclass to hide 'implements Module, Provider' from the public * API. */ static final class RealMultibinder<T> extends Multibinder<T> implements Module, ProviderWithExtensionVisitor<Set<T>>, HasDependencies, MultibinderBinding<Set<T>> { private final TypeLiteral<T> elementType; private final String setName; private final Key<Set<T>> setKey; private final Key<Collection<Provider<T>>> collectionOfProvidersKey; private final Key<Boolean> permitDuplicatesKey; /* the target injector's binder. non-null until initialization, null afterwards */ private Binder binder; /* a binding for each element in the set. null until initialization, non-null afterwards */ private ImmutableList<Binding<T>> bindings; private Set<Dependency<?>> dependencies; /** whether duplicates are allowed. Possibly configured by a different instance */ private boolean permitDuplicates; private RealMultibinder(Binder binder, TypeLiteral<T> elementType, Key<Set<T>> setKey, Key<Collection<Provider<T>>> collectionOfProvidersKey) { this.binder = checkNotNull(binder, "binder"); this.elementType = checkNotNull(elementType, "elementType"); this.setKey = checkNotNull(setKey, "setKey"); this.collectionOfProvidersKey = checkNotNull(collectionOfProvidersKey, "collectionOfProviders"); this.setName = RealElement.nameOf(setKey); this.permitDuplicatesKey = Key.get(Boolean.class, named(toString() + " permits duplicates")); } public void configure(Binder binder) { checkConfiguration(!isInitialized(), "Multibinder was already initialized"); binder.bind(setKey).toProvider(this); binder.bind(collectionOfProvidersKey).toProvider( new RealMultibinderCollectionOfProvidersProvider()); } @Override public Multibinder<T> permitDuplicates() { binder.install(new PermitDuplicatesModule(permitDuplicatesKey)); return this; } @Override public LinkedBindingBuilder<T> addBinding() { checkConfiguration(!isInitialized(), "Multibinder was already initialized"); return binder.bind(Key.get(elementType, new RealElement(setName, MULTIBINDER, ""))); } /** * Invoked by Guice at Injector-creation time to prepare providers for each * element in this set. At this time the set's size is known, but its * contents are only evaluated when get() is invoked. */ @Toolable @Inject void initialize(Injector injector) { List<Binding<T>> bindings = Lists.newArrayList(); Set<Indexer.IndexedBinding> index = Sets.newHashSet(); Indexer indexer = new Indexer(injector); List<Dependency<?>> dependencies = Lists.newArrayList(); for (Binding<?> entry : injector.findBindingsByType(elementType)) { if (keyMatches(entry.getKey())) { @SuppressWarnings("unchecked") // protected by findBindingsByType() Binding<T> binding = (Binding<T>) entry; if (index.add(binding.acceptTargetVisitor(indexer))) { bindings.add(binding); dependencies.add(Dependency.get(binding.getKey())); } } } this.bindings = ImmutableList.copyOf(bindings); this.dependencies = ImmutableSet.copyOf(dependencies); this.permitDuplicates = permitsDuplicates(injector); this.binder = null; } // This is forked from com.google.common.collect.Maps.capacity private static int mapCapacity(int numBindings) { if (numBindings < 3) { return numBindings + 1; } else if (numBindings < MAX_POWER_OF_TWO) { return (int) (numBindings / 0.75F + 1.0F); } return Integer.MAX_VALUE; } boolean permitsDuplicates(Injector injector) { return injector.getBindings().containsKey(permitDuplicatesKey); } private boolean keyMatches(Key<?> key) { return key.getTypeLiteral().equals(elementType) && key.getAnnotation() instanceof Element && ((Element) key.getAnnotation()).setName().equals(setName) && ((Element) key.getAnnotation()).type() == MULTIBINDER; } private boolean isInitialized() { return binder == null; } public Set<T> get() { checkConfiguration(isInitialized(), "Multibinder is not initialized"); Map<T, Binding<T>> result = new LinkedHashMap<T, Binding<T>>(mapCapacity(bindings.size())); for (Binding<T> binding : bindings) { final T newValue = binding.getProvider().get(); checkConfiguration(newValue != null, "Set injection failed due to null element"); Binding<T> duplicateBinding = result.put(newValue, binding); if (!permitDuplicates && duplicateBinding != null) { throw newDuplicateValuesException(result, binding, newValue, duplicateBinding); } } return ImmutableSet.copyOf(result.keySet()); } @SuppressWarnings("unchecked") public <B, V> V acceptExtensionVisitor( BindingTargetVisitor<B, V> visitor, ProviderInstanceBinding<? extends B> binding) { if (visitor instanceof MultibindingsTargetVisitor) { return ((MultibindingsTargetVisitor<Set<T>, V>) visitor).visit(this); } else { return visitor.visit(binding); } } String getSetName() { return setName; } public TypeLiteral<?> getElementTypeLiteral() { return elementType; } public Key<Set<T>> getSetKey() { return setKey; } @SuppressWarnings("unchecked") public List<Binding<?>> getElements() { if (isInitialized()) { return (List<Binding<?>>) (List<?>) bindings; // safe because bindings is immutable. } else { throw new UnsupportedOperationException("getElements() not supported for module bindings"); } } public boolean permitsDuplicates() { if (isInitialized()) { return permitDuplicates; } else { throw new UnsupportedOperationException( "permitsDuplicates() not supported for module bindings"); } } public boolean containsElement(com.google.inject.spi.Element element) { if (element instanceof Binding) { Binding<?> binding = (Binding<?>) element; return keyMatches(binding.getKey()) || binding.getKey().equals(permitDuplicatesKey) || binding.getKey().equals(setKey) || binding.getKey().equals(collectionOfProvidersKey); } else { return false; } } public Set<Dependency<?>> getDependencies() { if (!isInitialized()) { return ImmutableSet.<Dependency<?>>of(Dependency.get(Key.get(Injector.class))); } else { return dependencies; } } @Override public boolean equals(Object o) { return o instanceof RealMultibinder && ((RealMultibinder<?>) o).setKey.equals(setKey); } @Override public int hashCode() { return setKey.hashCode(); } @Override public String toString() { return (setName.isEmpty() ? "" : setName + " ") + "Multibinder<" + elementType + ">"; } final class RealMultibinderCollectionOfProvidersProvider implements ProviderWithDependencies<Collection<Provider<T>>> { @Override public Collection<Provider<T>> get() { checkConfiguration(isInitialized(), "Multibinder is not initialized"); int size = bindings.size(); @SuppressWarnings("unchecked") // safe because we only put Provider<T> into it. Provider<T>[] providers = new Provider[size]; for (int i = 0; i < size; i++) { providers[i] = bindings.get(i).getProvider(); } return ImmutableList.copyOf(providers); } @Override public Set<Dependency<?>> getDependencies() { if (!isInitialized()) { return ImmutableSet.<Dependency<?>>of(Dependency.get(Key.get(Injector.class))); } ImmutableSet.Builder<Dependency<?>> setBuilder = ImmutableSet.builder(); for (Dependency<?> dependency : dependencies) { Key key = dependency.getKey(); setBuilder.add( Dependency.get(key.ofType(Types.providerOf(key.getTypeLiteral().getType())))); } return setBuilder.build(); } Key getCollectionKey() { return RealMultibinder.this.collectionOfProvidersKey; } @Override public boolean equals(Object o) { return o instanceof Multibinder.RealMultibinder.RealMultibinderCollectionOfProvidersProvider && ((Multibinder.RealMultibinder.RealMultibinderCollectionOfProvidersProvider) o) .getCollectionKey().equals(getCollectionKey()); } @Override public int hashCode() { return getCollectionKey().hashCode(); } } } /** * We install the permit duplicates configuration as its own binding, all by itself. This way, * if only one of a multibinder's users remember to call permitDuplicates(), they're still * permitted. */ private static class PermitDuplicatesModule extends AbstractModule { private final Key<Boolean> key; PermitDuplicatesModule(Key<Boolean> key) { this.key = key; } @Override protected void configure() { bind(key).toInstance(true); } @Override public boolean equals(Object o) { return o instanceof PermitDuplicatesModule && ((PermitDuplicatesModule) o).key.equals(key); } @Override public int hashCode() { return getClass().hashCode() ^ key.hashCode(); } } static void checkConfiguration(boolean condition, String format, Object... args) { if (condition) { return; } throw new ConfigurationException(ImmutableSet.of(new Message(Errors.format(format, args)))); } private static <T> ConfigurationException newDuplicateValuesException( Map<T, Binding<T>> existingBindings, Binding<T> binding, final T newValue, Binding<T> duplicateBinding) { T oldValue = getOnlyElement(filter(existingBindings.keySet(), equalTo(newValue))); String oldString = oldValue.toString(); String newString = newValue.toString(); if (Objects.equal(oldString, newString)) { // When the value strings match, just show the source of the bindings return new ConfigurationException(ImmutableSet.of(new Message(Errors.format( "Set injection failed due to duplicated element \"%s\"" + "\n Bound at %s\n Bound at %s", newValue, duplicateBinding.getSource(), binding.getSource())))); } else { // When the value strings don't match, include them both as they may be useful for debugging return new ConfigurationException(ImmutableSet.of(new Message(Errors.format( "Set injection failed due to multiple elements comparing equal:" + "\n \"%s\"\n bound at %s" + "\n \"%s\"\n bound at %s", oldValue, duplicateBinding.getSource(), newValue, binding.getSource())))); } } static <T> T checkNotNull(T reference, String name) { if (reference != null) { return reference; } NullPointerException npe = new NullPointerException(name); throw new ConfigurationException(ImmutableSet.of( new Message(npe.toString(), npe))); } }