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;
+    }
+  }
 }