diff options
Diffstat (limited to 'extensions/multibindings/src/com')
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(); +} |