blob: aa887f4e8cc8f078c56f72d8f05aacaf2a8f8a0d [file] [log] [blame]
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:
*
* <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. */
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. */
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 */
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.
*
* <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();
}
}
}