fixes issue 319 -- cleanup invalid leftover bindings that were added optimistically to support circular references.

git-svn-id: https://google-guice.googlecode.com/svn/trunk@1167 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/src/com/google/inject/internal/ConstructorBindingImpl.java b/src/com/google/inject/internal/ConstructorBindingImpl.java
index b363b50..85029df 100644
--- a/src/com/google/inject/internal/ConstructorBindingImpl.java
+++ b/src/com/google/inject/internal/ConstructorBindingImpl.java
@@ -121,7 +121,39 @@
     factory.constructorInjector
         = (ConstructorInjector<T>) injector.constructors.get(constructorInjectionPoint, errors);
   }
+  
+  /** True if this binding has been initialized and is ready for use. */
+  boolean isInitialized() {
+    return factory.constructorInjector != null;
+  }
 
+  /** Returns an injection point that can be used to clean up the constructor store. */
+  InjectionPoint getInternalConstructor() {
+    if(factory.constructorInjector != null) {
+      return factory.constructorInjector.getConstructionProxy().getInjectionPoint();
+    } else {
+      return constructorInjectionPoint;
+    }
+  }
+  
+  /** Returns a set of dependencies that can be iterated over to clean up stray JIT bindings. */
+  Set<Dependency<?>> getInternalDependencies() {
+    ImmutableSet.Builder<InjectionPoint> builder = ImmutableSet.builder();
+    if(factory.constructorInjector == null) {
+      builder.add(constructorInjectionPoint);
+      // If the below throws, it's OK -- we just ignore those dependencies, because no one
+      // could have used them anyway.
+      try {
+        builder.addAll(InjectionPoint.forInstanceMethodsAndFields(constructorInjectionPoint.getDeclaringType()));
+      } catch(ConfigurationException ignored) {}
+    } else {
+      builder.add(getConstructor())
+             .addAll(getInjectableMembers());
+    }
+    
+    return Dependency.forInjectionPoints(builder.build());   
+  }
+  
   public <V> V acceptTargetVisitor(BindingTargetVisitor<? super T, V> visitor) {
     checkState(factory.constructorInjector != null, "not initialized");
     return visitor.visit(this);
diff --git a/src/com/google/inject/internal/ConstructorInjectorStore.java b/src/com/google/inject/internal/ConstructorInjectorStore.java
index e1122a5..7ea52fa 100644
--- a/src/com/google/inject/internal/ConstructorInjectorStore.java
+++ b/src/com/google/inject/internal/ConstructorInjectorStore.java
@@ -47,6 +47,19 @@
       throws ErrorsException {
     return cache.get(constructorInjector, errors);
   }
+  
+  /**
+   * Purges an injection point from the cache. Use this only if the cache is not actually valid and
+   * needs to be purged. (See issue 319 and
+   * ImplicitBindingTest#testCircularJitBindingsLeaveNoResidue and
+   * #testInstancesRequestingProvidersForThemselvesWithChildInjectors for examples of when this is
+   * necessary.)
+   * 
+   * Returns true if the injector for that point was stored in the cache, false otherwise.
+   */
+  boolean remove(InjectionPoint ip) {
+    return cache.remove(ip);
+  }
 
   private <T> ConstructorInjector<T> createConstructor(InjectionPoint injectionPoint, Errors errors)
       throws ErrorsException {
diff --git a/src/com/google/inject/internal/FailableCache.java b/src/com/google/inject/internal/FailableCache.java
index 3522b9d..fea8cda 100644
--- a/src/com/google/inject/internal/FailableCache.java
+++ b/src/com/google/inject/internal/FailableCache.java
@@ -53,4 +53,8 @@
       return result;
     }
   }
+  
+  boolean remove(K key) {
+    return delegate.remove(key) != null;
+  }
 }
diff --git a/src/com/google/inject/internal/InjectorImpl.java b/src/com/google/inject/internal/InjectorImpl.java
index d471551..520789a 100644
--- a/src/com/google/inject/internal/InjectorImpl.java
+++ b/src/com/google/inject/internal/InjectorImpl.java
@@ -44,6 +44,7 @@
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -434,21 +435,81 @@
     // 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.
