It is now possible to inject a MembersInjector<T> anywhere, just like how we already support injecting a Provider<T> and a TypeLiteral<T> anywhere.

git-svn-id: https://google-guice.googlecode.com/svn/trunk@914 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/src/com/google/inject/BindingProcessor.java b/src/com/google/inject/BindingProcessor.java
index 46d4105..e3a6d38 100644
--- a/src/com/google/inject/BindingProcessor.java
+++ b/src/com/google/inject/BindingProcessor.java
@@ -260,8 +260,9 @@
       Binding.class,
       Injector.class,
       Key.class,
+      MembersInjector.class,
       Module.class,
-      Provider.class, 
+      Provider.class,
       Scope.class,
       TypeLiteral.class);
   // TODO(jessewilson): fix BuiltInModule, then add Stage
diff --git a/src/com/google/inject/InjectorImpl.java b/src/com/google/inject/InjectorImpl.java
index 9bd6006..8df8b78 100644
--- a/src/com/google/inject/InjectorImpl.java
+++ b/src/com/google/inject/InjectorImpl.java
@@ -176,12 +176,41 @@
     }
   }
 
-  /* Returns true if the key type is Provider<?> (but not a subclass of Provider<?>). */
+  /** Returns true if the key type is Provider (but not a subclass of Provider). */
   static boolean isProvider(Key<?> key) {
     return key.getTypeLiteral().getRawType().equals(Provider.class);
   }
 
-  /** Creates a synthetic binding to Provider<T>, i.e. a binding to the provider from Binding<T>. */
+  /** Returns true if the key type is MembersInjector (but not a subclass of MembersInjector). */
+  static boolean isMembersInjector(Key<?> key) {
+    return key.getTypeLiteral().getRawType().equals(MembersInjector.class)
+        && !key.hasAnnotationType();
+  }
+
+  private <T> BindingImpl<MembersInjector<T>> createMembersInjectorBinding(
+      Key<MembersInjector<T>> key, Errors errors) throws ErrorsException {
+    Type membersInjectorType = key.getTypeLiteral().getType();
+    if (!(membersInjectorType instanceof ParameterizedType)) {
+      throw errors.cannotInjectRawMembersInjector().toException();
+    }
+
+    @SuppressWarnings("unchecked") // safe because T came from Key<MembersInjector<T>>
+    TypeLiteral<T> instanceType = (TypeLiteral<T>) TypeLiteral.get(
+        ((ParameterizedType) membersInjectorType).getActualTypeArguments()[0]);
+    MembersInjector<T> membersInjector = getMembersInjectorOrThrow(instanceType, errors);
+
+    InternalFactory<MembersInjector<T>> factory = new ConstantFactory<MembersInjector<T>>(
+        Initializables.of(membersInjector));
+
+
+    return new InstanceBindingImpl<MembersInjector<T>>(this, key, SourceProvider.UNKNOWN_SOURCE, 
+        factory, ImmutableSet.<InjectionPoint>of(), membersInjector);
+  }
+
+  /**
+   * Creates a synthetic binding to {@code Provider<T>}, i.e. a binding to the provider from
+   * {@code Binding<T>}.
+   */
   private <T> BindingImpl<Provider<T>> createProviderBinding(Key<Provider<T>> key, Errors errors)
       throws ErrorsException {
     Type providerType = key.getTypeLiteral().getType();
@@ -585,6 +614,15 @@
       return binding;
     }
 
+    // Handle cases where T is a MembersInjector<?>
+    if (isMembersInjector(key)) {
+      // These casts are safe. T extends MembersInjector<X> and that given Key<MembersInjector<X>>,
+      // createMembersInjectorBinding() will return BindingImpl<MembersInjector<X>>.
+      @SuppressWarnings("unchecked")
+      BindingImpl binding = createMembersInjectorBinding((Key) key, errors);
+      return binding;
+    }
+
     // Try to convert a constant string binding to the requested type.
     BindingImpl<T> convertedBinding = convertConstantStringBinding(key, errors);
     if (convertedBinding != null) {
@@ -796,7 +834,7 @@
     };
   }
 
-  <T> MembersInjector<T> getMembersInjectorOrThrow(TypeLiteral<T> type, final Errors errors)
+  <T> MembersInjector<T> getMembersInjectorOrThrow(final TypeLiteral<T> type, final Errors errors)
       throws ErrorsException {
     final ImmutableList<SingleMemberInjector> memberInjectors = injectors.get(type, errors);
 
@@ -810,6 +848,10 @@
 
         errors.throwProvisionExceptionIfErrorsExist();
       }
+
+      @Override public String toString() {
+        return "MembersInjector<" + type + ">";
+      }
     };
   }
 
diff --git a/src/com/google/inject/internal/Errors.java b/src/com/google/inject/internal/Errors.java
index 47a0d77..83308b9 100644
--- a/src/com/google/inject/internal/Errors.java
+++ b/src/com/google/inject/internal/Errors.java
@@ -306,6 +306,10 @@
     return addMessage("Cannot inject a Provider that has no type parameter");
   }
 
+  public Errors cannotInjectRawMembersInjector() {
+    return addMessage("Cannot inject a MembersInjector that has no type parameter");
+  }
+
   public Errors cannotInjectTypeLiteralOf(Type unsupportedType) {
     return addMessage("Cannot inject a TypeLiteral of %s", unsupportedType);
   }
