fixes issue 425 - coalesce javax.inject.Named & com.google.inject.name.Named, patch from cgdecker.

git-svn-id: https://google-guice.googlecode.com/svn/trunk@1152 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/src/com/google/inject/Key.java b/src/com/google/inject/Key.java
index e1fae93..46c13aa 100644
--- a/src/com/google/inject/Key.java
+++ b/src/com/google/inject/Key.java
@@ -18,6 +18,7 @@
 
 import com.google.inject.internal.Annotations;
 import com.google.inject.internal.MoreTypes;
+
 import static com.google.inject.internal.Preconditions.checkArgument;
 import static com.google.inject.internal.Preconditions.checkNotNull;
 import java.lang.annotation.Annotation;
@@ -334,11 +335,11 @@
     ensureRetainedAtRuntime(annotationType);
     ensureIsBindingAnnotation(annotationType);
 
-    if (annotationType.getDeclaredMethods().length == 0) {
+    if (isMarker(annotationType)) {
       return new AnnotationTypeStrategy(annotationType, annotation);
     }
 
-    return new AnnotationInstanceStrategy(annotation);
+    return new AnnotationInstanceStrategy(Annotations.canonicalizeIfNamed(annotation));
   }
 
   /**
diff --git a/src/com/google/inject/internal/Annotations.java b/src/com/google/inject/internal/Annotations.java
index 9e7c929..336c952 100644
--- a/src/com/google/inject/internal/Annotations.java
+++ b/src/com/google/inject/internal/Annotations.java
@@ -20,6 +20,9 @@
 import com.google.inject.Key;
 import com.google.inject.ScopeAnnotation;
 import com.google.inject.TypeLiteral;
+import com.google.inject.name.Named;
+import com.google.inject.name.Names;
+
 import java.lang.annotation.Annotation;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -123,4 +126,17 @@
     return annotationType.isAnnotationPresent(BindingAnnotation.class) 
           || annotationType.isAnnotationPresent(Qualifier.class);
   }
+
+  /**
+   * If the annotation is an instance of {@code javax.inject.Named} or {@code
+   * com.google.inject.name.Named}, return a canonicalized instance that will be equal to instances
+   * of either that have the same value. Returns the given annotation otherwise.
+   */
+  public static Annotation canonicalizeIfNamed(Annotation annotation) {
+    if(annotation instanceof javax.inject.Named) {
+      return Names.named(((javax.inject.Named)annotation).value());       
+    } else {
+      return annotation;
+    }
+  }
 }
diff --git a/src/com/google/inject/util/Jsr330.java b/src/com/google/inject/util/Jsr330.java
index f98194b..b0b239d 100644
--- a/src/com/google/inject/util/Jsr330.java
+++ b/src/com/google/inject/util/Jsr330.java
@@ -17,9 +17,6 @@
 package com.google.inject.util;
 
 import static com.google.inject.internal.Preconditions.checkNotNull;