-    // TODO: for the above example, remove the binding for BarImpl if the binding for FooImpl fails
     if (binding instanceof ConstructorBindingImpl<?>) {
       Key<T> key = binding.getKey();
       jitBindings.put(key, binding);
       boolean successful = false;
+      ConstructorBindingImpl cb = (ConstructorBindingImpl)binding;
       try {
-        ((ConstructorBindingImpl) binding).initialize(this, errors);
+        cb.initialize(this, errors);
         successful = true;
       } finally {
         if (!successful) {
-          jitBindings.remove(key);
+          // We do not pass cb.getInternalConstructor as the second parameter
+          // so that cached exceptions while constructing it get stored.
+          // See TypeListenerTest#testTypeListenerThrows
+          removeFailedJitBinding(key, null);
+          cleanup(binding, new HashSet<Key>());
         }
       }
     }
   }
+  
+  /**
+   * Iterates through the binding's dependencies to clean up any stray bindings that were leftover
+   * from a failed JIT binding. This is required because the bindings are eagerly &
+   * optimistically added to allow circular dependency support, so dependencies may pass where they
+   * should have failed.
+   */
+  private boolean cleanup(BindingImpl<?> binding, Set<Key> encountered) {
+    boolean bindingFailed = false;
+    Set<Dependency<?>> deps = getInternalDependencies(binding);
+    for(Dependency dep : deps) {
+      Key<?> depKey = dep.getKey();
+      InjectionPoint ip = dep.getInjectionPoint();
+      if(encountered.add(depKey)) { // only check if we haven't looked at this key yet
+        BindingImpl depBinding = jitBindings.get(depKey);
+        if(depBinding != null) { // if the binding still exists, validate
+          boolean failed = cleanup(depBinding, encountered); // if children fail, we fail
+          if(depBinding instanceof ConstructorBindingImpl) {
+            ConstructorBindingImpl ctorBinding = (ConstructorBindingImpl)depBinding;
+            ip = ctorBinding.getInternalConstructor();
+            if(!ctorBinding.isInitialized()) {
+              failed = true;
+            }
+          }
+          if(failed) {
+            removeFailedJitBinding(depKey, ip);
+            bindingFailed = true;
+          }
+        } else if(state.getExplicitBinding(depKey) == null) {
+          // ignore keys if they were explicitly bound, but if neither JIT
+          // nor explicit, it's also invalid & should let parent know.
+          bindingFailed = true;
+        }
+      }
+    }
+    return bindingFailed;
+  }
+  
+  /** Cleans up any state that may have been cached when constructing the JIT binding. */
+  private void removeFailedJitBinding(Key<?> key, InjectionPoint ip) {
+    jitBindings.remove(key);
+    membersInjectorStore.remove(key.getTypeLiteral());
+    if(ip != null) {
+      constructors.remove(ip);
+    }
+  }
+  
+  /** Safely gets the dependencies of possibly not initialized bindings. */
+  @SuppressWarnings("unchecked")
+  private Set<Dependency<?>> getInternalDependencies(BindingImpl<?> binding) {
+    if(binding instanceof ConstructorBindingImpl) {
+      return ((ConstructorBindingImpl)binding).getInternalDependencies();
+    } else {
+      return ((HasDependencies)binding).getDependencies();
+    }
+  }
 
   /**
    * Creates a binding for an injectable type with the given scope. Looks for a scope on the type if
diff --git a/src/com/google/inject/internal/MembersInjectorStore.java b/src/com/google/inject/internal/MembersInjectorStore.java
index 417c437..7b7f266 100644
--- a/src/com/google/inject/internal/MembersInjectorStore.java
+++ b/src/com/google/inject/internal/MembersInjectorStore.java
@@ -64,6 +64,19 @@
   }
 
   /**
+   * Purges a type literal from the cache. Use this only if the type is not actually valid for
+   * binding and needs to be purged. (See issue 319 and
+   * ImplicitBindingTest#testCircularJitBindingsLeaveNoResidue and
+   * #testInstancesRequestingProvidersForThemselvesWithChildInjectors for examples of when this is
+   * necessary.)
+   * 
+   * Returns true if the type was stored in the cache, false otherwise.
+   */
+  boolean remove(TypeLiteral<?> type) {
+    return cache.remove(type);
+  }
+
+  /**
    * Creates a new members injector and attaches both injection listeners and method aspects.
    */
   private <T> MembersInjectorImpl<T> createWithListeners(TypeLiteral<T> type, Errors errors)
