net.yudichev.jiotty.common.inject.BindingSpec Maven / Gradle / Ivy
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:
*
* - If source binding is not a binding per se, but is
*
* - an actual instance of {@link T} — use {@link #literally(Object)}
* - supplied by a concrete {@link Provider}{@code <}{@link T}{@code >} instance, use {@link #providedBy(Provider)}
*
* - If source binding is exposed elsewhere:
*
* - source binding is a binding of type {@link T} annotated with a specified binding annotation — use {@link #annotatedWith(Annotation)} )} or overloads
* - source binding is a binding to a {@link Provider} of type {@link T} - use {@link #providedBy(Key)} or overloads
* - source binding is a binding of a {@link Key} or a type — use {@link #boundTo(Key)} or overloads
* - source binding is a binding of type {@link T} exposed in a specified module — use {@link #exposedBy(ExposedKeyModule)}
*
*
*
*
* @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 extends Provider> 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 extends Provider> 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 extends Provider> 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 extends Annotation> 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 extends T> 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 extends T> 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 extends T> 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 extends T> 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 super T, ? extends U> 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 extends Annotation> 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 extends Annotation> 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 super T> linkedBindingBuilder) {
return linkedBindingBuilder.toProvider(valueProvider);
}
};
}
}
private static final class ProviderKeyBindingSpec extends BindingSpec {
private final Key extends Provider> valueProviderKey;
private ProviderKeyBindingSpec(Key extends Provider> valueProviderKey) {
this.valueProviderKey = checkNotNull(valueProviderKey);
}
@Override
protected TargetBindingServiceModule createTargetBindingServiceModule(Key targetKey, Consumer scopeSpecifier) {
return new TargetBindingServiceModule(targetKey, scopeSpecifier) {
@Override
protected ScopedBindingBuilder doBind(LinkedBindingBuilder super T> 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 super T> linkedBindingBuilder) {
return linkedBindingBuilder.to(specifiedAnnotation.specify(targetKey.getTypeLiteral()));
}
};
}
}
private static final class KeyBindingSpec extends BindingSpec {
private final Key extends T> key;
private KeyBindingSpec(Key extends T> key) {
this.key = checkNotNull(key);
}
@Override
protected TargetBindingServiceModule createTargetBindingServiceModule(Key targetKey,
Consumer scopeSpecifier) {
return new TargetBindingServiceModule(targetKey, scopeSpecifier) {
@Override
protected ScopedBindingBuilder doBind(LinkedBindingBuilder super T> linkedBindingBuilder) {
return linkedBindingBuilder.to(key);
}
};
}
}
private static final class ModuleBindingSpec extends BindingSpec {
private final ExposedKeyModule extends T> exposedKeyModule;
private ModuleBindingSpec(ExposedKeyModule extends T> exposedKeyModule) {
this.exposedKeyModule = checkNotNull(exposedKeyModule);
}
@Override
protected TargetBindingServiceModule createTargetBindingServiceModule(Key targetKey, Consumer scopeSpecifier) {
return new TargetBindingServiceModule(targetKey, scopeSpecifier) {
@Override
protected ScopedBindingBuilder doBind(LinkedBindingBuilder super T> 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 super T> 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 super T, ? extends U> 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 extends Annotation> 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 extends Annotation> 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;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy