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 {}
}