aboutsummaryrefslogtreecommitdiff
path: root/extensions/multibindings/src/com
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/multibindings/src/com')
-rw-r--r--extensions/multibindings/src/com/google/inject/multibindings/ClassMapKey.java35
-rw-r--r--extensions/multibindings/src/com/google/inject/multibindings/MapBinder.java38
-rw-r--r--extensions/multibindings/src/com/google/inject/multibindings/MapKey.java58
-rw-r--r--extensions/multibindings/src/com/google/inject/multibindings/Multibinder.java45
-rw-r--r--extensions/multibindings/src/com/google/inject/multibindings/MultibindingsScanner.java192
-rw-r--r--extensions/multibindings/src/com/google/inject/multibindings/OptionalBinder.java32
-rw-r--r--extensions/multibindings/src/com/google/inject/multibindings/ProvidesIntoMap.java59
-rw-r--r--extensions/multibindings/src/com/google/inject/multibindings/ProvidesIntoOptional.java63
-rw-r--r--extensions/multibindings/src/com/google/inject/multibindings/ProvidesIntoSet.java53
-rw-r--r--extensions/multibindings/src/com/google/inject/multibindings/StringMapKey.java35
10 files changed, 572 insertions, 38 deletions
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/ClassMapKey.java b/extensions/multibindings/src/com/google/inject/multibindings/ClassMapKey.java
new file mode 100644
index 00000000..47c8c17d
--- /dev/null
+++ b/extensions/multibindings/src/com/google/inject/multibindings/ClassMapKey.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (C) 2015 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.multibindings;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Allows {@literal @}{@link ProvidesIntoMap} to specify a class map key.
+ */
+@MapKey(unwrapValue = true)
+@Documented
+@Target(METHOD)
+@Retention(RUNTIME)
+public @interface ClassMapKey {
+ Class<?> value();
+}
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/MapBinder.java b/extensions/multibindings/src/com/google/inject/multibindings/MapBinder.java
index 89df7713..fc3d74f5 100644
--- a/extensions/multibindings/src/com/google/inject/multibindings/MapBinder.java
+++ b/extensions/multibindings/src/com/google/inject/multibindings/MapBinder.java
@@ -143,7 +143,7 @@ public abstract class MapBinder<K, V> {
public static <K, V> MapBinder<K, V> newMapBinder(Binder binder,
TypeLiteral<K> keyType, TypeLiteral<V> valueType) {
binder = binder.skipSources(MapBinder.class, RealMapBinder.class);
- return newMapBinder(binder, keyType, valueType, Key.get(mapOf(keyType, valueType)),
+ return newRealMapBinder(binder, keyType, valueType, Key.get(mapOf(keyType, valueType)),
Multibinder.newSetBinder(binder, entryOfProviderOf(keyType, valueType)));
}
@@ -163,7 +163,7 @@ public abstract class MapBinder<K, V> {
public static <K, V> MapBinder<K, V> newMapBinder(Binder binder,
TypeLiteral<K> keyType, TypeLiteral<V> valueType, Annotation annotation) {
binder = binder.skipSources(MapBinder.class, RealMapBinder.class);
- return newMapBinder(binder, keyType, valueType,
+ return newRealMapBinder(binder, keyType, valueType,
Key.get(mapOf(keyType, valueType), annotation),
Multibinder.newSetBinder(binder, entryOfProviderOf(keyType, valueType), annotation));
}
@@ -184,7 +184,7 @@ public abstract class MapBinder<K, V> {
public static <K, V> MapBinder<K, V> newMapBinder(Binder binder, TypeLiteral<K> keyType,
TypeLiteral<V> valueType, Class<? extends Annotation> annotationType) {
binder = binder.skipSources(MapBinder.class, RealMapBinder.class);
- return newMapBinder(binder, keyType, valueType,
+ return newRealMapBinder(binder, keyType, valueType,
Key.get(mapOf(keyType, valueType), annotationType),
Multibinder.newSetBinder(binder, entryOfProviderOf(keyType, valueType), annotationType));
}
@@ -236,7 +236,19 @@ public abstract class MapBinder<K, V> {
Map.class, Entry.class, keyType.getType(), Types.providerOf(valueType.getType())));
}
- private static <K, V> MapBinder<K, V> newMapBinder(Binder binder,
+ // Note: We use valueTypeAndAnnotation effectively as a Pair<TypeLiteral, Annotation|Class>
+ // since it's an easy way to group a type and an optional annotation type or instance.
+ static <K, V> RealMapBinder<K, V> newRealMapBinder(Binder binder, TypeLiteral<K> keyType,
+ Key<V> valueTypeAndAnnotation) {
+ binder = binder.skipSources(MapBinder.class, RealMapBinder.class);
+ TypeLiteral<V> valueType = valueTypeAndAnnotation.getTypeLiteral();
+ return newRealMapBinder(binder, keyType, valueType,
+ valueTypeAndAnnotation.ofType(mapOf(keyType, valueType)),
+ Multibinder.newSetBinder(binder,
+ valueTypeAndAnnotation.ofType(entryOfProviderOf(keyType, valueType))));
+ }
+
+ private static <K, V> RealMapBinder<K, V> newRealMapBinder(Binder binder,
TypeLiteral<K> keyType, TypeLiteral<V> valueType, Key<Map<K, V>> mapKey,
Multibinder<Entry<K, Provider<V>>> entrySetBinder) {
RealMapBinder<K, V> mapBinder =
@@ -342,12 +354,8 @@ public abstract class MapBinder<K, V> {
multimapKey, providerMultimapKey, entrySetBinder.getSetKey()));
return this;
}
-
- /**
- * This creates two bindings. One for the {@code Map.Entry<K, Provider<V>>}
- * and another for {@code V}.
- */
- @Override public LinkedBindingBuilder<V> addBinding(K key) {
+
+ Key<V> getKeyForNewValue(K key) {
checkNotNull(key, "key");
checkConfiguration(!isInitialized(), "MapBinder was already initialized");
@@ -355,7 +363,15 @@ public abstract class MapBinder<K, V> {
new RealElement(entrySetBinder.getSetName(), MAPBINDER, keyType.toString()));
entrySetBinder.addBinding().toProvider(new ProviderMapEntry<K, V>(
key, binder.getProvider(valueKey), valueKey));
- return binder.bind(valueKey);
+ return valueKey;
+ }
+
+ /**
+ * This creates two bindings. One for the {@code Map.Entry<K, Provider<V>>}
+ * and another for {@code V}.
+ */
+ @Override public LinkedBindingBuilder<V> addBinding(K key) {
+ return binder.bind(getKeyForNewValue(key));
}
@Override public void configure(Binder binder) {
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/MapKey.java b/extensions/multibindings/src/com/google/inject/multibindings/MapKey.java
new file mode 100644
index 00000000..bcb6a3b8
--- /dev/null
+++ b/extensions/multibindings/src/com/google/inject/multibindings/MapKey.java
@@ -0,0 +1,58 @@
+/**
+ * Copyright (C) 2015 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.multibindings;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Allows users define customized key type annotations for map bindings by annotating an annotation
+ * of a {@code Map}'s key type. The custom key annotation can be applied to methods also annotated
+ * with {@literal @}{@link ProvidesIntoMap}.
+ *
+ * <p>A {@link StringMapKey} and {@link ClassMapKey} are provided for convenience with maps whose
+ * keys are strings or classes. For maps with enums or primitive types as keys, you must provide
+ * your own MapKey annotation, such as this one for an enum:
+ *
+ * <pre>
+ * {@literal @}MapKey(unwrapValue = true)
+ * {@literal @}Retention(RUNTIME)
+ * public {@literal @}interface MyCustomEnumKey {
+ * MyCustomEnum value();
+ * }
+ * </pre>
+ *
+ * You can also use the whole annotation as the key, if {@code unwrapValue=false}.
+ * When unwrapValue is false, the annotation type will be the key type for the injected map and
+ * the annotation instances will be the key values. If {@code unwrapValue=true}, the value() type
+ * will be the key type for injected map and the value() instances will be the keys values.
+ */
+@Documented
+@Target(ANNOTATION_TYPE)
+@Retention(RUNTIME)
+public @interface MapKey {
+ /**
+ * if {@code unwrapValue} is false, then the whole annotation will be the type and annotation
+ * instances will be the keys. If {@code unwrapValue} is true, the value() type of key type
+ * annotation will be the key type for injected map and the value instances will be the keys.
+ */
+ boolean unwrapValue();
+}
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/Multibinder.java b/extensions/multibindings/src/com/google/inject/multibindings/Multibinder.java
index 66a4951f..56433f78 100644
--- a/extensions/multibindings/src/com/google/inject/multibindings/Multibinder.java
+++ b/extensions/multibindings/src/com/google/inject/multibindings/Multibinder.java
@@ -121,10 +121,7 @@ public abstract class Multibinder<T> {
* itself bound with no binding annotation.
*/
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, Key.get(setOf(type)));
- binder.install(result);
- return result;
+ return newRealSetBinder(binder, Key.get(type));
}
/**
@@ -132,7 +129,7 @@ public abstract class Multibinder<T> {
* itself bound with no binding annotation.
*/
public static <T> Multibinder<T> newSetBinder(Binder binder, Class<T> type) {
- return newSetBinder(binder, TypeLiteral.get(type));
+ return newRealSetBinder(binder, Key.get(type));
}
/**
@@ -141,11 +138,7 @@ public abstract class Multibinder<T> {
*/
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, Key.get(setOf(type), annotation));
- binder.install(result);
- return result;
+ return newRealSetBinder(binder, Key.get(type, annotation));
}
/**
@@ -154,7 +147,7 @@ public abstract class Multibinder<T> {
*/
public static <T> Multibinder<T> newSetBinder(
Binder binder, Class<T> type, Annotation annotation) {
- return newSetBinder(binder, TypeLiteral.get(type), annotation);
+ return newRealSetBinder(binder, Key.get(type, annotation));
}
/**
@@ -163,9 +156,24 @@ public abstract class Multibinder<T> {
*/
public static <T> Multibinder<T> newSetBinder(Binder binder, TypeLiteral<T> type,
Class<? extends Annotation> annotationType) {
+ return newRealSetBinder(binder, Key.get(type, annotationType));
+ }
+
+ /**
+ * Returns a new multibinder that collects instances of the key's type in a {@link Set} that is
+ * itself bound with the annotation (if any) of the key.
+ */
+ public static <T> Multibinder<T> newSetBinder(Binder binder, Key<T> key) {
+ return newRealSetBinder(binder, key);
+ }
+
+ /**
+ * Implementation of newSetBinder.
+ */
+ static <T> RealMultibinder<T> newRealSetBinder(Binder binder, Key<T> key) {
binder = binder.skipSources(RealMultibinder.class, Multibinder.class);
- RealMultibinder<T> result =
- new RealMultibinder<T>(binder, type, Key.get(setOf(type), annotationType));
+ RealMultibinder<T> result = new RealMultibinder<T>(binder, key.getTypeLiteral(),
+ key.ofType(setOf(key.getTypeLiteral())));
binder.install(result);
return result;
}
@@ -176,7 +184,7 @@ public abstract class Multibinder<T> {
*/
public static <T> Multibinder<T> newSetBinder(Binder binder, Class<T> type,
Class<? extends Annotation> annotationType) {
- return newSetBinder(binder, TypeLiteral.get(type), annotationType);
+ return newSetBinder(binder, Key.get(type, annotationType));
}
@SuppressWarnings("unchecked") // wrapping a T in a Set safely returns a Set<T>
@@ -295,11 +303,14 @@ public abstract class Multibinder<T> {
binder.install(new PermitDuplicatesModule(permitDuplicatesKey));
return this;
}
-
- @Override public LinkedBindingBuilder<T> addBinding() {
+
+ Key<T> getKeyForNewItem() {
checkConfiguration(!isInitialized(), "Multibinder was already initialized");
+ return Key.get(elementType, new RealElement(setName, MULTIBINDER, ""));
+ }
- return binder.bind(Key.get(elementType, new RealElement(setName, MULTIBINDER, "")));
+ @Override public LinkedBindingBuilder<T> addBinding() {
+ return binder.bind(getKeyForNewItem());
}
/**
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/MultibindingsScanner.java b/extensions/multibindings/src/com/google/inject/multibindings/MultibindingsScanner.java
new file mode 100644
index 00000000..02fce320
--- /dev/null
+++ b/extensions/multibindings/src/com/google/inject/multibindings/MultibindingsScanner.java
@@ -0,0 +1,192 @@
+/**
+ * Copyright (C) 2015 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.multibindings;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.AbstractModule;
+import com.google.inject.Binder;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.TypeLiteral;
+import com.google.inject.spi.InjectionPoint;
+import com.google.inject.spi.ModuleAnnotatedMethodScanner;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Set;
+
+/**
+ * Scans a module for annotations that signal multibindings, mapbindings, and optional bindings.
+ */
+public class MultibindingsScanner {
+
+ private MultibindingsScanner() {}
+
+ /**
+ * Returns a module that, when installed, will scan all modules for methods with the annotations
+ * {@literal @}{@link ProvidesIntoMap}, {@literal @}{@link ProvidesIntoSet}, and
+ * {@literal @}{@link ProvidesIntoOptional}.
+ *
+ * <p>This is a convenience method, equivalent to doing
+ * {@code binder().scanModulesForAnnotatedMethods(MultibindingsScanner.scanner())}.
+ */
+ public static Module asModule() {
+ return new AbstractModule() {
+ @Override protected void configure() {
+ binder().scanModulesForAnnotatedMethods(Scanner.INSTANCE);
+ }
+ };
+ }
+
+ /**
+ * Returns a {@link ModuleAnnotatedMethodScanner} that, when bound, will scan all modules for
+ * methods with the annotations {@literal @}{@link ProvidesIntoMap},
+ * {@literal @}{@link ProvidesIntoSet}, and {@literal @}{@link ProvidesIntoOptional}.
+ */
+ public static ModuleAnnotatedMethodScanner scanner() {
+ return Scanner.INSTANCE;
+ }
+
+ private static class Scanner extends ModuleAnnotatedMethodScanner {
+ private static final Scanner INSTANCE = new Scanner();
+
+ @Override
+ public Set<? extends Class<? extends Annotation>> annotationClasses() {
+ return ImmutableSet.of(
+ ProvidesIntoSet.class, ProvidesIntoMap.class, ProvidesIntoOptional.class);
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"}) // mapKey doesn't know its key type
+ @Override
+ public <T> Key<T> prepareMethod(Binder binder, Annotation annotation, Key<T> key,
+ InjectionPoint injectionPoint) {
+ Method method = (Method) injectionPoint.getMember();
+ AnnotationOrError mapKey = findMapKeyAnnotation(binder, method);
+ if (annotation instanceof ProvidesIntoSet) {
+ if (mapKey.annotation != null) {
+ binder.addError("Found a MapKey annotation on non map binding at %s.", method);
+ }
+ return Multibinder.newRealSetBinder(binder, key).getKeyForNewItem();
+ } else if (annotation instanceof ProvidesIntoMap) {
+ if (mapKey.error) {
+ // Already failed on the MapKey, don't bother doing more work.
+ return key;
+ }
+ if (mapKey.annotation == null) {
+ // If no MapKey, make an error and abort.
+ binder.addError("No MapKey found for map binding at %s.", method);
+ return key;
+ }
+ TypeAndValue typeAndValue = typeAndValueOfMapKey(mapKey.annotation);
+ return MapBinder.newRealMapBinder(binder, typeAndValue.type, key)
+ .getKeyForNewValue(typeAndValue.value);
+ } else if (annotation instanceof ProvidesIntoOptional) {
+ if (mapKey.annotation != null) {
+ binder.addError("Found a MapKey annotation on non map binding at %s.", method);
+ }
+ switch (((ProvidesIntoOptional)annotation).value()) {
+ case DEFAULT:
+ return OptionalBinder.newRealOptionalBinder(binder, key).getKeyForDefaultBinding();
+ case ACTUAL:
+ return OptionalBinder.newRealOptionalBinder(binder, key).getKeyForActualBinding();
+ }
+ }
+ throw new IllegalStateException("Invalid annotation: " + annotation);
+ }
+ }
+
+ private static class AnnotationOrError {
+ final Annotation annotation;
+ final boolean error;
+ AnnotationOrError(Annotation annotation, boolean error) {
+ this.annotation = annotation;
+ this.error = error;
+ }
+
+ static AnnotationOrError forPossiblyNullAnnotation(Annotation annotation) {
+ return new AnnotationOrError(annotation, false);
+ }
+
+ static AnnotationOrError forError() {
+ return new AnnotationOrError(null, true);
+ }
+ }
+
+ private static AnnotationOrError findMapKeyAnnotation(Binder binder, Method method) {
+ Annotation foundAnnotation = null;
+ for (Annotation annotation : method.getAnnotations()) {
+ MapKey mapKey = annotation.annotationType().getAnnotation(MapKey.class);
+ if (mapKey != null) {
+ if (foundAnnotation != null) {
+ binder.addError("Found more than one MapKey annotations on %s.", method);
+ return AnnotationOrError.forError();
+ }
+ if (mapKey.unwrapValue()) {
+ try {
+ // validate there's a declared method called "value"
+ Method valueMethod = annotation.annotationType().getDeclaredMethod("value");
+ if (valueMethod.getReturnType().isArray()) {
+ binder.addError("Array types are not allowed in a MapKey with unwrapValue=true: %s",
+ annotation.annotationType());
+ return AnnotationOrError.forError();
+ }
+ } catch (NoSuchMethodException invalid) {
+ binder.addError("No 'value' method in MapKey with unwrapValue=true: %s",
+ annotation.annotationType());
+ return AnnotationOrError.forError();
+ }
+ }
+ foundAnnotation = annotation;
+ }
+ }
+ return AnnotationOrError.forPossiblyNullAnnotation(foundAnnotation);
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ static TypeAndValue<?> typeAndValueOfMapKey(Annotation mapKeyAnnotation) {
+ if (!mapKeyAnnotation.annotationType().getAnnotation(MapKey.class).unwrapValue()) {
+ return new TypeAndValue(TypeLiteral.get(mapKeyAnnotation.annotationType()), mapKeyAnnotation);
+ } else {
+ try {
+ Method valueMethod = mapKeyAnnotation.annotationType().getDeclaredMethod("value");
+ valueMethod.setAccessible(true);
+ TypeLiteral<?> returnType =
+ TypeLiteral.get(mapKeyAnnotation.annotationType()).getReturnType(valueMethod);
+ return new TypeAndValue(returnType, valueMethod.invoke(mapKeyAnnotation));
+ } catch (NoSuchMethodException e) {
+ throw new IllegalStateException(e);
+ } catch (SecurityException e) {
+ throw new IllegalStateException(e);
+ } catch (IllegalAccessException e) {
+ throw new IllegalStateException(e);
+ } catch (InvocationTargetException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+
+ private static class TypeAndValue<T> {
+ final TypeLiteral<T> type;
+ final T value;
+
+ TypeAndValue(TypeLiteral<T> type, T value) {
+ this.type = type;
+ this.value = value;
+ }
+ }
+}
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/OptionalBinder.java b/extensions/multibindings/src/com/google/inject/multibindings/OptionalBinder.java
index 60e72d9f..d18f974d 100644
--- a/extensions/multibindings/src/com/google/inject/multibindings/OptionalBinder.java
+++ b/extensions/multibindings/src/com/google/inject/multibindings/OptionalBinder.java
@@ -129,7 +129,7 @@ import javax.inject.Qualifier;
* public class FrameworkModule extends AbstractModule {
* protected void configure() {
* OptionalBinder.newOptionalBinder(binder(), Key.get(String.class, LookupUrl.class))
- * .setDefault().to(DEFAULT_LOOKUP_URL);
+ * .setDefault().toInstance(DEFAULT_LOOKUP_URL);
* }
* }</code></pre>
* With the above module, code can inject an {@code @LookupUrl String} and it
@@ -138,7 +138,7 @@ import javax.inject.Qualifier;
* public class UserLookupModule extends AbstractModule {
* protected void configure() {
* OptionalBinder.newOptionalBinder(binder(), Key.get(String.class, LookupUrl.class))
- * .setBinding().to(CUSTOM_LOOKUP_URL);
+ * .setBinding().toInstance(CUSTOM_LOOKUP_URL);
* }
* }</code></pre>
* ... which will override the default value.
@@ -150,12 +150,12 @@ import javax.inject.Qualifier;
* public class FrameworkModule extends AbstractModule {
* protected void configure() {
* OptionalBinder.newOptionalBinder(binder(), Key.get(String.class, LookupUrl.class))
- * .setDefault().to(DEFAULT_LOOKUP_URL);
+ * .setDefault().toInstance(DEFAULT_LOOKUP_URL);
* }
* }
* public class UserLookupModule extends AbstractModule {
* protected void configure() {
- * bind(Key.get(String.class, LookupUrl.class)).to(CUSTOM_LOOKUP_URL);
+ * bind(Key.get(String.class, LookupUrl.class)).toInstance(CUSTOM_LOOKUP_URL);
* }
* }</code></pre>
* ... would generate an error, because both the framework and the user are trying to bind
@@ -191,14 +191,18 @@ public abstract class OptionalBinder<T> {
private OptionalBinder() {}
public static <T> OptionalBinder<T> newOptionalBinder(Binder binder, Class<T> type) {
- return newOptionalBinder(binder, Key.get(type));
+ return newRealOptionalBinder(binder, Key.get(type));
}
public static <T> OptionalBinder<T> newOptionalBinder(Binder binder, TypeLiteral<T> type) {
- return newOptionalBinder(binder, Key.get(type));
+ return newRealOptionalBinder(binder, Key.get(type));
}
public static <T> OptionalBinder<T> newOptionalBinder(Binder binder, Key<T> type) {
+ return newRealOptionalBinder(binder, type);
+ }
+
+ static <T> RealOptionalBinder<T> newRealOptionalBinder(Binder binder, Key<T> type) {
binder = binder.skipSources(OptionalBinder.class, RealOptionalBinder.class);
RealOptionalBinder<T> optionalBinder = new RealOptionalBinder<T>(binder, type);
binder.install(optionalBinder);
@@ -349,16 +353,24 @@ public abstract class OptionalBinder<T> {
binder.bind(typeKey).toProvider(new RealDirectTypeProvider());
}
- @Override public LinkedBindingBuilder<T> setDefault() {
+ Key<T> getKeyForDefaultBinding() {
checkConfiguration(!isInitialized(), "already initialized");
addDirectTypeBinding(binder);
- return binder.bind(defaultKey);
+ return defaultKey;
}
- @Override public LinkedBindingBuilder<T> setBinding() {
+ @Override public LinkedBindingBuilder<T> setDefault() {
+ return binder.bind(getKeyForDefaultBinding());
+ }
+
+ Key<T> getKeyForActualBinding() {
checkConfiguration(!isInitialized(), "already initialized");
addDirectTypeBinding(binder);
- return binder.bind(actualKey);
+ return actualKey;
+ }
+
+ @Override public LinkedBindingBuilder<T> setBinding() {
+ return binder.bind(getKeyForActualBinding());
}
@Override public void configure(Binder binder) {
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/ProvidesIntoMap.java b/extensions/multibindings/src/com/google/inject/multibindings/ProvidesIntoMap.java
new file mode 100644
index 00000000..886a0fd1
--- /dev/null
+++ b/extensions/multibindings/src/com/google/inject/multibindings/ProvidesIntoMap.java
@@ -0,0 +1,59 @@
+/**
+ * Copyright (C) 2015 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.multibindings;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.inject.Module;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Annotates methods of a {@link Module} to add items to a {@link MapBinder}.
+ * The method's return type, binding annotation and additional key annotation determines
+ * what Map this will contribute to. For example,
+ *
+ * <pre>
+ * {@literal @}ProvidesIntoMap
+ * {@literal @}StringMapKey("Foo")
+ * {@literal @}Named("plugins")
+ * Plugin provideFooUrl(FooManager fm) { returm fm.getPlugin(); }
+ *
+ * {@literal @}ProvidesIntoMap
+ * {@literal @}StringMapKey("Bar")
+ * {@literal @}Named("urls")
+ * Plugin provideBarUrl(BarManager bm) { return bm.getPlugin(); }
+ * </pre>
+ *
+ * will add two items to the {@code @Named("urls") Map<String, Plugin>} map. The key 'Foo'
+ * will map to the provideFooUrl method, and the key 'Bar' will map to the provideBarUrl method.
+ * The values are bound as providers and will be evaluated at injection time.
+ *
+ * <p>Because the key is specified as an annotation, only Strings, Classes, enums, primitive
+ * types and annotation instances are supported as keys.
+ *
+ * @author sameb@google.com (Sam Berlin)
+ * @since 4.0
+ */
+@Documented
+@Target(METHOD)
+@Retention(RUNTIME)
+public @interface ProvidesIntoMap {
+}
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/ProvidesIntoOptional.java b/extensions/multibindings/src/com/google/inject/multibindings/ProvidesIntoOptional.java
new file mode 100644
index 00000000..6f021700
--- /dev/null
+++ b/extensions/multibindings/src/com/google/inject/multibindings/ProvidesIntoOptional.java
@@ -0,0 +1,63 @@
+/**
+ * Copyright (C) 2015 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.multibindings;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.inject.Module;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Annotates methods of a {@link Module} to add items to a {@link Multibinder}.
+ * The method's return type and binding annotation determines what Optional this will
+ * contribute to. For example,
+ *
+ * <pre>
+ * {@literal @}ProvidesIntoOptional(DEFAULT)
+ * {@literal @}Named("url")
+ * String provideFooUrl(FooManager fm) { returm fm.getUrl(); }
+ *
+ * {@literal @}ProvidesIntoOptional(ACTUAL)
+ * {@literal @}Named("url")
+ * String provideBarUrl(BarManager bm) { return bm.getUrl(); }
+ * </pre>
+ *
+ * will set the default value of {@code @Named("url") Optional<String>} to foo's URL,
+ * and then override it to bar's URL.
+ *
+ * @author sameb@google.com (Sam Berlin)
+ * @since 4.0
+ */
+@Documented
+@Target(METHOD)
+@Retention(RUNTIME)
+public @interface ProvidesIntoOptional {
+ enum Type {
+ /** Corresponds to {@link OptionalBinder#setBinding}. */
+ ACTUAL,
+
+ /** Corresponds to {@link OptionalBinder#setDefault}. */
+ DEFAULT
+ }
+
+ /** Specifies if the binding is for the actual or default value. */
+ Type value();
+}
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/ProvidesIntoSet.java b/extensions/multibindings/src/com/google/inject/multibindings/ProvidesIntoSet.java
new file mode 100644
index 00000000..b26df683
--- /dev/null
+++ b/extensions/multibindings/src/com/google/inject/multibindings/ProvidesIntoSet.java
@@ -0,0 +1,53 @@
+/**
+ * Copyright (C) 2015 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.multibindings;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.inject.Module;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Annotates methods of a {@link Module} to add items to a {@link Multibinder}.
+ * The method's return type and binding annotation determines what Set this will
+ * contribute to. For example,
+ *
+ * <pre>
+ * {@literal @}ProvidesIntoSet
+ * {@literal @}Named("urls")
+ * String provideFooUrl(FooManager fm) { returm fm.getUrl(); }
+ *
+ * {@literal @}ProvidesIntoSet
+ * {@literal @}Named("urls")
+ * String provideBarUrl(BarManager bm) { return bm.getUrl(); }
+ * </pre>
+ *
+ * will add two items to the {@code @Named("urls") Set<String>} set. The items are bound as
+ * providers and will be evaluated at injection time.
+ *
+ * @author sameb@google.com (Sam Berlin)
+ * @since 4.0
+ */
+@Documented
+@Target(METHOD)
+@Retention(RUNTIME)
+public @interface ProvidesIntoSet {
+}
diff --git a/extensions/multibindings/src/com/google/inject/multibindings/StringMapKey.java b/extensions/multibindings/src/com/google/inject/multibindings/StringMapKey.java
new file mode 100644
index 00000000..dd4d99bc
--- /dev/null
+++ b/extensions/multibindings/src/com/google/inject/multibindings/StringMapKey.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (C) 2015 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.multibindings;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Allows {@literal @}{@link ProvidesIntoMap} to specify a string map key.
+ */
+@MapKey(unwrapValue = true)
+@Documented
+@Target(METHOD)
+@Retention(RUNTIME)
+public @interface StringMapKey {
+ String value();
+}