Revision: 1508
Author: sberlin
Date: Sat Feb 26 16:02:03 2011
Log: significantly improve error reporting for binding a key already bound in a child injector or private module. include all sources in the error msg (since it can be in many sibling private modules or child injectors), including whether or not it as a JIT binding.
http://code.google.com/p/google-guice/source/detail?r=1508

Modified:
 /trunk/core/src/com/google/inject/internal/Errors.java
 /trunk/core/src/com/google/inject/internal/InheritingState.java
 /trunk/core/src/com/google/inject/internal/InjectorImpl.java
 /trunk/core/src/com/google/inject/internal/State.java
 /trunk/core/src/com/google/inject/internal/WeakKeySet.java
 /trunk/core/test/com/google/inject/ParentInjectorTest.java
 /trunk/core/test/com/google/inject/PrivateModuleTest.java

=======================================
--- /trunk/core/src/com/google/inject/internal/Errors.java Mon Feb 21 13:06:40 2011 +++ /trunk/core/src/com/google/inject/internal/Errors.java Sat Feb 26 16:02:03 2011
@@ -50,6 +50,7 @@
 import java.util.Comparator;
 import java.util.Formatter;
 import java.util.List;
+import java.util.Set;

 /**
* A collection of error messages. If this type is passed as a method parameter, the method is
@@ -298,11 +299,22 @@
return addMessage("A just-in-time binding to %s was already configured on a parent injector.", key);
   }

-  public Errors childBindingAlreadySet(Key<?> key, Object source) {
-    return addMessage(
-        "A binding to %s was already configured at %s.%n"
- + " (If it was in a PrivateModule, did you forget to expose the binding?)",
-        key, convert(source));
+  public Errors childBindingAlreadySet(Key<?> key, Set<Object> sources) {
+    Formatter allSources = new Formatter();
+    for (Object source : sources) {
+      if (source == null) {
+        allSources.format("%n    (bound by a just-in-time binding)");
+      } else {
+        allSources.format("%n    bound at %s", source);
+      }
+    }
+    Errors errors = addMessage(
+        "Unable to create binding for %s."
+ + " It was already configured on one or more child injectors or private modules"
+      + "%s%n"
+ + " If it was in a PrivateModule, did you forget to expose the binding?",
+        key, allSources.out());
+    return errors;
   }

public Errors errorCheckingDuplicateBinding(Key<?> key, Object source, Throwable t) {
=======================================
--- /trunk/core/src/com/google/inject/internal/InheritingState.java Sat Feb 19 16:05:28 2011 +++ /trunk/core/src/com/google/inject/internal/InheritingState.java Sat Feb 26 16:02:03 2011
@@ -31,6 +31,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;

 /**
  * @author [email protected] (Jesse Wilson)
@@ -143,8 +144,8 @@
     return blacklistedKeys.contains(key);
   }

-  public Object getSourceForBlacklistedKey(Key<?> key) {
-    return blacklistedKeys.getSource(key);
+  public Set<Object> getSourcesForBlacklistedKey(Key<?> key) {
+    return blacklistedKeys.getSources(key);
   }

   public Object lock() {
=======================================
--- /trunk/core/src/com/google/inject/internal/InjectorImpl.java Sat Feb 19 16:05:28 2011 +++ /trunk/core/src/com/google/inject/internal/InjectorImpl.java Sat Feb 26 16:02:03 2011
@@ -763,8 +763,8 @@
     }

     if (state.isBlacklisted(key)) {
-      Object source = state.getSourceForBlacklistedKey(key);
-      throw errors.childBindingAlreadySet(key, source).toException();
+      Set<Object> sources = state.getSourcesForBlacklistedKey(key);
+      throw errors.childBindingAlreadySet(key, sources).toException();
     }

     BindingImpl<T> binding = createJustInTimeBinding(key, errors);
@@ -791,8 +791,8 @@
     int numErrorsBefore = errors.size();

     if (state.isBlacklisted(key)) {
-      Object source = state.getSourceForBlacklistedKey(key);
-      throw errors.childBindingAlreadySet(key, source).toException();
+      Set<Object> sources = state.getSourcesForBlacklistedKey(key);
+      throw errors.childBindingAlreadySet(key, sources).toException();
     }

     // Handle cases where T is a Provider<?>.
=======================================
--- /trunk/core/src/com/google/inject/internal/State.java Sat Feb 19 16:05:28 2011 +++ /trunk/core/src/com/google/inject/internal/State.java Sat Feb 26 16:02:03 2011
@@ -28,6 +28,7 @@
 import java.lang.annotation.Annotation;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;

 /**
* The inheritable data within an injector. This class is intended to allow parent and local
@@ -100,7 +101,7 @@
       return true;
     }

-    public Object getSourceForBlacklistedKey(Key<?> key) {
+    public Set<Object> getSourcesForBlacklistedKey(Key<?> key) {
       throw new UnsupportedOperationException();
     }

@@ -161,7 +162,7 @@
   boolean isBlacklisted(Key<?> key);

   /** Returns the source of a blacklisted key. */
