Support deferred lookups of providers and members injectors from the Encounter. 

The behaviour is slightly different depending on when the InjectableTypeListener.hear() method is invoked...

A) If it's invoked before the Injector is created, then getProvider() always returns immediately. The provider may not be used until the injector has been created.

B) If it's invoked after the Injector has been created, then getProvider() builds the provider immediately. This includes the work to build the binding (recursively) and this method may fail with a ConfigurationException.

It would be possible to change B) to behave more like A), but the utility of doing so is quite limited. One case that still needs test coverage is when the listener calls getProvider(B.class) while hearing about B. For now, this will probably fail. That's probably okay for now.

git-svn-id: https://google-guice.googlecode.com/svn/trunk@922 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/src/com/google/inject/ConstructorInjectorStore.java b/src/com/google/inject/ConstructorInjectorStore.java
index d9393e8..944c8f5 100644
--- a/src/com/google/inject/ConstructorInjectorStore.java
+++ b/src/com/google/inject/ConstructorInjectorStore.java
@@ -79,7 +79,7 @@
     ImmutableSet<InjectionPoint> injectableMembers = membersInjector.getInjectionPoints();
 
     ProxyFactory<T> proxyFactory = new ProxyFactory<T>(injectionPoint, injector.methodAspects);
-    EncounterImpl<T> encounter = new EncounterImpl<T>();
+    EncounterImpl<T> encounter = new EncounterImpl<T>(errors, injector.lookups);
     InjectableType<T> injectableType = new InjectableType<T>(
         injectionPoint, type, injectableMembers, proxyFactory.getInterceptors());
 
diff --git a/src/com/google/inject/DeferredLookups.java b/src/com/google/inject/DeferredLookups.java
new file mode 100644
index 0000000..bf28c28
--- /dev/null
+++ b/src/com/google/inject/DeferredLookups.java
@@ -0,0 +1,59 @@
+/**
+ * Copyright (C) 2009 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;
+
+import com.google.inject.internal.Errors;
+import com.google.inject.internal.Lists;
+import com.google.inject.spi.Element;
+import com.google.inject.spi.MembersInjectorLookup;
+import com.google.inject.spi.ProviderLookup;
+import java.util.List;
+
+/**
+ * Returns providers and members injectors that haven't yet been initialized. As a part of injector
+ * creation it's necessary to {@link #initialize initialize} these lookups.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+public class DeferredLookups implements Lookups {
+  private final InjectorImpl injector;
+  private final List<Element> lookups = Lists.newArrayList();
+
+  public DeferredLookups(InjectorImpl injector) {
+    this.injector = injector;
+  }
+
+  /**
+   * Initialize the specified lookups, either immediately or when the injector is created.
+   */
+  public void initialize(Errors errors) {
+    injector.lookups = injector;
+    new LookupProcessor(errors).process(injector, lookups);
+  }
+
+  public <T> Provider<T> getProvider(Key<T> key) {
+    ProviderLookup<T> lookup = new ProviderLookup<T>(key, key);
+    lookups.add(lookup);
+    return lookup.getProvider();
+  }
+
+  public <T> MembersInjector<T> getMembersInjector(TypeLiteral<T> type) {
+    MembersInjectorLookup<T> lookup = new MembersInjectorLookup<T>(type, type);
+    lookups.add(lookup);
+    return lookup.getMembersInjector();
+  }
+}
diff --git a/src/com/google/inject/EncounterImpl.java b/src/com/google/inject/EncounterImpl.java
index ac55d9b..09dc8f6 100644
--- a/src/com/google/inject/EncounterImpl.java
+++ b/src/com/google/inject/EncounterImpl.java
@@ -16,25 +16,34 @@
 
 package com.google.inject;
 
-import com.google.inject.spi.InjectableType;
-import com.google.inject.spi.InjectionListener;
-import com.google.inject.spi.Message;
+import com.google.inject.internal.Errors;
 import com.google.inject.internal.ImmutableList;
 import com.google.inject.internal.Lists;
 import com.google.inject.matcher.Matcher;
 import com.google.inject.matcher.Matchers;
-import java.util.List;
+import com.google.inject.spi.InjectableType;
+import com.google.inject.spi.InjectionListener;
+import com.google.inject.spi.Message;
 import java.lang.reflect.Method;