-import java.io.Serializable;
-import java.lang.annotation.Annotation;
-import javax.inject.Named;
 import javax.inject.Provider;
 
 /**
@@ -32,10 +29,6 @@
 
   private Jsr330() {}
 
-  public static Named named(String name) {
-    return new NamedImpl(name);
-  }
-
   /**
    * Returns a Guice-friendly {@code com.google.inject.Provider} for the given
    * JSR-330 {@code javax.inject.Provider}. The converse method is unnecessary,
@@ -57,42 +50,4 @@
       }
     };
   }
-
-  // TODO: support binding properties like Names does?
-
-  private static class NamedImpl implements Named, Serializable {
-    private final String value;
-
-    NamedImpl(String value) {
-      this.value = checkNotNull(value, "name");
-    }
-
-    public String value() {
-      return value;
-    }
-
-    @Override public int hashCode() {
-      // This is specified in java.lang.Annotation.
-      return (127 * "value".hashCode()) ^ value.hashCode();
-    }
-
-    @Override public boolean equals(Object o) {
-      if (!(o instanceof Named)) {
-        return false;
-      }
-
-      Named other = (Named) o;
-      return value.equals(other.value());
-    }
-
-    @Override public String toString() {
-      return "@" + Named.class.getName() + "(value=" + value + ")";
-    }
-
-    public Class<? extends Annotation> annotationType() {
-      return Named.class;
-    }
-
-    private static final long serialVersionUID = 0;
-  }
 }
diff --git a/test/com/google/inject/AllTests.java b/test/com/google/inject/AllTests.java
index 172d648..54bf0ed 100644
--- a/test/com/google/inject/AllTests.java
+++ b/test/com/google/inject/AllTests.java
@@ -24,6 +24,7 @@
 import com.google.inject.internal.ProxyFactoryTest;
 import com.google.inject.internal.UniqueAnnotationsTest;
 import com.google.inject.matcher.MatcherTest;
+import com.google.inject.name.NamedEquivalanceTest;
 import com.google.inject.name.NamesTest;
 import com.google.inject.spi.BindingTargetVisitorTest;
 import com.google.inject.spi.ElementApplyToTest;
@@ -113,6 +114,7 @@
 
     // names
     suite.addTestSuite(NamesTest.class);
+    suite.addTestSuite(NamedEquivalanceTest.class);
 
     // spi
     suite.addTestSuite(BindingTargetVisitorTest.class);
diff --git a/test/com/google/inject/name/NamedEquivalanceTest.java b/test/com/google/inject/name/NamedEquivalanceTest.java
new file mode 100644
index 0000000..aecbfd3
--- /dev/null
+++ b/test/com/google/inject/name/NamedEquivalanceTest.java
@@ -0,0 +1,245 @@
+/**
+ * Copyright (C) 2010 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.name;
+
+import static com.google.inject.Asserts.assertContains;
+
+import java.io.Serializable;
+import java.lang.annotation.Annotation;
+import java.util.Properties;
+
+import junit.framework.TestCase;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.ConfigurationException;
+import com.google.inject.CreationException;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.Provides;
+
+/**
+ * Tests that {@code javax.inject.Named} and {@code com.google.inject.name.Named} are completely
+ * interchangeable: bindings for one can be used to inject the other.
+ * 
+ * @author cgdecker@gmail.com (Colin Decker)
+ */
+public class NamedEquivalanceTest extends TestCase {
+
+  private static final Module GUICE_BINDING_MODULE = moduleWithAnnotation(Names.named("foo"));
+  private static final Module JSR330_BINDING_MODULE = moduleWithAnnotation(new JsrNamed("foo"));
+  private static final Module GUICE_PROVIDER_METHOD_MODULE = getGuiceBindingProviderMethodModule();
+  private static final Module JSR330_PROVIDER_METHOD_MODULE = getJsr330BindingProviderMethodModule();
+
+  public void testKeysCreatedWithDifferentTypesAreEqual() {
+    assertEquals(keyForAnnotation(new GuiceNamed("foo")), keyForAnnotation(new JsrNamed("foo")));
+    assertEquals(keyForAnnotation(Names.named("foo")), keyForAnnotation(new GuiceNamed("foo")));
+    assertEquals(keyForAnnotation(Names.named("foo")), keyForAnnotation(new JsrNamed("foo")));
+  }
+
+  private static Key<String> keyForAnnotation(Annotation annotation) {
+    return Key.get(String.class, annotation);
+  }
+
+  public void testBindingWithNamesCanInjectBothTypes() {
+    assertInjectionsSucceed(GUICE_BINDING_MODULE);
+  }
+
+  public void testBindingWithJsr330AnnotationCanInjectBothTypes() {
+    assertInjectionsSucceed(JSR330_BINDING_MODULE);
+  }
+
+  public void testBindingWithGuiceNamedAnnotatedProviderMethodCanInjectBothTypes() {
+    assertInjectionsSucceed(GUICE_PROVIDER_METHOD_MODULE);
+  }
+
+  public void testBindingWithJsr330NamedAnnotatedProviderMethodCanInjectBothTypes() {
+    assertInjectionsSucceed(JSR330_PROVIDER_METHOD_MODULE);
+  }
+
+  public void testBindingDifferentTypesWithSameValueIsAnError() {
+    assertDuplicateBinding(GUICE_BINDING_MODULE, JSR330_BINDING_MODULE);
+    assertDuplicateBinding(JSR330_BINDING_MODULE, GUICE_BINDING_MODULE);
+  }
+
+  public void testBindingDifferentTypesWithSameValueIsAnErrorWithProviderMethods() {
+    assertDuplicateBinding(GUICE_PROVIDER_METHOD_MODULE, JSR330_PROVIDER_METHOD_MODULE);
+    assertDuplicateBinding(JSR330_PROVIDER_METHOD_MODULE, GUICE_PROVIDER_METHOD_MODULE);
+  }
+
+  public void testBindingDifferentTypesWithSameValueIsAnErrorMixed() {
+    assertDuplicateBinding(GUICE_BINDING_MODULE, JSR330_PROVIDER_METHOD_MODULE);
+    assertDuplicateBinding(JSR330_BINDING_MODULE, GUICE_PROVIDER_METHOD_MODULE);
+  }
+
+  public void testMissingBindingForGuiceNamedUsesSameTypeInErrorMessage() {
+    assertMissingBindingErrorMessageUsesType(GuiceNamedClient.class);
+  }
+
+  public void testMissingBindingForJsr330NamedUsesSameTypeInErrorMessage() {
+    assertMissingBindingErrorMessageUsesType(Jsr330NamedClient.class);
+  }
+
+  public void testBindPropertiesWorksWithJsr330() {
+    assertInjectionsSucceed(new AbstractModule() {
+      @Override protected void configure() {
+        Properties properties = new Properties();
+        properties.put("foo", "bar");
+        Names.bindProperties(binder(), properties);
+      }
+    });
+  }
+
+  private static void assertMissingBindingErrorMessageUsesType(Class<?> clientType) {
+    try {
+      Guice.createInjector().getInstance(clientType);
+      fail("should have thrown ConfigurationException");
+    } catch (ConfigurationException e) {
+      assertContains(e.getMessage(),
+          "No implementation for java.lang.String annotated with @com.google.inject.name.Named(value=foo) was bound.");
+    }
+  }
+
+  private static void assertDuplicateBinding(Module... modules) {
+    try {
+      Guice.createInjector(modules);
+      fail("should have thrown CreationException");
+    } catch (CreationException e) {
+      assertContains(e.getMessage(),
+          "A binding to java.lang.String annotated with @com.google.inject.name.Named(value=foo) was already configured");
+    }
+  }
+
+  private static Module moduleWithAnnotation(final Annotation annotation) {
+    return new AbstractModule() {
+      @Override protected void configure() {
+        bindConstant().annotatedWith(annotation).to("bar");
+      }
+    };
+  }
+
+  private static void assertInjectionsSucceed(Module module) {
+    Injector injector = Guice.createInjector(module);
+    assertInjected(injector.getInstance(GuiceNamedClient.class), injector
+        .getInstance(Jsr330NamedClient.class));
+  }
+
+  private static void assertInjected(GuiceNamedClient guiceClient, Jsr330NamedClient jsr330Client) {
+    assertEquals("bar", guiceClient.foo);
+    assertEquals("bar", jsr330Client.foo);
+  }
+
+  private static Module getJsr330BindingProviderMethodModule() {
+    return new AbstractModule() {
+      @Override protected void configure() {}
+      @SuppressWarnings("unused") @Provides @javax.inject.Named("foo") String provideFoo() {
+        return "bar";
+      }
+    };
+  }
+
+  private static Module getGuiceBindingProviderMethodModule() {
+    return new AbstractModule() {
+      @Override protected void configure() {}
+      @SuppressWarnings("unused") @Provides @Named("foo") String provideFoo() {
+        return "bar";
+      }
+    };
+  }
+
+  private static class GuiceNamedClient {
+    @Inject @Named("foo") String foo;
+  }
+
+  private static class Jsr330NamedClient {
+    @Inject @javax.inject.Named("foo") String foo;
+  }
+
+  private static class JsrNamed implements javax.inject.Named, Serializable {
+    private final String value;
+
+    public JsrNamed(String value) {
+      this.value = value;
+    }
+
+    public String value() {
+      return this.value;
+    }
+
+    public int hashCode() {
+      // This is specified in java.lang.Annotation.
+      return (127 * "value".hashCode()) ^ value.hashCode();
+    }
+
+    public boolean equals(Object o) {
+      if (!(o instanceof javax.inject.Named)) {
+        return false;
+      }
+
+      javax.inject.Named other = (javax.inject.Named) o;
+      return value.equals(other.value());
+    }
+
+    public String toString() {
+      return "@" + javax.inject.Named.class.getName() + "(value=" + value + ")";
+    }
+
+    public Class<? extends Annotation> annotationType() {
+      return javax.inject.Named.class;
+    }
+
+    private static final long serialVersionUID = 0;
+  }
+
+  private static class GuiceNamed implements com.google.inject.name.Named, Serializable {
+    private final String value;
+    
+    public GuiceNamed(String value) {
+      this.value = value;
+    }
+    
+    public String value() {
+      return this.value;
+    }
+    
+    public int hashCode() {
+      // This is specified in java.lang.Annotation.
+      return (127 * "value".hashCode()) ^ value.hashCode();
+    }
+
+    public boolean equals(Object o) {
+      if (!(o instanceof com.google.inject.name.Named)) {
+        return false;
+      }
+
+      com.google.inject.name.Named other = (com.google.inject.name.Named) o;
+      return value.equals(other.value());
+    }
+
+    public String toString() {
+      return "@" + com.google.inject.name.Named.class.getName() + "(value=" + value + ")";
+    }
+
+    public Class<? extends Annotation> annotationType() {
+      return com.google.inject.name.Named.class;
+    }
+
+    private static final long serialVersionUID = 0;
+  }
+}
diff --git a/test/com/googlecode/guice/Jsr330Test.java b/test/com/googlecode/guice/Jsr330Test.java
index b4ece59..66ea301 100644
--- a/test/com/googlecode/guice/Jsr330Test.java
+++ b/test/com/googlecode/guice/Jsr330Test.java
@@ -26,6 +26,7 @@
 import com.google.inject.Scopes;
 import com.google.inject.Stage;
 import com.google.inject.TypeLiteral;
