Checking in dpb's MapBinder code. This code is a dramatic improvement over the previous rev of MapBinder

git-svn-id: https://google-guice.googlecode.com/svn/trunk@482 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/Element.java b/extensions/multibindings/src/com/google/inject/multibindings/Element.java
index 9d5bfa6..3a4420d 100644
--- a/extensions/multibindings/src/com/google/inject/multibindings/Element.java
+++ b/extensions/multibindings/src/com/google/inject/multibindings/Element.java
@@ -31,7 +31,6 @@
  */
 @Retention(RUNTIME) @BindingAnnotation
 @interface Element {
-  public abstract String setName();
-  public abstract String role();
-  public abstract int uniqueId();
+  String setName();
+  int uniqueId();
 }
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/MapBinder.java b/extensions/multibindings/src/com/google/inject/multibindings/MapBinder.java
index 4f69c1b..e8af001 100644
--- a/extensions/multibindings/src/com/google/inject/multibindings/MapBinder.java
+++ b/extensions/multibindings/src/com/google/inject/multibindings/MapBinder.java
@@ -25,9 +25,10 @@
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Type;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
 
 /**
  * An API to bind multiple map entries separately, only to later inject them as
@@ -95,11 +96,11 @@
    */
   public static <K, V> MapBinder<K, V> newMapBinder(Binder binder, 
       Type keyType, Type valueType) {
-    return newMapBinder(binder,
+    return newMapBinder(binder, valueType,
         Key.get(MapBinder.<K, V>mapOf(keyType, valueType)),
-        Key.get(MapBinder.<K, V>mapOfProviderOf(keyType, valueType)),
-        Multibinder.<K>newSetBinder(binder, keyType),
-        Multibinder.<V>newSetBinder(binder, valueType));
+        Key.get(MapBinder.<K, V>mapOfProviderOf(keyType, valueType)), 
+        Multibinder.<Entry<K, Provider<V>>>newSetBinder(
+            binder, entryOfProviderOf(keyType, valueType)));
   }
 
   /**
@@ -108,11 +109,11 @@
    */
   public static <K, V> MapBinder<K, V> newMapBinder(Binder binder, 
       Type keyType, Type valueType, Annotation annotation) {
-    return newMapBinder(binder,
+    return newMapBinder(binder, valueType,
         Key.get(MapBinder.<K, V>mapOf(keyType, valueType), annotation),
-        Key.get(MapBinder.<K, V>mapOfProviderOf(keyType, valueType), annotation),
-        Multibinder.<K>newSetBinder(binder, keyType, annotation),
-        Multibinder.<V>newSetBinder(binder, valueType, annotation));
+        Key.get(MapBinder.<K, V>mapOfProviderOf(keyType, valueType), annotation), 
+        Multibinder.<Entry<K, Provider<V>>>newSetBinder(
+            binder, entryOfProviderOf(keyType, valueType), annotation));
   }
 
   /**
@@ -121,11 +122,11 @@
    */
   public static <K, V> MapBinder<K, V> newMapBinder(Binder binder, 
       Type keyType, Type valueType, Class<? extends Annotation> annotationType) {
-    return newMapBinder(binder,
+    return newMapBinder(binder, valueType,
         Key.get(MapBinder.<K, V>mapOf(keyType, valueType), annotationType),
-        Key.get(MapBinder.<K, V>mapOfProviderOf(keyType, valueType), annotationType),
-        Multibinder.<K>newSetBinder(binder, keyType, annotationType),
-        Multibinder.<V>newSetBinder(binder, valueType, annotationType));
+        Key.get(MapBinder.<K, V>mapOfProviderOf(keyType, valueType), annotationType), 
+        Multibinder.<Entry<K, Provider<V>>>newSetBinder(
+            binder, entryOfProviderOf(keyType, valueType), annotationType));
   }
 
   @SuppressWarnings("unchecked")
@@ -138,14 +139,19 @@
       Type keyType, Type valueType) {
     return mapOf(keyType, new TypeWithArgument(Provider.class, valueType));
   }
