/* * Copyright 2013 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.gwt.inject.client.multibindings; import static com.google.gwt.inject.client.multibindings.TypeLiterals.mapOf; import static com.google.gwt.inject.client.multibindings.TypeLiterals.providerOf; import static com.google.gwt.inject.client.multibindings.TypeLiterals.setOf; import com.google.gwt.inject.client.binder.GinBinder; import com.google.gwt.inject.client.binder.GinLinkedBindingBuilder; import com.google.gwt.inject.client.binder.GinScopedBindingBuilder; import com.google.gwt.inject.client.multibindings.InternalModule.SingletonInternalModule; import com.google.inject.Key; import com.google.inject.Provider; import com.google.inject.Singleton; import com.google.inject.TypeLiteral; import java.lang.annotation.Annotation; import java.util.Map; /** * A utility that mimics the behavior and API of Guice MapBinder for GIN. * * <p>Example usage: * <pre> * interface X {}; * * class X1Impl implements X {}; * * class X2Impl implements X {}; * * class X3Provider implements Provider<X> {}; * * GinMapBinder<String, X> mapBinder = * GinMapBinder.newMapBinder(binder(), String.class, X.class); * mapBinder.addBinding("id1").to(X1Impl.class); * mapBinder.addBinding("id2").to(X2Impl.class); * mapBinder.addBinding("id3").toProvider(X3Provider.class); * </pre> * * <p> * GIN supports instance binding for only limited set of types. To overcome this limitation, * GinMapBinder provides {@link #addBinding(Class)} method so bindings can be added via a key * provider class that will instantiate the actual key during runtime. This alternative approach * is needed to used for all key types that cannot be bound via * {@link com.google.gwt.inject.client.binder.GinConstantBindingBuilder}: * <pre> * class Place { * public Place(String key) { ... } * } * * class HomePlaceProvider implements Provider<Place> { * public Place get() { * return new Place("home"); * } * } * * class AboutPlaceProvider implements Provider<Place> { * public Place get() { * return new Place("about"); * } * } * * GinMapBinder<Place, X> mapBinder = * GinMapBinder.newMapBinder(binder(), Place.class, X.class); * mapBinder.addBinding(HomePlaceProvider.class).to(XImpl1.class); * mapBinder.addBinding(AboutPlaceProvider.class).to(XImpl2.class); * </pre> * <p> * * @param <K> type of key for map * @param <V> type of value for map */ public final class GinMapBinder<K, V> { /** * Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a * {@link Map} that is itself bound with no binding annotation. */ public static <K, V> GinMapBinder<K, V> newMapBinder( GinBinder binder, TypeLiteral<K> keyType, TypeLiteral<V> valueType) { return newMapBinder(binder, keyType, valueType, Key.get(entryOf(keyType, valueType))); } /** * Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a * {@link Map} that is itself bound with no binding annotation. */ public static <K, V> GinMapBinder<K, V> newMapBinder( GinBinder binder, Class<K> keyType, Class<V> valueType) { return newMapBinder(binder, TypeLiteral.get(keyType), TypeLiteral.get(valueType)); } /** * Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a * {@link Map} that is itself bound with {@code annotation}. */ public static <K, V> GinMapBinder<K, V> newMapBinder( GinBinder binder, TypeLiteral<K> keyType, TypeLiteral<V> valueType, Annotation annotation) { return newMapBinder( binder, keyType, valueType, Key.get(entryOf(keyType, valueType), annotation)); } /** * Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a * {@link Map} that is itself bound with {@code annotation}. */ public static <K, V> GinMapBinder<K, V> newMapBinder( GinBinder binder, Class<K> keyType, Class<V> valueType, Annotation annotation) { return newMapBinder(binder, TypeLiteral.get(keyType), TypeLiteral.get(valueType), annotation); } /** * Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a * {@link Map} that is itself bound with {@code annotationType}. */ public static <K, V> GinMapBinder<K, V> newMapBinder(GinBinder binder, TypeLiteral<K> keyType, TypeLiteral<V> valueType, Class<? extends Annotation> annotationType) { return newMapBinder( binder, keyType, valueType, Key.get(entryOf(keyType, valueType), annotationType)); } /** * Returns a new mapbinder that collects entries of {@code keyType}/{@code valueType} in a * {@link Map} that is itself bound with {@code annotationType}. */ public static <K, V> GinMapBinder<K, V> newMapBinder(GinBinder binder, Class<K> keyType, Class<V> valueType, Class<? extends Annotation> annotationType) { return newMapBinder( binder, TypeLiteral.get(keyType), TypeLiteral.get(valueType), annotationType); } private static <K, V> GinMapBinder<K, V> newMapBinder(GinBinder binder, TypeLiteral<K> keyType, TypeLiteral<V> valueType, Key<MapEntry<K, V>> registryKey) { GinMapBinder<K, V> mapBinder = new GinMapBinder<K, V>(binder, keyType, valueType, registryKey); mapBinder.install(); return mapBinder; } private final GinBinder ginBinder; private final TypeLiteral<K> keyType; private final TypeLiteral<V> valueType; private final Key<MapEntry<K, V>> multibindingKey; private GinMapBinder(GinBinder ginBinder, TypeLiteral<K> keyType, TypeLiteral<V> valueType, Key<MapEntry<K, V>> keyForMultibinding) { this.ginBinder = ginBinder; this.keyType = keyType; this.valueType = valueType; this.multibindingKey = keyForMultibinding; } private void install() { ginBinder.install(new RuntimeBindingsRegistryModule<MapEntry<K, V>>(multibindingKey)); ginBinder.install(new MapModule()); } /** * Configures the {@code MapBinder} to handle duplicate entries. * <p> * When multiple equal keys are bound, the value that gets included in the map is arbitrary. * <p> * In addition to the {@code Map<K, V>} and {@code Map<K, Provider<V>>} maps that are normally * bound, a {@code Map<K, Set<V>>} and {@code Map<K, Set<Provider<V>>>} are <em>also</em> bound, * which contain all values bound to each key. * <p> * When multiple modules contribute elements to the map, this configuration option impacts all of * them. * * @return this map binder */ public GinMapBinder<K, V> permitDuplicates() { ginBinder.install(new PermitDuplicatesModule<MapEntry<K, V>>(multibindingKey)); ginBinder.install(new MultimapModule()); return this; } /** * Returns a binding builder used to add a new entry in the map. Each key must be distinct (and * non-null). Bound providers will be evaluated each time the map is injected. * <p> * It is an error to call this method without also calling one of the {@code to} methods on the * returned binding builder. * <p> * Scoping elements independently is supported. Use the {@code in} method to specify a binding * scope. */ public GinLinkedBindingBuilder<V> addBinding(K key) { BindingRecorder recorder = createRecorder(); if (key instanceof String) { recorder.bindConstant().to((String) key); } else if (key instanceof Enum<?>) { recorder.bindConstant().to((Enum) key); } else if (key instanceof Integer) { recorder.bindConstant().to((Integer) key); } else if (key instanceof Long) { recorder.bindConstant().to((Long) key); } else if (key instanceof Float) { recorder.bindConstant().to((Float) key); } else if (key instanceof Double) { recorder.bindConstant().to((Double) key); } else if (key instanceof Short) { recorder.bindConstant().to((Short) key); } else if (key instanceof Boolean) { recorder.bindConstant().to((Boolean) key); } else if (key instanceof Character) { recorder.bindConstant().to((Character) key); } else if (key instanceof Class<?>) { recorder.bindConstant().to((Class<?>) key); } else { throw new IllegalArgumentException( "Key type " + keyType + " is non-constant and can only be added using providers"); } return recorder.bind(valueType); } /** * Returns a binding builder used to add a new entry in the map using a key provider. * <p> * This API is not compatible with Guice however it is provided as GIN has limitation to bind * 'instances'. For that reason for all key types that are not defined in * {@link com.google.gwt.inject.client.binder.GinConstantBindingBuilder} needs to use a provider * class for each key together with this method. * * @see #addBinding(Object) */ public GinLinkedBindingBuilder<V> addBinding( Class<? extends javax.inject.Provider<? extends K>> keyProvider) { return addBinding(TypeLiteral.get(keyProvider)); } /** * Returns a binding builder used to add a new entry in the map using a key provider. * <p> * This API is not compatible with Guice however it is provided as GIN has limitation to bind * 'instances'. For that reason for all key types that are not defined in * {@link com.google.gwt.inject.client.binder.GinConstantBindingBuilder} needs to use a provider * class for each key together with this method. * * @see #addBinding(Object) */ public GinLinkedBindingBuilder<V> addBinding( TypeLiteral<? extends javax.inject.Provider<? extends K>> keyProvider) { BindingRecorder recorder = createRecorder(); recorder.bind(keyType).toProvider(Key.get(keyProvider)); return recorder.bind(valueType); } private BindingRecorder createRecorder() { BindingRecorder recorder = new BindingRecorder(ginBinder, multibindingKey); // binds @Internal MapEntry<K, V> to MapEntry recorder.bind(multibindingKey.getTypeLiteral()).to(multibindingKey.getTypeLiteral()); return recorder; } // TODO(user): not private due to http://code.google.com/p/google-gin/issues/detail?id=184 final class MapModule extends AbstractMapModule { @Override protected void configure() { bindInternalBindingsRegistry(); bindMap(valueType, providerForMapOf(keyType, valueType)); bindMap(providerOf(valueType), providerForProviderMapOf(keyType, valueType)) .in(Singleton.class); } } // TODO(user): not private due to http://code.google.com/p/google-gin/issues/detail?id=184 final class MultimapModule extends AbstractMapModule { @Override protected void configure() { bindInternalBindingsRegistry(); bindMap(setOf(valueType), providerForMultiMapOf(keyType, valueType)); bindMap(setOf(providerOf(valueType)), providerForProviderMultiMapOf(keyType, valueType)) .in(Singleton.class); } } private abstract class AbstractMapModule extends SingletonInternalModule<MapEntry<K, V>> { public AbstractMapModule() { super(multibindingKey); } protected <V> GinScopedBindingBuilder bindMap( TypeLiteral<V> valueType, TypeLiteral<? extends Provider<Map<K, V>>> providerType) { return bindAndExpose(mapOf(keyType, valueType)).toProvider(Key.get(providerType)); } } @SuppressWarnings("unchecked") private static <K, V> TypeLiteral<MapEntry<K, V>> entryOf( TypeLiteral<K> keyType, TypeLiteral<V> valueType) { return TypeLiterals.newParameterizedType(MapEntry.class, keyType, valueType); } @SuppressWarnings("unchecked") private static <K, V> TypeLiteral<ProviderForMap<K, V>> providerForMapOf(TypeLiteral<K> keyType, TypeLiteral<V> valueType) { return TypeLiterals.newParameterizedType(ProviderForMap.class, keyType, valueType); } @SuppressWarnings("unchecked") private static <K, V> TypeLiteral<ProviderForMultiMap<K, V>> providerForMultiMapOf( TypeLiteral<K> keyType, TypeLiteral<V> valueType) { return TypeLiterals.newParameterizedType(ProviderForMultiMap.class, keyType, valueType); } @SuppressWarnings("unchecked") private static <K, V> TypeLiteral<ProviderForProviderMap<K, V>> providerForProviderMapOf( TypeLiteral<K> keyType, TypeLiteral<V> valueType) { return TypeLiterals.newParameterizedType(ProviderForProviderMap.class, keyType, valueType); } @SuppressWarnings("unchecked") private static <K, V> TypeLiteral<ProviderForProviderMultiMap<K, V>> providerForProviderMultiMapOf(TypeLiteral<K> keyType, TypeLiteral<V> valueType) { return TypeLiterals.newParameterizedType(ProviderForProviderMultiMap.class, keyType, valueType); } }