+import com.google.inject.name.Names;
 import com.google.inject.util.Jsr330;
 import java.lang.annotation.Annotation;
 import java.lang.annotation.Retention;
@@ -70,10 +71,10 @@
   public void testQualifiedInject() {
     Injector injector = Guice.createInjector(new AbstractModule() {
       protected void configure() {
-        bind(B.class).annotatedWith(Jsr330.named("jodie")).toInstance(b);
+        bind(B.class).annotatedWith(Names.named("jodie")).toInstance(b);
         bind(C.class).annotatedWith(Red.class).toInstance(c);
         bind(D.class).annotatedWith(RED).toInstance(d);
-        bind(E.class).annotatedWith(Jsr330.named("jesse")).toInstance(e);
+        bind(E.class).annotatedWith(Names.named("jesse")).toInstance(e);
         bind(F.class);
       }
     });
@@ -88,7 +89,7 @@
   public void testProviderInject() {
     Injector injector = Guice.createInjector(new AbstractModule() {
       protected void configure() {
-        bind(B.class).annotatedWith(Jsr330.named("jodie")).toInstance(b);
+        bind(B.class).annotatedWith(Names.named("jodie")).toInstance(b);
         bind(C.class).toInstance(c);
         bind(D.class).annotatedWith(RED).toInstance(d);
         bind(E.class).toInstance(e);
@@ -233,16 +234,16 @@
     Injector injector = Guice.createInjector(new AbstractModule() {
       protected void configure() {
         bind(B.class).toProvider(BProvider.class);
-        bind(B.class).annotatedWith(Jsr330.named("1")).toProvider(BProvider.class);
-        bind(B.class).annotatedWith(Jsr330.named("2")).toProvider(Key.get(BProvider.class));
-        bind(B.class).annotatedWith(Jsr330.named("3")).toProvider(TypeLiteral.get(BProvider.class));
+        bind(B.class).annotatedWith(Names.named("1")).toProvider(BProvider.class);
+        bind(B.class).annotatedWith(Names.named("2")).toProvider(Key.get(BProvider.class));
+        bind(B.class).annotatedWith(Names.named("3")).toProvider(TypeLiteral.get(BProvider.class));
       }
     });
     
     injector.getInstance(Key.get(B.class));
-    injector.getInstance(Key.get(B.class, Jsr330.named("1")));
-    injector.getInstance(Key.get(B.class, Jsr330.named("2")));
-    injector.getInstance(Key.get(B.class, Jsr330.named("3")));
+    injector.getInstance(Key.get(B.class, Names.named("1")));
+    injector.getInstance(Key.get(B.class, Names.named("2")));
+    injector.getInstance(Key.get(B.class, Names.named("3")));
   }
 
   public void testGuicify330Provider() {