/*
* Copyright (C) 2007 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 static com.google.inject.name.Names.named;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.Runnables;
import com.google.inject.matcher.Matchers;
import com.google.inject.spi.InjectionPoint;
import com.google.inject.spi.TypeEncounter;
import com.google.inject.spi.TypeListener;
import junit.framework.TestCase;
/*if[AOP]*/
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/*end[AOP]*/
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
/**
* @author crazybob@google.com (Bob Lee)
*/
public class BindingTest extends TestCase {
static class Dependent {
@Inject A a;
@Inject Dependent(A a, B b) {}
@Inject void injectBob(Bob bob) {}
}
public void testExplicitCyclicDependency() {
Guice.createInjector(new AbstractModule() {
protected void configure() {
bind(A.class);
bind(B.class);
}
}).getInstance(A.class);
}
static class A { @Inject B b; }
static class B { @Inject A a; }
static class Bob {}
static class MyModule extends AbstractModule {
protected void configure() {
// Linked.
bind(Object.class).to(Runnable.class).in(Scopes.SINGLETON);
// Instance.
bind(Runnable.class).toInstance(Runnables.doNothing());
// Provider instance.
bind(Foo.class).toProvider(new Provider<Foo>() {
public Foo get() {
return new Foo();
}
}).in(Scopes.SINGLETON);
// Provider.
bind(Foo.class)
.annotatedWith(named("provider"))
.toProvider(FooProvider.class);
// Class.
bind(Bar.class).in(Scopes.SINGLETON);
// Constant.
bindConstant().annotatedWith(named("name")).to("Bob");
}
}
static class Foo {}
public static class FooProvider implements Provider<Foo> {
public Foo get() {
throw new UnsupportedOperationException();
}
}
public static class Bar {}
public void testBindToUnboundLinkedBinding() {
try {
Guice.createInjector(new AbstractModule() {
protected void configure() {
bind(Collection.class).to(List.class);
}
});
fail();
} catch (CreationException expected) {
assertContains(expected.getMessage(), "No implementation for java.util.List was bound.");
}
}
/**
* This test ensures that the asEagerSingleton() scoping applies to the key,
* not to what the key is linked to.
*/
public void testScopeIsAppliedToKeyNotTarget() {
Injector injector = Guice.createInjector(new AbstractModule() {
protected void configure() {
bind(Integer.class).toProvider(Counter.class).asEagerSingleton();
bind(Number.class).toProvider(Counter.class).asEagerSingleton();
}
});
assertNotSame(injector.getInstance(Integer.class), injector.getInstance(Number.class));
}
static class Counter implements Provider<Integer> {
static AtomicInteger next = new AtomicInteger(1);
public Integer get() {
return next.getAndIncrement();
}
}
public void testAnnotatedNoArgConstructor() {
assertBindingSucceeds(PublicNoArgAnnotated.class);
assertBindingSucceeds(ProtectedNoArgAnnotated.class);
assertBindingSucceeds(PackagePrivateNoArgAnnotated.class);
assertBindingSucceeds(PrivateNoArgAnnotated.class);
}
static class PublicNoArgAnnotated {
@Inject public PublicNoArgAnnotated() { }
}
static class ProtectedNoArgAnnotated {
@Inject protected ProtectedNoArgAnnotated() { }
}
static class PackagePrivateNoArgAnnotated {
@Inject PackagePrivateNoArgAnnotated() { }
}
static class PrivateNoArgAnnotated {
@Inject private PrivateNoArgAnnotated() { }
}
public void testUnannotatedNoArgConstructor() throws Exception{
assertBindingSucceeds(PublicNoArg.class);
assertBindingSucceeds(ProtectedNoArg.class);
assertBindingSucceeds(PackagePrivateNoArg.class);
assertBindingSucceeds(PrivateNoArgInPrivateClass.class);
assertBindingFails(PrivateNoArg.class);
}
static class PublicNoArg {
public PublicNoArg() { }
}
static class ProtectedNoArg {
protected ProtectedNoArg() { }
}
static class PackagePrivateNoArg {
PackagePrivateNoArg() { }
}
private static class PrivateNoArgInPrivateClass {
PrivateNoArgInPrivateClass() { }
}
static class PrivateNoArg {
private PrivateNoArg() { }
}
private void assertBindingSucceeds(final Class<?> clazz) {
assertNotNull(Guice.createInjector().getInstance(clazz));
}
private void assertBindingFails(final Class<?> clazz) throws NoSuchMethodException {
try {
Guice.createInjector().getInstance(clazz);
fail();
} catch (ConfigurationException expected) {
assertContains(expected.getMessage(),
"Could not find a suitable constructor in " + PrivateNoArg.class.getName(),
"at " + PrivateNoArg.class.getName() + ".class(BindingTest.java:");
}
}
public void testTooManyConstructors() {
try {
Guice.createInjector().getInstance(TooManyConstructors.class);
fail();
} catch (ConfigurationException expected) {
assertContains(expected.getMessage(),
TooManyConstructors.class.getName() + " has more than one constructor annotated with "
+ "@Inject. Classes must have either one (and only one) constructor",
"at " + TooManyConstructors.class.getName() + ".class(BindingTest.java:");
}
}
static class TooManyConstructors {
@Inject TooManyConstructors(Injector i) {}
@Inject TooManyConstructors() {}
}
public void testToConstructorBinding() throws NoSuchMethodException {
final Constructor<D> constructor = D.class.getConstructor(Stage.class);
Injector injector = Guice.createInjector(new AbstractModule() {
protected void configure() {
bind(Object.class).toConstructor(constructor);
}
});
D d = (D) injector.getInstance(Object.class);
assertEquals(Stage.DEVELOPMENT, d.stage);
}
public void testToConstructorBindingsOnParameterizedTypes() throws NoSuchMethodException {
final Constructor<C> constructor = C.class.getConstructor(Stage.class, Object.class);
final Key<Object> s = new Key<Object>(named("s")) {};
final Key<Object> i = new Key<Object>(named("i")) {};
Injector injector = Guice.createInjector(new AbstractModule() {
protected void configure() {
bind(s).toConstructor(constructor, new TypeLiteral<C<Stage>>() {});
bind(i).toConstructor(constructor, new TypeLiteral<C<Injector>>() {});
}
});
C<Stage> one = (C<Stage>) injector.getInstance(s);
assertEquals(Stage.DEVELOPMENT, one.stage);
assertEquals(Stage.DEVELOPMENT, one.t);
assertEquals(Stage.DEVELOPMENT, one.anotherT);
C<Injector> two = (C<Injector>) injector.getInstance(i);
assertEquals(Stage.DEVELOPMENT, two.stage);
assertEquals(injector, two.t);
assertEquals(injector, two.anotherT);
}
public void testToConstructorBindingsFailsOnRawTypes() throws NoSuchMethodException {
final Constructor constructor = C.class.getConstructor(Stage.class, Object.class);
try {
Guice.createInjector(new AbstractModule() {
protected void configure() {
bind(Object.class).toConstructor(constructor);
}
});
fail();
} catch (CreationException expected) {
assertContains(expected.getMessage(),
"1) T cannot be used as a key; It is not fully specified.",
"at " + C.class.getName() + ".<init>(BindingTest.java:",
"2) T cannot be used as a key; It is not fully specified.",
"at " + C.class.getName() + ".anotherT(BindingTest.java:");
}
}
/*if[AOP]*/
public void testToConstructorAndMethodInterceptors() throws NoSuchMethodException {
final Constructor<D> constructor = D.class.getConstructor(Stage.class);
final AtomicInteger count = new AtomicInteger();
final MethodInterceptor countingInterceptor = new MethodInterceptor() {
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
count.incrementAndGet();
return methodInvocation.proceed();
}
};
Injector injector = Guice.createInjector(new AbstractModule() {
protected void configure() {
bind(Object.class).toConstructor(constructor);
bindInterceptor(Matchers.any(), Matchers.any(), countingInterceptor);
}
});
D d = (D) injector.getInstance(Object.class);
d.hashCode();
d.hashCode();
assertEquals(2, count.get());
}
/*end[AOP]*/
public void testInaccessibleConstructor() throws NoSuchMethodException {
final Constructor<E> constructor = E.class.getDeclaredConstructor(Stage.class);
Injector injector = Guice.createInjector(new AbstractModule() {
protected void configure() {
bind(E.class).toConstructor(constructor);
}
});
E e = injector.getInstance(E.class);
assertEquals(Stage.DEVELOPMENT, e.stage);
}
public void testToConstructorAndScopes() throws NoSuchMethodException {
final Constructor<F> constructor = F.class.getConstructor(Stage.class);
final Key<Object> d = Key.get(Object.class, named("D")); // default scoping
final Key<Object> s = Key.get(Object.class, named("S")); // singleton
final Key<Object> n = Key.get(Object.class, named("N")); // "N" instances
final Key<Object> r = Key.get(Object.class, named("R")); // a regular binding
Injector injector = Guice.createInjector(new AbstractModule() {
protected void configure() {
bind(d).toConstructor(constructor);
bind(s).toConstructor(constructor).in(Singleton.class);
bind(n).toConstructor(constructor).in(Scopes.NO_SCOPE);
bind(r).to(F.class);
}
});
assertDistinct(injector, 1, d, d, d, d);
assertDistinct(injector, 1, s, s, s, s);
assertDistinct(injector, 4, n, n, n, n);
assertDistinct(injector, 1, r, r, r, r);
assertDistinct(injector, 4, d, d, r, r, s, s, n);
}
public void assertDistinct(Injector injector, int expectedCount, Key<?>... keys) {
ImmutableSet.Builder<Object> builder = ImmutableSet.builder();
for (Key<?> k : keys) {
builder.add(injector.getInstance(k));
}
assertEquals(expectedCount, builder.build().size());
}
public void testToConstructorSpiData() throws NoSuchMethodException {
final Set<TypeLiteral<?>> heardTypes = Sets.newHashSet();
final Constructor<D> constructor = D.class.getConstructor(Stage.class);
final TypeListener listener = new TypeListener() {
public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) {
if (!heardTypes.add(type)) {
fail("Heard " + type + " multiple times!");
}
}
};
Guice.createInjector(new AbstractModule() {
protected void configure() {
bind(Object.class).toConstructor(constructor);
bind(D.class).toConstructor(constructor);
bindListener(Matchers.any(), listener);
}
});
assertEquals(ImmutableSet.of(TypeLiteral.get(D.class)), heardTypes);
}
public void testInterfaceToImplementationConstructor() throws NoSuchMethodException {
final Constructor<CFoo> constructor = CFoo.class.getDeclaredConstructor();
Injector injector = Guice.createInjector(new AbstractModule() {
protected void configure() {
bind(IFoo.class).toConstructor(constructor);
}
});
injector.getInstance(IFoo.class);
}
public static interface IFoo {}
public static class CFoo implements IFoo {}
public void testGetAllBindings() {
Injector injector = Guice.createInjector(new AbstractModule() {
protected void configure() {
bind(D.class).toInstance(new D(Stage.PRODUCTION));
bind(Object.class).to(D.class);
getProvider(new Key<C<Stage>>() {});
}
});
Map<Key<?>,Binding<?>> bindings = injector.getAllBindings();
assertEquals(ImmutableSet.of(Key.get(Injector.class), Key.get(Stage.class), Key.get(D.class),
Key.get(Logger.class), Key.get(Object.class), new Key<C<Stage>>() {}),
bindings.keySet());
// add a JIT binding
injector.getInstance(F.class);
Map<Key<?>,Binding<?>> bindings2 = injector.getAllBindings();
assertEquals(ImmutableSet.of(Key.get(Injector.class), Key.get(Stage.class), Key.get(D.class),
Key.get(Logger.class), Key.get(Object.class), new Key<C<Stage>>() {}, Key.get(F.class)),
bindings2.keySet());
// the original map shouldn't have changed
assertEquals(ImmutableSet.of(Key.get(Injector.class), Key.get(Stage.class), Key.get(D.class),
Key.get(Logger.class), Key.get(Object.class), new Key<C<Stage>>() {}),
bindings.keySet());
// check the bindings' values
assertEquals(injector, bindings.get(Key.get(Injector.class)).getProvider().get());
}
public void testGetAllServletBindings() throws Exception {
Injector injector = Guice.createInjector(new AbstractModule() {
protected void configure() {
bind(F.class); // an explicit binding that uses a JIT binding for a constructor
}
});
injector.getAllBindings();
}
public static class C<T> {
private Stage stage;
private T t;
@Inject T anotherT;
public C(Stage stage, T t) {
this.stage = stage;
this.t = t;
}
@Inject C() {}
}
public static class D {
Stage stage;
public D(Stage stage) {
this.stage = stage;
}
}
private static class E {
Stage stage;
private E(Stage stage) {
this.stage = stage;
}
}
@Singleton
public static class F {
Stage stage;
@Inject public F(Stage stage) {
this.stage = stage;
}
}
public void testTurkeyBaconProblemUsingToConstuctor() {
Injector injector = Guice.createInjector(new AbstractModule() {
@SuppressWarnings("unchecked")
@Override
public void configure() {
bind(Bacon.class).to(UncookedBacon.class);
bind(Bacon.class).annotatedWith(named("Turkey")).to(TurkeyBacon.class);
bind(Bacon.class).annotatedWith(named("Cooked")).toConstructor(
(Constructor)InjectionPoint.forConstructorOf(Bacon.class).getMember());
}
});
Bacon bacon = injector.getInstance(Bacon.class);
assertEquals(Food.PORK, bacon.getMaterial());
assertFalse(bacon.isCooked());
Bacon turkeyBacon = injector.getInstance(Key.get(Bacon.class, named("Turkey")));
assertEquals(Food.TURKEY, turkeyBacon.getMaterial());
assertTrue(turkeyBacon.isCooked());
Bacon cookedBacon = injector.getInstance(Key.get(Bacon.class, named("Cooked")));
assertEquals(Food.PORK, cookedBacon.getMaterial());
assertTrue(cookedBacon.isCooked());
}
enum Food { TURKEY, PORK }
private static class Bacon {
public Food getMaterial() { return Food.PORK; }
public boolean isCooked() { return true; }
}
private static class TurkeyBacon extends Bacon {
public Food getMaterial() { return Food.TURKEY; }
}
private static class UncookedBacon extends Bacon {
public boolean isCooked() { return false; }
}
}