Author: netdpb
Date: Tue Jul  7 13:13:32 2009
New Revision: 1041

Modified:
     
trunk/extensions/multibindings/src/com/google/inject/multibindings/MapBinder.java
     
trunk/extensions/multibindings/src/com/google/inject/multibindings/Multibinder.java
     
trunk/extensions/multibindings/test/com/google/inject/multibindings/MapBinderTest.java

Log:
Added mutlimap support to MapBinder.

Modified:  
trunk/extensions/multibindings/src/com/google/inject/multibindings/MapBinder.java
==============================================================================
---  
trunk/extensions/multibindings/src/com/google/inject/multibindings/MapBinder.java
        
(original)
+++  
trunk/extensions/multibindings/src/com/google/inject/multibindings/MapBinder.java
        
Tue Jul  7 13:13:32 2009
@@ -22,12 +22,14 @@
  import com.google.inject.Key;
  import com.google.inject.Module;
  import com.google.inject.Provider;
+import com.google.inject.ProvisionException;
  import com.google.inject.TypeLiteral;
  import com.google.inject.binder.LinkedBindingBuilder;
  import com.google.inject.internal.ImmutableSet;
  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.Dependency;
  import com.google.inject.spi.ProviderWithDependencies;
  import com.google.inject.util.Types;
@@ -36,6 +38,7 @@
  import java.lang.annotation.Annotation;
  import java.util.Collections;
  import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
  import java.util.Map;
  import java.util.Map.Entry;
  import java.util.Set;
@@ -54,14 +57,14 @@
   *   }
   * }</code></pre>
   *
- * <p>With this binding, a {...@link ma...@code <String, Snack>} can now be
+ * <p>With this binding, a {...@link ma...@code <String, Snack>} can now be
   * injected:
   * <pre><code>
   * class SnackMachine {
   *   {...@literal @}Inject
   *   public SnackMachine(Map&lt;String, Snack&gt; snacks) { ... }
   * }</code></pre>
- *
+ *
   * <p>In addition to binding {...@code Map<K, V>}, a mapbinder will also bind
   * {...@code Map<K, Provider<V>>} for lazy value provision:
   * <pre><code>
@@ -72,8 +75,8 @@
   *
   * <p>Creating mapbindings from different modules is supported. For  
example, it
   * is okay to have both {...@code CandyModule} and {...@code ChipsModule} both
- * create their own {...@code MapBinder<String, Snack>}, and to each  
contribute
- * bindings to the snacks map. When that map is injected, it will contain
+ * create their own {...@code MapBinder<String, Snack>}, and to each  
contribute
+ * bindings to the snacks map. When that map is injected, it will contain
   * entries from both modules.
   *
   * <p>Values are resolved at map injection time. If a value is bound to a
@@ -84,9 +87,11 @@
   * type. Each distinct annotation gets its own independent map.
   *
   * <p><strong>Keys must be distinct.</strong> If the same key is bound  
more than
- * once, map injection will fail.
+ * once, map injection will fail. However, use {...@link #permitDuplicates()}  
in
+ * order to allow duplicate keys; extra bindings to {...@code Map<K, Set<V>>}  
and
+ * {...@code Map<K, Set<Provider<V>>} will be added.
   *
- * <p><strong>Keys must be non-null.</strong> {...@code addBinding(null)} will
+ * <p><strong>Keys must be non-null.</strong> {...@code addBinding(null)} will
   * throw an unchecked exception.
   *
   * <p><strong>Values must be non-null to use map injection.</strong> If any
@@ -102,12 +107,14 @@
     * Returns a new mapbinder that collects entries of {...@code  
keyType}/{...@code valueType} in a
     * {...@link Map} that is itself bound with no binding annotation.
     */
-  public static <K, V> MapBinder<K, V> newMapBinder(Binder binder,
+  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,
          Key.get(mapOf(keyType, valueType)),
          Key.get(mapOfProviderOf(keyType, valueType)),
+        Key.get(mapOf(keyType, setOf(valueType))),
+        Key.get(mapOfSetOfProviderOf(keyType, valueType)),
          Multibinder.newSetBinder(binder, entryOfProviderOf(keyType,  
valueType)));
    }

@@ -124,12 +131,14 @@
     * Returns a new mapbinder that collects entries of {...@code  
keyType}/{...@code valueType} in a
     * {...@link Map} that is itself bound with {...@code annotation}.
     */
-  public static <K, V> MapBinder<K, V> newMapBinder(Binder binder,
+  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,
          Key.get(mapOf(keyType, valueType), annotation),
          Key.get(mapOfProviderOf(keyType, valueType), annotation),
+        Key.get(mapOf(keyType, setOf(valueType)), annotation),
+        Key.get(mapOfSetOfProviderOf(keyType, valueType), annotation),
          Multibinder.newSetBinder(binder, entryOfProviderOf(keyType,  
valueType), annotation));
    }

@@ -152,6 +161,8 @@
      return newMapBinder(binder, valueType,
          Key.get(mapOf(keyType, valueType), annotationType),
          Key.get(mapOfProviderOf(keyType, valueType), annotationType),
+        Key.get(mapOf(keyType, setOf(valueType)), annotationType),
+        Key.get(mapOfSetOfProviderOf(keyType, valueType), annotationType),
          Multibinder.newSetBinder(binder, entryOfProviderOf(keyType,  
valueType), annotationType));
    }

@@ -178,7 +189,15 @@
      return (TypeLiteral<Map<K, Provider<V>>>) TypeLiteral.get(
          Types.mapOf(keyType.getType(),  
newParameterizedType(Provider.class, 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(
+      TypeLiteral<K> keyType, TypeLiteral<V> valueType) {
+    return (TypeLiteral<Map<K, Set<Provider<V>>>>) TypeLiteral.get(
+        Types.mapOf(keyType.getType(),
+            Types.setOf(newParameterizedType(Provider.class,  
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(
        TypeLiteral<K> keyType, TypeLiteral<V> valueType) {
@@ -188,17 +207,28 @@

    private static <K, V> MapBinder<K, V> newMapBinder(Binder binder,  
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, entrySetBinder);
+        binder, valueType, mapKey, providerMapKey, multimapKey,  
providerMultimapKey,
+        entrySetBinder);
      binder.install(mapBinder);
      return mapBinder;
    }

    /**
-   * Configures the bound map to silently discard duplicate entries. When  
multiple equal keys are
-   * bound, the value that gets included is arbitrary. When multible  
modules contribute elements to
-   * the map, this configuration option impacts all of them.
+   * Configures the {...@code MapBinder} to handle duplicate entries.
+   * <ul>
+   * <li>When multiple equal keys are bound, the value that gets included  
in the map is
+   * arbitrary.</li>
+   * <li>In addition to the {...@code Map<K, V>} and {...@code Map<K,  
Provider<V>>}
+   * maps that are normally bound, a {...@code Map<K, Set<V>>} and
+   * {...@code Map<K, Set<Provider<V>>>} are <em>also</em> bound, which  
contain
+   * all values bound to each key.</li>
+   * </ul>
+   * <p>
+   * When multiple modules contribute elements to the map, this  
configuration
+   * option impacts all of them.
     *
     * @return this map binder
     */
@@ -225,11 +255,11 @@
     * entries (keys to value providers).
     *
     * <p>As a Module, it installs the binding to the map itself, as well as  
to
-   * a corresponding map whose values are providers. It uses the entry set
+   * a corresponding map whose values are providers. It uses the entry set
     * multibinder to construct the map and the provider map.
-   *
-   * <p>As a module, this implements equals() and hashcode() in order to  
trick
-   * Guice into executing its configure() method only once. That makes it  
so
+   *
+   * <p>As a module, this implements equals() and hashcode() in order to  
trick
+   * Guice into executing its configure() method only once. That makes it  
so
     * that multiple mapbinders can be created for the same target map, but
     * only one is bound. Since the list of bindings is retrieved from the
     * injector itself (and not the mapbinder), each mapbinder has access to
@@ -245,6 +275,8 @@
      private final TypeLiteral<V> valueType;
      private final Key<Map<K, V>> mapKey;
      private final Key<Map<K, Provider<V>>> providerMapKey;
+    private final Key<Map<K, Set<V>>> multimapKey;
+    private final Key<Map<K, Set<Provider<V>>>> providerMultimapKey;
      private final RealMultibinder<Map.Entry<K, Provider<V>>>  
entrySetBinder;

      /* the target injector's binder. non-null until initialization, null  
afterwards */
@@ -252,14 +284,18 @@

      private RealMapBinder(Binder binder, 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.valueType = valueType;
        this.mapKey = mapKey;
        this.providerMapKey = providerMapKey;
+      this.multimapKey = multimapKey;
+      this.providerMultimapKey = providerMultimapKey;
        this.entrySetBinder = (RealMultibinder<Entry<K, Provider<V>>>)  
entrySetBinder;
        this.binder = binder;
      }

+    @Override
      public MapBinder<K, V> permitDuplicates() {
        entrySetBinder.permitDuplicates();
        return this;
@@ -285,7 +321,7 @@
        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 Map<Entry<K,  
Provider<V>>.
        final Provider<Set<Entry<K, Provider<V>>>> entrySetProvider = binder
            .getProvider(entrySetBinder.getSetKey());
        binder.bind(providerMapKey).toProvider(new  
ProviderWithDependencies<Map<K, Provider<V>>>() {
@@ -333,6 +369,67 @@
            return dependencies;
          }
        });
+
+      // Binds a Map<K, Set<Provider<V>>> from a collection of  
Map<Entry<K, Provider<V>> if
+      // permitDuplicates was called.
+      binder.bind(providerMultimapKey).toProvider(
+          new ProviderWithDependencies<Map<K, Set<Provider<V>>>>() {
+            private Map<K, Set<Provider<V>>> providerMultimap;
+
+            @SuppressWarnings("unused")
+            @Inject void initialize(Injector injector) {
+              if (entrySetBinder.permitsDuplicates(injector)) {
+                Map<K, Set<Provider<V>>> providerMultimapMutable = new  
LinkedHashMap<K, Set<Provider<V>>>();
+                for (Entry<K, Provider<V>> entry : entrySetProvider.get())  
{
+                  if  
(!providerMultimapMutable.containsKey(entry.getKey())) {
+                    providerMultimapMutable.put(entry.getKey(), new  
LinkedHashSet<Provider<V>>());
+                  }
+                   
providerMultimapMutable.get(entry.getKey()).add(entry.getValue());
+                }
+
+                for (Entry<K, Set<Provider<V>>> entry :  
providerMultimapMutable.entrySet()) {
+                   
entry.setValue(Collections.unmodifiableSet(entry.getValue()));
+                }
+                providerMultimap =  
Collections.unmodifiableMap(providerMultimapMutable);
+              }
+            }
+
+            public Map<K, Set<Provider<V>>> get() {
+              if (providerMultimap == null) {
+                throw new ProvisionException("no binding for " +  
providerMultimapKey
+                    + " because permitDuplicates was never called");
+              }
+              return providerMultimap;
+            }
+
+            public Set<Dependency<?>> getDependencies() {
+              return dependencies;
+            }
+          });
+
+      final Provider<Map<K, Set<Provider<V>>>> multimapProvider =
+          binder.getProvider(providerMultimapKey);
+      binder.bind(multimapKey).toProvider(new  
ProviderWithDependencies<Map<K, Set<V>>>() {
+        public Map<K, Set<V>> get() {
+          Map<K,Set<V>> multimap = new LinkedHashMap<K, Set<V>>();
+          for (Entry<K, Set<Provider<V>>> entry :  
multimapProvider.get().entrySet()) {
+            K key = entry.getKey();
+            Set<V> values = new LinkedHashSet<V>();
+            for (Provider<V> valueProvider : entry.getValue()) {
+              V value = valueProvider.get();
+              checkConfiguration(value != null,
+                  "Multimap injection failed due to null value for key  
\"%s\"", key);
+              values.add(value);
+            }
+            multimap.put(key, Collections.unmodifiableSet(values));
+          }
+          return Collections.unmodifiableMap(multimap);
+        }
+
+        public Set<Dependency<?>> getDependencies() {
+          return dependencies;
+        }
+      });
      }

      private boolean isInitialized() {
@@ -370,8 +467,8 @@
        }

        @Override public boolean equals(Object obj) {
-        return obj instanceof Map.Entry
-            && key.equals(((Map.Entry<?, ?>) obj).getKey())
+        return obj instanceof Map.Entry
+            && key.equals(((Map.Entry<?, ?>) obj).getKey())
              && value.equals(((Map.Entry<?, ?>) obj).getValue());
        }


Modified:  
trunk/extensions/multibindings/src/com/google/inject/multibindings/Multibinder.java
==============================================================================
---  
trunk/extensions/multibindings/src/com/google/inject/multibindings/Multibinder.java
      
(original)
+++  
trunk/extensions/multibindings/src/com/google/inject/multibindings/Multibinder.java
      
Tue Jul  7 13:13:32 2009
@@ -155,14 +155,14 @@
    }

    @SuppressWarnings("unchecked") // wrapping a T in a Set safely returns a  
Set<T>
-  private static <T> TypeLiteral<Set<T>> setOf(TypeLiteral<T> elementType)  
{
+  static <T> TypeLiteral<Set<T>> setOf(TypeLiteral<T> elementType) {
      Type type = Types.setOf(elementType.getType());
      return (TypeLiteral<Set<T>>) TypeLiteral.get(type);
    }

    /**
     * Configures the bound set to silently discard duplicate elements. When  
multiple equal values are
-   * bound, the one that gets included is arbitrary. When multible modules  
contribute elements to
+   * bound, the one that gets included is arbitrary. When multiple modules  
contribute elements to
     * the set, this configuration option impacts all of them.
     *
     * @return this multibinder
@@ -236,6 +236,7 @@
        binder.bind(setKey).toProvider(this);
      }

+    @Override
      public Multibinder<T> permitDuplicates() {
        binder.install(new PermitDuplicatesModule(permitDuplicatesKey));
        return this;
@@ -296,11 +297,11 @@
        }
        return Collections.unmodifiableSet(result);
      }
-
+
      String getSetName() {
        return setName;
      }
-
+
      Key<Set<T>> getSetKey() {
        return setKey;
      }
@@ -341,6 +342,7 @@
        this.key = key;
      }

+    @Override
      protected void configure() {
        bind(key).toInstance(true);
      }

Modified:  
trunk/extensions/multibindings/test/com/google/inject/multibindings/MapBinderTest.java
==============================================================================
---  
trunk/extensions/multibindings/test/com/google/inject/multibindings/MapBinderTest.java
   
(original)
+++  
trunk/extensions/multibindings/test/com/google/inject/multibindings/MapBinderTest.java
   
Tue Jul  7 13:13:32 2009
@@ -20,6 +20,7 @@
  import static com.google.inject.Asserts.assertContains;
  import com.google.inject.Binding;
  import com.google.inject.BindingAnnotation;
+import com.google.inject.ConfigurationException;
  import com.google.inject.CreationException;
  import com.google.inject.Guice;
  import com.google.inject.Injector;
@@ -37,8 +38,10 @@
  import com.google.inject.util.Providers;
  import java.lang.annotation.Retention;
  import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import java.util.Arrays;
  import java.util.Collections;
  import java.util.HashMap;
+import java.util.HashSet;
  import java.util.Iterator;
  import java.util.Map;
  import java.util.Set;
@@ -51,6 +54,8 @@

    final TypeLiteral<Map<String, String>> mapOfString = new  
TypeLiteral<Map<String, String>>() {};
    final TypeLiteral<Map<String, Integer>> mapOfInteger = new  
TypeLiteral<Map<String, Integer>>() {};
+  final TypeLiteral<Map<String, Set<String>>> mapOfSetOfString =
+      new TypeLiteral<Map<String, Set<String>>>() {};

    public void testMapBinderAggregatesMultipleModules() {
      Module abc = new AbstractModule() {
@@ -256,6 +261,82 @@
      assertEquals(mapOf("a", "A", "b", "B", "c", "C"),  
injector.getInstance(Key.get(mapOfString)));
    }

+  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();
+          }
+        });
+
+    assertEquals(mapOf("a", setOf("A"), "b", setOf("B1", "B2"), "c",  
setOf("C")),
+        injector.getInstance(Key.get(mapOfSetOfString)));
+  }
+
+  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();
+          }
+        });
+
+    assertEquals(mapOf("a", setOf("A"), "b", setOf("B1", "B2"), "c",  
setOf("C")),
+        injector.getInstance(Key.get(mapOfSetOfString, Abc.class)));
+    try {
+      injector.getInstance(Key.get(mapOfSetOfString));
+      fail();
+    } catch (ConfigurationException expected) {}
+  }
+
+  public void testMapBinderMultimapIsUnmodifiable() {
+    Injector injector = Guice.createInjector(new AbstractModule() {
+      @Override protected void configure() {
+        MapBinder<String, String> mapBinder = MapBinder.newMapBinder(
+            binder(), String.class, String.class);
+        mapBinder.addBinding("a").toInstance("A");
+        mapBinder.permitDuplicates();
+      }
+    });
+
+    Map<String, Set<String>> map =  
injector.getInstance(Key.get(mapOfSetOfString));
+    try {
+      map.clear();
+      fail();
+    } catch(UnsupportedOperationException expected) {
+    }
+    try {
+      map.get("a").clear();
+      fail();
+    } catch(UnsupportedOperationException expected) {
+    }
+  }
+
    public void testMapBinderMapForbidsNullKeys() {
      try {
        Guice.createInjector(new AbstractModule() {
@@ -323,7 +404,7 @@
    public void testMultibinderDependencies() {
      Injector injector = Guice.createInjector(new AbstractModule() {
        protected void configure() {
-        MapBinder<Integer, String> mapBinder
+        MapBinder<Integer, String> mapBinder
              = MapBinder.newMapBinder(binder(), Integer.class,  
String.class);
          mapBinder.addBinding(1).toInstance("A");
          mapBinder.addBinding(2).to(Key.get(String.class,  
Names.named("b")));
@@ -388,5 +469,10 @@
        result.put((K)elements[i], (V)elements[i+1]);
      }
      return result;
+  }
+
+  @SuppressWarnings("unchecked")
+  private <V> Set<V> setOf(Object... elements) {
+    return new HashSet(Arrays.asList(elements));
    }
  }

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"google-guice-dev" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to 
[email protected]
For more options, visit this group at 
http://groups.google.com/group/google-guice-dev?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to