-  Object getSourceForBlacklistedKey(Key<?> key);
+  Set<Object> getSourcesForBlacklistedKey(Key<?> key);

   /**
* Returns the shared lock for all injector data. This is a low-granularity, high-contention lock
=======================================
--- /trunk/core/src/com/google/inject/internal/WeakKeySet.java Sat Feb 19 16:05:28 2011 +++ /trunk/core/src/com/google/inject/internal/WeakKeySet.java Sat Feb 26 16:02:03 2011
@@ -18,8 +18,11 @@

 import com.google.inject.Key;
 import com.google.inject.internal.util.Maps;
+import com.google.inject.internal.util.Sets;
+import com.google.inject.internal.util.SourceProvider;

 import java.util.Map;
+import java.util.Set;

 /**
  * Minimal set that doesn't hold strong references to the contained keys.
@@ -35,13 +38,24 @@
* keys whose class names are equal but class loaders are different. This shouldn't be an issue
    * in practice.
    */
-  private Map<String, Object> backingSet;
+  private Map<String, Set<Object>> backingSet;

   public void add(Key<?> key, Object source) {
     if (backingSet == null) {
       backingSet = Maps.newHashMap();
     }
-    backingSet.put(key.toString(), source);
+    // if it's an instanceof Class, it was a JIT binding, which we don't
+    // want to retain.
+ if (source instanceof Class || source == SourceProvider.UNKNOWN_SOURCE) {
+      source = null;
+    }
+    String k = key.toString();
+    Set<Object> sources = backingSet.get(k);
+    if (sources == null) {
+      sources = Sets.newLinkedHashSet();
+      backingSet.put(k, sources);
+    }
+    sources.add(Errors.convert(source));
   }

   public boolean contains(Key<?> key) {
@@ -50,7 +64,7 @@
     return backingSet != null && backingSet.containsKey(key.toString());
   }

-  public Object getSource(Key<?> key) {
+  public Set<Object> getSources(Key<?> key) {
     return backingSet.get(key.toString());
   }
 }
=======================================
--- /trunk/core/test/com/google/inject/ParentInjectorTest.java Mon Feb 21 13:09:26 2011 +++ /trunk/core/test/com/google/inject/ParentInjectorTest.java Sat Feb 26 16:02:03 2011
@@ -17,6 +17,7 @@
 package com.google.inject;

 import static com.google.inject.Asserts.assertContains;
+
 import com.google.inject.internal.util.ImmutableList;
 import com.google.inject.internal.util.Iterables;
 import com.google.inject.matcher.Matchers;
@@ -54,7 +55,11 @@
fail("Created a just-in-time binding on the parent that's the same as a child's binding");
     } catch (ConfigurationException e) {
       assertContains(e.getMessage(),
- "A binding to " + A.class.getName() + " was already configured at " + bindsA.getClass().getName());
+          "Unable to create binding for " + A.class.getName(),
+ "It was already configured on one or more child injectors or private modules",
+          "bound at " + bindsA.getClass().getName() + ".configure(",
+ "If it was in a PrivateModule, did you forget to expose the binding?",
+          "while locating " + A.class.getName());
     }
   }

=======================================
--- /trunk/core/test/com/google/inject/PrivateModuleTest.java Sat Feb 19 16:05:28 2011 +++ /trunk/core/test/com/google/inject/PrivateModuleTest.java Sat Feb 26 16:02:03 2011
@@ -29,6 +29,8 @@
 import com.google.inject.spi.Dependency;
 import com.google.inject.spi.ExposedBinding;
 import com.google.inject.spi.PrivateElements;