diff --git a/test/com/google/inject/MembersInjectorTest.java b/test/com/google/inject/MembersInjectorTest.java
index e182ccb..cf0bf86 100644
--- a/test/com/google/inject/MembersInjectorTest.java
+++ b/test/com/google/inject/MembersInjectorTest.java
@@ -17,6 +17,8 @@
 package com.google.inject;
 
 import static com.google.inject.Asserts.assertContains;
+import com.google.inject.name.Names;
+import com.google.inject.util.Providers;
 import java.util.concurrent.atomic.AtomicReference;
 import junit.framework.AssertionFailedError;
 import junit.framework.TestCase;
@@ -69,6 +71,9 @@
         aMembersInjectorReference.set(aMembersInjector);
         bMembersInjectorReference.set(bMembersInjector);
 
+        assertEquals("MembersInjector<java.lang.String>",
+            getMembersInjector(String.class).toString());
+
         bind(C.class).toInstance(myFavouriteC);
       }
     });
@@ -110,6 +115,9 @@
     B anotherInjectableB = new B();
     bMembersInjector.injectMembers(anotherInjectableB);
     assertSame(myFavouriteC, anotherInjectableB.c);
+
+    assertEquals("MembersInjector<java.lang.String>",
+        injector.getMembersInjector(String.class).toString());
   }
 
   public void testMembersInjectorWithNonInjectedTypes() {
@@ -144,6 +152,102 @@
     membersInjector.injectMembers(new InjectionFailure());
   }
 
+  public void testInjectingMembersInjector() {
+    InjectsMembersInjector injectsMembersInjector = Guice.createInjector(new AbstractModule() {
+      protected void configure() {
+        bind(C.class).toInstance(myFavouriteC);
+      }
+    }).getInstance(InjectsMembersInjector.class);
+
+    A<C> a = new A<C>();
+    injectsMembersInjector.aMembersInjector.injectMembers(a);
+    assertSame(myFavouriteC, a.t);
+    assertSame(myFavouriteC, a.b.c);
+  }
+
+  public void testCannotBindMembersInjector() {
+    try {
+      Guice.createInjector(new AbstractModule() {
+        protected void configure() {
+          bind(MembersInjector.class).toProvider(Providers.<MembersInjector>of(null));
+        }
+      });
+      fail();
+    } catch (CreationException expected) {
+      assertContains(expected.getMessage(),
+          "1) Binding to core guice framework type is not allowed: MembersInjector.");
+    }
+
+    try {
+      Guice.createInjector(new AbstractModule() {
+        protected void configure() {
+          bind(new TypeLiteral<MembersInjector<A<C>>>() {})
+              .toProvider(Providers.<MembersInjector<A<C>>>of(null));
+        }
+      });
+      fail();
+    } catch (CreationException expected) {
+      assertContains(expected.getMessage(),
+          "1) Binding to core guice framework type is not allowed: MembersInjector.");
+    }
+  }
+
+  public void testInjectingMembersInjectorWithErrorsInDependencies() {
+    try {
+      Guice.createInjector().getInstance(InjectsBrokenMembersInjector.class);
+      fail();
+    } catch (ConfigurationException expected) {
+      assertContains(expected.getMessage(),
+          "1) No implementation for " + Unimplemented.class.getName() + " was bound.",
+          "while locating " + Unimplemented.class.getName(),
+          "for field at " + A.class.getName() + ".t(MembersInjectorTest.java:",
+          "while locating com.google.inject.MembersInjector<",
+          "for field at " + InjectsBrokenMembersInjector.class.getName() + ".aMembersInjector(",
+          "while locating " + InjectsBrokenMembersInjector.class.getName());
+    }
+  }
+
+  public void testLookupMembersInjectorBinding() {
+    Injector injector = Guice.createInjector(new AbstractModule() {
+      protected void configure() {
+        bind(C.class).toInstance(myFavouriteC);
+      }
+    });
+    MembersInjector<A<C>> membersInjector =
+        injector.getInstance(new Key<MembersInjector<A<C>>>() {});
+
+    A<C> a = new A<C>();
+    membersInjector.injectMembers(a);
+    assertSame(myFavouriteC, a.t);
+    assertSame(myFavouriteC, a.b.c);
+
+    assertEquals("MembersInjector<java.lang.String>",
+        injector.getInstance(new Key<MembersInjector<String>>() {}).toString());
+  }
+
+  public void testGettingRawMembersInjector() {
+    Injector injector = Guice.createInjector();
+    try {
+      injector.getInstance(MembersInjector.class);
+      fail();
+    } catch (ConfigurationException expected) {
+      assertContains(expected.getMessage(),
+          "Cannot inject a MembersInjector that has no type parameter");
+    }
+  }
+
+  public void testGettingAnnotatedMembersInjector() {
+    Injector injector = Guice.createInjector();
+    try {
+      injector.getInstance(new Key<MembersInjector<String>>(Names.named("foo")) {});
+      fail();
+    } catch (ConfigurationException expected) {
+      assertContains(expected.getMessage(),
+          "1) No implementation for com.google.inject.MembersInjector<java.lang.String> "
+              + "annotated with @com.google.inject.name.Named(value=foo) was bound.");
+    }
+  }
+
   static class A<T> {
     @Inject B b;
     @Inject T t;
@@ -166,4 +270,15 @@
       throw new ClassCastException("whoops, failure #" + (++failures));
     }
   }
+
+  static class InjectsMembersInjector {
+    @Inject MembersInjector<A<C>> aMembersInjector;
+    @Inject A<B> ab;
+  }
+
+  static class InjectsBrokenMembersInjector {
+    @Inject MembersInjector<A<Unimplemented>> aMembersInjector;
+  }
+
+  static interface Unimplemented {}
 }