Update MapBinder so it fails on duplicate bindings with a more descriptive error message, including all the keys that had duplicates and where the duplicate bindings were bound. Example error message that has 'a' bound twice and 'b' bound twice:
1) Map injection failed due to duplicated key "a", from bindings at:
com.google.inject.multibindings.MapBinderTest.configure(MapBinderTest.java:337) (via modules: com.google.inject.multibindings.MapBinderTest$1Main -> com.google.inject.multibindings.MapBinderTest$1Module1)
com.google.inject.multibindings.MapBinderTest.configure(MapBinderTest.java:344) (via modules: com.google.inject.multibindings.MapBinderTest$1Main -> com.google.inject.multibindings.MapBinderTest$1Module2)
and key: "b", from bindings at:
com.google.inject.multibindings.MapBinderTest.configure(MapBinderTest.java:345) (via modules: com.google.inject.multibindings.MapBinderTest$1Main -> com.google.inject.multibindings.MapBinderTest$1Module2)
com.google.inject.multibindings.MapBinderTest.configure(MapBinderTest.java:352) (via modules: com.google.inject.multibindings.MapBinderTest$1Main -> com.google.inject.multibindings.MapBinderTest$1Module3)
at com.google.inject.multibindings.MapBinder.initialize(MapBinder.java:380)
at com.google.inject.multibindings.MapBinderTest.configure(MapBinderTest.java:357) (via modules: com.google.inject.multibindings.MapBinderTest$1Main -> com.google.inject.multibindings.MapBinder$RealMapBinder)
-------------
Created by MOE: http://code.google.com/p/moe-java
MOE_MIGRATED_REVID=62865462
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/MapBinder.java b/extensions/multibindings/src/com/google/inject/multibindings/MapBinder.java
index d423597..f5f1a24 100644
--- a/extensions/multibindings/src/com/google/inject/multibindings/MapBinder.java
+++ b/extensions/multibindings/src/com/google/inject/multibindings/MapBinder.java
@@ -25,11 +25,15 @@
import static com.google.inject.util.Types.newParameterizedType;
import static com.google.inject.util.Types.newParameterizedTypeWithOwner;
+import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.LinkedHashMultimap;
import com.google.inject.Binder;
import com.google.inject.Binding;
import com.google.inject.Inject;
@@ -38,6 +42,7 @@
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
+import com.google.inject.internal.Errors;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.multibindings.Multibinder.RealMultibinder;
import com.google.inject.spi.BindingTargetVisitor;
@@ -302,7 +307,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 {
+ 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;
@@ -377,14 +382,39 @@
Map<K, Provider<V>> providerMapMutable = new LinkedHashMap<K, Provider<V>>();
List<Map.Entry<K, Binding<V>>> bindingsMutable = Lists.newArrayList();
+ Set<K> duplicateKeys = null;
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());
+ if (previous != null && !permitDuplicates) {
+ if (duplicateKeys == null) {
+ duplicateKeys = Sets.newHashSet();
+ }
+ duplicateKeys.add(entry.getKey());
+ }
ProviderMapEntry<K, V> providerEntry = (ProviderMapEntry<K, V>) entry;
Key<V> valueKey = providerEntry.getValueKey();
bindingsMutable.add(Maps.immutableEntry(entry.getKey(), injector.getBinding(valueKey)));
}
+ if (duplicateKeys != null) {
+ Multimap<K, String> dups = LinkedHashMultimap.create();
+ for (Map.Entry<K, Binding<V>> entry : bindingsMutable) {
+ if (duplicateKeys.contains(entry.getKey())) {
+ dups.put(entry.getKey(), "\t" + Errors.convert(entry.getValue().getSource()));
+ }
+ }
+ StringBuilder sb = new StringBuilder("Map injection failed due to duplicated key ");
+ boolean first = true;
+ for (K key : dups.keySet()) {
+ if (first) {
+ first = false;
+ sb.append("\"" + key + "\", from bindings at:\n");
+ } else {
+ sb.append("\n and key: \"" + key + "\", from bindings at:\n");
+ }
+ Joiner.on('\n').appendTo(sb, dups.get(key)).append("\n");
+ }
+ checkConfiguration(false, sb.toString());
+ }
providerMap = ImmutableMap.copyOf(providerMapMutable);
mapBindings = ImmutableList.copyOf(bindingsMutable);
diff --git a/extensions/multibindings/test/com/google/inject/multibindings/MapBinderTest.java b/extensions/multibindings/test/com/google/inject/multibindings/MapBinderTest.java
index 31aeac0..caa2e74 100644
--- a/extensions/multibindings/test/com/google/inject/multibindings/MapBinderTest.java
+++ b/extensions/multibindings/test/com/google/inject/multibindings/MapBinderTest.java
@@ -16,6 +16,7 @@
package com.google.inject.multibindings;
+import static com.google.inject.Asserts.asModuleChain;
import static com.google.inject.Asserts.assertContains;
import static com.google.inject.multibindings.SpiUtils.VisitType.BOTH;
import static com.google.inject.multibindings.SpiUtils.VisitType.MODULE;
@@ -40,6 +41,7 @@
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Provider;
+import com.google.inject.Provides;
import com.google.inject.ProvisionException;
import com.google.inject.Stage;
import com.google.inject.TypeLiteral;
@@ -82,7 +84,6 @@
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() {
@@ -327,6 +328,56 @@
instance("a", "A"), instance("a", "B"));
}
+ public void testExhaustiveDuplicateErrorMessage() throws Exception {
+ class Module1 extends AbstractModule {
+ @Override protected void configure() {
+ MapBinder<String, Object> mapbinder =
+ MapBinder.newMapBinder(binder(), String.class, Object.class);
+ mapbinder.addBinding("a").to(String.class);
+ }
+ }
+ class Module2 extends AbstractModule {
+ @Override protected void configure() {
+ MapBinder<String, Object> mapbinder =
+ MapBinder.newMapBinder(binder(), String.class, Object.class);
+ mapbinder.addBinding("a").to(Integer.class);
+ mapbinder.addBinding("b").to(String.class);
+ }
+ }
+ class Module3 extends AbstractModule {
+ @Override protected void configure() {
+ MapBinder<String, Object> mapbinder =
+ MapBinder.newMapBinder(binder(), String.class, Object.class);
+ mapbinder.addBinding("b").to(Integer.class);
+ }
+ }
+ class Main extends AbstractModule {
+ @Override protected void configure() {
+ MapBinder.newMapBinder(binder(), String.class, Object.class);
+ install(new Module1());
+ install(new Module2());
+ install(new Module3());
+ }
+ @Provides String provideString() { return "foo"; }
+ @Provides Integer provideInt() { return 42; }
+ }
+ try {
+ Guice.createInjector(new Main());
+ fail();
+ } catch(CreationException ce) {
+ assertContains(ce.getMessage(),
+ "Map injection failed due to duplicated key \"a\", from bindings at:",
+ asModuleChain(Main.class, Module1.class),
+ asModuleChain(Main.class, Module2.class),
+ "and key: \"b\", from bindings at:",
+ asModuleChain(Main.class, Module2.class),
+ asModuleChain(Main.class, Module3.class),
+ "at " + Main.class.getName() + ".configure(",
+ asModuleChain(Main.class, MapBinder.RealMapBinder.class));
+ assertEquals(1, ce.getErrorMessages().size());
+ }
+ }
+
public void testMapBinderMapPermitDuplicateElements() {
Module ab = new AbstractModule() {
@Override protected void configure() {