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() {