+import java.util.List;
 import org.aopalliance.intercept.MethodInterceptor;
 
 /**
  * @author jessewilson@google.com (Jesse Wilson)
  */
-class EncounterImpl<T> implements InjectableType.Encounter<T> {
+public final class EncounterImpl<T> implements InjectableType.Encounter<T> {
+
+  private final Errors errors;
+  private final Lookups lookups;
   private List<InjectionListener<? super T>> injectionListeners; // lazy
   private List<MethodAspect> aspects; // lazy
 
-  boolean hasAddedAspects() {
+  public EncounterImpl(Errors errors, Lookups lookups) {
+    this.errors = errors;
+    this.lookups = lookups;
+  }
+
+  public boolean hasAddedAspects() {
     return aspects != null;
   }
 
@@ -46,7 +55,7 @@
     return aspects;
   }
 
-  ImmutableList<InjectionListener<? super T>> getInjectionListeners() {
+  public ImmutableList<InjectionListener<? super T>> getInjectionListeners() {
     return injectionListeners == null
         ? ImmutableList.<InjectionListener<? super T>>of()
         : ImmutableList.copyOf(injectionListeners);
@@ -58,7 +67,7 @@
       injectionListeners = Lists.newArrayList();
     }
 
-    injectionListeners.add((InjectionListener<T>) injectionListener);
+    injectionListeners.add(injectionListener);
   }
 
   public void bindInterceptor(Matcher<? super Method> methodMatcher,
@@ -72,30 +81,30 @@
   }
 
   public void addError(String message, Object... arguments) {
-    throw new UnsupportedOperationException("TODO");
+    errors.addMessage(message, arguments);
   }
 
   public void addError(Throwable t) {
-    throw new UnsupportedOperationException("TODO");
+    errors.errorInUserCode(t, "An exception was caught and reported. Message: %s", t.getMessage());
   }
 
   public void addError(Message message) {
-    throw new UnsupportedOperationException("TODO");
+    errors.addMessage(message);
   }
 
   public <T> Provider<T> getProvider(Key<T> key) {
-    throw new UnsupportedOperationException("TODO");
+    return lookups.getProvider(key);
   }
 
   public <T> Provider<T> getProvider(Class<T> type) {
-    throw new UnsupportedOperationException("TODO");
+    return getProvider(Key.get(type));
   }
 
   public <T> MembersInjector<T> getMembersInjector(TypeLiteral<T> typeLiteral) {
-    throw new UnsupportedOperationException("TODO");
+    return lookups.getMembersInjector(typeLiteral);
   }
 
   public <T> MembersInjector<T> getMembersInjector(Class<T> type) {
-    throw new UnsupportedOperationException("TODO");
+    return getMembersInjector(TypeLiteral.get(type));
   }
-}
+}
\ No newline at end of file
diff --git a/src/com/google/inject/InjectorBuilder.java b/src/com/google/inject/InjectorBuilder.java
index 8740af3..2db59f2 100644
--- a/src/com/google/inject/InjectorBuilder.java
+++ b/src/com/google/inject/InjectorBuilder.java
@@ -138,6 +138,9 @@
     stopwatch.resetAndLog("Instance member validation");
 
     new LookupProcessor(errors).process(shells);