+  
+  private static Type entryOfProviderOf(Type keyType, final Type valueType) {
+    return new TypeWithArgument(Map.Entry.class, keyType, 
+        new TypeWithArgument(Provider.class, valueType));
+  }
 
   private static <K, V> MapBinder<K, V> newMapBinder(Binder binder,
-      Key<Map<K, V>> mapKey, Key<Map<K, Provider<V>>> valueKey,
-      Multibinder<K> keyMultibinder, Multibinder<V> valueMultibinder) {
-    RealMapBinder<K, V> realMapBinder = new RealMapBinder<K, V>(
-        mapKey, valueKey, keyMultibinder, valueMultibinder);
-    binder.install(realMapBinder);
-    return realMapBinder;
+      Type valueType, Key<Map<K, V>> mapKey, Key<Map<K, Provider<V>>> providerMapKey,
+      Multibinder<Entry<K, Provider<V>>> entrySetBinder) {
+    RealMapBinder<K, V> mapBinder = new RealMapBinder<K, V>(binder, 
+        valueType, mapKey, providerMapKey, entrySetBinder);
+    binder.install(mapBinder);
+    return mapBinder;
   }
 
   /**
@@ -186,81 +192,77 @@
    * <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 Type valueType;
     private final Key<Map<K, V>> mapKey;
     private final Key<Map<K, Provider<V>>> providerMapKey;
-    private final RealMultibinder<K> keyBinder;
-    private final RealMultibinder<V> valueBinder;
+    private final RealMultibinder<Map.Entry<K, Provider<V>>> entrySetBinder;
+    
+    /* the target injector's binder. non-null until initialization, null afterwards */
+    private Binder binder;
 
