New tests for toConstructor().
I found one bug - we were looking for scoping annotations on the bind source type rather than the bind target type. This is fixed now.
One weird behaviour - @Singleton is per-binding, not per-type. Check the scoping test for an illustration.
git-svn-id: https://google-guice.googlecode.com/svn/trunk@1024 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/src/com/google/inject/internal/ConstructorBindingImpl.java b/src/com/google/inject/internal/ConstructorBindingImpl.java
index 5154126..5e07306 100644
--- a/src/com/google/inject/internal/ConstructorBindingImpl.java
+++ b/src/com/google/inject/internal/ConstructorBindingImpl.java
@@ -17,8 +17,8 @@
package com.google.inject.internal;
import com.google.inject.Binder;
-import com.google.inject.Key;
import com.google.inject.ConfigurationException;
+import com.google.inject.Key;
import static com.google.inject.internal.Preconditions.checkState;
import static com.google.inject.internal.Annotations.findScopeAnnotation;
import com.google.inject.spi.BindingTargetVisitor;
@@ -76,9 +76,21 @@
errors.cannotInjectInnerClass(rawType);
}
+ errors.throwIfNewErrors(numErrors);
+
+ // Find a constructor annotated @Inject
+ if (constructorInjector == null) {
+ try {
+ constructorInjector = InjectionPoint.forConstructorOf(key.getTypeLiteral());
+ } catch (ConfigurationException e) {
+ throw errors.merge(e.getErrorMessages()).toException();
+ }
+ }
+
// if no scope is specified, look for a scoping annotation on the concrete class
if (!scoping.isExplicitlyScoped()) {
- Class<? extends Annotation> scopeAnnotation = findScopeAnnotation(errors, rawType);
+ Class<?> annotatedType = constructorInjector.getMember().getDeclaringClass();
+ Class<? extends Annotation> scopeAnnotation = findScopeAnnotation(errors, annotatedType);
if (scopeAnnotation != null) {
scoping = Scoping.makeInjectable(Scoping.forAnnotation(scopeAnnotation),
injector, errors.withSource(rawType));
@@ -91,15 +103,6 @@
InternalFactory<? extends T> scopedFactory
= Scoping.scope(key, injector, factoryFactory, scoping);
- // Find a constructor annotated @Inject
- if (constructorInjector == null) {
- try {
- constructorInjector = InjectionPoint.forConstructorOf(key.getTypeLiteral());
- } catch (ConfigurationException e) {
- throw errors.merge(e.getErrorMessages()).toException();
- }
- }
-
return new ConstructorBindingImpl<T>(
injector, key, source, scopedFactory, scoping, factoryFactory, constructorInjector);
}
diff --git a/test/com/google/inject/BindingTest.java b/test/com/google/inject/BindingTest.java
index 83d53b7..e5b7bf3 100644
--- a/test/com/google/inject/BindingTest.java
+++ b/test/com/google/inject/BindingTest.java
@@ -17,12 +17,20 @@
package com.google.inject;
import static com.google.inject.Asserts.assertContains;
-import com.google.inject.name.Names;
+import com.google.inject.internal.ImmutableSet;
+import com.google.inject.internal.Sets;
+import com.google.inject.matcher.Matchers;
+import static com.google.inject.name.Names.named;
+import com.google.inject.spi.TypeEncounter;
+import com.google.inject.spi.TypeListener;
+import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
-import java.lang.reflect.Constructor;
import junit.framework.TestCase;
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
/**
* @author crazybob@google.com (Bob Lee)
@@ -69,14 +77,14 @@
// Provider.
bind(Foo.class)
- .annotatedWith(Names.named("provider"))
+ .annotatedWith(named("provider"))
.toProvider(FooProvider.class);
// Class.
bind(Bar.class).in(Scopes.SINGLETON);
// Constant.
- bindConstant().annotatedWith(Names.named("name")).to("Bob");
+ bindConstant().annotatedWith(named("name")).to("Bob");
}
}
@@ -229,20 +237,6 @@
assertEquals(injector, two.anotherT);
}
- 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 void testToConstructorBinding() throws NoSuchMethodException {
final Constructor<D> constructor = D.class.getConstructor(Stage.class);
@@ -256,10 +250,130 @@
assertEquals(Stage.DEVELOPMENT, d.stage);
}
+ 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());
+ }
+
+ 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(Stage.class), TypeLiteral.get(D.class)),
+ heardTypes);
+ }
+
+ 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;
+ }
+ }
}