Initializer cleanup. Now each InjectableReference takes care of itself, which should make the code a bit simpler to use.

git-svn-id: https://google-guice.googlecode.com/svn/trunk@631 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/src/com/google/inject/Initializer.java b/src/com/google/inject/Initializer.java
index 643492f..4bb14aa 100644
--- a/src/com/google/inject/Initializer.java
+++ b/src/com/google/inject/Initializer.java
@@ -17,17 +17,19 @@
 package com.google.inject;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.inject.internal.Errors;
 import com.google.inject.internal.ErrorsException;
 import com.google.inject.spi.InjectionPoint;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 
 /**
- * Manages and injects instances at injector-creation time.
+ * Manages and injects instances at injector-creation time. This is made more complicated by
+ * instances that request other instances while they're being injected. We overcome this by using
+ * {@link Initializable}, which attempts to perform injection before use.
  *
  * @author jessewilson@google.com (Jesse Wilson)
  */
@@ -39,7 +41,7 @@
   private final CountDownLatch ready = new CountDownLatch(1);
 
   /** Maps instances that need injection to a source that registered them */
-  private final Map<Object, Object> outstandingInjections = Maps.newIdentityHashMap();
+  private final Map<Object, InjectableReference<?>> pendingInjection = Maps.newIdentityHashMap();
   private final InjectorImpl injector;
 
   Initializer(InjectorImpl injector) {
@@ -56,8 +58,15 @@
   public <T> Initializable<T> requestInjection(T instance, Object source,
       Set<InjectionPoint> injectionPoints) {
     checkNotNull(source);
-    outstandingInjections.put(instance, source);
-    return new InjectableReference<T>(this, instance);
+
+    // short circuit if the object doesn't have any @Inject members
+    if (injectionPoints.isEmpty()) {
+      return Initializables.of(instance);
+    }
+
+    InjectableReference<T> initializable = new InjectableReference<T>(instance, source);
+    pendingInjection.put(instance, initializable);
+    return initializable;
   }
 
   /**
@@ -65,11 +74,9 @@
    * on the injected instances.
    */
   void validateOustandingInjections(Errors errors) {
-    for (Map.Entry<Object, Object> entry : outstandingInjections.entrySet()) {
+    for (InjectableReference<?> reference : pendingInjection.values()) {
       try {
-        Object toInject = entry.getKey();
-        Object source = entry.getValue();
-        injector.injectors.get(toInject.getClass(), errors.withSource(source));
+        reference.validate(errors);
       } catch (ErrorsException e) {
         errors.merge(e.getErrors());
       }
@@ -78,48 +85,65 @@
 
   /**
    * Performs creation-time injections on all objects that require it. Whenever fulfilling an
-   * injection depends on another object that requires injection, we use {@link
-   * #ensureInjected(Object,com.google.inject.internal.Errors)} to inject that member first.
-   *
-   * <p>If the two objects are codependent (directly or transitively), ordering of injection is
-   * arbitrary.
+   * injection depends on another object that requires injection, we inject it first. If the two
+   * instances are codependent (directly or transitively), ordering of injection is arbitrary.
    */
   void injectAll(final Errors errors) {
     // loop over a defensive copy since ensureInjected() mutates the set. Unfortunately, that copy
     // is made complicated by a bug in IBM's JDK, wherein entrySet().toArray(Object[]) doesn't work
-    for (Object entryObject : outstandingInjections.entrySet().toArray()) {
-      @SuppressWarnings("unchecked")
-      Entry<Object, Object> entry = (Entry<Object, Object>) entryObject;
+    for (InjectableReference<?> reference : Lists.newArrayList(pendingInjection.values())) {
       try {
-        Object toInject = entry.getKey();
-        Object source = entry.getValue();
-        ensureInjected(toInject, errors.withSource(source));
+        reference.get(errors);
       } catch (ErrorsException e) {
         errors.merge(e.getErrors());
       }
     }
 
-    if (!outstandingInjections.isEmpty()) {
-      throw new AssertionError("Failed to satisfy " + outstandingInjections);
+    if (!pendingInjection.isEmpty()) {
+      throw new AssertionError("Failed to satisfy " + pendingInjection);
     }
 
     ready.countDown();
   }
 
   private class InjectableReference<T> implements Initializable<T> {
-    private final Initializer initializer;
     private final T instance;
+    private final Object source;
 
-    public InjectableReference(Initializer initializer, T instance) {
-      this.initializer = checkNotNull(initializer, "initializer");
+    public InjectableReference(T instance, Object source) {
       this.instance = checkNotNull(instance, "instance");
+      this.source = checkNotNull(source, "source");
+    }
+
+    public void validate(Errors errors) throws ErrorsException {
+      injector.injectors.get(instance.getClass(), errors.withSource(source));
     }
 
     /**
-     * Ensures that the instance has been injected, and returns it.
+     * Reentrant. If {@code instance} was registered for injection at injector-creation time, this
+     * method will ensure that all its members have been injected before returning.
      */
     public T get(Errors errors) throws ErrorsException {
-      initializer.ensureInjected(instance, errors);
+      if (ready.getCount() == 0) {
+        return instance;
+      }
+
+      // just wait for everything to be injected by another thread
+      if (Thread.currentThread() != creatingThread) {
+        try {
+          ready.await();
+          return instance;
+        } catch (InterruptedException e) {
+          // Give up, since we don't know if our injection is ready
+          throw new RuntimeException(e);
+        }
+      }
+
+      // toInject needs injection, do it right away
+      if (pendingInjection.remove(instance) != null) {
+        injector.injectMembersOrThrow(errors.withSource(source), instance);
+      }
+
       return instance;
     }
 
@@ -127,33 +151,4 @@
       return instance.toString();
     }
   }
-
-
-  /**
-   * Reentrant. If {@code toInject} was registered for injection at injector-creation time, this
-   * method will ensure that all its members have been injected before returning. This method is
-   * used both internally, and by {@code InternalContext} to satisfy injections while satisfying
-   * other injections.
-   */
-  void ensureInjected(Object toInject, Errors errors) throws ErrorsException {
-    if (ready.getCount() == 0) {
-      return;
-    }
-
-    // just wait for everything to be injected by another thread
-    if (Thread.currentThread() != creatingThread) {
-      try {
-        ready.await();
-        return;
-      } catch (InterruptedException e) {
-        // Give up, since we don't know if our injection is ready
-        throw new RuntimeException(e);
-      }
-    }
-
-    // toInject needs injection, do it right away
-    if (outstandingInjections.remove(toInject) != null) {
-      injector.injectMembersOrThrow(errors, toInject);
-    }
-  }
 }
\ No newline at end of file