+    for (InjectorShell shell : shells) {
+      ((DeferredLookups) shell.getInjector().lookups).initialize(errors);
+    }
     stopwatch.resetAndLog("Provider verification");
 
     for (InjectorShell shell : shells) {
diff --git a/src/com/google/inject/InjectorImpl.java b/src/com/google/inject/InjectorImpl.java
index 5e51ab7..c3115c8 100644
--- a/src/com/google/inject/InjectorImpl.java
+++ b/src/com/google/inject/InjectorImpl.java
@@ -60,7 +60,7 @@
  * @author crazybob@google.com (Bob Lee)
  * @see InjectorBuilder
  */
-class InjectorImpl implements Injector {
+class InjectorImpl implements Injector, Lookups {
   final State state;
   final InjectorImpl parent;
   final BindingsMultimap bindingsMultimap = new BindingsMultimap();
@@ -70,6 +70,8 @@
   /** Just-in-time binding cache. Guarded by state.lock() */
   final Map<Key<?>, BindingImpl<?>> jitBindings = Maps.newHashMap();
 
+  Lookups lookups = new DeferredLookups(this);
+
   InjectorImpl(@Nullable InjectorImpl parent, State state, Initializer initializer) {
     this.parent = parent;
     this.state = state;
diff --git a/src/com/google/inject/InjectorShell.java b/src/com/google/inject/InjectorShell.java
index 59e02f1..02b832c 100644
--- a/src/com/google/inject/InjectorShell.java
+++ b/src/com/google/inject/InjectorShell.java
@@ -148,11 +148,11 @@
       end[NO_AOP]*/
 
       new InjectableTypeListenerBindingProcessor(errors).process(injector, elements);
-      stopwatch.resetAndLog("InjectableType listeners creation");
       List<InjectableTypeListenerBinding> listenerBindings
           = injector.state.getInjectableTypeListenerBindings();
       injector.constructors = new ConstructorInjectorStore(injector, listenerBindings);
       injector.membersInjectorStore = new MembersInjectorStore(injector, listenerBindings);
+      stopwatch.resetAndLog("InjectableType listeners creation");
 
       new ScopeBindingProcessor(errors).process(injector, elements);
       stopwatch.resetAndLog("Scopes creation");
diff --git a/src/com/google/inject/Lookups.java b/src/com/google/inject/Lookups.java
new file mode 100644
index 0000000..183e2ae
--- /dev/null
+++ b/src/com/google/inject/Lookups.java
@@ -0,0 +1,30 @@
+/**
+ * Copyright (C) 2009 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;
+
+/**
+ * Accessors for providers and members injectors. The returned values will not be functional until
+ * the injector has been created.
+ *
+ * @author jessewilson@google.com (Jesse Wilson)
+ */
+interface Lookups {
+
+  <T> Provider<T> getProvider(Key<T> key);
+
+  <T> MembersInjector<T> getMembersInjector(TypeLiteral<T> type);
+}
diff --git a/src/com/google/inject/MembersInjectorStore.java b/src/com/google/inject/MembersInjectorStore.java
index c825ed8..2f55b95 100644
--- a/src/com/google/inject/MembersInjectorStore.java
+++ b/src/com/google/inject/MembersInjectorStore.java
@@ -21,15 +21,12 @@
 import com.google.inject.internal.FailableCache;
 import com.google.inject.internal.ImmutableList;
 import com.google.inject.internal.Lists;
-import com.google.inject.matcher.Matcher;
 import com.google.inject.spi.InjectableType;
 import com.google.inject.spi.InjectableTypeListenerBinding;
 import com.google.inject.spi.InjectionPoint;
 import java.lang.reflect.Field;
-import java.lang.reflect.Method;
 import java.util.List;
 import java.util.Set;
-import org.aopalliance.intercept.MethodInterceptor;
 
 /**
  * Members injectors by type.
@@ -92,13 +89,7 @@
     InjectableType<T> injectableType = new InjectableType<T>(null, type,
         membersInjector.getInjectionPoints());
 
-    EncounterImpl<T> encounter = new EncounterImpl<T>() {
-      @Override public void bindInterceptor(Matcher<? super Method> methodMatcher,
-          MethodInterceptor... interceptors) {
-        throw new UnsupportedOperationException("TODO");
-        // TODO: add an error here
-      }
-    };
+    EncounterImpl<T> encounter = new EncounterImpl<T>(errors, injector.lookups);
 
     for (InjectableTypeListenerBinding typeListener : injectableTypeListenerBindings) {
       if (typeListener.getTypeMatcher().matches(type)) {
diff --git a/src/com/google/inject/spi/Elements.java b/src/com/google/inject/spi/Elements.java
index 0bf6a0a..48accf9 100644
--- a/src/com/google/inject/spi/Elements.java
+++ b/src/com/google/inject/spi/Elements.java
@@ -38,7 +38,6 @@
 import com.google.inject.internal.ImmutableList;
 import com.google.inject.internal.Lists;
 import static com.google.inject.internal.Preconditions.checkArgument;
-import static com.google.inject.internal.Preconditions.checkState;
 import com.google.inject.internal.PrivateElementsImpl;
 import com.google.inject.internal.ProviderMethodsModule;
 import com.google.inject.internal.Sets;
@@ -182,18 +181,7 @@
       final MembersInjectorLookup<T> element
           = new MembersInjectorLookup<T>(getSource(), typeLiteral);
       elements.add(element);
-      return new MembersInjector<T>() {
-        public void injectMembers(T instance) {
-          MembersInjector<T> delegate = element.getDelegate();
-          checkState(delegate != null,
-              "This MembersInjector cannot be used until the Injector has been created.");
-          delegate.injectMembers(instance);
-        }
-
-        @Override public String toString() {
-          return "MembersInjector<" + typeLiteral + ">";
-        }
-      };
+      return element.getMembersInjector();
     }
 
     public <T> MembersInjector<T> getMembersInjector(Class<T> type) {
@@ -268,18 +256,7 @@
     public <T> Provider<T> getProvider(final Key<T> key) {
       final ProviderLookup<T> element = new ProviderLookup<T>(getSource(), key);
       elements.add(element);
-      return new Provider<T>() {
-        public T get() {
-          Provider<T> delegate = element.getDelegate();
-          checkState(delegate != null,
-              "This Provider cannot be used until the Injector has been created.");
-          return delegate.get();
-        }
-
-        @Override public String toString() {
-          return "Provider<" + key.getTypeLiteral() + ">";
-        }
-      };
+      return element.getProvider();
     }
 
     public <T> Provider<T> getProvider(Class<T> type) {
diff --git a/src/com/google/inject/spi/MembersInjectorLookup.java b/src/com/google/inject/spi/MembersInjectorLookup.java
index 6357ab6..c3ab792 100644
--- a/src/com/google/inject/spi/MembersInjectorLookup.java
+++ b/src/com/google/inject/spi/MembersInjectorLookup.java
@@ -38,7 +38,7 @@
   private final TypeLiteral<T> type;
   private MembersInjector<T> delegate;
 
-  MembersInjectorLookup(Object source, TypeLiteral<T> type) {
+  public MembersInjectorLookup(Object source, TypeLiteral<T> type) {
     this.source = checkNotNull(source, "source");
     this.type = checkNotNull(type, "type");
   }
@@ -80,4 +80,23 @@
   public MembersInjector<T> getDelegate() {
     return delegate;
   }
+
+  /**
+   * Returns the looked up members injector. The result is not valid until this lookup has been
+   * initialized, which usually happens when the injector is created. The members injector will
+   * throw an {@code IllegalStateException} if you try to use it beforehand.
+   */
+  public MembersInjector<T> getMembersInjector() {
+    return new MembersInjector<T>() {
+      public void injectMembers(T instance) {
+        checkState(delegate != null,
+            "This MembersInjector cannot be used until the Injector has been created.");
+        delegate.injectMembers(instance);
+      }
+
+      @Override public String toString() {
+        return "MembersInjector<" + type + ">";
+      }
+    };
+  }
 }
\ No newline at end of file
diff --git a/src/com/google/inject/spi/ProviderLookup.java b/src/com/google/inject/spi/ProviderLookup.java
index fbbe856..a5a3162 100644
--- a/src/com/google/inject/spi/ProviderLookup.java
+++ b/src/com/google/inject/spi/ProviderLookup.java
@@ -37,7 +37,7 @@
   private final Key<T> key;
   private Provider<T> delegate;
 
-  ProviderLookup(Object source, Key<T> key) {
+  public ProviderLookup(Object source, Key<T> key) {
     this.source = checkNotNull(source, "source");
     this.key = checkNotNull(key, "key");
   }
@@ -75,4 +75,23 @@
   public Provider<T> getDelegate() {
     return delegate;
   }
+
+  /**
+   * Returns the looked up provider. The result is not valid until this lookup has been initialized,
+   * which usually happens when the injector is created. The provider will throw an {@code
+   * IllegalStateException} if you try to use it beforehand.
+   */
+  public Provider<T> getProvider() {
+    return new Provider<T>() {
+      public T get() {
+        checkState(delegate != null,
+            "This Provider cannot be used until the Injector has been created.");
+        return delegate.get();
+      }
+
+      @Override public String toString() {
+        return "Provider<" + key.getTypeLiteral() + ">";
+      }
+    };
+  }
 }
diff --git a/test/com/google/inject/InjectableTypeListenerTest.java b/test/com/google/inject/InjectableTypeListenerTest.java
index 661bde5..9c5e831 100644
--- a/test/com/google/inject/InjectableTypeListenerTest.java
+++ b/test/com/google/inject/InjectableTypeListenerTest.java
@@ -16,20 +16,21 @@
 
 package com.google.inject;
 
+import static com.google.inject.Asserts.assertContains;
 import com.google.inject.internal.ImmutableList;
 import com.google.inject.internal.ImmutableMap;
 import com.google.inject.internal.Lists;
 import com.google.inject.matcher.Matcher;
-import com.google.inject.matcher.Matchers;
 import static com.google.inject.matcher.Matchers.any;
+import static com.google.inject.matcher.Matchers.only;
 import com.google.inject.spi.InjectableType;
 import com.google.inject.spi.InjectableType.Encounter;
 import com.google.inject.spi.InjectionListener;
-import static com.google.inject.Asserts.assertContains;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
 import junit.framework.TestCase;
 import org.aopalliance.intercept.MethodInterceptor;
 import org.aopalliance.intercept.MethodInvocation;
@@ -134,7 +135,7 @@
   }
   
   public void testAddingInterceptors() throws NoSuchMethodException {
-    final Matcher<Object> buzz = Matchers.only(C.class.getMethod("buzz"));
+    final Matcher<Object> buzz = only(C.class.getMethod("buzz"));
 
     Injector injector = Guice.createInjector(new AbstractModule() {
       protected void configure() {
@@ -344,6 +345,69 @@
     assertEquals(2, memberInjectionCounts.getAndSet(0));
   }
 
+  public void testLookupsAtInjectorCreateTime() {
+    final AtomicReference<Provider<B>> bProviderReference = new AtomicReference<Provider<B>>();
+    final AtomicReference<MembersInjector<A>> aMembersInjectorReference
+        = new AtomicReference<MembersInjector<A>>();
+
+    Guice.createInjector(new AbstractModule() {
+      protected void configure() {
+        bindListener(only(TypeLiteral.get(C.class)), new InjectableType.Listener() {
+          public <I> void hear(InjectableType<I> injectableType, Encounter<I> encounter) {
+            Provider<B> bProvider = encounter.getProvider(B.class);
+            try {
+              bProvider.get();
+              fail();
+            } catch (IllegalStateException expected) {
+              assertEquals("This Provider cannot be used until the Injector has been created.",
+                  expected.getMessage());
+            }
+            bProviderReference.set(bProvider);
+
+            MembersInjector<A> aMembersInjector = encounter.getMembersInjector(A.class);
+            try {
+              aMembersInjector.injectMembers(new A());
+              fail();
+            } catch (IllegalStateException expected) {
+              assertEquals(
+                  "This MembersInjector cannot be used until the Injector has been created.",
+                  expected.getMessage());
+            }
+            aMembersInjectorReference.set(aMembersInjector);
+          }
+        });
+
+        bind(C.class);
+      }
+    });
+
+    assertNotNull(bProviderReference.get().get());
+
+    A a = new A();
+    aMembersInjectorReference.get().injectMembers(a);
+    assertNotNull(a.injector);
+  }
+
+  public void testLookupsPostCreate() {
+    Injector injector = Guice.createInjector(new AbstractModule() {
+      protected void configure() {
+        bindListener(only(TypeLiteral.get(C.class)), new InjectableType.Listener() {
+          public <I> void hear(InjectableType<I> injectableType, Encounter<I> encounter) {
+            assertNotNull(encounter.getProvider(B.class).get());
+
+            A a = new A();
+            encounter.getMembersInjector(A.class).injectMembers(a);
+            assertNotNull(a.injector);
+          }
+        });
+      }
+    });
+    
+    injector.getInstance(C.class);
+  }
+
+  // TODO: recursively accessing a lookup should fail
+
   static class A {
     @Inject Injector injector;
     @Inject Stage stage;