issue 539 - extension SPI for Multibinder/MapBinder.
git-svn-id: https://google-guice.googlecode.com/svn/trunk@1253 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/MapBinder.java b/extensions/multibindings/src/com/google/inject/multibindings/MapBinder.java
index 072996a..ef5daa4 100644
--- a/extensions/multibindings/src/com/google/inject/multibindings/MapBinder.java
+++ b/extensions/multibindings/src/com/google/inject/multibindings/MapBinder.java
@@ -17,6 +17,7 @@
package com.google.inject.multibindings;
import com.google.inject.Binder;
+import com.google.inject.Binding;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
@@ -24,19 +25,28 @@
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import com.google.inject.binder.LinkedBindingBuilder;
+import com.google.inject.internal.util.ImmutableList;
import com.google.inject.internal.util.ImmutableMap;
import com.google.inject.internal.util.ImmutableSet;
+import com.google.inject.internal.util.Lists;
import com.google.inject.multibindings.Multibinder.RealMultibinder;
import static com.google.inject.multibindings.Multibinder.checkConfiguration;
import static com.google.inject.multibindings.Multibinder.checkNotNull;
import static com.google.inject.multibindings.Multibinder.setOf;
+
+import com.google.inject.spi.BindingTargetVisitor;
import com.google.inject.spi.Dependency;
+import com.google.inject.spi.ProviderInstanceBinding;
+import com.google.inject.spi.ProviderLookup;
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 static com.google.inject.util.Types.newParameterizedTypeWithOwner;
import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
@@ -114,7 +124,7 @@
public static <K, V> MapBinder<K, V> newMapBinder(Binder binder,
TypeLiteral<K> keyType, TypeLiteral<V> valueType) {
binder = binder.skipSources(MapBinder.class, RealMapBinder.class);
- return newMapBinder(binder, valueType,
+ return newMapBinder(binder, keyType, valueType,
Key.get(mapOf(keyType, valueType)),
Key.get(mapOfProviderOf(keyType, valueType)),
Key.get(mapOf(keyType, setOf(valueType))),
@@ -138,7 +148,7 @@
public static <K, V> MapBinder<K, V> newMapBinder(Binder binder,
TypeLiteral<K> keyType, TypeLiteral<V> valueType, Annotation annotation) {
binder = binder.skipSources(MapBinder.class, RealMapBinder.class);
- return newMapBinder(binder, valueType,
+ return newMapBinder(binder, keyType, valueType,
Key.get(mapOf(keyType, valueType), annotation),
Key.get(mapOfProviderOf(keyType, valueType), annotation),
Key.get(mapOf(keyType, setOf(valueType)), annotation),
@@ -162,7 +172,7 @@
public static <K, V> MapBinder<K, V> newMapBinder(Binder binder, TypeLiteral<K> keyType,
TypeLiteral<V> valueType, Class<? extends Annotation> annotationType) {
binder = binder.skipSources(MapBinder.class, RealMapBinder.class);
- return newMapBinder(binder, valueType,
+ return newMapBinder(binder, keyType, valueType,
Key.get(mapOf(keyType, valueType), annotationType),
Key.get(mapOfProviderOf(keyType, valueType), annotationType),
Key.get(mapOf(keyType, setOf(valueType)), annotationType),
@@ -181,40 +191,41 @@
}
@SuppressWarnings("unchecked") // a map of <K, V> is safely a Map<K, V>
- private static <K, V> TypeLiteral<Map<K, V>> mapOf(
+ static <K, V> TypeLiteral<Map<K, V>> mapOf(
TypeLiteral<K> keyType, TypeLiteral<V> valueType) {
return (TypeLiteral<Map<K, V>>) TypeLiteral.get(
Types.mapOf(keyType.getType(), valueType.getType()));
}
@SuppressWarnings("unchecked") // a provider map <K, V> is safely a Map<K, Provider<V>>
- private static <K, V> TypeLiteral<Map<K, Provider<V>>> mapOfProviderOf(
+ static <K, V> TypeLiteral<Map<K, Provider<V>>> mapOfProviderOf(
TypeLiteral<K> keyType, TypeLiteral<V> valueType) {
return (TypeLiteral<Map<K, Provider<V>>>) TypeLiteral.get(
Types.mapOf(keyType.getType(), Types.providerOf(valueType.getType())));
}
@SuppressWarnings("unchecked") // a provider map <K, Set<V>> is safely a Map<K, Set<Provider<V>>>
- private static <K, V> TypeLiteral<Map<K, Set<Provider<V>>>> mapOfSetOfProviderOf(
+ static <K, V> TypeLiteral<Map<K, Set<Provider<V>>>> mapOfSetOfProviderOf(
TypeLiteral<K> keyType, TypeLiteral<V> valueType) {
return (TypeLiteral<Map<K, Set<Provider<V>>>>) TypeLiteral.get(
Types.mapOf(keyType.getType(), Types.setOf(Types.providerOf(valueType.getType()))));
}
@SuppressWarnings("unchecked") // a provider entry <K, V> is safely a Map.Entry<K, Provider<V>>
- private static <K, V> TypeLiteral<Map.Entry<K, Provider<V>>> entryOfProviderOf(
+ static <K, V> TypeLiteral<Map.Entry<K, Provider<V>>> entryOfProviderOf(
TypeLiteral<K> keyType, TypeLiteral<V> valueType) {
return (TypeLiteral<Entry<K, Provider<V>>>) TypeLiteral.get(newParameterizedTypeWithOwner(
Map.class, Entry.class, keyType.getType(), Types.providerOf(valueType.getType())));
}
- private static <K, V> MapBinder<K, V> newMapBinder(Binder binder, TypeLiteral<V> valueType,
+ private static <K, V> MapBinder<K, V> newMapBinder(Binder binder,
+ TypeLiteral<K> keyType, TypeLiteral<V> valueType,
Key<Map<K, V>> mapKey, Key<Map<K, Provider<V>>> providerMapKey,
Key<Map<K, Set<V>>> multimapKey, Key<Map<K, Set<Provider<V>>>> providerMultimapKey,
Multibinder<Entry<K, Provider<V>>> entrySetBinder) {
RealMapBinder<K, V> mapBinder = new RealMapBinder<K, V>(
- binder, valueType, mapKey, providerMapKey, multimapKey, providerMultimapKey,
- entrySetBinder);
+ binder, keyType, valueType, mapKey, providerMapKey, multimapKey,
+ providerMultimapKey, entrySetBinder);
binder.install(mapBinder);
return mapBinder;
}
@@ -273,6 +284,7 @@
* <p>We use a subclass to hide 'implements Module' from the public API.
*/
private static final class RealMapBinder<K, V> extends MapBinder<K, V> implements Module {
+ private final TypeLiteral<K> keyType;
private final TypeLiteral<V> valueType;
private final Key<Map<K, V>> mapKey;
private final Key<Map<K, Provider<V>>> providerMapKey;
@@ -282,11 +294,15 @@
/* the target injector's binder. non-null until initialization, null afterwards */
private Binder binder;
+
+ private boolean permitDuplicates;
+ private ImmutableList<Map.Entry<K, Binding<V>>> mapBindings;
- private RealMapBinder(Binder binder, TypeLiteral<V> valueType,
+ private RealMapBinder(Binder binder, TypeLiteral<K> keyType, TypeLiteral<V> valueType,
Key<Map<K, V>> mapKey, Key<Map<K, Provider<V>>> providerMapKey,
Key<Map<K, Set<V>>> multimapKey, Key<Map<K, Set<Provider<V>>>> providerMultimapKey,
Multibinder<Map.Entry<K, Provider<V>>> entrySetBinder) {
+ this.keyType = keyType;
this.valueType = valueType;
this.mapKey = mapKey;
this.providerMapKey = providerMapKey;
@@ -314,7 +330,7 @@
Key<V> valueKey = Key.get(valueType, new RealElement(entrySetBinder.getSetName()));
entrySetBinder.addBinding().toInstance(new MapEntry<K, Provider<V>>(key,
- binder.getProvider(valueKey)));
+ binder.getProvider(valueKey), valueKey));
return binder.bind(valueKey);
}
@@ -324,25 +340,30 @@
final ImmutableSet<Dependency<?>> dependencies
= ImmutableSet.<Dependency<?>>of(Dependency.get(entrySetBinder.getSetKey()));
- // Binds a Map<K, Provider<V>> from a collection of Map<Entry<K, Provider<V>>.
+ // Binds a Map<K, Provider<V>> from a collection of Set<Entry<K, Provider<V>>.
final Provider<Set<Entry<K, Provider<V>>>> entrySetProvider = binder
.getProvider(entrySetBinder.getSetKey());
binder.bind(providerMapKey).toProvider(new RealMapBinderProviderWithDependencies<Map<K, Provider<V>>>(mapKey) {
private Map<K, Provider<V>> providerMap;
- @SuppressWarnings("unused")
- @Inject void initialize(Injector injector) {
+ @SuppressWarnings({ "unused", "unchecked" })
+ @Toolable @Inject void initialize(Injector injector) {
RealMapBinder.this.binder = null;
- boolean permitDuplicates = entrySetBinder.permitsDuplicates(injector);
+ permitDuplicates = entrySetBinder.permitsDuplicates(injector);
Map<K, Provider<V>> providerMapMutable = new LinkedHashMap<K, Provider<V>>();
+ List<Map.Entry<K, Binding<V>>> bindingsMutable = Lists.newArrayList();
for (Entry<K, Provider<V>> entry : entrySetProvider.get()) {
Provider<V> previous = providerMapMutable.put(entry.getKey(), entry.getValue());
checkConfiguration(previous == null || permitDuplicates,
"Map injection failed due to duplicated key \"%s\"", entry.getKey());
+ Key<V> valueKey = (Key<V>)((MapEntry)entry).getValueKey();
+ bindingsMutable.add(new MapEntry(entry.getKey(),
+ injector.getBinding(valueKey), valueKey));
}
providerMap = ImmutableMap.copyOf(providerMapMutable);
+ mapBindings = ImmutableList.copyOf(bindingsMutable);
}
public Map<K, Provider<V>> get() {
@@ -355,7 +376,7 @@
});
final Provider<Map<K, Provider<V>>> mapProvider = binder.getProvider(providerMapKey);
- binder.bind(mapKey).toProvider(new RealMapBinderProviderWithDependencies<Map<K, V>>(mapKey) {
+ binder.bind(mapKey).toProvider(new RealMapWithExtensionProvider<Map<K, V>>(mapKey) {
public Map<K, V> get() {
Map<K, V> map = new LinkedHashMap<K, V>();
for (Entry<K, Provider<V>> entry : mapProvider.get().entrySet()) {
@@ -371,9 +392,77 @@
public Set<Dependency<?>> getDependencies() {
return dependencies;
}
+
+ @SuppressWarnings("unchecked")
+ public <R, B> R acceptExtensionVisitor(BindingTargetVisitor<B, R> visitor,
+ ProviderInstanceBinding<? extends B> binding) {
+ if (visitor instanceof MultibindingsTargetVisitor) {
+ return ((MultibindingsTargetVisitor<Map<K, V>, R>)visitor).visit(this);
+ } else {
+ return visitor.visit(binding);
+ }
+ }
+
+ public Key<Map<K, V>> getMapKey() {
+ return mapKey;
+ }
+
+ public TypeLiteral<?> getKeyTypeLiteral() {
+ return keyType;
+ }
+
+ public TypeLiteral<?> getValueTypeLiteral() {
+ return valueType;
+ }
+
+ @SuppressWarnings("unchecked")
+ public List<Entry<?, Binding<?>>> getEntries() {
+ if (isInitialized()) {
+ return (List)mapBindings; // safe because mapBindings 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 (entrySetBinder.containsElement(element)) {
+ return true;
+ } else {
+ Key<?> key;
+ if (element instanceof Binding) {
+ key = ((Binding)element).getKey();
+ } else if (element instanceof ProviderLookup) {
+ key = ((ProviderLookup)element).getKey();
+ } else {
+ return false; // cannot match;
+ }
+
+ return key.equals(mapKey)
+ || key.equals(providerMapKey)
+ || key.equals(multimapKey)
+ || key.equals(providerMultimapKey)
+ || key.equals(entrySetBinder.getSetKey())
+ || matchesValueKey(key);
+ }
+ }
});
}
+ /** Returns true if the key indicates this is a value in the map. */
+ private boolean matchesValueKey(Key key) {
+ return key.getAnnotation() instanceof Element
+ && ((Element) key.getAnnotation()).setName().equals(entrySetBinder.getSetName())
+ && key.getTypeLiteral().equals(valueType);
+ }
+
private boolean isInitialized() {
return binder == null;
}
@@ -477,10 +566,16 @@
private static final class MapEntry<K, V> implements Map.Entry<K, V> {
private final K key;
private final V value;
+ private final Key<?> valueKey;
- private MapEntry(K key, V value) {
+ private MapEntry(K key, V value, Key<?> valueKey) {
this.key = key;
this.value = value;
+ this.valueKey = valueKey;
+ }
+
+ public Key<?> getValueKey() {
+ return valueKey;
}
public K getKey() {
@@ -510,6 +605,14 @@
return "MapEntry(" + key + ", " + value + ")";
}
}
+
+ private static abstract class RealMapWithExtensionProvider<T>
+ extends RealMapBinderProviderWithDependencies<T>
+ implements ProviderWithExtensionVisitor<T>, MapBinderBinding<T> {
+ public RealMapWithExtensionProvider(Object equality) {
+ super(equality);
+ }
+ }
/**
* A base class for ProviderWithDependencies that need equality
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/MapBinderBinding.java b/extensions/multibindings/src/com/google/inject/multibindings/MapBinderBinding.java
new file mode 100644
index 0000000..8d6d030
--- /dev/null
+++ b/extensions/multibindings/src/com/google/inject/multibindings/MapBinderBinding.java
@@ -0,0 +1,98 @@
+/**
+ * Copyright (C) 2010 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 java.util.List;
+import java.util.Map;
+
+import com.google.inject.Binding;
+import com.google.inject.Key;
+import com.google.inject.TypeLiteral;
+import com.google.inject.spi.Element;
+import com.google.inject.spi.Elements;
+
+/**
+ * A binding for a MapBinder.
+ * <p>
+ * Although MapBinders may be injected through a variety of generic types (Map{@literal<}K, V>, Map
+ * {@literal<}K, Provider{@literal<}V>>, Map{@literal<}K, Set{@literal<}V>>, Map<K, Set{@literal<}
+ * Provider{@literal<}V>>, and even Set{@literal<}Map.Entry{@literal<}K, Provider{@literal<}V>>), a
+ * MapBinderBinding exists only on the Binding associated with the Map{@literal<}K, V> key. Other
+ * bindings can be validated to be derived from this MapBinderBinding using
+ * {@link #containsElement(Element)}.
+ *
+ * @param <T> The fully qualified type of the map, including Map. For example:
+ * <code>MapBinderBinding{@literal<}Map{@literal<}String, Snack>></code>
+ *
+ * @author sameb@google.com (Sam Berlin)
+ */
+public interface MapBinderBinding<T> {
+
+ /** Returns the {@link Key} for the map. */
+ Key<T> getMapKey();
+
+ /**
+ * Returns the TypeLiteral describing the keys of the map.
+ * <p>
+ * The TypeLiteral will always match the type Map's generic type. For example, if getMapKey
+ * returns a key of <code>Map{@literal<}String, Snack></code>, then this will always return a
+ * <code>TypeLiteral{@literal<}String></code>.
+ */
+ TypeLiteral<?> getKeyTypeLiteral();
+
+ /**
+ * Returns the TypeLiteral describing the values of the map.
+ * <p>
+ * The TypeLiteral will always match the type Map's generic type. For example, if getMapKey
+ * returns a key of <code>Map{@literal<}String, Snack></code>, then this will always return a
+ * <code>TypeLiteral{@literal<}Snack></code>.
+ */
+ TypeLiteral<?> getValueTypeLiteral();
+
+ /**
+ * Returns all entries in the Map. The returned list of Map.Entries contains the key and a binding
+ * to the value. Duplicate keys or values will exist as separate Map.Entries in the returned list.
+ * This is only supported on bindings returned from an injector. This will throw
+ * {@link UnsupportedOperationException} if it is called on an element retrieved from
+ * {@link Elements#getElements}.
+ * <p>
+ * The elements will always match the type Map's generic type. For example, if getMapKey returns a
+ * key of <code>Map{@literal<}String, Snack></code>, then this will always return a list of type
+ * <code>List{@literal<}Map.Entry{@literal<}String, Binding{@literal<}Snack>>></code>.
+ */
+ List<Map.Entry<?, Binding<?>>> getEntries();
+
+ /**
+ * Returns true if the MapBinder permits duplicates. This is only supported on bindings returned
+ * from an injector. This will throw {@link UnsupportedOperationException} if it is called on a
+ * MapBinderBinding retrieved from {@link Elements#getElements}.
+ */
+ boolean permitsDuplicates();
+
+ /**
+ * Returns true if this MapBinder contains the given Element in order to build the map or uses the
+ * given Element in order to support building and injecting the map. This will work for
+ * MapBinderBindings retrieved from an injector and {@link Elements#getElements}. Usually this is
+ * only necessary if you are working with elements retrieved from modules (without an Injector),
+ * otherwise {@link #getEntries} and {@link #permitsDuplicates} are better options.
+ * <p>
+ * If you need to introspect the details of the map, such as the keys, values or if it permits
+ * duplicates, it is necessary to pass the elements through an Injector and use
+ * {@link #getEntries()} and {@link #permitsDuplicates()}.
+ */
+ boolean containsElement(Element element);
+}
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/Multibinder.java b/extensions/multibindings/src/com/google/inject/multibindings/Multibinder.java
index 7332a70..93b2900 100644
--- a/extensions/multibindings/src/com/google/inject/multibindings/Multibinder.java
+++ b/extensions/multibindings/src/com/google/inject/multibindings/Multibinder.java
@@ -24,7 +24,6 @@
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.Annotations;
@@ -34,9 +33,12 @@
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;
@@ -212,7 +214,7 @@
* API.
*/
static final class RealMultibinder<T> extends Multibinder<T>
- implements Module, Provider<Set<T>>, HasDependencies {
+ implements Module, ProviderWithExtensionVisitor<Set<T>>, HasDependencies, MultibinderBinding<Set<T>> {
private final TypeLiteral<T> elementType;
private final String setName;
@@ -222,8 +224,8 @@
/* the target injector's binder. non-null until initialization, null afterwards */
private Binder binder;
- /* a provider for each element in the set. null until initialization, non-null afterwards */
- private List<Provider<T>> providers;
+ /* a binding for each element in the set. null until initialization, non-null afterwards */
+ private ImmutableList<Binding<T>> bindings;
private Set<Dependency<?>> dependencies;
/** whether duplicates are allowed. Possibly configured by a different instance */
@@ -281,18 +283,18 @@
* contents are only evaluated when get() is invoked.
*/
@Toolable @Inject void initialize(Injector injector) {
- providers = Lists.newArrayList();
+ List<Binding<T>> bindings = Lists.newArrayList();
List<Dependency<?>> dependencies = Lists.newArrayList();
for (Binding<?> entry : injector.findBindingsByType(elementType)) {
-
if (keyMatches(entry.getKey())) {
@SuppressWarnings("unchecked") // protected by findBindingsByType()
Binding<T> binding = (Binding<T>) entry;
- providers.add(binding.getProvider());
+ 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;
@@ -316,22 +318,65 @@
checkConfiguration(isInitialized(), "Multibinder is not initialized");
Set<T> result = new LinkedHashSet<T>();
- for (Provider<T> provider : providers) {
- final T newValue = provider.get();
+ for (Binding<T> 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, B> 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);
+ }
+ }
String getSetName() {
return setName;
}
+
+ public TypeLiteral<?> getElementTypeLiteral() {
+ return elementType;
+ }
- Key<Set<T>> getSetKey() {
+ public Key<Set<T>> getSetKey() {
return setKey;
}
+
+ @SuppressWarnings("unchecked")
+ public List<Binding<?>> 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<Dependency<?>> getDependencies() {
if (!isInitialized()) {
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/MultibinderBinding.java b/extensions/multibindings/src/com/google/inject/multibindings/MultibinderBinding.java
new file mode 100644
index 0000000..b19286a
--- /dev/null
+++ b/extensions/multibindings/src/com/google/inject/multibindings/MultibinderBinding.java
@@ -0,0 +1,79 @@
+/**
+ * Copyright (C) 2010 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 java.util.List;
+
+import com.google.inject.Binding;
+import com.google.inject.Key;
+import com.google.inject.TypeLiteral;
+import com.google.inject.spi.Element;
+import com.google.inject.spi.Elements;
+
+/**
+ * A binding for a Multibinder.
+ *
+ * @param <T> The fully qualified type of the set, including Set. For example:
+ * <code>MultibinderBinding{@literal<}Set{@literal<}Boolean>></code>
+ *
+ * @author sameb@google.com (Sam Berlin)
+ */
+public interface MultibinderBinding<T> {
+
+ /** Returns the key for the set. */
+ Key<T> getSetKey();
+
+ /**
+ * Returns the TypeLiteral that describes the type of elements in the set.
+ * <p>
+ * The elements will always match the type Set's generic type. For example, if getSetKey returns a
+ * key of <code>Set{@literal<}String></code>, then this will always return a
+ * <code>TypeLiteral{@literal<}String></code>.
+ */
+ TypeLiteral<?> getElementTypeLiteral();
+
+ /**
+ * Returns all bindings that make up the set. This is only supported on bindings returned from an
+ * injector. This will throw {@link UnsupportedOperationException} if it is called on an element
+ * retrieved from {@link Elements#getElements}.
+ * <p>
+ * The elements will always match the type Set's generic type. For example, if getSetKey returns a
+ * key of <code>Set{@literal<}String></code>, then this will always return a list of type
+ * <code>List{@literal<}Binding{@literal<}String>></code>.
+ */
+ List<Binding<?>> getElements();
+
+ /**
+ * Returns true if the multibinder permits duplicates. This is only supported on bindings returned
+ * from an injector. This will throw {@link UnsupportedOperationException} if it is called on a
+ * MultibinderBinding retrieved from {@link Elements#getElements}.
+ */
+ boolean permitsDuplicates();
+
+ /**
+ * Returns true if this Multibinder uses the given Element. This will be true for bindings that
+ * derive the elements of the set and other bindings that Multibinder uses internally. This will
+ * work for MultibinderBindings retrieved from an injector and {@link Elements#getElements}.
+ * Usually this is only necessary if you are working with elements retrieved from modules (without
+ * an Injector), otherwise {@link #getElements} and {@link #permitsDuplicates} are better options.
+ * <p>
+ * If you need to introspect the details of the set, such as the values or if it permits
+ * duplicates, it is necessary to pass the elements through an Injector and use
+ * {@link #getElements()} and {@link #permitsDuplicates()}.
+ */
+ boolean containsElement(Element element);
+}
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/MultibindingsTargetVisitor.java b/extensions/multibindings/src/com/google/inject/multibindings/MultibindingsTargetVisitor.java
new file mode 100644
index 0000000..7f02dbe
--- /dev/null
+++ b/extensions/multibindings/src/com/google/inject/multibindings/MultibindingsTargetVisitor.java
@@ -0,0 +1,41 @@
+/**
+ * Copyright (C) 2010 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.spi.BindingTargetVisitor;
+
+/**
+ * A visitor for the multibinder extension.
+ * <p>
+ * If your {@link BindingTargetVisitor} implements this interface, bindings created by using
+ * {@link Multibinder} or {@link MapBinder} will be visited through this interface.
+ *
+ * @author sameb@google.com (Sam Berlin)
+ */
+public interface MultibindingsTargetVisitor<T, V> extends BindingTargetVisitor<T, V> {
+
+ /**
+ * Visits a binding created through {@link Multibinder}.
+ */
+ V visit(MultibinderBinding<? extends T> multibinding);
+
+ /**
+ * Visits a binding created through {@link MapBinder}.
+ */
+ V visit(MapBinderBinding<? extends T> mapbinding);
+
+}
diff --git a/extensions/multibindings/test/com/google/inject/multibindings/MapBinderTest.java b/extensions/multibindings/test/com/google/inject/multibindings/MapBinderTest.java
index 36acb19..b133fb8 100644
--- a/extensions/multibindings/test/com/google/inject/multibindings/MapBinderTest.java
+++ b/extensions/multibindings/test/com/google/inject/multibindings/MapBinderTest.java
@@ -17,6 +17,11 @@
package com.google.inject.multibindings;
import static com.google.inject.Asserts.assertContains;
+import static com.google.inject.multibindings.SpiUtils.assertMapVisitor;
+import static com.google.inject.multibindings.SpiUtils.instance;
+import static com.google.inject.multibindings.SpiUtils.providerInstance;
+import static com.google.inject.multibindings.SpiUtils.VisitType.BOTH;
+import static com.google.inject.multibindings.SpiUtils.VisitType.MODULE;
import static com.google.inject.name.Names.named;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@@ -36,6 +41,7 @@
import com.google.inject.TypeLiteral;
import com.google.inject.internal.util.ImmutableSet;
import com.google.inject.internal.util.Maps;
+import com.google.inject.multibindings.SpiUtils.VisitType;
import com.google.inject.name.Names;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.HasDependencies;
@@ -67,6 +73,10 @@
final TypeLiteral<Map<String, Integer>> mapOfInteger = new TypeLiteral<Map<String, Integer>>() {};
final TypeLiteral<Map<String, Set<String>>> mapOfSetOfString =
new TypeLiteral<Map<String, Set<String>>>() {};
+
+ private final TypeLiteral<String> stringType = TypeLiteral.get(String.class);
+ private final TypeLiteral<Integer> intType = TypeLiteral.get(Integer.class);
+ private final TypeLiteral<Set<String>> stringSetType = new TypeLiteral<Set<String>>() {};
public void testMapBinderAggregatesMultipleModules() {
Module abc = new AbstractModule() {
@@ -91,10 +101,12 @@
Map<String, String> abcde = injector.getInstance(Key.get(mapOfString));
assertEquals(mapOf("a", "A", "b", "B", "c", "C", "d", "D", "e", "E"), abcde);
+ assertMapVisitor(Key.get(mapOfString), stringType, stringType, setOf(abc, de), BOTH, false, 0,
+ instance("a", "A"), instance("b", "B"), instance("c", "C"), instance("d", "D"), instance("e", "E"));
}
public void testMapBinderAggregationForAnnotationInstance() {
- Injector injector = Guice.createInjector(new AbstractModule() {
+ Module module = new AbstractModule() {
@Override protected void configure() {
MapBinder<String, String> multibinder = MapBinder.newMapBinder(
binder(), String.class, String.class, Names.named("abc"));
@@ -105,14 +117,18 @@
binder(), String.class, String.class, Names.named("abc"));
multibinder.addBinding("c").toInstance("C");
}
- });
+ };
+ Injector injector = Guice.createInjector(module);
- Map<String, String> abc = injector.getInstance(Key.get(mapOfString, Names.named("abc")));
+ Key<Map<String, String>> key = Key.get(mapOfString, Names.named("abc"));
+ Map<String, String> abc = injector.getInstance(key);
assertEquals(mapOf("a", "A", "b", "B", "c", "C"), abc);
+ assertMapVisitor(key, stringType, stringType, setOf(module), BOTH, false, 0,
+ instance("a", "A"), instance("b", "B"), instance("c", "C"));
}
public void testMapBinderAggregationForAnnotationType() {
- Injector injector = Guice.createInjector(new AbstractModule() {
+ Module module = new AbstractModule() {
@Override protected void configure() {
MapBinder<String, String> multibinder = MapBinder.newMapBinder(
binder(), String.class, String.class, Abc.class);
@@ -123,14 +139,18 @@
binder(), String.class, String.class, Abc.class);
multibinder.addBinding("c").toInstance("C");
}
- });
+ };
+ Injector injector = Guice.createInjector(module);
- Map<String, String> abc = injector.getInstance(Key.get(mapOfString, Abc.class));
+ Key<Map<String, String>> key = Key.get(mapOfString, Abc.class);
+ Map<String, String> abc = injector.getInstance(key);
assertEquals(mapOf("a", "A", "b", "B", "c", "C"), abc);
+ assertMapVisitor(key, stringType, stringType, setOf(module), BOTH, false, 0,
+ instance("a", "A"), instance("b", "B"), instance("c", "C"));
}
public void testMapBinderWithMultipleAnnotationValueSets() {
- Injector injector = Guice.createInjector(new AbstractModule() {
+ Module module = new AbstractModule() {
@Override protected void configure() {
MapBinder<String, String> abcMapBinder = MapBinder.newMapBinder(
binder(), String.class, String.class, named("abc"));
@@ -143,16 +163,23 @@
deMapBinder.addBinding("d").toInstance("D");
deMapBinder.addBinding("e").toInstance("E");
}
- });
+ };
+ Injector injector = Guice.createInjector(module);
- Map<String, String> abc = injector.getInstance(Key.get(mapOfString, named("abc")));
- Map<String, String> de = injector.getInstance(Key.get(mapOfString, named("de")));
+ Key<Map<String, String>> abcKey = Key.get(mapOfString, named("abc"));
+ Map<String, String> abc = injector.getInstance(abcKey);
+ Key<Map<String, String>> deKey = Key.get(mapOfString, named("de"));
+ Map<String, String> de = injector.getInstance(deKey);
assertEquals(mapOf("a", "A", "b", "B", "c", "C"), abc);
assertEquals(mapOf("d", "D", "e", "E"), de);
+ assertMapVisitor(abcKey, stringType, stringType, setOf(module), BOTH, false, 1,
+ instance("a", "A"), instance("b", "B"), instance("c", "C"));
+ assertMapVisitor(deKey, stringType, stringType, setOf(module), BOTH, false, 1,
+ instance("d", "D"), instance("e", "E"));
}
public void testMapBinderWithMultipleAnnotationTypeSets() {
- Injector injector = Guice.createInjector(new AbstractModule() {
+ Module module = new AbstractModule() {
@Override protected void configure() {
MapBinder<String, String> abcMapBinder = MapBinder.newMapBinder(
binder(), String.class, String.class, Abc.class);
@@ -165,37 +192,51 @@
deMapBinder.addBinding("d").toInstance("D");
deMapBinder.addBinding("e").toInstance("E");
}
- });
+ };
+ Injector injector = Guice.createInjector(module);
- Map<String, String> abc = injector.getInstance(Key.get(mapOfString, Abc.class));
- Map<String, String> de = injector.getInstance(Key.get(mapOfString, De.class));
+ Key<Map<String, String>> abcKey = Key.get(mapOfString, Abc.class);
+ Map<String, String> abc = injector.getInstance(abcKey);
+ Key<Map<String, String>> deKey = Key.get(mapOfString, De.class);
+ Map<String, String> de = injector.getInstance(deKey);
assertEquals(mapOf("a", "A", "b", "B", "c", "C"), abc);
assertEquals(mapOf("d", "D", "e", "E"), de);
+ assertMapVisitor(abcKey, stringType, stringType, setOf(module), BOTH, false, 1,
+ instance("a", "A"), instance("b", "B"), instance("c", "C"));
+ assertMapVisitor(deKey, stringType, stringType, setOf(module), BOTH, false, 1,
+ instance("d", "D"), instance("e", "E"));
}
public void testMapBinderWithMultipleTypes() {
- Injector injector = Guice.createInjector(new AbstractModule() {
+ Module module = new AbstractModule() {
@Override protected void configure() {
MapBinder.newMapBinder(binder(), String.class, String.class)
.addBinding("a").toInstance("A");
MapBinder.newMapBinder(binder(), String.class, Integer.class)
.addBinding("1").toInstance(1);
}
- });
+ };
+ Injector injector = Guice.createInjector(module);
assertEquals(mapOf("a", "A"), injector.getInstance(Key.get(mapOfString)));
assertEquals(mapOf("1", 1), injector.getInstance(Key.get(mapOfInteger)));
+ assertMapVisitor(Key.get(mapOfString), stringType, stringType, setOf(module), BOTH, false, 1,
+ instance("a", "A"));
+ assertMapVisitor(Key.get(mapOfInteger), stringType, intType, setOf(module), BOTH, false, 1,
+ instance("1", 1));
}
public void testMapBinderWithEmptyMap() {
- Injector injector = Guice.createInjector(new AbstractModule() {
+ Module module = new AbstractModule() {
@Override protected void configure() {
MapBinder.newMapBinder(binder(), String.class, String.class);
}
- });
+ };
+ Injector injector = Guice.createInjector(module);
Map<String, String> map = injector.getInstance(Key.get(mapOfString));
assertEquals(Collections.emptyMap(), map);
+ assertMapVisitor(Key.get(mapOfString), stringType, stringType, setOf(module), BOTH, false, 0);
}
public void testMapBinderMapIsUnmodifiable() {
@@ -215,7 +256,7 @@
}
public void testMapBinderMapIsLazy() {
- Injector injector = Guice.createInjector(new AbstractModule() {
+ Module module = new AbstractModule() {
@Override protected void configure() {
MapBinder.newMapBinder(binder(), String.class, Integer.class)
.addBinding("num").toProvider(new Provider<Integer>() {
@@ -225,97 +266,108 @@
}
});
}
- });
+ };
+ Injector injector = Guice.createInjector(module);
assertEquals(mapOf("num", 1), injector.getInstance(Key.get(mapOfInteger)));
assertEquals(mapOf("num", 2), injector.getInstance(Key.get(mapOfInteger)));
assertEquals(mapOf("num", 3), injector.getInstance(Key.get(mapOfInteger)));
+ assertMapVisitor(Key.get(mapOfInteger), stringType, intType, setOf(module), BOTH, false, 0,
+ providerInstance("num", 1));
}
public void testMapBinderMapForbidsDuplicateKeys() {
+ Module module = new AbstractModule() {
+ @Override protected void configure() {
+ MapBinder<String, String> multibinder = MapBinder.newMapBinder(
+ binder(), String.class, String.class);
+ multibinder.addBinding("a").toInstance("A");
+ multibinder.addBinding("a").toInstance("B");
+ }
+ };
try {
- Guice.createInjector(new AbstractModule() {
- @Override protected void configure() {
- MapBinder<String, String> multibinder = MapBinder.newMapBinder(
- binder(), String.class, String.class);
- multibinder.addBinding("a").toInstance("A");
- multibinder.addBinding("a").toInstance("B");
- }
- });
+ Guice.createInjector(module);
fail();
} catch(CreationException expected) {
assertContains(expected.getMessage(),
"Map injection failed due to duplicated key \"a\"");
}
+
+ assertMapVisitor(Key.get(mapOfString), stringType, stringType, setOf(module), MODULE, false, 0,
+ instance("a", "A"), instance("a", "B"));
}
public void testMapBinderMapPermitDuplicateElements() {
- Injector injector = Guice.createInjector(
- new AbstractModule() {
- @Override protected void configure() {
- MapBinder<String, String> multibinder = MapBinder.newMapBinder(
- binder(), String.class, String.class);
- multibinder.addBinding("a").toInstance("A");
- multibinder.addBinding("b").toInstance("B");
- }
- },
- new AbstractModule() {
- @Override protected void configure() {
- MapBinder<String, String> multibinder = MapBinder.newMapBinder(
- binder(), String.class, String.class);
- multibinder.addBinding("b").toInstance("B");
- multibinder.addBinding("c").toInstance("C");
- multibinder.permitDuplicates();
- }
- });
+ Module ab = new AbstractModule() {
+ @Override protected void configure() {
+ MapBinder<String, String> multibinder = MapBinder.newMapBinder(
+ binder(), String.class, String.class);
+ multibinder.addBinding("a").toInstance("A");
+ multibinder.addBinding("b").toInstance("B");
+ }
+ };
+ Module bc = new AbstractModule() {
+ @Override protected void configure() {
+ MapBinder<String, String> multibinder = MapBinder.newMapBinder(
+ binder(), String.class, String.class);
+ multibinder.addBinding("b").toInstance("B");
+ multibinder.addBinding("c").toInstance("C");
+ multibinder.permitDuplicates();
+ }
+ };
+ Injector injector = Guice.createInjector(ab, bc);
assertEquals(mapOf("a", "A", "b", "B", "c", "C"), injector.getInstance(Key.get(mapOfString)));
+ assertMapVisitor(Key.get(mapOfString), stringType, stringType, setOf(ab, bc), BOTH, true, 0,
+ instance("a", "A"), instance("b", "B"), instance("b", "B"), instance("c", "C"));
}
public void testMapBinderMultimap() {
- Injector injector = Guice.createInjector(
- new AbstractModule() {
- @Override protected void configure() {
- MapBinder<String, String> multibinder = MapBinder.newMapBinder(
- binder(), String.class, String.class);
- multibinder.addBinding("a").toInstance("A");
- multibinder.addBinding("b").toInstance("B1");
- multibinder.addBinding("c").toInstance("C");
- }
- },
- new AbstractModule() {
- @Override protected void configure() {
- MapBinder<String, String> multibinder = MapBinder.newMapBinder(
- binder(), String.class, String.class);
- multibinder.addBinding("b").toInstance("B2");
- multibinder.addBinding("c").toInstance("C");
- multibinder.permitDuplicates();
- }
- });
+ AbstractModule ab1c = new AbstractModule() {
+ @Override protected void configure() {
+ MapBinder<String, String> multibinder = MapBinder.newMapBinder(
+ binder(), String.class, String.class);
+ multibinder.addBinding("a").toInstance("A");
+ multibinder.addBinding("b").toInstance("B1");
+ multibinder.addBinding("c").toInstance("C");
+ }
+ };
+ AbstractModule b2c = new AbstractModule() {
+ @Override protected void configure() {
+ MapBinder<String, String> multibinder = MapBinder.newMapBinder(
+ binder(), String.class, String.class);
+ multibinder.addBinding("b").toInstance("B2");
+ multibinder.addBinding("c").toInstance("C");
+ multibinder.permitDuplicates();
+ }
+ };
+ Injector injector = Guice.createInjector(ab1c, b2c);
assertEquals(mapOf("a", setOf("A"), "b", setOf("B1", "B2"), "c", setOf("C")),
injector.getInstance(Key.get(mapOfSetOfString)));
+ assertMapVisitor(Key.get(mapOfString), stringType, stringType, setOf(ab1c, b2c), BOTH, true, 0,
+ instance("a", "A"), instance("b", "B1"), instance("b", "B2"), instance("c", "C"), instance("c", "C"));
}
public void testMapBinderMultimapWithAnotation() {
- Injector injector = Guice.createInjector(
- new AbstractModule() {
- @Override protected void configure() {
- MapBinder<String, String> multibinder = MapBinder.newMapBinder(
- binder(), String.class, String.class, Abc.class);
- multibinder.addBinding("a").toInstance("A");
- multibinder.addBinding("b").toInstance("B1");
- }
- },
- new AbstractModule() {
- @Override protected void configure() {
- MapBinder<String, String> multibinder = MapBinder.newMapBinder(
- binder(), String.class, String.class, Abc.class);
- multibinder.addBinding("b").toInstance("B2");
- multibinder.addBinding("c").toInstance("C");
- multibinder.permitDuplicates();
- }
- });
+ AbstractModule ab1 = new AbstractModule() {
+ @Override protected void configure() {
+ MapBinder<String, String> multibinder = MapBinder.newMapBinder(
+ binder(), String.class, String.class, Abc.class);
+ multibinder.addBinding("a").toInstance("A");
+ multibinder.addBinding("b").toInstance("B1");
+ }
+ };
+ AbstractModule b2c = new AbstractModule() {
+ @Override protected void configure() {
+ MapBinder<String, String> multibinder = MapBinder.newMapBinder(
+ binder(), String.class, String.class, Abc.class);
+ multibinder.addBinding("b").toInstance("B2");
+ multibinder.addBinding("c").toInstance("C");
+ multibinder.permitDuplicates();
+ }
+ };
+ Injector injector = Guice.createInjector(ab1, b2c);
assertEquals(mapOf("a", setOf("A"), "b", setOf("B1", "B2"), "c", setOf("C")),
injector.getInstance(Key.get(mapOfSetOfString, Abc.class)));
@@ -323,6 +375,9 @@
injector.getInstance(Key.get(mapOfSetOfString));
fail();
} catch (ConfigurationException expected) {}
+
+ assertMapVisitor(Key.get(mapOfString, Abc.class), stringType, stringType, setOf(ab1, b2c), BOTH, true, 0,
+ instance("a", "A"), instance("b", "B1"), instance("b", "B2"), instance("c", "C"));
}
public void testMapBinderMultimapIsUnmodifiable() {
@@ -519,18 +574,21 @@
Injector injector = Guice.createInjector(abcd, ef);
assertEquals(mapOf("a", "A", "b", "B", "c", "C", "d", "D", "e", "E", "f", "F"),
injector.getInstance(Key.get(mapOfString)));
-
+ assertMapVisitor(Key.get(mapOfString), stringType, stringType, setOf(abcd, ef), BOTH, false, 0,
+ instance("a", "A"), instance("b", "B"), instance("c", "C"), instance("d", "D"), instance(
+ "e", "E"), instance("f", "F"));
}
/**
* With overrides, we should get the union of all map bindings.
*/
public void testModuleOverrideAndMapBindingsWithPermitDuplicates() {
- Module ab = new AbstractModule() {
+ Module abc = new AbstractModule() {
protected void configure() {
MapBinder<String, String> multibinder = MapBinder.newMapBinder(binder(), String.class, String.class);
multibinder.addBinding("a").toInstance("A");
multibinder.addBinding("b").toInstance("B");
+ multibinder.addBinding("c").toInstance("C");
multibinder.permitDuplicates();
}
};
@@ -551,10 +609,13 @@
}
};
- Module abcd = Modules.override(ab).with(cd);
+ Module abcd = Modules.override(abc).with(cd);
Injector injector = Guice.createInjector(abcd, ef);
assertEquals(mapOf("a", "A", "b", "B", "c", "C", "d", "D", "e", "E", "f", "F"),
injector.getInstance(Key.get(mapOfString)));
+ assertMapVisitor(Key.get(mapOfString), stringType, stringType, setOf(abcd, ef), BOTH, true, 0,
+ instance("a", "A"), instance("b", "B"), instance("c", "C"), instance("c", "C"), instance(
+ "d", "D"), instance("e", "E"), instance("f", "F"));
}
@@ -574,7 +635,7 @@
}
@SuppressWarnings("unchecked")
- private <V> Set<V> setOf(Object... elements) {
+ private <V> Set<V> setOf(V... elements) {
return new HashSet(Arrays.asList(elements));
}
diff --git a/extensions/multibindings/test/com/google/inject/multibindings/MultibinderTest.java b/extensions/multibindings/test/com/google/inject/multibindings/MultibinderTest.java
index 47a6ead..bbfa28e 100644
--- a/extensions/multibindings/test/com/google/inject/multibindings/MultibinderTest.java
+++ b/extensions/multibindings/test/com/google/inject/multibindings/MultibinderTest.java
@@ -16,9 +16,30 @@
package com.google.inject.multibindings;
-import com.google.inject.AbstractModule;
import static com.google.inject.Asserts.assertContains;
+import static com.google.inject.multibindings.SpiUtils.assertSetVisitor;
+import static com.google.inject.multibindings.SpiUtils.instance;
+import static com.google.inject.multibindings.SpiUtils.providerInstance;
+import static com.google.inject.multibindings.SpiUtils.VisitType.BOTH;
+import static com.google.inject.multibindings.SpiUtils.VisitType.MODULE;
+import static com.google.inject.name.Names.named;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import junit.framework.TestCase;
+
+import com.google.inject.AbstractModule;
import com.google.inject.Binding;
import com.google.inject.BindingAnnotation;
import com.google.inject.CreationException;
@@ -35,7 +56,6 @@
import com.google.inject.internal.util.ImmutableSet;
import com.google.inject.internal.util.Sets;
import com.google.inject.name.Names;
-import static com.google.inject.name.Names.named;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.HasDependencies;
import com.google.inject.spi.InstanceBinding;
@@ -43,21 +63,6 @@
import com.google.inject.util.Modules;
import com.google.inject.util.Providers;
-import java.lang.annotation.Annotation;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.lang.reflect.Method;
-
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import junit.framework.TestCase;
-
/**
* @author jessewilson@google.com (Jesse Wilson)
*/
@@ -65,6 +70,8 @@
final TypeLiteral<Set<String>> setOfString = new TypeLiteral<Set<String>>() {};
final TypeLiteral<Set<Integer>> setOfInteger = new TypeLiteral<Set<Integer>>() {};
+ final TypeLiteral<String> stringType = TypeLiteral.get(String.class);
+ final TypeLiteral<Integer> intType = TypeLiteral.get(Integer.class);
public void testMultibinderAggregatesMultipleModules() {
Module abc = new AbstractModule() {
@@ -84,13 +91,17 @@
};
Injector injector = Guice.createInjector(abc, de);
- Set<String> abcde = injector.getInstance(Key.get(setOfString));
+ Key<Set<String>> setKey = Key.get(setOfString);
+ Set<String> abcde = injector.getInstance(setKey);
+ Set<String> results = setOf("A", "B", "C", "D", "E");
- assertEquals(setOf("A", "B", "C", "D", "E"), abcde);
+ assertEquals(results, abcde);
+ assertSetVisitor(setKey, stringType, setOf(abc, de), BOTH, false, 0, instance("A"),
+ instance("B"), instance("C"), instance("D"), instance("E"));
}
public void testMultibinderAggregationForAnnotationInstance() {
- Injector injector = Guice.createInjector(new AbstractModule() {
+ Module module = new AbstractModule() {
protected void configure() {
Multibinder<String> multibinder
= Multibinder.newSetBinder(binder(), String.class, Names.named("abc"));
@@ -100,14 +111,19 @@
multibinder = Multibinder.newSetBinder(binder(), String.class, Names.named("abc"));
multibinder.addBinding().toInstance("C");
}
- });
+ };
+ Injector injector = Guice.createInjector(module);
- Set<String> abcde = injector.getInstance(Key.get(setOfString, Names.named("abc")));
- assertEquals(setOf("A", "B", "C"), abcde);
+ Key<Set<String>> setKey = Key.get(setOfString, Names.named("abc"));
+ Set<String> abc = injector.getInstance(setKey);
+ Set<String> results = setOf("A", "B", "C");
+ assertEquals(results, abc);
+ assertSetVisitor(setKey, stringType, setOf(module), BOTH, false, 0, instance("A"),
+ instance("B"), instance("C"));
}
public void testMultibinderAggregationForAnnotationType() {
- Injector injector = Guice.createInjector(new AbstractModule() {
+ Module module = new AbstractModule() {
protected void configure() {
Multibinder<String> multibinder
= Multibinder.newSetBinder(binder(), String.class, Abc.class);
@@ -117,14 +133,19 @@
multibinder = Multibinder.newSetBinder(binder(), String.class, Abc.class);
multibinder.addBinding().toInstance("C");
}
- });
+ };
+ Injector injector = Guice.createInjector(module);
- Set<String> abcde = injector.getInstance(Key.get(setOfString, Abc.class));
- assertEquals(setOf("A", "B", "C"), abcde);
+ Key<Set<String>> setKey = Key.get(setOfString, Abc.class);
+ Set<String> abcde = injector.getInstance(setKey);
+ Set<String> results = setOf("A", "B", "C");
+ assertEquals(results, abcde);
+ assertSetVisitor(setKey, stringType, setOf(module), BOTH, false, 0, instance("A"),
+ instance("B"), instance("C"));
}
public void testMultibinderWithMultipleAnnotationValueSets() {
- Injector injector = Guice.createInjector(new AbstractModule() {
+ Module module = new AbstractModule() {
protected void configure() {
Multibinder<String> abcMultibinder
= Multibinder.newSetBinder(binder(), String.class, named("abc"));
@@ -137,16 +158,24 @@
deMultibinder.addBinding().toInstance("D");
deMultibinder.addBinding().toInstance("E");
}
- });
+ };
+ Injector injector = Guice.createInjector(module);
- Set<String> abc = injector.getInstance(Key.get(setOfString, named("abc")));
- Set<String> de = injector.getInstance(Key.get(setOfString, named("de")));
- assertEquals(setOf("A", "B", "C"), abc);
- assertEquals(setOf("D", "E"), de);
+ Key<Set<String>> abcSetKey = Key.get(setOfString, named("abc"));
+ Set<String> abc = injector.getInstance(abcSetKey);
+ Key<Set<String>> deSetKey = Key.get(setOfString, named("de"));
+ Set<String> de = injector.getInstance(deSetKey);
+ Set<String> abcResults = setOf("A", "B", "C");
+ assertEquals(abcResults, abc);
+ Set<String> deResults = setOf("D", "E");
+ assertEquals(deResults, de);
+ assertSetVisitor(abcSetKey, stringType, setOf(module), BOTH, false, 1, instance("A"),
+ instance("B"), instance("C"));
+ assertSetVisitor(deSetKey, stringType, setOf(module), BOTH, false, 1, instance("D"), instance("E"));
}
public void testMultibinderWithMultipleAnnotationTypeSets() {
- Injector injector = Guice.createInjector(new AbstractModule() {
+ Module module = new AbstractModule() {
protected void configure() {
Multibinder<String> abcMultibinder
= Multibinder.newSetBinder(binder(), String.class, Abc.class);
@@ -159,37 +188,50 @@
deMultibinder.addBinding().toInstance("D");
deMultibinder.addBinding().toInstance("E");
}
- });
+ };
+ Injector injector = Guice.createInjector(module);
- Set<String> abc = injector.getInstance(Key.get(setOfString, Abc.class));
- Set<String> de = injector.getInstance(Key.get(setOfString, De.class));
- assertEquals(setOf("A", "B", "C"), abc);
- assertEquals(setOf("D", "E"), de);
+ Key<Set<String>> abcSetKey = Key.get(setOfString, Abc.class);
+ Set<String> abc = injector.getInstance(abcSetKey);
+ Key<Set<String>> deSetKey = Key.get(setOfString, De.class);
+ Set<String> de = injector.getInstance(deSetKey);
+ Set<String> abcResults = setOf("A", "B", "C");
+ assertEquals(abcResults, abc);
+ Set<String> deResults = setOf("D", "E");
+ assertEquals(deResults, de);
+ assertSetVisitor(abcSetKey, stringType, setOf(module), BOTH, false, 1, instance("A"),
+ instance("B"), instance("C"));
+ assertSetVisitor(deSetKey, stringType, setOf(module), BOTH, false, 1, instance("D"), instance("E"));
}
public void testMultibinderWithMultipleSetTypes() {
- Injector injector = Guice.createInjector(new AbstractModule() {
+ Module module = new AbstractModule() {
protected void configure() {
Multibinder.newSetBinder(binder(), String.class)
.addBinding().toInstance("A");
Multibinder.newSetBinder(binder(), Integer.class)
.addBinding().toInstance(1);
}
- });
+ };
+ Injector injector = Guice.createInjector(module);
assertEquals(setOf("A"), injector.getInstance(Key.get(setOfString)));
assertEquals(setOf(1), injector.getInstance(Key.get(setOfInteger)));
+ assertSetVisitor(Key.get(setOfString), stringType, setOf(module), BOTH, false, 1, instance("A"));
+ assertSetVisitor(Key.get(setOfInteger), intType, setOf(module), BOTH, false, 1, instance(1));
}
public void testMultibinderWithEmptySet() {
- Injector injector = Guice.createInjector(new AbstractModule() {
+ Module module = new AbstractModule() {
protected void configure() {
Multibinder.newSetBinder(binder(), String.class);
}
- });
+ };
+ Injector injector = Guice.createInjector(module);
Set<String> set = injector.getInstance(Key.get(setOfString));
assertEquals(Collections.emptySet(), set);
+ assertSetVisitor(Key.get(setOfString), stringType, setOf(module), BOTH, false, 0);
}
public void testMultibinderSetIsUnmodifiable() {
@@ -209,7 +251,7 @@
}
public void testMultibinderSetIsLazy() {
- Injector injector = Guice.createInjector(new AbstractModule() {
+ Module module = new AbstractModule() {
protected void configure() {
Multibinder.newSetBinder(binder(), Integer.class)
.addBinding().toProvider(new Provider<Integer>() {
@@ -219,21 +261,24 @@
}
});
}
- });
+ };
+ Injector injector = Guice.createInjector(module);
assertEquals(setOf(1), injector.getInstance(Key.get(setOfInteger)));
assertEquals(setOf(2), injector.getInstance(Key.get(setOfInteger)));
assertEquals(setOf(3), injector.getInstance(Key.get(setOfInteger)));
+ assertSetVisitor(Key.get(setOfInteger), intType, setOf(module), BOTH, false, 0, providerInstance(1));
}
public void testMultibinderSetForbidsDuplicateElements() {
- Injector injector = Guice.createInjector(new AbstractModule() {
+ Module module = new AbstractModule() {
protected void configure() {
final Multibinder<String> multibinder = Multibinder.newSetBinder(binder(), String.class);
multibinder.addBinding().toInstance("A");
multibinder.addBinding().toInstance("A");
}
- });
+ };
+ Injector injector = Guice.createInjector(module);
try {
injector.getInstance(Key.get(setOfString));
@@ -242,49 +287,57 @@
assertContains(expected.getMessage(),
"1) Set injection failed due to duplicated element \"A\"");
}
+
+ // But we can still visit the module!
+ assertSetVisitor(Key.get(setOfString), stringType, setOf(module), MODULE, false, 0,
+ instance("A"), instance("A"));
}
-
+
public void testMultibinderSetPermitDuplicateElements() {
- Injector injector = Guice.createInjector(
- new AbstractModule() {
- protected void configure() {
- Multibinder<String> multibinder = Multibinder.newSetBinder(binder(), String.class);
- multibinder.addBinding().toInstance("A");
- multibinder.addBinding().toInstance("B");
- }
- },
- new AbstractModule() {
- protected void configure() {
- Multibinder<String> multibinder = Multibinder.newSetBinder(binder(), String.class);
- multibinder.permitDuplicates();
- multibinder.addBinding().toInstance("B");
- multibinder.addBinding().toInstance("C");
- }
- });
+ Module ab = new AbstractModule() {
+ protected void configure() {
+ Multibinder<String> multibinder = Multibinder.newSetBinder(binder(), String.class);
+ multibinder.addBinding().toInstance("A");
+ multibinder.addBinding().toInstance("B");
+ }
+ };
+ Module bc = new AbstractModule() {
+ protected void configure() {
+ Multibinder<String> multibinder = Multibinder.newSetBinder(binder(), String.class);
+ multibinder.permitDuplicates();
+ multibinder.addBinding().toInstance("B");
+ multibinder.addBinding().toInstance("C");
+ }
+ };
+ Injector injector = Guice.createInjector(ab, bc);
assertEquals(setOf("A", "B", "C"), injector.getInstance(Key.get(setOfString)));
+ assertSetVisitor(Key.get(setOfString), stringType, setOf(ab, bc), BOTH, true, 0,
+ instance("A"), instance("B"), instance("B"), instance("C"));
}
public void testMultibinderSetPermitDuplicateCallsToPermitDuplicates() {
- Injector injector = Guice.createInjector(
- new AbstractModule() {
- protected void configure() {
- Multibinder<String> multibinder = Multibinder.newSetBinder(binder(), String.class);
- multibinder.permitDuplicates();
- multibinder.addBinding().toInstance("A");
- multibinder.addBinding().toInstance("B");
- }
- },
- new AbstractModule() {
- protected void configure() {
- Multibinder<String> multibinder = Multibinder.newSetBinder(binder(), String.class);
- multibinder.permitDuplicates();
- multibinder.addBinding().toInstance("B");
- multibinder.addBinding().toInstance("C");
- }
- });
+ Module ab = new AbstractModule() {
+ protected void configure() {
+ Multibinder<String> multibinder = Multibinder.newSetBinder(binder(), String.class);
+ multibinder.permitDuplicates();
+ multibinder.addBinding().toInstance("A");
+ multibinder.addBinding().toInstance("B");
+ }
+ };
+ Module bc = new AbstractModule() {
+ protected void configure() {
+ Multibinder<String> multibinder = Multibinder.newSetBinder(binder(), String.class);
+ multibinder.permitDuplicates();
+ multibinder.addBinding().toInstance("B");
+ multibinder.addBinding().toInstance("C");
+ }
+ };
+ Injector injector = Guice.createInjector(ab, bc);
assertEquals(setOf("A", "B", "C"), injector.getInstance(Key.get(setOfString)));
+ assertSetVisitor(Key.get(setOfString), stringType, setOf(ab, bc), BOTH, true, 0,
+ instance("A"), instance("B"), instance("B"), instance("C"));
}
public void testMultibinderSetForbidsNullElements() {
@@ -468,17 +521,20 @@
assertEquals(ImmutableSet.of("A", "B", "C", "D", "E", "F"),
injector.getInstance(Key.get(setOfString)));
+ assertSetVisitor(Key.get(setOfString), stringType, setOf(abcd, ef), BOTH, false, 0,
+ instance("A"), instance("B"), instance("C"), instance("D"), instance("E"), instance("F"));
}
-
+
/**
* With overrides, we should get the union of all multibindings.
*/
public void testModuleOverrideAndMultibindingsWithPermitDuplicates() {
- Module ab = new AbstractModule() {
+ Module abc = new AbstractModule() {
protected void configure() {
Multibinder<String> multibinder = Multibinder.newSetBinder(binder(), String.class);
multibinder.addBinding().toInstance("A");
multibinder.addBinding().toInstance("B");
+ multibinder.addBinding().toInstance("C");
multibinder.permitDuplicates();
}
};
@@ -499,13 +555,15 @@
}
};
- Module abcd = Modules.override(ab).with(cd);
+ Module abcd = Modules.override(abc).with(cd);
Injector injector = Guice.createInjector(abcd, ef);
assertEquals(ImmutableSet.of("A", "B", "C", "D", "E", "F"),
injector.getInstance(Key.get(setOfString)));
+ assertSetVisitor(Key.get(setOfString), stringType, setOf(abcd, ef), BOTH, true, 0,
+ instance("A"), instance("B"), instance("C"), instance("C"), instance("D"), instance("E"), instance("F"));
}
-
+
@BindingAnnotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@@ -544,5 +602,5 @@
expected.add(1);
expected.add(2);
assertEquals(expected, s1);
- }
+ }
}
diff --git a/extensions/multibindings/test/com/google/inject/multibindings/SpiUtils.java b/extensions/multibindings/test/com/google/inject/multibindings/SpiUtils.java
new file mode 100644
index 0000000..2885d73
--- /dev/null
+++ b/extensions/multibindings/test/com/google/inject/multibindings/SpiUtils.java
@@ -0,0 +1,574 @@
+/**
+ * Copyright (C) 2010 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.inject.multibindings.MapBinder.entryOfProviderOf;
+import static com.google.inject.multibindings.MapBinder.mapOf;
+import static com.google.inject.multibindings.MapBinder.mapOfProviderOf;
+import static com.google.inject.multibindings.MapBinder.mapOfSetOfProviderOf;
+import static com.google.inject.multibindings.Multibinder.setOf;
+import static com.google.inject.multibindings.SpiUtils.BindType.INSTANCE;
+import static com.google.inject.multibindings.SpiUtils.BindType.LINKED;
+import static com.google.inject.multibindings.SpiUtils.BindType.PROVIDER_INSTANCE;
+import static com.google.inject.multibindings.SpiUtils.VisitType.BOTH;
+import static com.google.inject.multibindings.SpiUtils.VisitType.INJECTOR;
+import static com.google.inject.multibindings.SpiUtils.VisitType.MODULE;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.google.inject.Binding;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.TypeLiteral;
+import com.google.inject.internal.util.Lists;
+import com.google.inject.spi.DefaultBindingTargetVisitor;
+import com.google.inject.spi.Element;
+import com.google.inject.spi.Elements;
+import com.google.inject.spi.InstanceBinding;
+import com.google.inject.spi.LinkedKeyBinding;
+import com.google.inject.spi.ProviderInstanceBinding;
+import com.google.inject.spi.ProviderLookup;
+
+/**
+ * Utilities for testing the Multibinder & MapBinder extension SPI.
+ *
+ * @author sameb@google.com (Sam Berlin)
+ */
+public class SpiUtils {
+
+ /** The kind of test we should perform. A live Injector, a raw Elements (Module) test, or both. */
+ enum VisitType { INJECTOR, MODULE, BOTH }
+
+ /**
+ * Asserts that MapBinderBinding visitors for work correctly.
+ *
+ * @param <T> The type of the binding
+ * @param mapKey The key the map belongs to.
+ * @param keyType the TypeLiteral of the key of the map
+ * @param valueType the TypeLiteral of the value of the map
+ * @param modules The modules that define the mapbindings
+ * @param visitType The kind of test we should perform. A live Injector, a raw Elements (Module) test, or both.
+ * @param allowDuplicates If duplicates are allowed.
+ * @param expectedMapBindings The number of other mapbinders we expect to see.
+ * @param results The kind of bindings contained in the mapbinder.
+ */
+ static <T> void assertMapVisitor(Key<T> mapKey, TypeLiteral<?> keyType, TypeLiteral<?> valueType,
+ Iterable<? extends Module> modules, VisitType visitType, boolean allowDuplicates,
+ int expectedMapBindings, MapResult... results) {
+ if(visitType == null) {
+ fail("must test something");
+ }
+
+ if (visitType == BOTH || visitType == INJECTOR) {
+ mapInjectorTest(mapKey, keyType, valueType, modules, allowDuplicates, expectedMapBindings,
+ results);
+ }
+
+ if (visitType == BOTH || visitType == MODULE) {
+ mapModuleTest(mapKey, keyType, valueType, modules, allowDuplicates, expectedMapBindings,
+ results);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <T> void mapInjectorTest(Key<T> mapKey, TypeLiteral<?> keyType,
+ TypeLiteral<?> valueType, Iterable<? extends Module> modules, boolean allowDuplicates,
+ int expectedMapBindings, MapResult... results) {
+ Injector injector = Guice.createInjector(modules);
+ Visitor<T> visitor = new Visitor<T>();
+ Binding<T> mapBinding = injector.getBinding(mapKey);
+ MapBinderBinding<T> mapbinder = (MapBinderBinding<T>)mapBinding.acceptTargetVisitor(visitor);
+ assertNotNull(mapbinder);
+ assertEquals(keyType, mapbinder.getKeyTypeLiteral());
+ assertEquals(valueType, mapbinder.getValueTypeLiteral());
+ assertEquals(allowDuplicates, mapbinder.permitsDuplicates());
+ List<Map.Entry<?, Binding<?>>> entries = Lists.newArrayList(mapbinder.getEntries());
+ List<MapResult> mapResults = Lists.newArrayList(results);
+ assertEquals("wrong entries, expected: " + mapResults + ", but was: " + entries,
+ mapResults.size(), entries.size());
+
+ for(MapResult result : mapResults) {
+ Map.Entry<?, Binding<?>> found = null;
+ for(Map.Entry<?, Binding<?>> entry : entries) {
+ Object key = entry.getKey();
+ Binding<?> value = entry.getValue();
+ if(!key.equals(result.k)) {
+ continue;
+ }
+ switch (result.v.type) {
+ case INSTANCE:
+ if (value instanceof InstanceBinding
+ && ((InstanceBinding) value).getInstance().equals(result.v.instance)) {
+ found = entry;
+ }
+ break;
+ case LINKED:
+ if (value instanceof LinkedKeyBinding
+ && ((LinkedKeyBinding) value).getKey().equals(result.v.key)) {
+ found = entry;
+ }
+ break;
+ case PROVIDER_INSTANCE:
+ if (value instanceof ProviderInstanceBinding
+ && ((ProviderInstanceBinding) value).getProviderInstance().get().equals(
+ result.v.instance)) {
+ found = entry;
+ }
+ break;
+ }
+ }
+ if(found == null) {
+ fail("Could not find entry: " + result + " in remaining entries: " + entries);
+ } else {
+ assertTrue(mapbinder.containsElement(found.getValue()));
+ entries.remove(found);
+ }
+ }
+
+ if(!entries.isEmpty()) {
+ fail("Found all entries of: " + mapResults + ", but more were left over: " + entries);
+ }
+
+ Key<?> mapOfProvider = adapt(mapKey, mapOfProviderOf(keyType, valueType));
+ Key<?> mapOfSetOfProvider = adapt(mapKey, mapOfSetOfProviderOf(keyType, valueType));
+ Key<?> mapOfSet = adapt(mapKey, mapOf(keyType, setOf(valueType)));
+ Key<?> setOfEntry = adapt(mapKey, setOf(entryOfProviderOf(keyType, valueType)));
+ boolean entrySetMatch = false;
+ boolean mapProviderMatch = false;
+ boolean mapSetMatch = false;
+ boolean mapSetProviderMatch = false;
+ List<Object> otherMapBindings = Lists.newArrayList();
+ List<Binding> otherMatches = Lists.newArrayList();
+ for(Binding b : injector.getAllBindings().values()) {
+ boolean contains = mapbinder.containsElement(b);
+ Object visited = b.acceptTargetVisitor(visitor);
+ if(visited instanceof MapBinderBinding) {
+ if(visited.equals(mapbinder)) {
+ assertTrue(contains);
+ } else {
+ otherMapBindings.add(visited);
+ }
+ } else if(b.getKey().equals(mapOfProvider)) {
+ assertTrue(contains);
+ mapProviderMatch = true;
+ } else if(b.getKey().equals(mapOfSet)) {
+ assertTrue(contains);
+ mapSetMatch = true;
+ } else if(b.getKey().equals(mapOfSetOfProvider)) {
+ assertTrue(contains);
+ mapSetProviderMatch = true;
+ } else if(b.getKey().equals(setOfEntry)) {
+ assertTrue(contains);
+ entrySetMatch = true;
+ // Validate that this binding is also a MultibinderBinding.
+ assertTrue(b.acceptTargetVisitor(visitor) instanceof MultibinderBinding);
+ } else if (contains) {
+ otherMatches.add(b);
+ }
+ }
+
+ int sizeOfOther = otherMatches.size();
+ if(allowDuplicates) {
+ sizeOfOther--; // account for 1 duplicate binding
+ }
+ sizeOfOther = sizeOfOther / 2; // account for 1 value & 1 Map.Entry of each expected binding.
+ assertEquals("Incorrect other matches: " + otherMatches, mapResults.size(), sizeOfOther);
+ assertTrue(entrySetMatch);
+ assertTrue(mapProviderMatch);
+ assertEquals(allowDuplicates, mapSetMatch);
+ assertEquals(allowDuplicates, mapSetProviderMatch);
+ assertEquals("other MapBindings found: " + otherMapBindings, expectedMapBindings,
+ otherMapBindings.size());
+ }
+
+ /** Adapts a key, keeping the original annotation, using the new type literal. */
+ private static Key<?> adapt(Key<?> mapKey, TypeLiteral<?> resultType) {
+ if(mapKey.getAnnotation() != null) {
+ return Key.get(resultType, mapKey.getAnnotation());
+ } else if(mapKey.getAnnotationType() != null) {
+ return Key.get(resultType, mapKey.getAnnotationType());
+ } else {
+ return Key.get(resultType);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <T> void mapModuleTest(Key<T> mapKey, TypeLiteral<?> keyType,
+ TypeLiteral<?> valueType, Iterable<? extends Module> modules, boolean allowDuplicates,
+ int expectedMapBindings, MapResult... results) {
+ List<Element> elements = Elements.getElements(modules);
+ Visitor<T> visitor = new Visitor<T>();
+ MapBinderBinding<T> mapbinder = null;
+ for(Element element : elements) {
+ if(element instanceof Binding && ((Binding)element).getKey().equals(mapKey)) {
+ mapbinder = (MapBinderBinding<T>)((Binding)element).acceptTargetVisitor(visitor);
+ break;
+ }
+ }
+ assertNotNull(mapbinder);
+
+ assertEquals(keyType, mapbinder.getKeyTypeLiteral());
+ assertEquals(valueType, mapbinder.getValueTypeLiteral());
+ List<MapResult> mapResults = Lists.newArrayList(results);
+
+ Key<?> mapOfProvider = adapt(mapKey, mapOfProviderOf(keyType, valueType));
+ Key<?> mapOfSetOfProvider = adapt(mapKey, mapOfSetOfProviderOf(keyType, valueType));
+ Key<?> mapOfSet = adapt(mapKey, mapOf(keyType, setOf(valueType)));
+ Key<?> setOfEntry = adapt(mapKey, setOf(entryOfProviderOf(keyType, valueType)));
+ boolean entrySetMatch = false;
+ boolean mapProviderMatch = false;
+ boolean mapSetMatch = false;
+ boolean mapSetProviderMatch = false;
+ List<Object> otherMapBindings = Lists.newArrayList();
+ List<Element> otherMatches = Lists.newArrayList();
+ List<Element> otherElements = Lists.newArrayList();
+ for(Element element : elements) {
+ boolean contains = mapbinder.containsElement(element);
+ if(!contains) {
+ otherElements.add(element);
+ }
+ boolean matched = false;
+ Key key = null;
+ Binding b = null;
+ if(element instanceof Binding) {
+ b = (Binding)element;
+ key = b.getKey();
+ Object visited = b.acceptTargetVisitor(visitor);
+ if(visited instanceof MapBinderBinding) {
+ matched = true;
+ if(visited.equals(mapbinder)) {
+ assertTrue(contains);
+ } else {
+ otherMapBindings.add(visited);
+ }
+ }
+ } else if(element instanceof ProviderLookup) {
+ key = ((ProviderLookup)element).getKey();
+ }
+
+ if(!matched && key != null) {
+ if(key.equals(mapOfProvider)) {
+ matched = true;
+ assertTrue(contains);
+ mapProviderMatch = true;
+ } else if(key.equals(mapOfSet)) {
+ matched = true;
+ assertTrue(contains);
+ mapSetMatch = true;
+ } else if(key.equals(mapOfSetOfProvider)) {
+ matched = true;
+ assertTrue(contains);
+ mapSetProviderMatch = true;
+ } else if(key.equals(setOfEntry)) {
+ matched = true;
+ assertTrue(contains);
+ entrySetMatch = true;
+ // Validate that this binding is also a MultibinderBinding.
+ if(b != null) {
+ assertTrue(b.acceptTargetVisitor(visitor) instanceof MultibinderBinding);
+ }
+ }
+ }
+
+ if(!matched && contains) {
+ otherMatches.add(element);
+ }
+ }
+
+ int otherMatchesSize = otherMatches.size();
+ if(allowDuplicates) {
+ otherMatchesSize--; // allow for 1 duplicate binding
+ }
+ otherMatchesSize = otherMatchesSize / 3; // value, ProviderLookup per value, Map.Entry per value
+ assertEquals("incorrect number of contains, leftover matches: " + otherMatches, mapResults
+ .size(), otherMatchesSize);
+
+ assertTrue(entrySetMatch);
+ assertTrue(mapProviderMatch);
+ assertEquals(allowDuplicates, mapSetMatch);
+ assertEquals(allowDuplicates, mapSetProviderMatch);
+ assertEquals("other MapBindings found: " + otherMapBindings, expectedMapBindings,
+ otherMapBindings.size());
+
+ // Validate that we can construct an injector out of the remaining bindings.
+ Guice.createInjector(Elements.getModule(otherElements));
+ }
+
+ /**
+ * Asserts that MultibinderBinding visitors work correctly.
+ *
+ * @param <T> The type of the binding
+ * @param setKey The key the set belongs to.
+ * @param elementType the TypeLiteral of the element
+ * @param modules The modules that define the multibindings
+ * @param visitType The kind of test we should perform. A live Injector, a raw Elements (Module) test, or both.
+ * @param allowDuplicates If duplicates are allowed.
+ * @param expectedMultibindings The number of other multibinders we expect to see.
+ * @param results The kind of bindings contained in the multibinder.
+ */
+ static <T> void assertSetVisitor(Key<T> setKey, TypeLiteral<?> elementType,
+ Iterable<? extends Module> modules, VisitType visitType, boolean allowDuplicates,
+ int expectedMultibindings, BindResult... results) {
+ if(visitType == null) {
+ fail("must test something");
+ }
+
+ if(visitType == BOTH || visitType == INJECTOR) {
+ setInjectorTest(setKey, elementType, modules, allowDuplicates, expectedMultibindings, results);
+ }
+
+ if(visitType == BOTH || visitType == MODULE) {
+ setModuleTest(setKey, elementType, modules, allowDuplicates, expectedMultibindings, results);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <T> void setInjectorTest(Key<T> setKey, TypeLiteral<?> elementType,
+ Iterable<? extends Module> modules, boolean allowDuplicates, int otherMultibindings,
+ BindResult... results) {
+ Injector injector = Guice.createInjector(modules);
+ Visitor<T> visitor = new Visitor<T>();
+ Binding<T> binding = injector.getBinding(setKey);
+ MultibinderBinding<T> multibinder = (MultibinderBinding<T>)binding.acceptTargetVisitor(visitor);
+ assertNotNull(multibinder);
+ assertEquals(elementType, multibinder.getElementTypeLiteral());
+ assertEquals(allowDuplicates, multibinder.permitsDuplicates());
+ List<Binding<?>> elements = Lists.newArrayList(multibinder.getElements());
+ List<BindResult> bindResults = Lists.newArrayList(results);
+ assertEquals("wrong bind elements, expected: " + bindResults + ", but was: " + multibinder.getElements(),
+ bindResults.size(), elements.size());
+
+ for(BindResult result : bindResults) {
+ Binding found = null;
+ for(Binding item : elements) {
+ switch (result.type) {
+ case INSTANCE:
+ if (item instanceof InstanceBinding
+ && ((InstanceBinding) item).getInstance().equals(result.instance)) {
+ found = item;
+ }
+ break;
+ case LINKED:
+ if (item instanceof LinkedKeyBinding
+ && ((LinkedKeyBinding) item).getKey().equals(result.key)) {
+ found = item;
+ }
+ break;
+ case PROVIDER_INSTANCE:
+ if (item instanceof ProviderInstanceBinding
+ && ((ProviderInstanceBinding) item).getProviderInstance().get().equals(
+ result.instance)) {
+ found = item;
+ }
+ break;
+ }
+ }
+ if(found == null) {
+ fail("Could not find element: " + result + " in remaining elements: " + elements);
+ } else {
+ elements.remove(found);
+ }
+ }
+
+ if(!elements.isEmpty()) {
+ fail("Found all elements of: " + bindResults + ", but more were left over: " + elements);
+ }
+
+ Set<Binding> setOfElements = new HashSet<Binding>(multibinder.getElements());
+
+ List<Object> otherMultibinders = Lists.newArrayList();
+ List<Binding> otherContains = Lists.newArrayList();
+ for(Binding b : injector.getAllBindings().values()) {
+ boolean contains = multibinder.containsElement(b);
+ Object visited = b.acceptTargetVisitor(visitor);
+ if(visited != null) {
+ if(visited.equals(multibinder)) {
+ assertTrue(contains);
+ } else {
+ otherMultibinders.add(visited);
+ }
+ } else if(setOfElements.contains(b)) {
+ assertTrue(contains);
+ } else if(contains) {
+ otherContains.add(b);
+ }
+ }
+
+ if(allowDuplicates) {
+ assertEquals("contained more than it should: " + otherContains, 1, otherContains.size());
+ } else {
+ assertTrue("contained more than it should: " + otherContains, otherContains.isEmpty());
+ }
+ assertEquals("other multibindings found: " + otherMultibinders, otherMultibindings,
+ otherMultibinders.size());
+
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <T> void setModuleTest(Key<T> setKey, TypeLiteral<?> elementType,
+ Iterable<? extends Module> modules, boolean allowDuplicates, int otherMultibindings,
+ BindResult... results) {
+ List<BindResult> bindResults = Lists.newArrayList(results);
+ List<Element> elements = Elements.getElements(modules);
+ Visitor<T> visitor = new Visitor<T>();
+ MultibinderBinding<T> multibinder = null;
+ for(Element element : elements) {
+ if(element instanceof Binding && ((Binding)element).getKey().equals(setKey)) {
+ multibinder = (MultibinderBinding<T>)((Binding)element).acceptTargetVisitor(visitor);
+ break;
+ }
+ }
+ assertNotNull(multibinder);
+
+ assertEquals(elementType, multibinder.getElementTypeLiteral());
+ List<Object> otherMultibinders = Lists.newArrayList();
+ Set<Element> otherContains = new HashSet<Element>();
+ List<Element> otherElements = Lists.newArrayList();
+ for(Element element : elements) {
+ boolean contains = multibinder.containsElement(element);
+ if(!contains) {
+ otherElements.add(element);
+ }
+ boolean matched = false;
+ if(element instanceof Binding) {
+ Binding binding = (Binding)element;
+ Object visited = binding.acceptTargetVisitor(visitor);
+ if(visited != null) {
+ matched = true;
+ if(visited.equals(multibinder)) {
+ assertTrue(contains);
+ } else {
+ otherMultibinders.add(visited);
+ }
+ }
+ }
+
+ if(!matched && contains) {
+ otherContains.add(element);
+ }
+ }
+
+ if(allowDuplicates) {
+ assertEquals("wrong contained elements: " + otherContains, bindResults.size() + 1, otherContains.size());
+ } else {
+ assertEquals("wrong contained elements: " + otherContains, bindResults.size(), otherContains.size());
+ }
+
+ assertEquals("other multibindings found: " + otherMultibinders, otherMultibindings,
+ otherMultibinders.size());
+
+ // Validate that we can construct an injector out of the remaining bindings.
+ Guice.createInjector(Elements.getModule(otherElements));
+ }
+
+ static <K, V> MapResult instance(K k, V v) {
+ return new MapResult<K, V>(k, new BindResult<V>(INSTANCE, v, null));
+ }
+
+ static <K, V> MapResult linked(K k, Class<? extends V> clazz) {
+ return new MapResult<K, V>(k, new BindResult<V>(LINKED, null, Key.get(clazz)));
+ }
+
+ static <K, V> MapResult linked(K k, Key<? extends V> key) {
+ return new MapResult<K, V>(k, new BindResult<V>(LINKED, null, key));
+ }
+
+ static <K, V> MapResult providerInstance(K k, V v) {
+ return new MapResult<K, V>(k, new BindResult<V>(PROVIDER_INSTANCE, v, null));
+ }
+
+ private static class MapResult<K, V> {
+ private final K k;
+ private final BindResult<V> v;
+
+ MapResult(K k, BindResult<V> v) {
+ this.k = k;
+ this.v = v;
+ }
+
+ @Override
+ public String toString() {
+ return "entry[key[" + k + "],value[" + v + "]]";
+ }
+ }
+
+ static <T> BindResult instance(T t) {
+ return new BindResult<T>(INSTANCE, t, null);
+ }
+
+ static <T> BindResult linked(Class<? extends T> clazz) {
+ return new BindResult<T>(LINKED, null, Key.get(clazz));
+ }
+
+ static <T> BindResult linked(Key<? extends T> key) {
+ return new BindResult<T>(LINKED, null, key);
+ }
+
+ static <T> BindResult providerInstance(T t) {
+ return new BindResult<T>(PROVIDER_INSTANCE, t, null);
+ }
+
+ /** The kind of binding. */
+ static enum BindType { INSTANCE, LINKED, PROVIDER_INSTANCE }
+ /** The result of the binding. */
+ private static class BindResult<T> {
+ private final BindType type;
+ private final Key<? extends T> key;
+ private final T instance;
+
+ private BindResult(BindType type, T instance, Key<? extends T> key) {
+ this.type = type;
+ this.instance = instance;
+ this.key = key;
+ }
+
+ @Override
+ public String toString() {
+ switch(type) {
+ case INSTANCE:
+ return "instance[" + instance + "]";
+ case LINKED:
+ return "linkedKey[" + key + "]";
+ case PROVIDER_INSTANCE:
+ return "providerInstance[" + instance + "]";
+ }
+ return null;
+ }
+ }
+
+ private static class Visitor<T> extends
+ DefaultBindingTargetVisitor<T, Object> implements MultibindingsTargetVisitor<T, Object> {
+
+ public Object visit(MultibinderBinding<? extends T> multibinding) {
+ return multibinding;
+ }
+
+ public Object visit(MapBinderBinding<? extends T> mapbinding) {
+ return mapbinding;
+ }
+ }
+}
+