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;

+    }  

+  }

+}

+