-    private RealMapBinder(Key<Map<K, V>> mapKey, Key<Map<K, Provider<V>>> providerMapKey,
-        Multibinder<K> keyBinder, Multibinder<V> valueBinder) {
+    private RealMapBinder(Binder binder, Type valueType, 
+        Key<Map<K, V>> mapKey, Key<Map<K, Provider<V>>> providerMapKey,
+        Multibinder<Map.Entry<K, Provider<V>>> entrySetBinder) {
+      this.valueType = valueType;
       this.mapKey = mapKey;
       this.providerMapKey = providerMapKey;
-      this.keyBinder = (RealMultibinder<K>) keyBinder;
-      this.valueBinder = (RealMultibinder<V>) valueBinder;
+      this.entrySetBinder = (RealMultibinder<Entry<K, Provider<V>>>) entrySetBinder;
+      this.binder = binder;
     }
 
-    public LinkedBindingBuilder<V> addBinding(K key) {
-      // this code is currently quite crufty - we depend on the fact that the
-      // key and the value have the same unique ID. A better approach would be
-      // to create an element annotation that knows the Map.Entry type
+    /**
+     * This creates two bindings. One for the {@code Map.Entry<K, Provider<V>>}
+     * and another for {@code V}.
+     */
+    @Override public LinkedBindingBuilder<V> addBinding(K key) {
       Objects.nonNull(key, "key");
-      int uniqueId = RealMultibinder.nextUniqueId.getAndIncrement();
-      keyBinder.addBinding("key", uniqueId).toInstance(key);
-      return valueBinder.addBinding("value", uniqueId);
+      if (isInitialized()) {
+        throw new IllegalStateException("MapBinder was already initialized");
+      }
+
+      @SuppressWarnings("unchecked")
+      Key<V> valueKey = (Key<V>) Key.get(valueType, new RealElement(entrySetBinder.getSetName()));
+      entrySetBinder.addBinding()
+          .toInstance(new MapEntry<K, Provider<V>>(key, binder.getProvider(valueKey)));
+      return binder.bind(valueKey);
     }
 
     public void configure(Binder binder) {
-      final Provider<Map<K, Provider<V>>> providerMapProvider
-          = new Provider<Map<K, Provider<V>>>() {
+      if (isInitialized()) {
+        throw new IllegalStateException("MapBinder was already initialized");
+      }
+
+      // binds a Map<K, Provider<V>> from a collection of Map<Entry<K, Provider<V>>
+      final Provider<Set<Entry<K,Provider<V>>>> entrySetProvider
+          = binder.getProvider(entrySetBinder.getSetKey());
+      binder.bind(providerMapKey).toProvider(new Provider<Map<K, Provider<V>>>() {
         private Map<K, Provider<V>> providerMap;
-
-        @Inject void init(Injector injector) {
-          Map<Integer, K> keys = new LinkedHashMap<Integer, K>();
-          Map<Integer, Provider<V>> valueProviders = new HashMap<Integer, Provider<V>>();
-
-          // find the bindings
-          for (Map.Entry<Key<?>, Binding<?>> entry : injector.getBindings().entrySet()) {
-            if (keyBinder.keyMatches(entry.getKey(), "key")) {
-              Element element = (Element) entry.getKey().getAnnotation();
-              @SuppressWarnings("unchecked")
-              Binding<K> binding = (Binding<K>) entry.getValue();
-              keys.put(element.uniqueId(), binding.getProvider().get());
-            } else if (valueBinder.keyMatches(entry.getKey(), "value")) {
-              Element element = (Element) entry.getKey().getAnnotation();
-              @SuppressWarnings("unchecked")
-              Binding<V> binding = (Binding<V>) entry.getValue();
-              valueProviders.put(element.uniqueId(), binding.getProvider());
-            }
-          }
-
-          // build the map
+    
+        @SuppressWarnings("unused")
+        @Inject void initialize() {
+          RealMapBinder.this.binder = null;
+          
           Map<K, Provider<V>> providerMapMutable = new LinkedHashMap<K, Provider<V>>();
-          for (Map.Entry<Integer, K> entry : keys.entrySet()) {
-            K key = entry.getValue();
-            Provider<V> valueProvider = valueProviders.get(entry.getKey());
-            if (valueProvider == null) {
-              continue;
-            }
-            if (providerMapMutable.put(key, valueProvider) != null) {
+          for (Map.Entry<K, Provider<V>> entry : entrySetProvider.get()) {
+            if (providerMapMutable.put(entry.getKey(), entry.getValue()) != null) {
               throw new IllegalStateException("Map injection failed due to duplicated key \""
-                  + key + "\"");
+                  + entry.getKey() + "\"");
             }
           }
-
+    
           providerMap = Collections.unmodifiableMap(providerMapMutable);
         }
-
+    
         public Map<K, Provider<V>> get() {
           return providerMap;
         }
-      };
+       });
 
-      binder.bind(providerMapKey).toProvider(providerMapProvider);
-
+      final Provider<Map<K, Provider<V>>> mapProvider = binder.getProvider(providerMapKey);
       binder.bind(mapKey).toProvider(new Provider<Map<K, V>>() {
         public Map<K, V> get() {
           Map<K, V> map = new LinkedHashMap<K, V>();
-          for (Map.Entry<K, Provider<V>> entry : providerMapProvider.get().entrySet()) {
+          for (Map.Entry<K, Provider<V>> entry : mapProvider.get().entrySet()) {
             V value = entry.getValue().get();
             K key = entry.getKey();
             if (value == null) {
@@ -274,13 +276,54 @@
       });
     }
 
+    private boolean isInitialized() {
+      return binder == null;
+    }
+
     @Override public boolean equals(Object o) {
       return o instanceof RealMapBinder
-          && ((RealMapBinder) o).mapKey.equals(mapKey);
+          && ((RealMapBinder<?, ?>) o).mapKey.equals(mapKey);
     }
 
     @Override public int hashCode() {
       return mapKey.hashCode();
     }
+
+    private static final class MapEntry<K, V> implements Map.Entry<K, V> {
+      private final K key;
+      private final V value;
+
+      private MapEntry(K key, V value) {
+        this.key = key;
+        this.value = value;
+      }
+
+      public K getKey() {
+        return key;
+      }
+
+      public V getValue() {
+        return value;
+      }
+
+      public V setValue(V value) {
+        throw new UnsupportedOperationException();
+      }
+
+      @Override public boolean equals(Object obj) {
+        return obj instanceof Map.Entry 
+            && key.equals(((Map.Entry<?, ?>) obj).getKey()) 
+            && value.equals(((Map.Entry<?, ?>) obj).getValue());
+      }
+
+      @Override public int hashCode() {
+        return 127 * ("key".hashCode() ^ key.hashCode())
+            + 127 * ("value".hashCode() ^ value.hashCode());
+      }
+
+      @Override public String toString() {
+        return "MapEntry(" + key + ", " + value + ")";
+      }
+    }
   }
 }
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/Multibinder.java b/extensions/multibindings/src/com/google/inject/multibindings/Multibinder.java
index 413dfd0..8a414a3 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 java.lang.annotation.Annotation;
 import java.lang.reflect.Type;
 import java.util.*;
-import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * An API to bind multiple values separately, only to later inject them as a
@@ -148,7 +147,6 @@
    */
   static final class RealMultibinder<T>
       extends Multibinder<T> implements Module, Provider<Set<T>> {
-    static final AtomicInteger nextUniqueId = new AtomicInteger(1);
 
     private final Type elementType;
     private final String setName;
@@ -177,18 +175,13 @@
       binder.bind(setKey).toProvider(this);
     }
 
-    public LinkedBindingBuilder<T> addBinding() {
-      return addBinding("element", nextUniqueId.getAndIncrement());
-    }
-
     @SuppressWarnings("unchecked")
-    LinkedBindingBuilder<T> addBinding(String role, int uniqueId) {
+    @Override public LinkedBindingBuilder<T> addBinding() {
       if (isInitialized()) {
         throw new IllegalStateException("Multibinder was already initialized");
       }
 
-      return (LinkedBindingBuilder<T>) binder.bind(
-          Key.get(elementType, new RealElement(setName, role, uniqueId)));
+      return binder.bind((Key<T>) Key.get(elementType, new RealElement(setName)));
     }
 
     /**
@@ -199,7 +192,7 @@
     @Inject void initialize(Injector injector) {
       providers = new ArrayList<Provider<T>>();
       for (Map.Entry<Key<?>, Binding<?>> entry : injector.getBindings().entrySet()) {
-        if (keyMatches(entry.getKey(), "element")) {
+        if (keyMatches(entry.getKey())) {
           @SuppressWarnings("unchecked")
           Binding<T> binding = (Binding<T>) entry.getValue();
           providers.add(binding.getProvider());
@@ -209,11 +202,10 @@
       this.binder = null;
     }
 
-    boolean keyMatches(Key<?> key, String role) {
+    private boolean keyMatches(Key<?> key) {
       return key.getTypeLiteral().getType().equals(elementType)
           && key.getAnnotation() instanceof Element
-          && ((Element) key.getAnnotation()).setName().equals(setName)
-          && ((Element) key.getAnnotation()).role().equals(role);
+          && ((Element) key.getAnnotation()).setName().equals(setName);
     }
 
     private boolean isInitialized() {
@@ -238,10 +230,18 @@
       }
       return Collections.unmodifiableSet(result);
     }
+    
+    String getSetName() {
+      return setName;
+    }
+    
+    Key<Set<T>> getSetKey() {
+      return setKey;
+    }
 
     @Override public boolean equals(Object o) {
       return o instanceof RealMultibinder
-          && ((RealMultibinder) o).setKey.equals(setKey);
+          && ((RealMultibinder<?>) o).setKey.equals(setKey);
     }
 
     @Override public int hashCode() {
@@ -262,4 +262,4 @@
       return elementType;
     }
   }
-}
\ No newline at end of file
+}
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/RealElement.java b/extensions/multibindings/src/com/google/inject/multibindings/RealElement.java
index 49cd350..d297f90 100644
--- a/extensions/multibindings/src/com/google/inject/multibindings/RealElement.java
+++ b/extensions/multibindings/src/com/google/inject/multibindings/RealElement.java
@@ -17,29 +17,26 @@
 package com.google.inject.multibindings;
 
 import java.lang.annotation.Annotation;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * @author jessewilson@google.com (Jesse Wilson)
  */
 class RealElement implements Element {
-  private final String setName;
-  private final String role;
-  private final int uniqueId;
+  private static final AtomicInteger nextUniqueId = new AtomicInteger(1);
 
-  RealElement(String setName, String role, int uniqueId) {
+  private final int uniqueId;
+  private final String setName;
+
+  RealElement(String setName) {
+    uniqueId = nextUniqueId.getAndIncrement();
     this.setName = setName;
-    this.role = role;
-    this.uniqueId = uniqueId;
   }
 
   public String setName() {
     return setName;
   }
 
-  public String role() {
-    return role;
-  }
-
   public int uniqueId() {
     return uniqueId;
   }
@@ -50,19 +47,17 @@
 
   @Override public String toString() {
     return "@" + Element.class.getName() + "(setName=" + setName
-        + ",role=" + role + ",uniqueId=" + uniqueId + ")";
+        + ",uniqueId=" + uniqueId + ")";
   }
 
   @Override public boolean equals(Object o) {
     return o instanceof Element
         && ((Element) o).setName().equals(setName())
-        && ((Element) o).role().equals(role())
         && ((Element) o).uniqueId() == uniqueId();
   }
 
   @Override public int hashCode() {
     return 127 * ("setName".hashCode() ^ setName.hashCode())
-        + 127 * ("role".hashCode() ^ role.hashCode())
         + 127 * ("uniqueId".hashCode() ^ uniqueId);
   }
 }