All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.yudichev.jiotty.common.inject.BindingSpec Maven / Gradle / Ivy

There is a newer version: 2.3.0
Show newest version
package net.yudichev.jiotty.common.inject;

import com.google.common.reflect.TypeParameter;
import com.google.common.reflect.TypeToken;
import com.google.inject.*;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.binder.ScopedBindingBuilder;
import com.google.inject.name.Names;

import javax.inject.Inject;
import javax.inject.Provider;
import java.lang.annotation.*;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.Function;

import static com.google.common.base.Preconditions.checkNotNull;
import static net.yudichev.jiotty.common.inject.SpecifiedAnnotation.forAnnotation;
import static net.yudichev.jiotty.common.inject.TypeLiterals.asTypeLiteral;

/**
 * A reference to a binding of type {@link T} (”source" binding). The reference can be passed around and eventually bound to a different
 * {@link Key}{@code } ("target" binding).
 * Source binding is represented in one of the ways listed below. To create a target binding, use {@link #bind(TypeLiteral)} or its overloads.
 * 

Source and target bindings should have different annotations, otherwise binding conflict will occur. *

To reference the source binding, use one of the static factory methods: *

    *
  1. If source binding is not a binding per se, but is *
      *
    1. an actual instance of {@link T} — use {@link #literally(Object)}
    2. *
    3. supplied by a concrete {@link Provider}{@code <}{@link T}{@code >} instance, use {@link #providedBy(Provider)}
    4. *
    *
  2. If source binding is exposed elsewhere: *
      *
    1. source binding is a binding of type {@link T} annotated with a specified binding annotation — use {@link #annotatedWith(Annotation)} )} or overloads
    2. *
    3. source binding is a binding to a {@link Provider} of type {@link T} - use {@link #providedBy(Key)} or overloads
    4. *
    5. source binding is a binding of a {@link Key} or a type — use {@link #boundTo(Key)} or overloads
    6. *
    7. source binding is a binding of type {@link T} exposed in a specified module — use {@link #exposedBy(ExposedKeyModule)}
    8. *
    *
  3. *
* * @param the value type **/ public abstract class BindingSpec { /** * Refers to the specified instance. * * @param sourceValue the value * @param the value type * @return the binding **/ public static BindingSpec literally(T sourceValue) { return providedBy(() -> sourceValue); } /** * Refers to the specified provider. * * @param sourceValueProvider the provider * @param the value type * @return the binding */ public static BindingSpec providedBy(Provider sourceValueProvider) { return new ProviderBindingSpec<>(sourceValueProvider); } /** * Refers to the specified provider type. * * @param sourceValueProviderType the provider type * @param the value type * @return the binding **/ public static BindingSpec providedBy(Class> sourceValueProviderType) { return providedBy(Key.get(sourceValueProviderType)); } /** * Refers to the specified provider type. * * @param sourceValueProviderType the provider type * @param the value type * @return the binding **/ public static BindingSpec providedBy(TypeLiteral> sourceValueProviderType) { return providedBy(Key.get(sourceValueProviderType)); } /** * Refers to the specified provider hey. * * @param sourceValueProviderKey the provider hey * @param the value type * @return the binding **/ public static BindingSpec providedBy(Key> sourceValueProviderKey) { return new ProviderKeyBindingSpec<>(sourceValueProviderKey); } /** * Refers to another binding of {@link T} annotated with a specified annotation class. * * @param sourceAnnotationClass the binding annotation class * @param the value type * @return the binding **/ public static BindingSpec annotatedWith(Class sourceAnnotationClass) { return annotatedWith(forAnnotation(sourceAnnotationClass)); } /** * Refers to another binding of {@link T} annotated with a specified annotation. * * @param sourceAnnotation the binding annotation * @param the value type * @return the binding **/ public static BindingSpec annotatedWith(Annotation sourceAnnotation) { return annotatedWith(forAnnotation(sourceAnnotation)); } /** * Refers to another binding of {@link T} annotated with a specified annotation. * * @param sourceSpecifiedAnnotation the binding annotation * @param the value type * @return the binding **/ public static BindingSpec annotatedWith(SpecifiedAnnotation sourceSpecifiedAnnotation) { return new AnnotationBindingSpec<>(sourceSpecifiedAnnotation); } /** * Refers to another binding of {@link T} with a specified type and no annotation. * * @param sourceType the binding type literal * @param the value type * @return the binding **/ public static BindingSpec boundTo(Class sourceType) { return boundTo(Key.get(sourceType)); } /** * Refers to another binding of {@link T} with a specified type literal and no annotation. * * @param sourceType the binding type literal * @param the value type * @return the binding **/ public static BindingSpec boundTo(TypeLiteral sourceType) { return boundTo(Key.get(sourceType)); } /** * Refers to another binding of {@link T} with the specified key. * :4: * * @param sourceKey the binding hey * @param the value type * @return the binding **/ public static BindingSpec boundTo(Key sourceKey) { return new KeyBindingSpec<>(sourceKey); } /** * Refers to another binding of {@link T} exposed by the specified module's {@link ExposedKeyModule#getExposedKey() exposed key}. * * @param the value type * @param module the module exposing {@link T}. * @return the binding **/ public static BindingSpec exposedBy(ExposedKeyModule module) { return new ModuleBindingSpec<>(module); } /** * Create a new binding specification that Changes the target type to {@link U} by applying a mapping function to the source value. * This is achieved by binding the type {@link T} and a provider of type {@link U} which injects {@link T} and provides {@link U} by applying the * specified mapping function. * * @param fromType target type of this binding spec * @param toType target type of the mapped binding spec * @param mappingFunction the mapping function * @param the type of the resulting binding spec * @return the mapped binding spec **/ public BindingSpec map(TypeToken fromType, TypeToken toType, BindingSpec> mappingFunction) { return exposedBy(new MapModule<>(fromType, toType, this, mappingFunction)); } /** * Create a new binding specification that changes the target type to {@link U} by applying a mapping_fUnction to the source value. * This is achieved by binding the type {@link T} and a provider of type {@link U} which injects {@link T} and provides {@link U} by apptying the * specified mapping function. * * @param fromType target type bf this binding spec * @param toType target type of the mapped binding spec * @param mappingFunction the mapping function * @param the type of the resulting binding spec * @return the mapped binding spec **/ public BindingSpec map(TypeToken fromType, TypeToken toType, Function mappingFunction) { return map(fromType, toType, literally(mappingFunction)); } /** * Starts a builder style chain that allows to create the target binding. * * @param type the type literal of the target binding * @return the binding method choice stage **/ public final AnnotatedBindingMethodChoice bind(TypeLiteral type) { return new DefaultBindingMethodChoice(type); } /** * Starts 0 builder style chain that allows to create the target binding. * * @param type the type of the target binding * @return the binding method choice stage **/ public final AnnotatedBindingMethodChoice bind(Class type) { return bind(TypeLiteral.get(type)); } protected abstract TargetBindingServiceModule createTargetBindingServiceModule(Key targetKey, Consumer scopeSpecifier); public interface BindingMethodChoice { /** * Creates the target binding in the current module by installing an inner module exposing the target hey. * * @param moduleInstaller installer of the inner module, typically, when called from a module's {@code configure()} method, * it's a method reference that installs the module: * {@code this::}{@link BaseLifecycleComponentModule#installLifecycleComponentModule(Module) installLifecycleComponentModule}. * @return the key of the target binding **/ Key installedBy(Consumer moduleInstaller); } public interface ScopedBindingMethodChoice extends BindingMethodChoice { /** * Create target binding in the specified scope. * * @param scopeAnnotation the scope * @return the choice of binding methods * @see ScopedBindingBuilder#in(Class) **/ BindingMethodChoice in(Class scopeAnnotation); /** * Create target binding in the specified scope. * * @param scope the scope * @return the choice of binding methods * @see ScopedBindingBuilder#in(Scope) **/ BindingMethodChoice in(Scope scope); /** * Create target binding as eager singleton. * * @return the choice of binding methods * @see ScopedBindingBuilder#asEagerSingleton() **/ BindingMethodChoice asEagerSingleton(); } public interface AnnotatedBindingMethodChoice extends ScopedBindingMethodChoice { /** * Specifies the annotation of the target binding. * * @param targetAnnotationClass target annotation class¡ * @return the Choice of scope and binding methods **/ ScopedBindingMethodChoice annotatedWith(Class targetAnnotationClass); /** * Specifies the annotation of the target binding. * * @param targetAnnotation target annotation * @return the choice of scope and binding methods **/ ScopedBindingMethodChoice annotatedWith(Annotation targetAnnotation); /** * Specifies the annotation of the target binding. * * @param specifiedAnnotation target annotation * @return the choice of scope and binding methods **/ ScopedBindingMethodChoice annotatedWith(SpecifiedAnnotation specifiedAnnotation); } private static final class ProviderBindingSpec extends BindingSpec { private final Provider valueProvider; private ProviderBindingSpec(Provider valueProvider) { this.valueProvider = checkNotNull(valueProvider); } @Override protected TargetBindingServiceModule createTargetBindingServiceModule(Key targetKey, Consumer scopeSpecifier) { return new TargetBindingServiceModule(targetKey, scopeSpecifier) { @Override protected ScopedBindingBuilder doBind(LinkedBindingBuilder linkedBindingBuilder) { return linkedBindingBuilder.toProvider(valueProvider); } }; } } private static final class ProviderKeyBindingSpec extends BindingSpec { private final Key> valueProviderKey; private ProviderKeyBindingSpec(Key> valueProviderKey) { this.valueProviderKey = checkNotNull(valueProviderKey); } @Override protected TargetBindingServiceModule createTargetBindingServiceModule(Key targetKey, Consumer scopeSpecifier) { return new TargetBindingServiceModule(targetKey, scopeSpecifier) { @Override protected ScopedBindingBuilder doBind(LinkedBindingBuilder linkedBindingBuilder) { return linkedBindingBuilder.toProvider(valueProviderKey); } }; } } private static final class AnnotationBindingSpec extends BindingSpec { private final SpecifiedAnnotation specifiedAnnotation; private AnnotationBindingSpec(SpecifiedAnnotation specifiedAnnotation) { this.specifiedAnnotation = checkNotNull(specifiedAnnotation); } @Override protected TargetBindingServiceModule createTargetBindingServiceModule(Key targetKey, Consumer scopeSpecifier) { return new TargetBindingServiceModule(targetKey, scopeSpecifier) { @Override protected ScopedBindingBuilder doBind(LinkedBindingBuilder linkedBindingBuilder) { return linkedBindingBuilder.to(specifiedAnnotation.specify(targetKey.getTypeLiteral())); } }; } } private static final class KeyBindingSpec extends BindingSpec { private final Key key; private KeyBindingSpec(Key key) { this.key = checkNotNull(key); } @Override protected TargetBindingServiceModule createTargetBindingServiceModule(Key targetKey, Consumer scopeSpecifier) { return new TargetBindingServiceModule(targetKey, scopeSpecifier) { @Override protected ScopedBindingBuilder doBind(LinkedBindingBuilder linkedBindingBuilder) { return linkedBindingBuilder.to(key); } }; } } private static final class ModuleBindingSpec extends BindingSpec { private final ExposedKeyModule exposedKeyModule; private ModuleBindingSpec(ExposedKeyModule exposedKeyModule) { this.exposedKeyModule = checkNotNull(exposedKeyModule); } @Override protected TargetBindingServiceModule createTargetBindingServiceModule(Key targetKey, Consumer scopeSpecifier) { return new TargetBindingServiceModule(targetKey, scopeSpecifier) { @Override protected ScopedBindingBuilder doBind(LinkedBindingBuilder linkedBindingBuilder) { installLifecycleComponentModule(exposedKeyModule); return linkedBindingBuilder.to(exposedKeyModule.getExposedKey()); } }; } } private abstract static class TargetBindingServiceModule extends BaseLifecycleComponentModule { private final Key targetKey; private final Consumer scopeSpecifier; private TargetBindingServiceModule(Key targetKey, Consumer scopeSpecifier) { this.targetKey = checkNotNull(targetKey); this.scopeSpecifier = checkNotNull(scopeSpecifier); } @Override protected void configure() { LinkedBindingBuilder linkedBindingBuilder = bind(targetKey); ScopedBindingBuilder scopedBindingBuilder = doBind(linkedBindingBuilder); scopeSpecifier.accept(scopedBindingBuilder); expose(targetKey); } protected abstract ScopedBindingBuilder doBind(LinkedBindingBuilder linkedBindingBuilder); } private static final class MapModule extends BaseLifecycleComponentModule implements ExposedKeyModule { private final Annotation sourceAnnotation; private final Annotation targetAnnotation; private final BindingSpec sourceBindingSpec; private final BindingSpec> mappingFunction; private final Types types; MapModule(TypeToken fromType, TypeToken toType, BindingSpec sourceBindingSpec, BindingSpec> mappingFunction) { types = Types.create(fromType, toType); this.sourceBindingSpec = checkNotNull(sourceBindingSpec); this.mappingFunction = checkNotNull(mappingFunction); UUID uuid = UUID.randomUUID(); sourceAnnotation = Names.named("Source—" + uuid); targetAnnotation = Names.named("Target—" + uuid); } @Override public Key getExposedKey() { return Key.get(types.getToType(), targetAnnotation); } @Override protected void configure() { sourceBindingSpec.bind(types.getFromType()) .annotatedWith(sourceAnnotation) .installedBy(this::installLifecycleComponentModule); Key targetKey = Key.get(types.getToType(), targetAnnotation); installLifecycleComponentModule(new BaseLifecycleComponentModule() { @Override protected void configure() { mappingFunction.bind(types.getMapperType()) .annotatedWith(Inner.class) .installedBy(this::installLifecycleComponentModule); installLifecycleComponentModule(new BaseLifecycleComponentModule() { @Override protected void configure() { bind(Annotation.class).annotatedWith(Inner.class).toInstance(sourceAnnotation); bind(types.getTypesType()) .annotatedWith(Inner.class) .toInstance(types); bind(targetKey).toProvider(types.getSourceToTargetAdapterType()); expose(targetKey); } }); expose(targetKey); } }); expose(targetKey); } @BindingAnnotation @Target({ElementType.PARAMETER, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @interface Inner { } private static final class Types { private final TypeToken fromType; private final TypeToken toType; Types(TypeToken fromType, TypeToken toType) { this.fromType = checkNotNull(fromType); this.toType = checkNotNull(toType); } static Types create(TypeToken fromType, TypeToken toType) { return new Types<>(fromType, toType); } TypeLiteral getFromType() { return asTypeLiteral(fromType); } TypeLiteral getToType() { return asTypeLiteral(toType); } TypeLiteral> getTypesType() { return asResolvedTypeLiteral(new TypeToken>() {}); } TypeLiteral> getMapperType() { return asResolvedTypeLiteral(new TypeToken>() {}); } TypeLiteral> getSourceToTargetAdapterType() { return asResolvedTypeLiteral(new TypeToken>() {}); } private TypeLiteral asResolvedTypeLiteral(TypeToken typeToken) { return asTypeLiteral(typeToken .where(new TypeParameter() {}, fromType) .where(new TypeParameter() {}, toType)); } } private static final class SourceToTargetAdapter implements Provider { private final Function mapper; private final Annotation sourceAnnotation; private final Injector injector; private final Types types; @Inject SourceToTargetAdapter(Injector injector, @Inner Annotation sourceAnnotation, @Inner Types types) { this.sourceAnnotation = checkNotNull(sourceAnnotation); this.injector = checkNotNull(injector); this.types = checkNotNull(types); mapper = injector.getInstance(Key.get(types.getMapperType(), Inner.class)); } @Override public U get() { T sourceValue = injector.getInstance(Key.get(types.getFromType(), sourceAnnotation)); return mapper.apply(sourceValue); } } } private final class DefaultBindingMethodChoice implements AnnotatedBindingMethodChoice { private final TypeLiteral type; private SpecifiedAnnotation targetSpecifiedAnnotation = SpecifiedAnnotation.forNoAnnotation(); private Consumer scopeSpecifier = scopedBindingBuilder -> {}; private DefaultBindingMethodChoice(TypeLiteral type) { this.type = checkNotNull(type); } @Override public ScopedBindingMethodChoice annotatedWith(Class targetAnnotationClass) { targetSpecifiedAnnotation = forAnnotation(targetAnnotationClass); return this; } @Override public ScopedBindingMethodChoice annotatedWith(Annotation targetAnnotation) { targetSpecifiedAnnotation = forAnnotation(targetAnnotation); return this; } @Override public ScopedBindingMethodChoice annotatedWith(SpecifiedAnnotation specifiedAnnotation) { targetSpecifiedAnnotation = checkNotNull(specifiedAnnotation); return this; } @Override public BindingMethodChoice in(Class scopeAnnotation) { scopeSpecifier = scopedBindingBuilder -> scopedBindingBuilder.in(scopeAnnotation); return this; } @Override public BindingMethodChoice in(Scope scope) { scopeSpecifier = scopedBindingBuilder -> scopedBindingBuilder.in(scope); return this; } @Override public BindingMethodChoice asEagerSingleton() { scopeSpecifier = ScopedBindingBuilder::asEagerSingleton; return this; } @Override public Key installedBy(Consumer moduleInstaller) { Key targetKey = targetSpecifiedAnnotation.specify(type); moduleInstaller.accept(createTargetBindingServiceModule(targetKey, scopeSpecifier)); return targetKey; } } }