+import com.google.inject.util.Types;
+
 import junit.framework.TestCase;

 /**
@@ -459,25 +461,101 @@
     } catch(CreationException expected) {
       assertEquals(1, expected.getErrorMessages().size());
       assertContains(expected.toString(),
-          "A binding to java.util.List was already configured at",
-          FailingPrivateModule.class.getName() + ".configure(",
- "(If it was in a PrivateModule, did you forget to expose the binding?)",
+          "Unable to create binding for java.util.List.",
+ "It was already configured on one or more child injectors or private modules", + "bound at " + FailingPrivateModule.class.getName() + ".configure(", + "bound at " + SecondFailingPrivateModule.class.getName() + ".configure(", + "If it was in a PrivateModule, did you forget to expose the binding?",
           "at " + FailingModule.class.getName() + ".configure(");
     }
   }
+
+  public void testParentBindingToPrivateLinkedJitBinding() {
+    Injector injector = Guice.createInjector(new ManyPrivateModules());
+    try {
+      injector.getBinding(Key.get(Types.providerOf(List.class)));
+      fail();
+    } catch(ConfigurationException expected) {
+      assertEquals(1, expected.getErrorMessages().size());
+      assertContains(expected.toString(),
+ "Unable to create binding for com.google.inject.Provider<java.util.List>.", + "It was already configured on one or more child injectors or private modules", + "bound at " + FailingPrivateModule.class.getName() + ".configure(", + "bound at " + SecondFailingPrivateModule.class.getName() + ".configure(", + "If it was in a PrivateModule, did you forget to expose the binding?",
+          "while locating com.google.inject.Provider<java.util.List>");
+    }
+  }
+
+  public void testParentBindingToPrivateJitBinding() {
+    Injector injector = Guice.createInjector(new ManyPrivateModules());
+    try {
+      injector.getBinding(PrivateFoo.class);
+      fail();
+    } catch(ConfigurationException expected) {
+      assertEquals(1, expected.getErrorMessages().size());
+      assertContains(expected.toString(),
+          "Unable to create binding for " + PrivateFoo.class.getName(),
+ "It was already configured on one or more child injectors or private modules",
+          "(bound by a just-in-time binding)",
+ "If it was in a PrivateModule, did you forget to expose the binding?",
+          "while locating " + PrivateFoo.class.getName());
+    }
+  }

   private static class FailingModule extends AbstractModule {
     @Override
     protected void configure() {
       bind(Collection.class).to(List.class);
+      install(new ManyPrivateModules());
+    }
+  }
+
+  private static class ManyPrivateModules extends AbstractModule {
+    @Override
+    protected void configure() {
+      // make sure duplicate sources are collapsed
       install(new FailingPrivateModule());
+      install(new FailingPrivateModule());
+      // but additional sources are listed
+      install(new SecondFailingPrivateModule());
     }
   }
-
+
   private static class FailingPrivateModule extends PrivateModule {
     @Override
     protected void configure() {
       bind(List.class).toInstance(new ArrayList());
+
+      // Add the Provider<List> binding, created just-in-time,
+      // to make sure our linked JIT bindings have the correct source.
+      getProvider(Key.get(Types.providerOf(List.class)));
+
+      // Request a JIT binding for PrivateFoo, which can only
+      // be created in the private module because it depends
+      // on List.
+      getProvider(PrivateFoo.class);
     }
   }
-}
+
+  /** A second class, so we can see another name in the source list. */
+  private static class SecondFailingPrivateModule extends PrivateModule {
+    @Override
+    protected void configure() {
+      bind(List.class).toInstance(new ArrayList());
+
+      // Add the Provider<List> binding, created just-in-time,
+      // to make sure our linked JIT bindings have the correct source.
+      getProvider(Key.get(Types.providerOf(List.class)));
+
+      // Request a JIT binding for PrivateFoo, which can only
+      // be created in the private module because it depends
+      // on List.
+      getProvider(PrivateFoo.class);
+    }
+  }
+
+  private static class PrivateFoo {
+    @Inject List list;
+  }
+}

--
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