All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.inject.multibindings.Multibinder Maven / Gradle / Ivy

There is a newer version: 4.2.3
Show newest version
/**
 * 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 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.TypeLiteral;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.internal.Annotations;
import com.google.inject.internal.Errors;
import com.google.inject.internal.util.ImmutableList;
import com.google.inject.internal.util.ImmutableSet;
import com.google.inject.internal.util.Lists;
import static com.google.inject.name.Names.named;

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.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.Collections;
import java.util.LinkedHashSet;
import java.util.List;
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) { ... }
 * }
* *

Contributing multibindings from different modules is supported. For * example, it is okay to have both {@code CandyModule} and {@code ChipsModule} * to both 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) { binder = binder.skipSources(RealMultibinder.class, Multibinder.class); RealMultibinder result = new RealMultibinder(binder, type, Key.get(Multibinder.setOf(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 Multibinder newSetBinder(Binder binder, Class 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 Multibinder newSetBinder( Binder binder, TypeLiteral type, Annotation annotation) { binder = binder.skipSources(RealMultibinder.class, Multibinder.class); RealMultibinder result = new RealMultibinder(binder, type, Key.get(Multibinder.setOf(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 Multibinder newSetBinder( Binder binder, Class 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 Multibinder newSetBinder(Binder binder, TypeLiteral type, Class annotationType) { binder = binder.skipSources(RealMultibinder.class, Multibinder.class); RealMultibinder result = new RealMultibinder(binder, type, Key.get(Multibinder.setOf(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 Multibinder newSetBinder(Binder binder, Class type, Class annotationType) { return newSetBinder(binder, TypeLiteral.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); } /** * 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 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.setName = nameOf(setKey); this.permitDuplicatesKey = Key.get(Boolean.class, named(toString() + " permits duplicates")); } /** * Returns the name the set should use. This is based on the annotation. * If the annotation has an instance and is not a marker annotation, * we ask the annotation for its toString. If it was a marker annotation * or just an annotation type, we use the annotation's name. Otherwise, * the name is the empty string. */ private String nameOf(Key key) { Annotation annotation = setKey.getAnnotation(); Class annotationType = setKey.getAnnotationType(); if (annotation != null && !Annotations.isMarker(annotationType)) { return setKey.getAnnotation().toString(); } else if(setKey.getAnnotationType() != null) { return "@" + setKey.getAnnotationType().getName(); } else { return ""; } } @SuppressWarnings("unchecked") public void configure(Binder binder) { checkConfiguration(!isInitialized(), "Multibinder was already initialized"); binder.bind(setKey).toProvider(this); } @Override public Multibinder permitDuplicates() { binder.install(new PermitDuplicatesModule(permitDuplicatesKey)); return this; } @Override public LinkedBindingBuilder addBinding() { checkConfiguration(!isInitialized(), "Multibinder was already initialized"); return binder.bind(Key.get(elementType, new RealElement(setName))); } /** * 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(); List> dependencies = Lists.newArrayList(); for (Binding entry : injector.findBindingsByType(elementType)) { if (keyMatches(entry.getKey())) { @SuppressWarnings("unchecked") // protected by findBindingsByType() Binding binding = (Binding) entry; 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; } 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); } private boolean isInitialized() { return binder == null; } public Set get() { checkConfiguration(isInitialized(), "Multibinder is not initialized"); Set result = new LinkedHashSet(); for (Binding binding : bindings) { final T newValue = binding.getProvider().get(); checkConfiguration(newValue != null, "Set injection failed due to null element"); checkConfiguration(result.add(newValue) || permitDuplicates, "Set injection failed due to duplicated element \"%s\"", newValue); } return Collections.unmodifiableSet(result); } @SuppressWarnings("unchecked") public V acceptExtensionVisitor( BindingTargetVisitor visitor, ProviderInstanceBinding 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)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); } 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 new StringBuilder() .append(setName) .append(setName.length() > 0 ? " " : "") .append("Multibinder<") .append(elementType) .append(">") .toString(); } } /** * 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)))); } static T checkNotNull(T reference, String name) { if (reference != null) { return reference; } NullPointerException npe = new NullPointerException(name); throw new ConfigurationException(ImmutableSet.of( new Message(ImmutableList.of(), npe.toString(), npe))); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy