fix @ProvidedBy circular dependencies.

git-svn-id: https://google-guice.googlecode.com/svn/trunk@1546 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/core/src/com/google/inject/internal/ConstructorBindingImpl.java b/core/src/com/google/inject/internal/ConstructorBindingImpl.java
index fcb2ea5..fdd6756 100644
--- a/core/src/com/google/inject/internal/ConstructorBindingImpl.java
+++ b/core/src/com/google/inject/internal/ConstructorBindingImpl.java
@@ -38,7 +38,8 @@
 import java.util.Map;
 import java.util.Set;
 
-final class ConstructorBindingImpl<T> extends BindingImpl<T> implements ConstructorBinding<T> {
+final class ConstructorBindingImpl<T> extends BindingImpl<T>
+    implements ConstructorBinding<T>, DelayedInitialize {
 
   private final Factory<T> factory;
   private final InjectionPoint constructorInjectionPoint;
diff --git a/core/src/com/google/inject/internal/DelayedInitialize.java b/core/src/com/google/inject/internal/DelayedInitialize.java
new file mode 100644
index 0000000..80e52d1
--- /dev/null
+++ b/core/src/com/google/inject/internal/DelayedInitialize.java
@@ -0,0 +1,17 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+
+package com.google.inject.internal;
+
+/**
+ * Something that needs some delayed initialization, typically
+ * a binding or internal factory that needs to be created & put
+ * into the bindings map & then initialized later.
+ * 
+ * @author sameb@google.com (Sam Berlin)
+ */
+interface DelayedInitialize {
+  
+  /** Initializes this binding, throwing any errors if necessary. */
+  void initialize(InjectorImpl injector, Errors errors) throws ErrorsException;
+
+}
diff --git a/core/src/com/google/inject/internal/InjectorImpl.java b/core/src/com/google/inject/internal/InjectorImpl.java
index 715f933..bfc7b10 100644
--- a/core/src/com/google/inject/internal/InjectorImpl.java
+++ b/core/src/com/google/inject/internal/InjectorImpl.java
@@ -503,22 +503,22 @@
   }
 
   <T> void initializeBinding(BindingImpl<T> binding, Errors errors) throws ErrorsException {
-    if (binding instanceof ConstructorBindingImpl<?>) {
-      ((ConstructorBindingImpl) binding).initialize(this, errors);
+    if (binding instanceof DelayedInitialize) {
+      ((DelayedInitialize) binding).initialize(this, errors);
     }
   }
 
   <T> void initializeJitBinding(BindingImpl<T> binding, Errors errors) throws ErrorsException {
     // Put the partially constructed binding in the map a little early. This enables us to handle
     // circular dependencies. Example: FooImpl -> BarImpl -> FooImpl.
-    // Note: We don't need to synchronize on state.lock() during injector creation.
-    if (binding instanceof ConstructorBindingImpl<?>) {
+    // Note: We don't need to synchronize on state.lock() during injector creation.    
+    if (binding instanceof DelayedInitialize) {
       Key<T> key = binding.getKey();
       jitBindings.put(key, binding);
       boolean successful = false;
-      ConstructorBindingImpl cb = (ConstructorBindingImpl)binding;
+      DelayedInitialize delayed = (DelayedInitialize)binding;
       try {
-        cb.initialize(this, errors);
+        delayed.initialize(this, errors);
         successful = true;
       } finally {
         if (!successful) {
@@ -662,8 +662,8 @@
   /** Creates a binding for a type annotated with @ProvidedBy. */
   <T> BindingImpl<T> createProvidedByBinding(Key<T> key, Scoping scoping,
       ProvidedBy providedBy, Errors errors) throws ErrorsException {
-    final Class<?> rawType = key.getTypeLiteral().getRawType();
-    final Class<? extends Provider<?>> providerType = providedBy.value();
+    Class<?> rawType = key.getTypeLiteral().getRawType();
+    Class<? extends Provider<?>> providerType = providedBy.value();
 
     // Make sure it's not the same type. TODO: Can we check for deeper loops?
     if (providerType == rawType) {
@@ -672,41 +672,20 @@
 
     // Assume the provider provides an appropriate type. We double check at runtime.
     @SuppressWarnings("unchecked")
-    final Key<? extends Provider<T>> providerKey
-        = (Key<? extends Provider<T>>) Key.get(providerType);
-    final BindingImpl<? extends Provider<?>> providerBinding
-        = getBindingOrThrow(providerKey, errors, JitLimitation.NEW_OR_EXISTING_JIT);
-
-    InternalFactory<T> internalFactory =
-        new ProviderInternalFactory<T>(providerKey, !options.disableCircularProxies) {
-      public T get(Errors errors, InternalContext context, Dependency dependency, boolean linked)
-          throws ErrorsException {
-        errors = errors.withSource(providerKey);
-        Provider provider = providerBinding.getInternalFactory().get(
-            errors, context, dependency, true);
-        try {
-          @SuppressWarnings("unchecked") // type is not checked within circularGet
-          Object o = circularGet(provider, errors, context, dependency, linked);
-          if (o != null && !rawType.isInstance(o)) {
-            throw errors.subtypeNotProvided(providerType, rawType).toException();
-          }
-          @SuppressWarnings("unchecked") // protected by isInstance() check above
-          T t = (T) o;
-          return t;
-        } catch (RuntimeException e) {
-          throw errors.errorInProvider(e).toException();
-        }
-      }
-    };
+    Key<? extends Provider<T>> providerKey = (Key<? extends Provider<T>>) Key.get(providerType);
+    ProvidedByInternalFactory<T> internalFactory =
+        new ProvidedByInternalFactory<T>(rawType, providerType,
+            providerKey, !options.disableCircularProxies);
 
     Object source = rawType;
-    return new LinkedProviderBindingImpl<T>(
+    return LinkedProviderBindingImpl.createWithInitializer(
         this,
         key,
         source,
         Scoping.<T>scope(key, this, internalFactory, source, scoping),
         scoping,
-        providerKey);
+        providerKey,
+        internalFactory);
   }
 
   /** Creates a binding for a type annotated with @ImplementedBy. */
diff --git a/core/src/com/google/inject/internal/LinkedProviderBindingImpl.java b/core/src/com/google/inject/internal/LinkedProviderBindingImpl.java
index ccbdf9e..d7a8599 100644
--- a/core/src/com/google/inject/internal/LinkedProviderBindingImpl.java
+++ b/core/src/com/google/inject/internal/LinkedProviderBindingImpl.java
@@ -28,21 +28,39 @@
 import java.util.Set;
 
 final class LinkedProviderBindingImpl<T>
-    extends BindingImpl<T> implements ProviderKeyBinding<T>, HasDependencies {
+    extends BindingImpl<T> implements ProviderKeyBinding<T>, HasDependencies, DelayedInitialize {
 
   final Key<? extends javax.inject.Provider<? extends T>> providerKey;
+  final DelayedInitialize delayedInitializer;
+  
+  private LinkedProviderBindingImpl(InjectorImpl injector, Key<T> key, Object source,
+      InternalFactory<? extends T> internalFactory, Scoping scoping,
+      Key<? extends javax.inject.Provider<? extends T>> providerKey,
+      DelayedInitialize delayedInitializer) {
+    super(injector, key, source, internalFactory, scoping);
+    this.providerKey = providerKey;
+    this.delayedInitializer = delayedInitializer;
+  }
 
   public LinkedProviderBindingImpl(InjectorImpl injector, Key<T> key, Object source,
       InternalFactory<? extends T> internalFactory, Scoping scoping,
       Key<? extends javax.inject.Provider<? extends T>> providerKey) {
-    super(injector, key, source, internalFactory, scoping);
-    this.providerKey = providerKey;
+    this(injector, key, source, internalFactory, scoping, providerKey, null);
   }
 
   LinkedProviderBindingImpl(Object source, Key<T> key, Scoping scoping,
       Key<? extends javax.inject.Provider<? extends T>> providerKey) {
     super(source, key, scoping);
     this.providerKey = providerKey;
+    this.delayedInitializer = null;
+  }
+  
+  static <T> LinkedProviderBindingImpl<T> createWithInitializer(InjectorImpl injector, Key<T> key,
+      Object source, InternalFactory<? extends T> internalFactory, Scoping scoping,
+      Key<? extends javax.inject.Provider<? extends T>> providerKey,
+      DelayedInitialize delayedInitializer) {
+    return new LinkedProviderBindingImpl<T>(injector, key, source, internalFactory, scoping,
+        providerKey, delayedInitializer);
   }
 
   public <V> V acceptTargetVisitor(BindingTargetVisitor<? super T, V> visitor) {
@@ -53,6 +71,12 @@
     return providerKey;
   }
   
+  public void initialize(InjectorImpl injector, Errors errors) throws ErrorsException {
+    if (delayedInitializer != null) {
+      delayedInitializer.initialize(injector, errors);
+    }
+  }
+  
   public Set<Dependency<?>> getDependencies() {
     return ImmutableSet.<Dependency<?>>of(Dependency.get(providerKey));
   }
diff --git a/core/src/com/google/inject/internal/ProvidedByInternalFactory.java b/core/src/com/google/inject/internal/ProvidedByInternalFactory.java
new file mode 100644
index 0000000..3ca75e5
--- /dev/null
+++ b/core/src/com/google/inject/internal/ProvidedByInternalFactory.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.google.inject.internal;
+
+import static com.google.inject.internal.util.Preconditions.checkState;
+
+import com.google.inject.Key;
+import com.google.inject.ProvidedBy;
+import com.google.inject.Provider;
+import com.google.inject.internal.InjectorImpl.JitLimitation;
+import com.google.inject.spi.Dependency;
+
+/**
+ * An {@link InternalFactory} for {@literal @}{@link ProvidedBy} bindings.
+ * 
+ * @author sameb@google.com (Sam Berlin)
+ */
+class ProvidedByInternalFactory<T> extends ProviderInternalFactory<T>
+    implements DelayedInitialize {
+  
+  private final Class<?> rawType;
+  private final Class<? extends Provider<?>> providerType;
+  private final Key<? extends Provider<T>> providerKey;
+  private BindingImpl<? extends Provider<T>> providerBinding;
+  
+  ProvidedByInternalFactory(
+      Class<?> rawType,
+      Class<? extends Provider<?>> providerType,
+      Key<? extends Provider<T>> providerKey,
+      boolean allowProxy) {
+    super(providerKey, allowProxy);
+    this.rawType = rawType;
+    this.providerType = providerType; 
+    this.providerKey = providerKey;
+  }
+  
+  public void initialize(InjectorImpl injector, Errors errors) throws ErrorsException {
+    providerBinding =
+        injector.getBindingOrThrow(providerKey, errors, JitLimitation.NEW_OR_EXISTING_JIT); 
+  }
+  
+  public T get(Errors errors, InternalContext context, Dependency dependency, boolean linked)
+      throws ErrorsException {
+    checkState(providerBinding != null, "not initialized");
+    
+    errors = errors.withSource(providerKey);
+    Provider provider = providerBinding.getInternalFactory().get(
+        errors, context, dependency, true);
+    try {
+      @SuppressWarnings("unchecked") // type is not checked within circularGet
+      Object o = circularGet(provider, errors, context, dependency, linked);
+      if (o != null && !rawType.isInstance(o)) {
+        throw errors.subtypeNotProvided(providerType, rawType).toException();
+      }
+      @SuppressWarnings("unchecked") // protected by isInstance() check above
+      T t = (T) o;
+      return t;
+    } catch (RuntimeException e) {
+      throw errors.errorInProvider(e).toException();
+    }
+  }
+}
diff --git a/core/test/com/google/inject/CircularDependencyTest.java b/core/test/com/google/inject/CircularDependencyTest.java
index 72f020b..28e5272 100644
--- a/core/test/com/google/inject/CircularDependencyTest.java
+++ b/core/test/com/google/inject/CircularDependencyTest.java
@@ -88,21 +88,11 @@
     assertCircularDependencies(injector);
   }
   
-  // TODO: This creates two As because circular dependencies between @ProvidedBy
-  // Providers aren't handled well right now.  When we bind A, it looks for its
-  // Provider, which goes into InjectorImpl.createProvidedByBinding, which
-  // goes into getBindingOrThrow for AutoAP.  That creates a ConstructorBinding
-  // for AutoAP and looks up its dependency for Provider<B>, which ends up
-  // in creativeProvidedByBinding for BP, which creates a ConstructorBinding
-  // for BP and looks up the dependency for Provider<A>.  That ends up creating
-  // another providedByBinding for AutoAP, because the first one hasn't been stored
-  // anywhere yet.  The solution is to initialize the dependency early, similar
-  // to what's done with ConstructorBindings.
-//  public void testCircularlyDependentConstructorsWithProvidedBy()
-//      throws CreationException {
-//    Injector injector = Guice.createInjector();
-//    assertCircularDependencies(injector);
-//  }
+  public void testCircularlyDependentConstructorsWithProvidedBy()
+      throws CreationException {
+    Injector injector = Guice.createInjector();
+    assertCircularDependencies(injector);
+  }
   
   private void assertCircularDependencies(Injector injector) {
     A a = injector.getInstance(A.class);
@@ -238,9 +228,7 @@
       fail();
     } catch (ProvisionException expected) {
       assertContains(expected.getMessage(),
-          // TODO: this should fail at C2, but because of strangeness with @ProvidedBy,
-          // it fails in the wrong place right now.
-          "Tried proxying " + D2.class.getName() + " to support a circular dependency, ",
+          "Tried proxying " + C2.class.getName() + " to support a circular dependency, ",
           "but it is not an interface.");
     }
   }
@@ -336,9 +324,7 @@
       fail();
     } catch (ProvisionException expected) {
       assertContains(expected.getMessage(),
-          // TODO: this should fail at C2, but because of strangeness with @ProvidedBy,
-          // it fails in the wrong place right now.
-          "Tried proxying " + D2.class.getName() + " to support a circular dependency, ",
+          "Tried proxying " + C2.class.getName() + " to support a circular dependency, ",
           "but circular proxies are disabled.");
     }
   }