com.google.inject.multibindings.Multibinder Maven / Gradle / Ivy
Show all versions of hello-world-plugin Show documentation
/**
* 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:
*
* 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);
* }
* }
*
* With this binding, a {@link Set}{@code } can now be injected:
*
* class SnackMachine {
* {@literal @}Inject
* public SnackMachine(Set<Snack> snacks) { ... }
* }
*
* If desired, {@link Collection}{@code >} can also be injected.
*
* 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}, and to each contribute
* bindings to the set of snacks. When that set is injected, it will contain
* elements from both modules.
*
* 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.
*
*
The set is unmodifiable. Elements can only be added to the set by
* configuring the multibinder. Elements can never be removed from the set.
*
*
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).
*
*
Annotations are be used to create different sets of the same element
* type. Each distinct annotation gets its own independent collection of
* elements.
*
*
Elements must be distinct. If multiple bound elements
* have the same value, set injection will fail.
*
*
Elements must be non-null. If any set element is null,
* set injection will fail.
*
* @author [email protected] (Jesse Wilson)
*/
public abstract class Multibinder {
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 Multibinder newSetBinder(Binder binder, TypeLiteral type) {
return newRealSetBinder(binder, Key.get(type));
}
/**
* Returns a new multibinder that collects instances of {@code type} in a {@link Set} that is
* itself bound with no binding annotation.
*/
public static Multibinder newSetBinder(Binder binder, Class type) {
return newRealSetBinder(binder, Key.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 Multibinder newSetBinder(
Binder binder, TypeLiteral type, Annotation annotation) {
return newRealSetBinder(binder, Key.get(type, annotation));
}
/**
* Returns a new multibinder that collects instances of {@code type} in a {@link Set} that is
* itself bound with {@code annotation}.
*/
public static Multibinder newSetBinder(
Binder binder, Class type, Annotation annotation) {
return newRealSetBinder(binder, Key.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 Multibinder newSetBinder(Binder binder, TypeLiteral type,
Class extends Annotation> annotationType) {
return newRealSetBinder(binder, Key.get(type, annotationType));
}
/**
* Returns a new multibinder that collects instances of the key's type in a {@link Set} that is
* itself bound with the annotation (if any) of the key.
*
* @since 4.0
*/
public static Multibinder newSetBinder(Binder binder, Key key) {
return newRealSetBinder(binder, key);
}
/**
* Implementation of newSetBinder.
*/
static RealMultibinder newRealSetBinder(Binder binder, Key key) {
binder = binder.skipSources(RealMultibinder.class, Multibinder.class);
RealMultibinder result = new RealMultibinder(binder, key.getTypeLiteral(),
key.ofType(setOf(key.getTypeLiteral())));
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 Multibinder newSetBinder(Binder binder, Class type,
Class extends Annotation> annotationType) {
return newSetBinder(binder, Key.get(type, annotationType));
}
@SuppressWarnings("unchecked") // wrapping a T in a Set safely returns a Set
static TypeLiteral> setOf(TypeLiteral elementType) {
Type type = Types.setOf(elementType.getType());
return (TypeLiteral>) TypeLiteral.get(type);
}
@SuppressWarnings("unchecked")
static TypeLiteral>> collectionOfProvidersOf(
TypeLiteral elementType) {
Type providerType = Types.providerOf(elementType.getType());
Type type = Types.collectionOf(providerType);
return (TypeLiteral>>) TypeLiteral.get(type);
}
@SuppressWarnings("unchecked")
static TypeLiteral>> collectionOfJavaxProvidersOf(
TypeLiteral elementType) {
Type providerType =
Types.newParameterizedType(javax.inject.Provider.class, elementType.getType());
Type type = Types.collectionOf(providerType);
return (TypeLiteral>>) 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 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.
*
* It is an error to call this method without also calling one of the
* {@code to} methods on the returned binding builder.
*
*
Scoping elements independently is supported. Use the {@code in} method
* to specify a binding scope.
*/
public abstract LinkedBindingBuilder addBinding();
/**
* 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.
*/
static final class RealMultibinder extends Multibinder
implements Module, ProviderWithExtensionVisitor>, HasDependencies,
MultibinderBinding> {
private final TypeLiteral elementType;
private final String setName;
private final Key> setKey;
private final Key>> collectionOfProvidersKey;
private final Key>> collectionOfJavaxProvidersKey;
private final Key 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> bindings;
private Set> dependencies;
/** whether duplicates are allowed. Possibly configured by a different instance */
private boolean permitDuplicates;
private RealMultibinder(Binder binder, TypeLiteral elementType, Key> setKey) {
this.binder = checkNotNull(binder, "binder");
this.elementType = checkNotNull(elementType, "elementType");
this.setKey = checkNotNull(setKey, "setKey");
this.collectionOfProvidersKey = setKey.ofType(collectionOfProvidersOf(elementType));
this.collectionOfJavaxProvidersKey = setKey.ofType(collectionOfJavaxProvidersOf(elementType));
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());
// 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")
Key key = (Key) collectionOfProvidersKey;
binder.bind(collectionOfJavaxProvidersKey).to(key);
}
@Override public Multibinder permitDuplicates() {
binder.install(new PermitDuplicatesModule(permitDuplicatesKey));
return this;
}
Key getKeyForNewItem() {
checkConfiguration(!isInitialized(), "Multibinder was already initialized");
return Key.get(elementType, new RealElement(setName, MULTIBINDER, ""));
}
@Override public LinkedBindingBuilder addBinding() {
return binder.bind(getKeyForNewItem());
}
/**
* 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> bindings = Lists.newArrayList();
Set index = Sets.newHashSet();
Indexer indexer = new Indexer(injector);
List> dependencies = Lists.newArrayList();
for (Binding> entry : injector.findBindingsByType(elementType)) {
if (keyMatches(entry.getKey())) {
@SuppressWarnings("unchecked") // protected by findBindingsByType()
Binding binding = (Binding) 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 get() {
checkConfiguration(isInitialized(), "Multibinder is not initialized");
Map> result = new LinkedHashMap>(mapCapacity(bindings.size()));
for (Binding binding : bindings) {
final T newValue = binding.getProvider().get();
checkConfiguration(newValue != null,
"Set injection failed due to null element bound at: %s",
binding.getSource());
Binding duplicateBinding = result.put(newValue, binding);
if (!permitDuplicates && duplicateBinding != null) {
throw newDuplicateValuesException(result, binding, newValue, duplicateBinding);
}
}
return ImmutableSet.copyOf(result.keySet());
}
@SuppressWarnings("unchecked")
public V acceptExtensionVisitor(
BindingTargetVisitor visitor,
ProviderInstanceBinding extends B> binding) {
if (visitor instanceof MultibindingsTargetVisitor) {
return ((MultibindingsTargetVisitor, V>) visitor).visit(this);
} else {
return visitor.visit(binding);
}
}
String getSetName() {
return setName;
}
public TypeLiteral> getElementTypeLiteral() {
return elementType;
}
public Key> getSetKey() {
return setKey;
}
@SuppressWarnings("unchecked")
public List> getElements() {
if (isInitialized()) {
return (List>) (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)
|| binding.getKey().equals(collectionOfJavaxProvidersKey);
} else {
return false;
}
}
public Set> getDependencies() {
if (!isInitialized()) {
return ImmutableSet.>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>> {
@Override public Collection> get() {
checkConfiguration(isInitialized(), "Multibinder is not initialized");
int size = bindings.size();
@SuppressWarnings("unchecked") // safe because we only put Provider into it.
Provider[] providers = new Provider[size];
for (int i = 0; i < size; i++) {
providers[i] = bindings.get(i).getProvider();
}
return ImmutableList.copyOf(providers);
}
@Override public Set> getDependencies() {
if (!isInitialized()) {
return ImmutableSet.>of(Dependency.get(Key.get(Injector.class)));
}
ImmutableSet.Builder> 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 key;
PermitDuplicatesModule(Key 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 ConfigurationException newDuplicateValuesException(
Map> existingBindings,
Binding binding,
final T newValue,
Binding 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 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)));
}
}