Home | History | Annotate | Download | only in multibindings
      1 /**
      2  * Copyright (C) 2008 Google Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.google.inject.multibindings;
     18 
     19 import static com.google.common.base.Predicates.equalTo;
     20 import static com.google.common.primitives.Ints.MAX_POWER_OF_TWO;
     21 import static com.google.common.collect.Iterables.filter;
     22 import static com.google.common.collect.Iterables.getOnlyElement;
     23 import static com.google.inject.multibindings.Element.Type.MULTIBINDER;
     24 import static com.google.inject.name.Names.named;
     25 
     26 import com.google.common.base.Objects;
     27 import com.google.common.collect.ImmutableList;
     28 import com.google.common.collect.ImmutableSet;
     29 import com.google.common.collect.Lists;
     30 import com.google.common.collect.Sets;
     31 import com.google.inject.AbstractModule;
     32 import com.google.inject.Binder;
     33 import com.google.inject.Binding;
     34 import com.google.inject.ConfigurationException;
     35 import com.google.inject.Inject;
     36 import com.google.inject.Injector;
     37 import com.google.inject.Key;
     38 import com.google.inject.Module;
     39 import com.google.inject.Provider;
     40 import com.google.inject.TypeLiteral;
     41 import com.google.inject.binder.LinkedBindingBuilder;
     42 import com.google.inject.internal.Errors;
     43 import com.google.inject.spi.BindingTargetVisitor;
     44 import com.google.inject.spi.Dependency;
     45 import com.google.inject.spi.HasDependencies;
     46 import com.google.inject.spi.Message;
     47 import com.google.inject.spi.ProviderInstanceBinding;
     48 import com.google.inject.spi.ProviderWithDependencies;
     49 import com.google.inject.spi.ProviderWithExtensionVisitor;
     50 import com.google.inject.spi.Toolable;
     51 import com.google.inject.util.Types;
     52 
     53 import java.lang.annotation.Annotation;
     54 import java.lang.reflect.Type;
     55 import java.util.Collection;
     56 import java.util.LinkedHashMap;
     57 import java.util.List;
     58 import java.util.Map;
     59 import java.util.Set;
     60 
     61 /**
     62  * An API to bind multiple values separately, only to later inject them as a
     63  * complete collection. Multibinder is intended for use in your application's
     64  * module:
     65  * <pre><code>
     66  * public class SnacksModule extends AbstractModule {
     67  *   protected void configure() {
     68  *     Multibinder&lt;Snack&gt; multibinder
     69  *         = Multibinder.newSetBinder(binder(), Snack.class);
     70  *     multibinder.addBinding().toInstance(new Twix());
     71  *     multibinder.addBinding().toProvider(SnickersProvider.class);
     72  *     multibinder.addBinding().to(Skittles.class);
     73  *   }
     74  * }</code></pre>
     75  *
     76  * <p>With this binding, a {@link Set}{@code <Snack>} can now be injected:
     77  * <pre><code>
     78  * class SnackMachine {
     79  *   {@literal @}Inject
     80  *   public SnackMachine(Set&lt;Snack&gt; snacks) { ... }
     81  * }</code></pre>
     82  *
     83  * If desired, {@link Collection}{@code <Provider<Snack>>} can also be injected.
     84  *
     85  * <p>Contributing multibindings from different modules is supported. For
     86  * example, it is okay for both {@code CandyModule} and {@code ChipsModule}
     87  * to create their own {@code Multibinder<Snack>}, and to each contribute
     88  * bindings to the set of snacks. When that set is injected, it will contain
     89  * elements from both modules.
     90  *
     91  * <p>The set's iteration order is consistent with the binding order. This is
     92  * convenient when multiple elements are contributed by the same module because
     93  * that module can order its bindings appropriately. Avoid relying on the
     94  * iteration order of elements contributed by different modules, since there is
     95  * no equivalent mechanism to order modules.
     96  *
     97  * <p>The set is unmodifiable.  Elements can only be added to the set by
     98  * configuring the multibinder.  Elements can never be removed from the set.
     99  *
    100  * <p>Elements are resolved at set injection time. If an element is bound to a
    101  * provider, that provider's get method will be called each time the set is
    102  * injected (unless the binding is also scoped).
    103  *
    104  * <p>Annotations are be used to create different sets of the same element
    105  * type. Each distinct annotation gets its own independent collection of
    106  * elements.
    107  *
    108  * <p><strong>Elements must be distinct.</strong> If multiple bound elements
    109  * have the same value, set injection will fail.
    110  *
    111  * <p><strong>Elements must be non-null.</strong> If any set element is null,
    112  * set injection will fail.
    113  *
    114  * @author jessewilson (at) google.com (Jesse Wilson)
    115  */
    116 public abstract class Multibinder<T> {
    117   private Multibinder() {}
    118 
    119   /**
    120    * Returns a new multibinder that collects instances of {@code type} in a {@link Set} that is
    121    * itself bound with no binding annotation.
    122    */
    123   public static <T> Multibinder<T> newSetBinder(Binder binder, TypeLiteral<T> type) {
    124     return newRealSetBinder(binder, Key.get(type));
    125   }
    126 
    127   /**
    128    * Returns a new multibinder that collects instances of {@code type} in a {@link Set} that is
    129    * itself bound with no binding annotation.
    130    */
    131   public static <T> Multibinder<T> newSetBinder(Binder binder, Class<T> type) {
    132     return newRealSetBinder(binder, Key.get(type));
    133   }
    134 
    135   /**
    136    * Returns a new multibinder that collects instances of {@code type} in a {@link Set} that is
    137    * itself bound with {@code annotation}.
    138    */
    139   public static <T> Multibinder<T> newSetBinder(
    140       Binder binder, TypeLiteral<T> type, Annotation annotation) {
    141     return newRealSetBinder(binder, Key.get(type, annotation));
    142   }
    143 
    144   /**
    145    * Returns a new multibinder that collects instances of {@code type} in a {@link Set} that is
    146    * itself bound with {@code annotation}.
    147    */
    148   public static <T> Multibinder<T> newSetBinder(
    149       Binder binder, Class<T> type, Annotation annotation) {
    150     return newRealSetBinder(binder, Key.get(type, annotation));
    151   }
    152 
    153   /**
    154    * Returns a new multibinder that collects instances of {@code type} in a {@link Set} that is
    155    * itself bound with {@code annotationType}.
    156    */
    157   public static <T> Multibinder<T> newSetBinder(Binder binder, TypeLiteral<T> type,
    158       Class<? extends Annotation> annotationType) {
    159     return newRealSetBinder(binder, Key.get(type, annotationType));
    160   }
    161 
    162   /**
    163    * Returns a new multibinder that collects instances of the key's type in a {@link Set} that is
    164    * itself bound with the annotation (if any) of the key.
    165    *
    166    * @since 4.0
    167    */
    168   public static <T> Multibinder<T> newSetBinder(Binder binder, Key<T> key) {
    169     return newRealSetBinder(binder, key);
    170   }
    171 
    172   /**
    173    * Implementation of newSetBinder.
    174    */
    175   static <T> RealMultibinder<T> newRealSetBinder(Binder binder, Key<T> key) {
    176     binder = binder.skipSources(RealMultibinder.class, Multibinder.class);
    177     RealMultibinder<T> result = new RealMultibinder<T>(binder, key.getTypeLiteral(),
    178         key.ofType(setOf(key.getTypeLiteral())));
    179     binder.install(result);
    180     return result;
    181   }
    182 
    183   /**
    184    * Returns a new multibinder that collects instances of {@code type} in a {@link Set} that is
    185    * itself bound with {@code annotationType}.
    186    */
    187   public static <T> Multibinder<T> newSetBinder(Binder binder, Class<T> type,
    188       Class<? extends Annotation> annotationType) {
    189     return newSetBinder(binder, Key.get(type, annotationType));
    190   }
    191 
    192   @SuppressWarnings("unchecked") // wrapping a T in a Set safely returns a Set<T>
    193   static <T> TypeLiteral<Set<T>> setOf(TypeLiteral<T> elementType) {
    194     Type type = Types.setOf(elementType.getType());
    195     return (TypeLiteral<Set<T>>) TypeLiteral.get(type);
    196   }
    197 
    198   @SuppressWarnings("unchecked")
    199   static <T> TypeLiteral<Collection<Provider<T>>> collectionOfProvidersOf(
    200       TypeLiteral<T> elementType) {
    201     Type providerType = Types.providerOf(elementType.getType());
    202     Type type = Types.newParameterizedType(Collection.class, providerType);
    203     return (TypeLiteral<Collection<Provider<T>>>) TypeLiteral.get(type);
    204   }
    205 
    206   @SuppressWarnings("unchecked")
    207   static <T> TypeLiteral<Collection<javax.inject.Provider<T>>> collectionOfJavaxProvidersOf(
    208       TypeLiteral<T> elementType) {
    209     Type providerType =
    210         Types.newParameterizedType(javax.inject.Provider.class, elementType.getType());
    211     Type type = Types.newParameterizedType(Collection.class, providerType);
    212     return (TypeLiteral<Collection<javax.inject.Provider<T>>>) TypeLiteral.get(type);
    213   }
    214 
    215   /**
    216    * Configures the bound set to silently discard duplicate elements. When multiple equal values are
    217    * bound, the one that gets included is arbitrary. When multiple modules contribute elements to
    218    * the set, this configuration option impacts all of them.
    219    *
    220    * @return this multibinder
    221    * @since 3.0
    222    */
    223   public abstract Multibinder<T> permitDuplicates();
    224 
    225   /**
    226    * Returns a binding builder used to add a new element in the set. Each
    227    * bound element must have a distinct value. Bound providers will be
    228    * evaluated each time the set is injected.
    229    *
    230    * <p>It is an error to call this method without also calling one of the
    231    * {@code to} methods on the returned binding builder.
    232    *
    233    * <p>Scoping elements independently is supported. Use the {@code in} method
    234    * to specify a binding scope.
    235    */
    236   public abstract LinkedBindingBuilder<T> addBinding();
    237 
    238   /**
    239    * The actual multibinder plays several roles:
    240    *
    241    * <p>As a Multibinder, it acts as a factory for LinkedBindingBuilders for
    242    * each of the set's elements. Each binding is given an annotation that
    243    * identifies it as a part of this set.
    244    *
    245    * <p>As a Module, it installs the binding to the set itself. As a module,
    246    * this implements equals() and hashcode() in order to trick Guice into
    247    * executing its configure() method only once. That makes it so that
    248    * multiple multibinders can be created for the same target collection, but
    249    * only one is bound. Since the list of bindings is retrieved from the
    250    * injector itself (and not the multibinder), each multibinder has access to
    251    * all contributions from all multibinders.
    252    *
    253    * <p>As a Provider, this constructs the set instances.
    254    *
    255    * <p>We use a subclass to hide 'implements Module, Provider' from the public
    256    * API.
    257    */
    258   static final class RealMultibinder<T> extends Multibinder<T>
    259       implements Module, ProviderWithExtensionVisitor<Set<T>>, HasDependencies,
    260           MultibinderBinding<Set<T>> {
    261 
    262     private final TypeLiteral<T> elementType;
    263     private final String setName;
    264     private final Key<Set<T>> setKey;
    265     private final Key<Collection<Provider<T>>> collectionOfProvidersKey;
    266     private final Key<Collection<javax.inject.Provider<T>>> collectionOfJavaxProvidersKey;
    267     private final Key<Boolean> permitDuplicatesKey;
    268 
    269     /* the target injector's binder. non-null until initialization, null afterwards */
    270     private Binder binder;
    271 
    272     /* a binding for each element in the set. null until initialization, non-null afterwards */
    273     private ImmutableList<Binding<T>> bindings;
    274     private Set<Dependency<?>> dependencies;
    275 
    276     /** whether duplicates are allowed. Possibly configured by a different instance */
    277     private boolean permitDuplicates;
    278 
    279     private RealMultibinder(Binder binder, TypeLiteral<T> elementType, Key<Set<T>> setKey) {
    280       this.binder = checkNotNull(binder, "binder");
    281       this.elementType = checkNotNull(elementType, "elementType");
    282       this.setKey = checkNotNull(setKey, "setKey");
    283       this.collectionOfProvidersKey = setKey.ofType(collectionOfProvidersOf(elementType));
    284       this.collectionOfJavaxProvidersKey = setKey.ofType(collectionOfJavaxProvidersOf(elementType));
    285       this.setName = RealElement.nameOf(setKey);
    286       this.permitDuplicatesKey = Key.get(Boolean.class, named(toString() + " permits duplicates"));
    287     }
    288 
    289     public void configure(Binder binder) {
    290       checkConfiguration(!isInitialized(), "Multibinder was already initialized");
    291 
    292       binder.bind(setKey).toProvider(this);
    293       binder.bind(collectionOfProvidersKey).toProvider(
    294           new RealMultibinderCollectionOfProvidersProvider());
    295 
    296       // The collection this exposes is internally an ImmutableList, so it's OK to massage
    297       // the guice Provider to javax Provider in the value (since the guice Provider implements
    298       // javax Provider).
    299       @SuppressWarnings("unchecked")
    300       Key key = (Key) collectionOfProvidersKey;
    301       binder.bind(collectionOfJavaxProvidersKey).to(key);
    302     }
    303 
    304     @Override public Multibinder<T> permitDuplicates() {
    305       binder.install(new PermitDuplicatesModule(permitDuplicatesKey));
    306       return this;
    307     }
    308 
    309     Key<T> getKeyForNewItem() {
    310       checkConfiguration(!isInitialized(), "Multibinder was already initialized");
    311       return Key.get(elementType, new RealElement(setName, MULTIBINDER, ""));
    312     }
    313 
    314     @Override public LinkedBindingBuilder<T> addBinding() {
    315       return binder.bind(getKeyForNewItem());
    316     }
    317 
    318     /**
    319      * Invoked by Guice at Injector-creation time to prepare providers for each
    320      * element in this set. At this time the set's size is known, but its
    321      * contents are only evaluated when get() is invoked.
    322      */
    323     @Toolable @Inject void initialize(Injector injector) {
    324       List<Binding<T>> bindings = Lists.newArrayList();
    325       Set<Indexer.IndexedBinding> index = Sets.newHashSet();
    326       Indexer indexer = new Indexer(injector);
    327       List<Dependency<?>> dependencies = Lists.newArrayList();
    328       for (Binding<?> entry : injector.findBindingsByType(elementType)) {
    329         if (keyMatches(entry.getKey())) {
    330           @SuppressWarnings("unchecked") // protected by findBindingsByType()
    331           Binding<T> binding = (Binding<T>) entry;
    332           if (index.add(binding.acceptTargetVisitor(indexer))) {
    333             bindings.add(binding);
    334             dependencies.add(Dependency.get(binding.getKey()));
    335           }
    336         }
    337       }
    338 
    339       this.bindings = ImmutableList.copyOf(bindings);
    340       this.dependencies = ImmutableSet.copyOf(dependencies);
    341       this.permitDuplicates = permitsDuplicates(injector);
    342       this.binder = null;
    343     }
    344 
    345     // This is forked from com.google.common.collect.Maps.capacity
    346     private static int mapCapacity(int numBindings) {
    347       if (numBindings < 3) {
    348         return numBindings + 1;
    349       } else  if (numBindings < MAX_POWER_OF_TWO) {
    350         return (int) (numBindings / 0.75F + 1.0F);
    351       }
    352       return Integer.MAX_VALUE;
    353     }
    354 
    355     boolean permitsDuplicates(Injector injector) {
    356       return injector.getBindings().containsKey(permitDuplicatesKey);
    357     }
    358 
    359     private boolean keyMatches(Key<?> key) {
    360       return key.getTypeLiteral().equals(elementType)
    361           && key.getAnnotation() instanceof Element
    362           && ((Element) key.getAnnotation()).setName().equals(setName)
    363           && ((Element) key.getAnnotation()).type() == MULTIBINDER;
    364     }
    365 
    366     private boolean isInitialized() {
    367       return binder == null;
    368     }
    369 
    370     public Set<T> get() {
    371       checkConfiguration(isInitialized(), "Multibinder is not initialized");
    372 
    373       Map<T, Binding<T>> result = new LinkedHashMap<T, Binding<T>>(mapCapacity(bindings.size()));
    374       for (Binding<T> binding : bindings) {
    375         final T newValue = binding.getProvider().get();
    376         checkConfiguration(newValue != null,
    377             "Set injection failed due to null element bound at: %s",
    378             binding.getSource());
    379         Binding<T> duplicateBinding = result.put(newValue, binding);
    380         if (!permitDuplicates && duplicateBinding != null) {
    381           throw newDuplicateValuesException(result, binding, newValue, duplicateBinding);
    382         }
    383       }
    384       return ImmutableSet.copyOf(result.keySet());
    385     }
    386 
    387     @SuppressWarnings("unchecked")
    388     public <B, V> V acceptExtensionVisitor(
    389         BindingTargetVisitor<B, V> visitor,
    390         ProviderInstanceBinding<? extends B> binding) {
    391       if (visitor instanceof MultibindingsTargetVisitor) {
    392         return ((MultibindingsTargetVisitor<Set<T>, V>) visitor).visit(this);
    393       } else {
    394         return visitor.visit(binding);
    395       }
    396     }
    397 
    398     String getSetName() {
    399       return setName;
    400     }
    401 
    402     public TypeLiteral<?> getElementTypeLiteral() {
    403       return elementType;
    404     }
    405 
    406     public Key<Set<T>> getSetKey() {
    407       return setKey;
    408     }
    409 
    410     @SuppressWarnings("unchecked")
    411     public List<Binding<?>> getElements() {
    412       if (isInitialized()) {
    413         return (List<Binding<?>>) (List<?>) bindings; // safe because bindings is immutable.
    414       } else {
    415         throw new UnsupportedOperationException("getElements() not supported for module bindings");
    416       }
    417     }
    418 
    419     public boolean permitsDuplicates() {
    420       if (isInitialized()) {
    421         return permitDuplicates;
    422       } else {
    423         throw new UnsupportedOperationException(
    424             "permitsDuplicates() not supported for module bindings");
    425       }
    426     }
    427 
    428     public boolean containsElement(com.google.inject.spi.Element element) {
    429       if (element instanceof Binding) {
    430         Binding<?> binding = (Binding<?>) element;
    431         return keyMatches(binding.getKey())
    432             || binding.getKey().equals(permitDuplicatesKey)
    433             || binding.getKey().equals(setKey)
    434             || binding.getKey().equals(collectionOfProvidersKey)
    435             || binding.getKey().equals(collectionOfJavaxProvidersKey);
    436       } else {
    437         return false;
    438       }
    439     }
    440 
    441     public Set<Dependency<?>> getDependencies() {
    442       if (!isInitialized()) {
    443         return ImmutableSet.<Dependency<?>>of(Dependency.get(Key.get(Injector.class)));
    444       } else {
    445         return dependencies;
    446       }
    447     }
    448 
    449     @Override public boolean equals(Object o) {
    450       return o instanceof RealMultibinder
    451           && ((RealMultibinder<?>) o).setKey.equals(setKey);
    452     }
    453 
    454     @Override public int hashCode() {
    455       return setKey.hashCode();
    456     }
    457 
    458     @Override public String toString() {
    459       return (setName.isEmpty() ? "" : setName + " ") + "Multibinder<" + elementType + ">";
    460     }
    461 
    462     final class RealMultibinderCollectionOfProvidersProvider
    463         implements ProviderWithDependencies<Collection<Provider<T>>> {
    464       @Override public Collection<Provider<T>> get() {
    465         checkConfiguration(isInitialized(), "Multibinder is not initialized");
    466         int size = bindings.size();
    467         @SuppressWarnings("unchecked")  // safe because we only put Provider<T> into it.
    468         Provider<T>[] providers = new Provider[size];
    469         for (int i = 0; i < size; i++) {
    470           providers[i] = bindings.get(i).getProvider();
    471         }
    472         return ImmutableList.copyOf(providers);
    473       }
    474 
    475       @Override public Set<Dependency<?>> getDependencies() {
    476         if (!isInitialized()) {
    477           return ImmutableSet.<Dependency<?>>of(Dependency.get(Key.get(Injector.class)));
    478         }
    479         ImmutableSet.Builder<Dependency<?>> setBuilder = ImmutableSet.builder();
    480         for (Dependency<?> dependency : dependencies) {
    481           Key key = dependency.getKey();
    482           setBuilder.add(
    483               Dependency.get(key.ofType(Types.providerOf(key.getTypeLiteral().getType()))));
    484         }
    485         return setBuilder.build();
    486       }
    487 
    488       Key getCollectionKey() {
    489         return RealMultibinder.this.collectionOfProvidersKey;
    490       }
    491 
    492       @Override public boolean equals(Object o) {
    493         return o instanceof Multibinder.RealMultibinder.RealMultibinderCollectionOfProvidersProvider
    494             && ((Multibinder.RealMultibinder.RealMultibinderCollectionOfProvidersProvider) o)
    495                 .getCollectionKey().equals(getCollectionKey());
    496       }
    497 
    498       @Override public int hashCode() {
    499         return getCollectionKey().hashCode();
    500       }
    501     }
    502   }
    503 
    504   /**
    505    * We install the permit duplicates configuration as its own binding, all by itself. This way,
    506    * if only one of a multibinder's users remember to call permitDuplicates(), they're still
    507    * permitted.
    508    */
    509   private static class PermitDuplicatesModule extends AbstractModule {
    510     private final Key<Boolean> key;
    511 
    512     PermitDuplicatesModule(Key<Boolean> key) {
    513       this.key = key;
    514     }
    515 
    516     @Override protected void configure() {
    517       bind(key).toInstance(true);
    518     }
    519 
    520     @Override public boolean equals(Object o) {
    521       return o instanceof PermitDuplicatesModule
    522           && ((PermitDuplicatesModule) o).key.equals(key);
    523     }
    524 
    525     @Override public int hashCode() {
    526       return getClass().hashCode() ^ key.hashCode();
    527     }
    528   }
    529 
    530   static void checkConfiguration(boolean condition, String format, Object... args) {
    531     if (condition) {
    532       return;
    533     }
    534 
    535     throw new ConfigurationException(ImmutableSet.of(new Message(Errors.format(format, args))));
    536   }
    537 
    538   private static <T> ConfigurationException newDuplicateValuesException(
    539       Map<T, Binding<T>> existingBindings,
    540       Binding<T> binding,
    541       final T newValue,
    542       Binding<T> duplicateBinding) {
    543     T oldValue = getOnlyElement(filter(existingBindings.keySet(), equalTo(newValue)));
    544     String oldString = oldValue.toString();
    545     String newString = newValue.toString();
    546     if (Objects.equal(oldString, newString)) {
    547       // When the value strings match, just show the source of the bindings
    548       return new ConfigurationException(ImmutableSet.of(new Message(Errors.format(
    549           "Set injection failed due to duplicated element \"%s\""
    550               + "\n    Bound at %s\n    Bound at %s",
    551           newValue,
    552           duplicateBinding.getSource(),
    553           binding.getSource()))));
    554     } else {
    555       // When the value strings don't match, include them both as they may be useful for debugging
    556       return new ConfigurationException(ImmutableSet.of(new Message(Errors.format(
    557           "Set injection failed due to multiple elements comparing equal:"
    558               + "\n    \"%s\"\n        bound at %s"
    559               + "\n    \"%s\"\n        bound at %s",
    560           oldValue,
    561           duplicateBinding.getSource(),
    562           newValue,
    563           binding.getSource()))));
    564     }
    565   }
    566 
    567   static <T> T checkNotNull(T reference, String name) {
    568     if (reference != null) {
    569       return reference;
    570     }
    571 
    572     NullPointerException npe = new NullPointerException(name);
    573     throw new ConfigurationException(ImmutableSet.of(
    574         new Message(npe.toString(), npe)));
    575   }
    576 }
    577