fixes issue 490 -- sets using marker annotations don't work properly all the time.
git-svn-id: https://google-guice.googlecode.com/svn/trunk@1173 d779f126-a31b-0410-b53b-1d3aecad763e
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/Multibinder.java b/extensions/multibindings/src/com/google/inject/multibindings/Multibinder.java
index 1ad9b3d..b846e04 100644
--- a/extensions/multibindings/src/com/google/inject/multibindings/Multibinder.java
+++ b/extensions/multibindings/src/com/google/inject/multibindings/Multibinder.java
@@ -27,6 +27,7 @@
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import com.google.inject.binder.LinkedBindingBuilder;
+import com.google.inject.internal.Annotations;
import com.google.inject.internal.Errors;
import com.google.inject.internal.ImmutableList;
import com.google.inject.internal.ImmutableSet;
@@ -103,7 +104,7 @@
*/
public static <T> Multibinder<T> newSetBinder(Binder binder, TypeLiteral<T> type) {
binder = binder.skipSources(RealMultibinder.class, Multibinder.class);
- RealMultibinder<T> result = new RealMultibinder<T>(binder, type, "",
+ RealMultibinder<T> result = new RealMultibinder<T>(binder, type,
Key.get(Multibinder.<T>setOf(type)));
binder.install(result);
return result;
@@ -124,7 +125,7 @@
public static <T> Multibinder<T> newSetBinder(
Binder binder, TypeLiteral<T> type, Annotation annotation) {
binder = binder.skipSources(RealMultibinder.class, Multibinder.class);
- RealMultibinder<T> result = new RealMultibinder<T>(binder, type, annotation.toString(),
+ RealMultibinder<T> result = new RealMultibinder<T>(binder, type,
Key.get(Multibinder.<T>setOf(type), annotation));
binder.install(result);
return result;
@@ -146,7 +147,7 @@
public static <T> Multibinder<T> newSetBinder(Binder binder, TypeLiteral<T> type,
Class<? extends Annotation> annotationType) {
binder = binder.skipSources(RealMultibinder.class, Multibinder.class);
- RealMultibinder<T> result = new RealMultibinder<T>(binder, type, "@" + annotationType.getName(),
+ RealMultibinder<T> result = new RealMultibinder<T>(binder, type,
Key.get(Multibinder.<T>setOf(type), annotationType));
binder.install(result);
return result;
@@ -227,14 +228,32 @@
/** whether duplicates are allowed. Possibly configured by a different instance */
private boolean permitDuplicates;
- private RealMultibinder(Binder binder, TypeLiteral<T> elementType,
- String setName, Key<Set<T>> setKey) {
+ private RealMultibinder(Binder binder, TypeLiteral<T> elementType, Key<Set<T>> setKey) {
this.binder = checkNotNull(binder, "binder");
this.elementType = checkNotNull(elementType, "elementType");
- this.setName = checkNotNull(setName, "setName");
this.setKey = checkNotNull(setKey, "setKey");
+ this.setName = nameOf(setKey);
this.permitDuplicatesKey = Key.get(Boolean.class, named(toString() + " permits duplicates"));
}
+
+ /**
+ * Returns the name the set should use. This is based on the annotation.
+ * If the annotation has an instance and is not a marker annotation,
+ * we ask the annotation for its toString. If it was a marker annotation
+ * or just an annotation type, we use the annotation's name. Otherwise,
+ * the name is the empty string.
+ */
+ private String nameOf(Key<?> key) {
+ Annotation annotation = setKey.getAnnotation();
+ Class<? extends Annotation> annotationType = setKey.getAnnotationType();
+ if(annotation != null && !Annotations.isMarker(annotationType)) {
+ return setKey.getAnnotation().toString();
+ } else if(setKey.getAnnotationType() != null) {
+ return "@" + setKey.getAnnotationType().getName();
+ } else {
+ return "";
+ }
+ }
@SuppressWarnings("unchecked")
public void configure(Binder binder) {
diff --git a/extensions/multibindings/test/com/google/inject/multibindings/MapBinderTest.java b/extensions/multibindings/test/com/google/inject/multibindings/MapBinderTest.java
index e362bd8..3ba1252 100644
--- a/extensions/multibindings/test/com/google/inject/multibindings/MapBinderTest.java
+++ b/extensions/multibindings/test/com/google/inject/multibindings/MapBinderTest.java
@@ -16,8 +16,11 @@
package com.google.inject.multibindings;
-import com.google.inject.AbstractModule;
import static com.google.inject.Asserts.assertContains;
+import static com.google.inject.name.Names.named;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.inject.AbstractModule;
import com.google.inject.Binding;
import com.google.inject.BindingAnnotation;
import com.google.inject.ConfigurationException;
@@ -34,13 +37,19 @@
import com.google.inject.internal.ImmutableSet;
import com.google.inject.internal.Maps;
import com.google.inject.name.Names;
-import static com.google.inject.name.Names.named;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.HasDependencies;
import com.google.inject.util.Modules;
import com.google.inject.util.Providers;
+
+import junit.framework.TestCase;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
@@ -48,7 +57,6 @@
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
-import junit.framework.TestCase;
/**
* @author dpb@google.com (David P. Baker)
@@ -569,4 +577,46 @@
private <V> Set<V> setOf(Object... elements) {
return new HashSet(Arrays.asList(elements));
}
+
+ @BindingAnnotation
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
+ private static @interface Marker {}
+
+ @Marker
+ public void testMapBinderMatching() throws Exception {
+ Method m = MapBinderTest.class.getDeclaredMethod("testMapBinderMatching");
+ assertNotNull(m);
+ final Annotation marker = m.getAnnotation(Marker.class);
+ Injector injector = Guice.createInjector(new AbstractModule() {
+ @Override public void configure() {
+ MapBinder<Integer, Integer> mb1 =
+ MapBinder.newMapBinder(binder(), Integer.class, Integer.class, Marker.class);
+ MapBinder<Integer, Integer> mb2 =
+ MapBinder.newMapBinder(binder(), Integer.class, Integer.class, marker);
+ mb1.addBinding(1).toInstance(1);
+ mb2.addBinding(2).toInstance(2);
+
+ // This assures us that the two binders are equivalent, so we expect the instance added to
+ // each to have been added to one set.
+ assertEquals(mb1, mb2);
+ }
+ });
+ TypeLiteral<Map<Integer, Integer>> t = new TypeLiteral<Map<Integer, Integer>>() {};
+ Map<Integer, Integer> s1 = injector.getInstance(Key.get(t, Marker.class));
+ Map<Integer, Integer> s2 = injector.getInstance(Key.get(t, marker));
+
+ // This assures us that the two sets are in fact equal. They may not be same set (as in Java
+ // object identical), but we shouldn't expect that, since probably Guice creates the set each
+ // time in case the elements are dependent on scope.
+ assertEquals(s1, s2);
+
+ // This ensures that MultiBinder is internally using the correct set name --
+ // making sure that instances of marker annotations have the same set name as
+ // MarkerAnnotation.class.
+ Map<Integer, Integer> expected = new HashMap<Integer, Integer>();
+ expected.put(1, 1);
+ expected.put(2, 2);
+ assertEquals(expected, s1);
+ }
}
diff --git a/extensions/multibindings/test/com/google/inject/multibindings/MultibinderTest.java b/extensions/multibindings/test/com/google/inject/multibindings/MultibinderTest.java
index 6046152..0cab926 100644
--- a/extensions/multibindings/test/com/google/inject/multibindings/MultibinderTest.java
+++ b/extensions/multibindings/test/com/google/inject/multibindings/MultibinderTest.java
@@ -18,6 +18,8 @@
import com.google.inject.AbstractModule;
import static com.google.inject.Asserts.assertContains;
+
+import com.google.inject.Binder;
import com.google.inject.Binding;
import com.google.inject.BindingAnnotation;
import com.google.inject.CreationException;
@@ -41,10 +43,18 @@
import com.google.inject.spi.LinkedKeyBinding;
import com.google.inject.util.Modules;
import com.google.inject.util.Providers;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Method;
+
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.Set;
import junit.framework.TestCase;
@@ -495,5 +505,45 @@
assertEquals(ImmutableSet.of("A", "B", "C", "D", "E", "F"),
injector.getInstance(Key.get(setOfString)));
+ }
+
+ @BindingAnnotation
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
+ private static @interface Marker {}
+
+ @Marker
+ public void testMultibinderMatching() throws Exception {
+ Method m = MultibinderTest.class.getDeclaredMethod("testMultibinderMatching");
+ assertNotNull(m);
+ final Annotation marker = m.getAnnotation(Marker.class);
+ Injector injector = Guice.createInjector(new AbstractModule() {
+ @Override public void configure() {
+ Multibinder<Integer> mb1 = Multibinder.newSetBinder(binder(), Integer.class, Marker.class);
+ Multibinder<Integer> mb2 = Multibinder.newSetBinder(binder(), Integer.class, marker);
+ mb1.addBinding().toInstance(1);
+ mb2.addBinding().toInstance(2);
+
+ // This assures us that the two binders are equivalent, so we expect the instance added to
+ // each to have been added to one set.
+ assertEquals(mb1, mb2);
+ }
+ });
+ TypeLiteral<Set<Integer>> t = new TypeLiteral<Set<Integer>>() {};
+ Set<Integer> s1 = injector.getInstance(Key.get(t, Marker.class));
+ Set<Integer> s2 = injector.getInstance(Key.get(t, marker));
+
+ // This assures us that the two sets are in fact equal. They may not be same set (as in Java
+ // object identical), but we shouldn't expect that, since probably Guice creates the set each
+ // time in case the elements are dependent on scope.
+ assertEquals(s1, s2);
+
+ // This ensures that MultiBinder is internally using the correct set name --
+ // making sure that instances of marker annotations have the same set name as
+ // MarkerAnnotation.class.
+ Set<Integer> expected = new HashSet<Integer>();
+ expected.add(1);
+ expected.add(2);
+ assertEquals(expected, s1);
}
}
diff --git a/src/com/google/inject/Key.java b/src/com/google/inject/Key.java
index 05d7b25..bc78b2c 100644
--- a/src/com/google/inject/Key.java
+++ b/src/com/google/inject/Key.java
@@ -320,13 +320,6 @@
}
/**
- * Returns {@code true} if the given annotation type has no attributes.
- */
- static boolean isMarker(Class<? extends Annotation> annotationType) {
- return annotationType.getDeclaredMethods().length == 0;
- }
-
- /**
* Gets the strategy for an annotation.
*/
static AnnotationStrategy strategyFor(Annotation annotation) {
@@ -335,7 +328,7 @@
ensureRetainedAtRuntime(annotationType);
ensureIsBindingAnnotation(annotationType);
- if (isMarker(annotationType)) {
+ if (Annotations.isMarker(annotationType)) {
return new AnnotationTypeStrategy(annotationType, annotation);
}
diff --git a/src/com/google/inject/internal/Annotations.java b/src/com/google/inject/internal/Annotations.java
index f748aef..d76d32a 100644
--- a/src/com/google/inject/internal/Annotations.java
+++ b/src/com/google/inject/internal/Annotations.java
@@ -37,6 +37,13 @@
public class Annotations {
/**
+ * Returns {@code true} if the given annotation type has no attributes.
+ */
+ public static boolean isMarker(Class<? extends Annotation> annotationType) {
+ return annotationType.getDeclaredMethods().length == 0;
+ }
+
+ /**
* Returns true if the given annotation is retained at runtime.
*/
public static boolean isRetainedAtRuntime(Class<? extends Annotation> annotationType) {