package com.google.inject.internal;
import static com.google.inject.internal.Element.Type.MULTIBINDER;
import static com.google.inject.internal.Errors.checkConfiguration;
import static com.google.inject.internal.Errors.checkNotNull;
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.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.MultibinderBinding;
import com.google.inject.multibindings.MultibindingsTargetVisitor;
import com.google.inject.spi.BindingTargetVisitor;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.ProviderInstanceBinding;
import com.google.inject.spi.ProviderWithExtensionVisitor;
import com.google.inject.util.Types;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.List;
import java.util.Set;
The actual multibinder plays several roles:
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.
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.
As a Provider, this constructs the set instances.
We use a subclass to hide 'implements Module, Provider' from the public API.
/**
* 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.
*/
public final class RealMultibinder<T> implements Module {
Implementation of newSetBinder. /** Implementation of newSetBinder. */
public static <T> RealMultibinder<T> newRealSetBinder(Binder binder, Key<T> key) {
binder = binder.skipSources(RealMultibinder.class);
RealMultibinder<T> result = new RealMultibinder<>(binder, key);
binder.install(result);
return result;
}
@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.collectionOf(providerType);
return (TypeLiteral<Collection<Provider<T>>>) TypeLiteral.get(type);
}
@SuppressWarnings("unchecked")
static <T> TypeLiteral<Collection<javax.inject.Provider<T>>> collectionOfJavaxProvidersOf(
TypeLiteral<T> elementType) {
Type providerType =
Types.newParameterizedType(javax.inject.Provider.class, elementType.getType());
Type type = Types.collectionOf(providerType);
return (TypeLiteral<Collection<javax.inject.Provider<T>>>) TypeLiteral.get(type);
}
private final BindingSelection<T> bindingSelection;
private final Binder binder;
RealMultibinder(Binder binder, Key<T> key) {
this.binder = checkNotNull(binder, "binder");
this.bindingSelection = new BindingSelection<>(key);
}
@Override
public void configure(Binder binder) {
checkConfiguration(!bindingSelection.isInitialized(), "Multibinder was already initialized");
binder
.bind(bindingSelection.getSetKey())
.toProvider(new RealMultibinderProvider<T>(bindingSelection));
Provider<Collection<Provider<T>>> collectionOfProvidersProvider =
new RealMultibinderCollectionOfProvidersProvider<T>(bindingSelection);
binder
.bind(bindingSelection.getCollectionOfProvidersKey())
.toProvider(collectionOfProvidersProvider);
// The collection this exposes is internally an ImmutableList, so it's OK to massage
// the guice Provider to javax Provider in the value (since the guice Provider implements
// javax Provider).
@SuppressWarnings("unchecked")
Provider<Collection<javax.inject.Provider<T>>> javaxProvider =
(Provider) collectionOfProvidersProvider;
binder.bind(bindingSelection.getCollectionOfJavaxProvidersKey()).toProvider(javaxProvider);
}
public void permitDuplicates() {
binder.install(new PermitDuplicatesModule(bindingSelection.getPermitDuplicatesKey()));
}
Adds a new entry to the set and returns the key for it. /** Adds a new entry to the set and returns the key for it. */
Key<T> getKeyForNewItem() {
checkConfiguration(!bindingSelection.isInitialized(), "Multibinder was already initialized");
return Key.get(
bindingSelection.getElementTypeLiteral(),
new RealElement(bindingSelection.getSetName(), MULTIBINDER, ""));
}
public LinkedBindingBuilder<T> addBinding() {
return binder.bind(getKeyForNewItem());
}
// These methods are used by RealMapBinder
Key<Set<T>> getSetKey() {
return bindingSelection.getSetKey();
}
TypeLiteral<T> getElementTypeLiteral() {
return bindingSelection.getElementTypeLiteral();
}
String getSetName() {
return bindingSelection.getSetName();
}
boolean permitsDuplicates(Injector injector) {
return bindingSelection.permitsDuplicates(injector);
}
boolean containsElement(com.google.inject.spi.Element element) {
return bindingSelection.containsElement(element);
}
private static final class RealMultibinderProvider<T>
extends InternalProviderInstanceBindingImpl.Factory<Set<T>>
implements ProviderWithExtensionVisitor<Set<T>>, MultibinderBinding<Set<T>> {
private final BindingSelection<T> bindingSelection;
private List<Binding<T>> bindings;
private SingleParameterInjector<T>[] injectors;
private boolean permitDuplicates;
RealMultibinderProvider(BindingSelection<T> bindingSelection) {
// While Multibinders 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
public Set<Dependency<?>> getDependencies() {
return bindingSelection.getDependencies();
}
@Override
void initialize(InjectorImpl injector, Errors errors) throws ErrorsException {
bindingSelection.initialize(injector, errors);
this.bindings = bindingSelection.getBindings();
this.injectors = bindingSelection.getParameterInjectors();
this.permitDuplicates = bindingSelection.permitsDuplicates();
}
@Override
protected Set<T> doProvision(InternalContext context, Dependency<?> dependency)
throws InternalProvisionException {
SingleParameterInjector<T>[] localInjectors = injectors;
if (localInjectors == null) {
// if localInjectors == null, then we have no bindings so return the empty set.
return ImmutableSet.of();
}
// Ideally we would just add to an ImmutableSet.Builder, but if we did that and there were
// duplicates we wouldn't be able to tell which one was the duplicate. So to manage this we
// first put everything into an array and then construct the set. This way if something gets
// dropped we can figure out what it is.
@SuppressWarnings("unchecked")
T[] values = (T[]) new Object[localInjectors.length];
for (int i = 0; i < localInjectors.length; i++) {
SingleParameterInjector<T> parameterInjector = localInjectors[i];
T newValue = parameterInjector.inject(context);
if (newValue == null) {
throw newNullEntryException(i);
}
values[i] = newValue;
}
ImmutableSet<T> set = ImmutableSet.copyOf(values);
// There are fewer items in the set than the array. Figure out which one got dropped.
if (!permitDuplicates && set.size() < values.length) {
throw newDuplicateValuesException(set, values);
}
return set;
}
private InternalProvisionException newNullEntryException(int i) {
return InternalProvisionException.create(
"Set injection failed due to null element bound at: %s", bindings.get(i).getSource());
}
@SuppressWarnings("unchecked")
@Override
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);
}
}
private InternalProvisionException newDuplicateValuesException(
ImmutableSet<T> set, T[] values) {
// TODO(lukes): consider reporting all duplicate values, the easiest way would be to rebuild
// a new set and detect dupes as we go
// Find the duplicate binding
// To do this we take advantage of the fact that set, values and bindings all have the same
// ordering for a non-empty prefix of the set.
// First we scan for the first item dropped from the set.
int newBindingIndex = 0;
for (T item : set) {
if (item != values[newBindingIndex]) {
break;
}
newBindingIndex++;
}
// once we exit the loop newBindingIndex will point at the first item in values that was
// dropped.
Binding<T> newBinding = bindings.get(newBindingIndex);
T newValue = values[newBindingIndex];
// Now we scan again to find the index of the value, we are guaranteed to find it.
int oldBindingIndex = set.asList().indexOf(newValue);
T oldValue = values[oldBindingIndex];
Binding<T> duplicateBinding = bindings.get(oldBindingIndex);
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 InternalProvisionException.create(
"Set injection failed due to duplicated element \"%s\""
+ "\n Bound at %s\n Bound at %s",
newValue, duplicateBinding.getSource(), newBinding.getSource());
} else {
// When the value strings don't match, include them both as they may be useful for debugging
return InternalProvisionException.create(
"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, newBinding.getSource());
}
}
@Override
public boolean equals(Object obj) {
return obj instanceof RealMultibinderProvider
&& bindingSelection.equals(((RealMultibinderProvider<?>) obj).bindingSelection);
}
@Override
public int hashCode() {
return bindingSelection.hashCode();
}
@Override
public Key<Set<T>> getSetKey() {
return bindingSelection.getSetKey();
}
@Override
public TypeLiteral<?> getElementTypeLiteral() {
return bindingSelection.getElementTypeLiteral();
}
@Override
public List<Binding<?>> getElements() {
return bindingSelection.getElements();
}
@Override
public boolean permitsDuplicates() {
return bindingSelection.permitsDuplicates();
}
@Override
public boolean containsElement(com.google.inject.spi.Element element) {
return bindingSelection.containsElement(element);
}
}
private static final class BindingSelection<T> {
// prior to initialization we declare just a dependency on the injector, but as soon as we are
// initialized we swap to dependencies on the elements.
private static final ImmutableSet<Dependency<?>> MODULE_DEPENDENCIES =
ImmutableSet.<Dependency<?>>of(Dependency.get(Key.get(Injector.class)));
private final TypeLiteral<T> elementType;
private final Key<Set<T>> setKey;
// these are all lazily allocated
private String setName;
private Key<Collection<Provider<T>>> collectionOfProvidersKey;
private Key<Collection<javax.inject.Provider<T>>> collectionOfJavaxProvidersKey;
private Key<Boolean> permitDuplicatesKey;
private boolean isInitialized;
/* a binding for each element in the set. null until initialization, non-null afterwards */
private ImmutableList<Binding<T>> bindings;
// Starts out as Injector and gets set up properly after initialization
private ImmutableSet<Dependency<?>> dependencies = MODULE_DEPENDENCIES;
private ImmutableSet<Dependency<?>> providerDependencies = MODULE_DEPENDENCIES;
whether duplicates are allowed. Possibly configured by a different instance /** whether duplicates are allowed. Possibly configured by a different instance */
private boolean permitDuplicates;
private SingleParameterInjector<T>[] parameterinjectors;
BindingSelection(Key<T> key) {
this.setKey = key.ofType(setOf(key.getTypeLiteral()));
this.elementType = key.getTypeLiteral();
}
void initialize(InjectorImpl injector, Errors errors) throws ErrorsException {
// This will be called multiple times, once by each Factory. We only want
// to do the work to initialize everything once, so guard this code with
// isInitialized.
if (isInitialized) {
return;
}
List<Binding<T>> bindings = Lists.newArrayList();
Set<Indexer.IndexedBinding> index = Sets.newHashSet();
Indexer indexer = new Indexer(injector);
List<Dependency<?>> dependencies = Lists.newArrayList();
List<Dependency<?>> providerDependencies = 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))) {
// TODO(lukes): most of these are linked bindings since user bindings are linked to
// a user binding through the @Element annotation. Since this is an implementation
// detail we could 'dereference' the @Element if it is a LinkedBinding and avoid
// provisioning through the FactoryProxy at runtime.
// Ditto for OptionalBinder/MapBinder
bindings.add(binding);
Key<T> key = binding.getKey();
// TODO(lukes): we should mark this as a non-nullable dependency since we don't accept
// null.
// Add a dependency on Key<T>
dependencies.add(Dependency.get(key));
// and add a dependency on Key<Provider<T>>
providerDependencies.add(
Dependency.get(key.ofType(Types.providerOf(key.getTypeLiteral().getType()))));
}
}
}
this.bindings = ImmutableList.copyOf(bindings);
this.dependencies = ImmutableSet.copyOf(dependencies);
this.providerDependencies = ImmutableSet.copyOf(providerDependencies);
this.permitDuplicates = permitsDuplicates(injector);
// This is safe because all our dependencies are assignable to T and we never assign to
// elements of this array.
@SuppressWarnings("unchecked")
SingleParameterInjector<T>[] typed =
(SingleParameterInjector<T>[]) injector.getParametersInjectors(dependencies, errors);
this.parameterinjectors = typed;
isInitialized = true;
}
boolean permitsDuplicates(Injector injector) {
return injector.getBindings().containsKey(getPermitDuplicatesKey());
}
ImmutableList<Binding<T>> getBindings() {
checkConfiguration(isInitialized, "not initialized");
return bindings;
}
SingleParameterInjector<T>[] getParameterInjectors() {
checkConfiguration(isInitialized, "not initialized");
return parameterinjectors;
}
ImmutableSet<Dependency<?>> getDependencies() {
return dependencies;
}
ImmutableSet<Dependency<?>> getProviderDependencies() {
return providerDependencies;
}
String getSetName() {
// lazily initialized since most selectors don't survive module installation.
if (setName == null) {
setName = Annotations.nameOf(setKey);
}
return setName;
}
Key<Boolean> getPermitDuplicatesKey() {
Key<Boolean> local = permitDuplicatesKey;
if (local == null) {
local =
permitDuplicatesKey = Key.get(Boolean.class, named(toString() + " permits duplicates"));
}
return local;
}
Key<Collection<Provider<T>>> getCollectionOfProvidersKey() {
Key<Collection<Provider<T>>> local = collectionOfProvidersKey;
if (local == null) {
local = collectionOfProvidersKey = setKey.ofType(collectionOfProvidersOf(elementType));
}
return local;
}
Key<Collection<javax.inject.Provider<T>>> getCollectionOfJavaxProvidersKey() {
Key<Collection<javax.inject.Provider<T>>> local = collectionOfJavaxProvidersKey;
if (local == null) {
local =
collectionOfJavaxProvidersKey =
setKey.ofType(collectionOfJavaxProvidersOf(elementType));
}
return local;
}
boolean isInitialized() {
return isInitialized;
}
// MultibinderBinding API methods
TypeLiteral<T> getElementTypeLiteral() {
return elementType;
}
Key<Set<T>> getSetKey() {
return setKey;
}
@SuppressWarnings("unchecked")
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");
}
}
boolean permitsDuplicates() {
if (isInitialized()) {
return permitDuplicates;
} else {
throw new UnsupportedOperationException(
"permitsDuplicates() not supported for module bindings");
}
}
boolean containsElement(com.google.inject.spi.Element element) {
if (element instanceof Binding) {
Binding<?> binding = (Binding<?>) element;
return keyMatches(binding.getKey())
|| binding.getKey().equals(getPermitDuplicatesKey())
|| binding.getKey().equals(setKey)
|| binding.getKey().equals(collectionOfProvidersKey)
|| binding.getKey().equals(collectionOfJavaxProvidersKey);
} else {
return false;
}
}
private boolean keyMatches(Key<?> key) {
return key.getTypeLiteral().equals(elementType)
&& key.getAnnotation() instanceof Element
&& ((Element) key.getAnnotation()).setName().equals(getSetName())
&& ((Element) key.getAnnotation()).type() == MULTIBINDER;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof BindingSelection) {
return setKey.equals(((BindingSelection<?>) obj).setKey);
}
return false;
}
@Override
public int hashCode() {
return setKey.hashCode();
}
@Override
public String toString() {
return (getSetName().isEmpty() ? "" : getSetName() + " ")
+ "Multibinder<"
+ elementType
+ ">";
}
}
@Override
public boolean equals(Object o) {
return o instanceof RealMultibinder
&& ((RealMultibinder<?>) o).bindingSelection.equals(bindingSelection);
}
@Override
public int hashCode() {
return bindingSelection.hashCode();
}
private static final class RealMultibinderCollectionOfProvidersProvider<T>
extends InternalProviderInstanceBindingImpl.Factory<Collection<Provider<T>>> {
private final BindingSelection<T> bindingSelection;
private ImmutableList<Provider<T>> collectionOfProviders;
RealMultibinderCollectionOfProvidersProvider(BindingSelection<T> bindingSelection) {
super(InitializationTiming.DELAYED); // See comment in RealMultibinderProvider
this.bindingSelection = bindingSelection;
}
@Override
void initialize(InjectorImpl injector, Errors errors) throws ErrorsException {
bindingSelection.initialize(injector, errors);
ImmutableList.Builder<Provider<T>> providers = ImmutableList.builder();
for (Binding<T> binding : bindingSelection.getBindings()) {
providers.add(binding.getProvider());
}
this.collectionOfProviders = providers.build();
}
@Override
protected Collection<Provider<T>> doProvision(
InternalContext context, Dependency<?> dependency) {
return collectionOfProviders;
}
@Override
public Set<Dependency<?>> getDependencies() {
return bindingSelection.getProviderDependencies();
}
@Override
public boolean equals(Object obj) {
return obj instanceof RealMultibinderCollectionOfProvidersProvider
&& bindingSelection.equals(
((RealMultibinderCollectionOfProvidersProvider<?>) obj).bindingSelection);
}
@Override
public int hashCode() {
return bindingSelection.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.
This is like setting a global variable in the injector so that each instance of the
multibinder will have the same value for permitDuplicates, even if it is only set on one of
them.
/**
* 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.
*
* <p>This is like setting a global variable in the injector so that each instance of the
* multibinder will have the same value for permitDuplicates, even if it is only set on one of
* them.
*/
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();
}
}
}