blob: 3c07127d3ab46dfe6b7af199b15e31984d7befed [file] [log] [blame]
/*
* 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 static com.google.inject.Asserts.assertContains;
import com.google.inject.internal.Annotations;
import com.google.inject.name.Names;
import com.google.inject.util.Providers;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import javax.inject.Inject;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
/** @author jessewilson@google.com (Jesse Wilson) */
public class MembersInjectorTest extends TestCase {
private static final long DEADLOCK_TIMEOUT_SECONDS = 1;
private static final A<C> uninjectableA =
new A<C>() {
@Inject
@Override
void doNothing() {
throw new AssertionFailedError();
}
};
private static final B uninjectableB =
new B() {
@Inject
@Override
void doNothing() {
throw new AssertionFailedError();
}
};
private static final C myFavouriteC = new C();
public void testMembersInjectorFromBinder() {
final AtomicReference<MembersInjector<A<C>>> aMembersInjectorReference =
new AtomicReference<MembersInjector<A<C>>>();
final AtomicReference<MembersInjector<B>> bMembersInjectorReference =
new AtomicReference<MembersInjector<B>>();
Guice.createInjector(
new AbstractModule() {
@Override
protected void configure() {
MembersInjector<A<C>> aMembersInjector = getMembersInjector(new TypeLiteral<A<C>>() {});
try {
aMembersInjector.injectMembers(uninjectableA);
fail();
} catch (IllegalStateException expected) {
assertContains(
expected.getMessage(),
"This MembersInjector cannot be used until the Injector has been created.");
}
MembersInjector<B> bMembersInjector = getMembersInjector(B.class);
try {
bMembersInjector.injectMembers(uninjectableB);
fail();
} catch (IllegalStateException expected) {
assertContains(
expected.getMessage(),
"This MembersInjector cannot be used until the Injector has been created.");
}
aMembersInjectorReference.set(aMembersInjector);
bMembersInjectorReference.set(bMembersInjector);
assertEquals(
"MembersInjector<java.lang.String>", getMembersInjector(String.class).toString());
bind(C.class).toInstance(myFavouriteC);
}
});
A<C> injectableA = new A<>();
aMembersInjectorReference.get().injectMembers(injectableA);
assertSame(myFavouriteC, injectableA.t);
assertSame(myFavouriteC, injectableA.b.c);
B injectableB = new B();
bMembersInjectorReference.get().injectMembers(injectableB);
assertSame(myFavouriteC, injectableB.c);
B anotherInjectableB = new B();
bMembersInjectorReference.get().injectMembers(anotherInjectableB);
assertSame(myFavouriteC, anotherInjectableB.c);
}
public void testMembersInjectorFromInjector() {
Injector injector =
Guice.createInjector(
new AbstractModule() {
@Override
protected void configure() {
bind(C.class).toInstance(myFavouriteC);
}
});
MembersInjector<A<C>> aMembersInjector =
injector.getMembersInjector(new TypeLiteral<A<C>>() {});
MembersInjector<B> bMembersInjector = injector.getMembersInjector(B.class);
A<C> injectableA = new A<>();
aMembersInjector.injectMembers(injectableA);
assertSame(myFavouriteC, injectableA.t);
assertSame(myFavouriteC, injectableA.b.c);
B injectableB = new B();
bMembersInjector.injectMembers(injectableB);
assertSame(myFavouriteC, injectableB.c);
B anotherInjectableB = new B();
bMembersInjector.injectMembers(anotherInjectableB);
assertSame(myFavouriteC, anotherInjectableB.c);
assertEquals(
"MembersInjector<java.lang.String>", injector.getMembersInjector(String.class).toString());
}
public void testMembersInjectorWithNonInjectedTypes() {
Injector injector = Guice.createInjector();
MembersInjector<NoInjectedMembers> membersInjector =
injector.getMembersInjector(NoInjectedMembers.class);
membersInjector.injectMembers(new NoInjectedMembers());
membersInjector.injectMembers(new NoInjectedMembers());
}
public void testInjectionFailure() {
Injector injector = Guice.createInjector();
MembersInjector<InjectionFailure> membersInjector =
injector.getMembersInjector(InjectionFailure.class);
try {
membersInjector.injectMembers(new InjectionFailure());
fail();
} catch (ProvisionException expected) {
assertContains(
expected.getMessage(),
"1) Error injecting method, java.lang.ClassCastException: whoops, failure #1");
}
}
public void testInjectionAppliesToSpecifiedType() {
Injector injector = Guice.createInjector();
MembersInjector<Object> membersInjector = injector.getMembersInjector(Object.class);
membersInjector.injectMembers(new InjectionFailure());
}
public void testInjectingMembersInjector() {
InjectsMembersInjector injectsMembersInjector =
Guice.createInjector(
new AbstractModule() {
@Override
protected void configure() {
bind(C.class).toInstance(myFavouriteC);
}
})
.getInstance(InjectsMembersInjector.class);
A<C> a = new A<>();
injectsMembersInjector.aMembersInjector.injectMembers(a);
assertSame(myFavouriteC, a.t);
assertSame(myFavouriteC, a.b.c);
}
public void testCannotBindMembersInjector() {
try {
Guice.createInjector(
new AbstractModule() {
@Override
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() {
@Override
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() {
@Override
protected void configure() {
bind(C.class).toInstance(myFavouriteC);
}
});
MembersInjector<A<C>> membersInjector =
injector.getInstance(new Key<MembersInjector<A<C>>>() {});
A<C> a = new A<>();
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="
+ Annotations.memberValueString("foo")
+ ") was bound.");
}
}
/** Callback for member injection. Uses a static type to be referable by getInstance(). */
abstract static class AbstractParallelMemberInjectionCallback {
volatile boolean called = false;
private final Thread mainThread;
private final Class<? extends AbstractParallelMemberInjectionCallback> otherCallbackClass;
AbstractParallelMemberInjectionCallback(
Class<? extends AbstractParallelMemberInjectionCallback> otherCallbackClass) {
this.mainThread = Thread.currentThread();
this.otherCallbackClass = otherCallbackClass;
}
@Inject
void callback(final Injector injector) throws Exception {
called = true;
if (mainThread != Thread.currentThread()) {
// only execute logic on the main thread
return;
}
// verify that other callback can be finished on a separate thread
AbstractParallelMemberInjectionCallback otherCallback =
Executors.newSingleThreadExecutor()
.submit(
new Callable<AbstractParallelMemberInjectionCallback>() {
@Override
public AbstractParallelMemberInjectionCallback call() throws Exception {
return injector.getInstance(otherCallbackClass);
}
})
.get(DEADLOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
assertTrue(otherCallback.called);
try {
// other thread would wait for callback to finish on this thread first
Executors.newSingleThreadExecutor()
.submit(
new Callable<Object>() {
@Override
public Object call() throws Exception {
return injector.getInstance(
AbstractParallelMemberInjectionCallback.this.getClass());
}
})
.get(DEADLOCK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
fail();
} catch (TimeoutException expected) {
// recursive call from another thread should time out
// as it would be waiting for this thread to finish
}
}
}
static class ParallelMemberInjectionCallback1 extends AbstractParallelMemberInjectionCallback {
ParallelMemberInjectionCallback1() {
super(ParallelMemberInjectionCallback2.class);
}
}
static class ParallelMemberInjectionCallback2 extends AbstractParallelMemberInjectionCallback {
ParallelMemberInjectionCallback2() {
super(ParallelMemberInjectionCallback1.class);
}
}
/**
* Tests that member injections could happen in parallel.
*
* <p>Additional check that when member injection happen other threads would wait for it to finish
* to provide proper resolution order semantics.
*/
public void testMemberInjectorParallelization() throws Exception {
final ParallelMemberInjectionCallback1 c1 = new ParallelMemberInjectionCallback1();
final ParallelMemberInjectionCallback2 c2 = new ParallelMemberInjectionCallback2();
Guice.createInjector(
new AbstractModule() {
@Override
protected void configure() {
bind(ParallelMemberInjectionCallback1.class).toInstance(c1);
bind(ParallelMemberInjectionCallback2.class).toInstance(c2);
}
});
assertTrue(c1.called);
assertTrue(c2.called);
}
/** Member injection callback that injects itself. */
static class RecursiveMemberInjection {
boolean called = false;
@Inject
void callback(RecursiveMemberInjection recursiveMemberInjection) {
if (called) {
fail("Should not be called twice");
}
called = true;
}
}
/** Verifies that member injection injecting itself would get a non initialized instance. */
public void testRecursiveMemberInjector() throws Exception {
final RecursiveMemberInjection rmi = new RecursiveMemberInjection();
Guice.createInjector(
new AbstractModule() {
@Override
protected void configure() {
bind(RecursiveMemberInjection.class).toInstance(rmi);
}
});
assertTrue("Member injection should happen", rmi.called);
}
static class A<T> {
@Inject B b;
@Inject T t;
@Inject
void doNothing() {}
}
static class B {
@Inject C c;
@Inject
void doNothing() {}
}
static class C {}
static class NoInjectedMembers {}
static class InjectionFailure {
int failures = 0;
@Inject
void fail() {
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 {}
}