Added support for surrogate injection annotations.

git-svn-id: https://google-guice.googlecode.com/svn/trunk@132 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/src/com/google/inject/ConstructorInjector.java b/src/com/google/inject/ConstructorInjector.java
index 24f56ed..d36ea31 100644
--- a/src/com/google/inject/ConstructorInjector.java
+++ b/src/com/google/inject/ConstructorInjector.java
@@ -16,6 +16,9 @@
 
 package com.google.inject;
 
+import com.google.inject.util.SurrogateAnnotations;
+import com.google.inject.util.DuplicateAnnotationException;
+
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
 
@@ -31,6 +34,9 @@
   final ContainerImpl.ParameterInjector<?>[] parameterInjectors;
   final ConstructionProxy<T> constructionProxy;
 
+  /** Annotation on the constructor. */
+  Inject inject;
+
   ConstructorInjector(ContainerImpl container, Class<T> implementation) {
     this.implementation = implementation;
     Constructor<T> constructor = findConstructorIn(container, implementation);
@@ -53,7 +59,6 @@
   ContainerImpl.ParameterInjector<?>[] createParameterInjector(
       ContainerImpl container, Constructor<T> constructor) {
     try {
-      Inject inject = constructor.getAnnotation(Inject.class);
       return inject == null
           ? null // default constructor.
           : container.getParametersInjectors(
@@ -76,13 +81,23 @@
     Constructor<T>[] constructors
         = (Constructor<T>[]) implementation.getDeclaredConstructors();
     for (Constructor<T> constructor : constructors) {
-      if (constructor.getAnnotation(Inject.class) != null) {
+      Inject inject = null;
+      try {
+        inject = SurrogateAnnotations.findAnnotation(Inject.class, constructor);
+      } catch (DuplicateAnnotationException e) {
+        container.errorHandler.handle(ErrorMessages.DUPLICATE_ANNOTATIONS,
+            Inject.class.getSimpleName(), constructor, e.getFirst(),
+            e.getSecond());
+      }
+
+      if (inject != null) {
         if (found != null) {
           container.errorHandler.handle(
               ErrorMessages.TOO_MANY_CONSTRUCTORS, implementation);
           return ContainerImpl.invalidConstructor();
         }
         found = constructor;
+        this.inject = inject;
       }
     }
     if (found != null) {
diff --git a/src/com/google/inject/ContainerImpl.java b/src/com/google/inject/ContainerImpl.java
index 582684e..7f49d68 100644
--- a/src/com/google/inject/ContainerImpl.java
+++ b/src/com/google/inject/ContainerImpl.java
@@ -21,6 +21,8 @@
 import com.google.inject.util.ReferenceCache;
 import com.google.inject.util.Strings;
 import com.google.inject.util.ToStringBuilder;
+import com.google.inject.util.SurrogateAnnotations;
+import com.google.inject.util.DuplicateAnnotationException;
 
 import net.sf.cglib.reflect.FastClass;
 import net.sf.cglib.reflect.FastMethod;
@@ -334,7 +336,15 @@
       InjectorFactory<M> injectorFactory) {
     for (M member : members) {
       if (isStatic(member) == statics) {
-        Inject inject = member.getAnnotation(Inject.class);
+        Inject inject = null;
+        try {
+          inject = SurrogateAnnotations.findAnnotation(Inject.class, member);
+        } catch (DuplicateAnnotationException e) {
+          errorHandler.handle(ErrorMessages.DUPLICATE_ANNOTATIONS,
+              Inject.class.getSimpleName(), member, e.getFirst(),
+              e.getSecond());
+        }
+
         if (inject != null) {
           try {
             injectors.add(injectorFactory.create(this, member, inject.value()));
@@ -463,18 +473,27 @@
         = Arrays.asList(annotations).iterator();
     int index = 0;
     for (Type parameterType : parameterTypes) {
-      Inject annotation = findInject(annotationsIterator.next());
+      Annotation[] parameterAnnotations = annotationsIterator.next();
+      Inject inject = null;
+      try {
+        inject = SurrogateAnnotations.findAnnotation(Inject.class,
+            parameterAnnotations);
+      } catch (DuplicateAnnotationException e) {
+        errorHandler.handle(ErrorMessages.DUPLICATE_ANNOTATIONS,
+            Inject.class.getSimpleName(), member, e.getFirst(),
+            e.getSecond());
+      }
 
       String name;
       if (defaultNameOverridden) {
         name = defaultName;
-        if (annotation != null) {
+        if (inject != null) {
           errorHandler.handle(
               ErrorMessages.NAME_ON_MEMBER_AND_PARAMETER, member);
         }
       }
       else {
-        name = annotation == null ? defaultName : annotation.value();
+        name = inject == null ? defaultName : inject.value();
       }
 
       Key<?> key = Key.get(parameterType, name);
@@ -497,18 +516,6 @@
     return new ParameterInjector<T>(externalContext, factory);
   }
 
-  /**
-   * Finds the {@link Inject} annotation in an array of annotations.
-   */
-  Inject findInject(Annotation[] annotations) {
-    for (Annotation annotation : annotations) {
-      if (annotation.annotationType() == Inject.class) {
-        return Inject.class.cast(annotation);
-      }
-    }
-    return null;
-  }
-
   static class MethodInjector implements Injector {
 
     final FastMethod fastMethod;
diff --git a/src/com/google/inject/Inject.java b/src/com/google/inject/Inject.java
index 087d6ca..10e4f7c 100644
--- a/src/com/google/inject/Inject.java
+++ b/src/com/google/inject/Inject.java
@@ -16,6 +16,7 @@
 
 package com.google.inject;
 
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
 import static java.lang.annotation.ElementType.CONSTRUCTOR;
 import static java.lang.annotation.ElementType.FIELD;
 import static java.lang.annotation.ElementType.METHOD;
@@ -26,11 +27,13 @@
 
 /**
  * <p>Annotates members and parameters which should have their value[s]
- * injected.
+ * injected. If applied to another annotation type, that annotation type
+ * can act as a surrogate and save you from having to repeat the dependency
+ * name over and over.
  *
  * @author crazybob@google.com (Bob Lee)
  */
-@Target({ METHOD, CONSTRUCTOR, FIELD, PARAMETER })
+@Target({ METHOD, CONSTRUCTOR, FIELD, PARAMETER, ANNOTATION_TYPE })
 @Retention(RUNTIME)
 public @interface Inject {
 
diff --git a/src/com/google/inject/util/SurrogateAnnotations.java b/src/com/google/inject/util/SurrogateAnnotations.java
index 3530f07..d461081 100644
--- a/src/com/google/inject/util/SurrogateAnnotations.java
+++ b/src/com/google/inject/util/SurrogateAnnotations.java
@@ -33,9 +33,9 @@
 public class SurrogateAnnotations {
 
   /**
-   * Finds an annotation of the given type on the given element. Looks for
-   * the annotation directly on the element as well as on annotations on the
-   * element non-recursively.
+   * Finds an annotation of the given type on the given element. Looks for the
+   * annotation directly on the element as well as on other annotations on the
+   * element.
    *
    * @returns an instance of the annotation or {@code null} if none is found
    * @throws DuplicateAnnotationException if more than one annotation is found
@@ -43,10 +43,24 @@
   public static <A extends Annotation> A findAnnotation(
       Class<A> annotationType, AnnotatedElement element)
       throws DuplicateAnnotationException {
+    return findAnnotation(annotationType, element.getAnnotations());
+  }
+
+  /**
+   * Finds an annotation of the given type in the given array of annotations.
+   * Looks for the annotation in the array as well as on other annotations in
+   * the array.
+   *
+   * @returns an instance of the annotation or {@code null} if none is found
+   * @throws DuplicateAnnotationException if more than one annotation is found
+   */
+  public static <A extends Annotation> A findAnnotation(
+      Class<A> annotationType, Annotation[] annotations)
+      throws DuplicateAnnotationException {
     Annotation firstAnnotation = null;
     A firstFound = null;
 
-    for (Annotation annotation : element.getAnnotations()) {
+    for (Annotation annotation : annotations) {
       A found = findAnnotation(annotationType, annotation);
       if (found != null) {
         if (firstFound == null) {
diff --git a/test/com/google/inject/AllTests.java b/test/com/google/inject/AllTests.java
index f761411..2e51369 100644
--- a/test/com/google/inject/AllTests.java
+++ b/test/com/google/inject/AllTests.java
@@ -49,6 +49,7 @@
     suite.addTestSuite(ReflectionTest.class);
     suite.addTestSuite(ScopesTest.class);
     suite.addTestSuite(ImplicitBindingTest.class);
+    suite.addTestSuite(InjectTest.class);
 
     suite.addTestSuite(QueryTest.class);
     suite.addTestSuite(ProxyFactoryTest.class);
diff --git a/test/com/google/inject/InjectTest.java b/test/com/google/inject/InjectTest.java
new file mode 100644
index 0000000..7b50c9c
--- /dev/null
+++ b/test/com/google/inject/InjectTest.java
@@ -0,0 +1,71 @@
+/**
+ * Copyright (C) 2006 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 junit.framework.TestCase;
+
+import java.lang.annotation.Retention;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * @author crazybob@google.com (Bob Lee)
+ */
+public class InjectTest extends TestCase {
+
+  public void testSurrogateInjection() throws ContainerCreationException {
+    ContainerBuilder builder = new ContainerBuilder();
+    Bar bar = new Bar(null);
+    builder.bind(Bar.class).named("bar").to(bar);
+    Container container = builder.create(false);
+    Foo foo = container.getInstance(Foo.class);
+    assertSame(bar, foo.field);
+    assertSame(bar, foo.fromConstructor);
+    assertSame(bar, foo.fromMethod);
+    assertSame(bar, foo.fromParameter);
+  }
+
+  @Retention(RUNTIME)
+  @Inject("bar")
+  @interface InjectBar {}
+
+  static class Foo {
+    @InjectBar Bar field;
+
+    Bar fromConstructor;
+    @InjectBar
+    public Foo(Bar fromConstructor) {
+      this.fromConstructor = fromConstructor;
+    }
+
+    Bar fromMethod;
+    @InjectBar
+    void setBar(Bar bar) {
+      fromMethod = bar;
+    }
+
+    Bar fromParameter;
+    @Inject
+    void setBar2(@InjectBar Bar bar) {
+      fromParameter= bar;
+    }
+  }
+
+  static class Bar {
+    // Not injectable.
+    Bar(String s) {}
+  }
+}