diff --git a/test/com/google/inject/AllTests.java b/test/com/google/inject/AllTests.java
index 6c2929c..4142863 100644
--- a/test/com/google/inject/AllTests.java
+++ b/test/com/google/inject/AllTests.java
@@ -54,8 +54,7 @@
 
   private static final Set<String> SUPPRESSED_TEST_NAMES = ImmutableSet.of(
       "testUnscopedProviderWorksOutsideOfRequestedScope(" + ScopesTest.class.getName() + ")",
-      "testCannotConvertUnannotatedBindings(" + TypeConversionTest.class.getName() + ")",
-      "testCircularJitBindingsLeaveNoResidue(" + ImplicitBindingTest.class.getName() + ")"
+      "testCannotConvertUnannotatedBindings(" + TypeConversionTest.class.getName() + ")"
   );
 
   public static Test suite() {
diff --git a/test/com/google/inject/ImplicitBindingTest.java b/test/com/google/inject/ImplicitBindingTest.java
index 7db69a7..40ff6bb 100644
--- a/test/com/google/inject/ImplicitBindingTest.java
+++ b/test/com/google/inject/ImplicitBindingTest.java
@@ -16,6 +16,9 @@
 
 package com.google.inject;
 
+import java.util.List;
+
+import com.google.inject.internal.Iterables;
 import com.google.inject.name.Named;
 import com.google.inject.name.Names;
 import junit.framework.TestCase;
@@ -107,31 +110,153 @@
    * When we're building the binding for A, we temporarily insert that binding to support circular
    * dependencies. And so we can successfully create a binding for B. But later, when the binding
    * for A ultimately fails, we need to clean up the dependent binding for B.
+   * 
+   * The test loops through linked bindings & bindings with constructor & member injections,
+   * to make sure that all are cleaned up and traversed.  It also makes sure we don't touch
+   * explicit bindings.
    */
   public void testCircularJitBindingsLeaveNoResidue() {
-    Injector injector = Guice.createInjector();
+    Injector injector = Guice.createInjector(new AbstractModule() {
+      @Override
+      protected void configure() {
+        bind(Valid.class);
+        bind(Valid2.class);
+      }
+    });
+    
+    // Capture good bindings.
+    Binding v1 = injector.getBinding(Valid.class);
+    Binding v2 = injector.getBinding(Valid2.class);
+    Binding jv1 = injector.getBinding(JitValid.class);
+    Binding jv2 = injector.getBinding(JitValid2.class);
 
+    // Then validate that a whole series of invalid bindings are erased.
+    assertFailure(injector, Invalid.class);
+    assertFailure(injector, InvalidLinked.class);
+    assertFailure(injector, InvalidLinkedImpl.class);
+    assertFailure(injector, InvalidLinked2.class);
+    assertFailure(injector, InvalidLinked2Impl.class);
+    assertFailure(injector, Invalid2.class);
+    
+    // Validate we didn't do anything to the valid explicit bindings.
+    assertSame(v1, injector.getBinding(Valid.class));
+    assertSame(v2, injector.getBinding(Valid2.class));
+    
+    // Validate that we didn't erase the valid JIT bindings
+    assertSame(jv1, injector.getBinding(JitValid.class));
+    assertSame(jv2, injector.getBinding(JitValid2.class));
+  }
+  
+  @SuppressWarnings("unchecked")
+  private void assertFailure(Injector injector, Class clazz) {
     try {
-      injector.getBinding(A.class);
-      fail();
-    } catch (ConfigurationException expected) {
-    }
-
-    try {
-      injector.getBinding(B.class);
-      fail();
-    } catch (ConfigurationException expected) {
+      injector.getBinding(clazz);
+      fail("Shouldn't have been able to get binding of: " + clazz);
+    } catch(ConfigurationException expected) {
+      List<Object> sources = Iterables.getOnlyElement(expected.getErrorMessages()).getSources();
+      // Assert that the first item in the sources if the key for the class we're looking up,
+      // ensuring that each lookup is "new".
+      assertEquals(Key.get(clazz).toString(), sources.get(0).toString());
+      // Assert that the last item in each lookup contains the InvalidInterface class
+      Asserts.assertContains(sources.get(sources.size()-1).toString(),
+          Key.get(InvalidInterface.class).toString());
     }
   }
 
-  static class A {
-    @Inject B b;
-    @Inject D d;
+  static class Invalid {
+    @Inject Valid a;
+    @Inject JitValid b;    
+    @Inject Invalid(InvalidLinked a) {}    
+    @Inject void foo(InvalidInterface a) {}
+    
   }
 
-  static class B {
-    @Inject A a;
+  @ImplementedBy(InvalidLinkedImpl.class)
+  static interface InvalidLinked {}
+  static class InvalidLinkedImpl implements InvalidLinked {
+    @Inject InvalidLinked2 a;
+  }
+  
+  @ImplementedBy(InvalidLinked2Impl.class)
+  static interface InvalidLinked2 {}
+  static class InvalidLinked2Impl implements InvalidLinked2 {
+    @Inject InvalidLinked2Impl(Invalid2 a) {}
+  }
+  
+  static class Invalid2 {
+    @Inject Invalid a;
   }
 
-  interface D {}
+  interface InvalidInterface {}
+  
+  static class Valid { @Inject Valid2 a; }
+  static class Valid2 {}
+  
+  static class JitValid { @Inject JitValid2 a; }
+  static class JitValid2 {}
+  
+  /**
+   * Regression test for http://code.google.com/p/google-guice/issues/detail?id=319
+   * 
+   * The bug is that a class that asks for a provider for itself during injection time, 
+   * where any one of the other types required to fulfill the object creation was bound 
+   * in a child constructor, explodes when the injected Provider is called.
+   * 
+   * It works just fine when the other types are bound in a main injector.
+   */  
+  public void testInstancesRequestingProvidersForThemselvesWithChildInjectors() {       
+    final Module testModule = new AbstractModule() {
+      @Override
+      protected void configure() {
+        bind(String.class)
+          .toProvider(TestStringProvider.class);
+      }            
+    };    
+    
+    // Verify it works when the type is setup in the parent.
+    Injector parentSetupRootInjector = Guice.createInjector(testModule);
+    Injector parentSetupChildInjector = parentSetupRootInjector.createChildInjector();
+    assertEquals(TestStringProvider.TEST_VALUE, 
+        parentSetupChildInjector.getInstance(
+            RequiresProviderForSelfWithOtherType.class).getValue());
+        
+    // Verify it works when the type is setup in the child, not the parent.
+    // If it still occurs, the bug will explode here.
+    Injector childSetupRootInjector = Guice.createInjector();
+    Injector childSetupChildInjector = childSetupRootInjector.createChildInjector(testModule);      
+    assertEquals(TestStringProvider.TEST_VALUE, 
+        childSetupChildInjector.getInstance(
+            RequiresProviderForSelfWithOtherType.class).getValue());
+  }
+  
+  static class TestStringProvider implements Provider<String> {
+    static final String TEST_VALUE = "This is to verify it all works";
+    
+    public String get() {
+      return TEST_VALUE;
+    }    
+  }    
+  
+  static class RequiresProviderForSelfWithOtherType {
+    private final Provider<RequiresProviderForSelfWithOtherType> selfProvider;
+    private final String providedStringValue;
+    
+    @Inject    
+    RequiresProviderForSelfWithOtherType(
+        String providedStringValue,
+        Provider<RequiresProviderForSelfWithOtherType> selfProvider
+        ) {
+      this.providedStringValue = providedStringValue;
+      this.selfProvider = selfProvider;      
+    }
+    
+    public String getValue() {
+      // Attempt to get another instance of ourself. This pattern
+      // is possible for recursive processing. 
+      selfProvider.get();
+      
+      return providedStringValue;
+    }
+  }  
+
 }