package tc.oc.commons.core.inject; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; import com.google.common.reflect.TypeToken; import com.google.inject.Binder; import com.google.inject.Key; import com.google.inject.Provider; import com.google.inject.TypeLiteral; import com.google.inject.binder.LinkedBindingBuilder; import com.google.inject.multibindings.MapBinder; import tc.oc.commons.core.reflect.ResolvableType; import tc.oc.commons.core.reflect.TypeArgument; import tc.oc.commons.core.reflect.Types; import tc.oc.commons.core.util.ImmutableTypeMap; import tc.oc.commons.core.util.TypeMap; /** * Binds the contents of an immutable {@link TypeMap} by wrapping a {@link MapBinder}. */ public class TypeMapBinder<K, V> { private final TypeLiteral<K> keyType; private final TypeLiteral<V> valueType; private final Key<TypeMap<K, V>> collectionKey; private final Key<Map<TypeToken<? extends K>, Set<V>>> backingCollectionKey; private final MapBinder<TypeToken<? extends K>, V> backingCollectionBinder; public TypeMapBinder(Binder binder, @Nullable TypeLiteral<K> keyType, @Nullable TypeLiteral<V> valueType) { this.keyType = keyType != null ? keyType : new ResolvableType<K>(){}.in(getClass()); this.valueType = valueType != null ? valueType : new ResolvableType<V>(){}.in(getClass()); final TypeArgument<K> keyTypeArg = new TypeArgument<K>(this.keyType){}; final TypeArgument<V> valueTypeArg = new TypeArgument<V>(this.valueType){}; this.collectionKey = Key.get(new ResolvableType<TypeMap<K, V>>(){}.with(keyTypeArg, valueTypeArg)); this.backingCollectionKey = Key.get(new ResolvableType<Map<TypeToken<? extends K>, Set<V>>>(){}.with(keyTypeArg, valueTypeArg)); this.backingCollectionBinder = MapBinder.newMapBinder( binder, new ResolvableType<TypeToken<? extends K>>(){}.with(keyTypeArg), this.valueType ).permitDuplicates(); binder.install(new KeyedManifest.Impl(collectionKey) { @Override public void configure() { final Provider<Map<TypeToken<? extends K>, Set<V>>> backingCollectionProvider = getProvider(backingCollectionKey); bind(collectionType()).toProvider(() -> ImmutableTypeMap.copyOf(backingCollectionProvider.get())); } }); } protected TypeMapBinder(Binder binder) { this(binder, null, null); } public static <K1, V1> TypeMapBinder<K1, V1> ofType(Binder binder, TypeLiteral<K1> keyType, TypeLiteral<V1> valueType) { return new TypeMapBinder<>(binder, Types.assertFullySpecified(keyType), Types.assertFullySpecified(valueType)); } public static <K1, V1> TypeMapBinder<K1, V1> inContext(Binder binder, Class<?> declaringClass) { return new TypeMapBinder<>(binder, new ResolvableType<K1>(){}.in(declaringClass), new ResolvableType<V1>(){}.in(declaringClass)); } public Key<TypeMap<K, V>> collectionKey() { return collectionKey; } public TypeLiteral<TypeMap<K, V>> collectionType() { return collectionKey().getTypeLiteral(); } public LinkedBindingBuilder<V> addBinding(Class<? extends K> type) { return addBinding(TypeToken.of(type)); } public LinkedBindingBuilder<V> addBinding(TypeLiteral<? extends K> type) { return addBinding(Types.toToken(type)); } public LinkedBindingBuilder<V> addBinding(TypeToken<? extends K> type) { return backingCollectionBinder.addBinding(type); } }