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

io.datakernel.di.module.AbstractModule Maven / Gradle / Ivy

package io.datakernel.di.module;

import io.datakernel.di.core.*;
import io.datakernel.di.util.ReflectionUtils;
import io.datakernel.di.util.Trie;
import io.datakernel.di.util.Types;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.annotation.Annotation;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiFunction;
import java.util.function.UnaryOperator;

import static io.datakernel.di.util.Utils.checkState;

/**
 * This class is an abstract module wrapper around {@link ModuleBuilder}.
 * It provides functionality that is similar to some other DI frameworks for the ease of transition.
 */
@SuppressWarnings({"SameParameterValue", "UnusedReturnValue"})
public abstract class AbstractModule implements Module {
	private Trie, Set>>> bindings;
	private Map>> bindingTransformers;
	private Map, Set>> bindingGenerators;
	private Map, Multibinder> multibinders;

	private final AtomicBoolean configured = new AtomicBoolean();

	@Nullable
	private ModuleBuilder builder = null;

	@Nullable
	private final StackTraceElement location;

	public AbstractModule() {
		StackTraceElement[] trace = Thread.currentThread().getStackTrace();
		StackTraceElement found = null;
		Class cls = getClass();
		for (int i = 2; i < trace.length; i++) {
			StackTraceElement element = trace[i];
			try {
				String className = element.getClassName();
				Class traceCls = Class.forName(className);
				if (!traceCls.isAssignableFrom(cls) && !className.startsWith("sun.reflect") && !className.startsWith("java.lang")) {
					found = element;
					break;
				}
			} catch (ClassNotFoundException ignored) {
				break;
			}
		}
		location = found;
	}

	/**
	 * This method is meant to be overridden to call all the bind(...) methods.
	 */
	protected void configure() {
	}

	/**
	 * @see ModuleBuilder#bind(Key)
	 */
	protected final  ModuleBuilderBinder bind(@NotNull Key key) {
		checkState(builder != null, "Cannot add bindings before or after configure() call");
		return builder.bind(Key.ofType(Types.resolveTypeVariables(key.getType(), getClass()), key.getName()));
	}

	/**
	 * @see ModuleBuilder#bind(Key)
	 */
	protected final  ModuleBuilderBinder bind(Class type) {
		checkState(builder != null, "Cannot add bindings before or after configure() call");
		return builder.bind(type);
	}

	/**
	 * @see ModuleBuilder#bind(Key)
	 */
	protected final  ModuleBuilderBinder bind(Class type, Name name) {
		checkState(builder != null, "Cannot add bindings before or after configure() call");
		return builder.bind(type, name);
	}

	/**
	 * @see ModuleBuilder#bind(Key)
	 */
	protected final  ModuleBuilderBinder bind(Class type, String name) {
		checkState(builder != null, "Cannot add bindings before or after configure() call");
		return builder.bind(type, name);
	}

	/**
	 * @see ModuleBuilder#bind(Key)
	 */
	protected final  ModuleBuilderBinder bind(Class type, Class annotationType) {
		checkState(builder != null, "Cannot add bindings before or after configure() call");
		return builder.bind(type, annotationType);
	}

	protected final  ModuleBuilder bindInstanceProvider(@NotNull Class key) {
		checkState(builder != null, "Cannot add bindings before or after configure() call");
		return builder.bindInstanceProvider(key);
	}

	protected final  ModuleBuilder bindInstanceProvider(@NotNull Key key) {
		checkState(builder != null, "Cannot add bindings before or after configure() call");
		return builder.bindInstanceProvider(key);
	}

	protected final  ModuleBuilder bindInstanceFactory(@NotNull Class key) {
		checkState(builder != null, "Cannot add bindings before or after configure() call");
		return builder.bindInstanceFactory(key);
	}

	protected final  ModuleBuilder bindInstanceFactory(@NotNull Key key) {
		checkState(builder != null, "Cannot add bindings before or after configure() call");
		return builder.bindInstanceFactory(key);
	}

	protected final  ModuleBuilder bindInstanceInjector(@NotNull Class key) {
		checkState(builder != null, "Cannot add bindings before or after configure() call");
		return builder.bindInstanceInjector(key);
	}

	protected final  ModuleBuilder bindInstanceInjector(@NotNull Key key) {
		checkState(builder != null, "Cannot add bindings before or after configure() call");
		return builder.bindInstanceInjector(Key.ofType(Types.parameterized(InstanceInjector.class, key.getType()), key.getName()));
	}

	/**
	 * @see ModuleBuilder#install
	 */
	protected final void install(Module module) {
		checkState(builder != null, "Cannot install modules before or after configure() call");
		builder.install(module);
	}

	/**
	 * @see ModuleBuilder#transform
	 */
	protected final  void transform(int priority, BindingTransformer bindingTransformer) {
		checkState(builder != null, "Cannot add transformers before or after configure() call");
		builder.transform(priority, bindingTransformer);
	}

	/**
	 * @see ModuleBuilder#generate
	 */
	protected final  void generate(Class pattern, BindingGenerator bindingGenerator) {
		checkState(builder != null, "Cannot add generators before or after configure() call");
		builder.generate(pattern, bindingGenerator);
	}

	/**
	 * @see ModuleBuilder#multibind
	 */
	protected final  void multibind(Key key, Multibinder multibinder) {
		checkState(builder != null, "Cannot add multibinders before or after configure() call");
		builder.multibind(key, multibinder);
	}

	protected final  void multibindToSet(Class type) {
		checkState(builder != null, "Cannot add bindings before or after configure() call");
		builder.multibindToSet(type);
	}

	protected final  void multibindToSet(Class type, String name) {
		checkState(builder != null, "Cannot add bindings before or after configure() call");
		builder.multibindToSet(type, name);
	}

	protected final  void multibindToSet(Class type, Name name) {
		checkState(builder != null, "Cannot add bindings before or after configure() call");
		builder.multibindToSet(type, name);
	}

	protected final  void multibindToSet(Key key) {
		checkState(builder != null, "Cannot add bindings before or after configure() call");
		builder.multibindToSet(key);
	}

	protected final  void multibindToMap(Class keyType, Class valueType) {
		checkState(builder != null, "Cannot add bindings before or after configure() call");
		builder.multibindToMap(keyType, valueType);
	}

	protected final  void multibindToMap(Class keyType, Class valueType, String name) {
		checkState(builder != null, "Cannot add bindings before or after configure() call");
		builder.multibindToMap(keyType, valueType, name);
	}

	protected final  void multibindToMap(Class keyType, Class valueType, Name name) {
		checkState(builder != null, "Cannot add bindings before or after configure() call");
		builder.multibindToMap(keyType, valueType, name);
	}

	/**
	 * @see ModuleBuilder#bindIntoSet(Key, Binding)
	 */
	protected final  void bindIntoSet(Key setOf, Binding binding) {
		checkState(builder != null, "Cannot bind into set before or after configure() call");
		builder.bindIntoSet(setOf, binding);
	}

	/**
	 * @see ModuleBuilder#bindIntoSet(Key, Binding)
	 */
	protected final  void bindIntoSet(Key setOf, Key item) {
		bindIntoSet(setOf, Binding.to(item));
	}

	/**
	 * @see ModuleBuilder#bindIntoSet(Key, Binding)
	 */
	protected final  void bindIntoSet(@NotNull Key setOf, @NotNull T element) {
		bindIntoSet(setOf, Binding.toInstance(element));
	}

	/**
	 * @see ModuleBuilder#postInjectInto(Key)
	 */
	protected final  void postInjectInto(Key key) {
		checkState(builder != null, "Cannot post inject into something before or after configure() call");
		builder.postInjectInto(key);
	}

	/**
	 * @see ModuleBuilder#postInjectInto(Key)
	 */
	protected final  void postInjectInto(Class type) {
		postInjectInto(Key.of(type));
	}

	protected final void scan(Object object) {
		checkState(builder != null, "Cannot add declarative bindings before or after configure() call");
		builder.scan(object);
	}

	protected final void scan(Class cls) {
		checkState(builder != null, "Cannot add declarative bindings before or after configure() call");
		builder.scan(cls);
	}

	private void finish() {
		if (!configured.compareAndSet(false, true)) {
			return;
		}

		ModuleBuilder b = Module.create().scan(getClass().getSuperclass(), this);
		ReflectionUtils.scanClassInto(getClass(), this, b); // so that provider methods and dsl bindings are in one 'export area'

		builder = b;
		configure();
		builder = null;

		bindings = b.getBindings();
		bindingTransformers = b.getBindingTransformers();
		bindingGenerators = b.getBindingGenerators();
		multibinders = b.getMultibinders();
	}

	@Override
	public final Trie, Set>>> getBindings() {
		finish();
		return bindings;
	}

	@Override
	public final Map>> getBindingTransformers() {
		finish();
		return bindingTransformers;
	}

	@Override
	public final Map, Set>> getBindingGenerators() {
		finish();
		return bindingGenerators;
	}

	@Override
	public final Map, Multibinder> getMultibinders() {
		finish();
		return multibinders;
	}

	// region forbid overriding default module methods

	@Override
	public Module combineWith(Module another) {
		return Module.super.combineWith(another);
	}

	@Override
	public Module overrideWith(Module another) {
		return Module.super.overrideWith(another);
	}

	@Override
	public Module transformWith(UnaryOperator fn) {
		return Module.super.transformWith(fn);
	}

	@Override
	public Module export(Key key, Key... keys) {
		return Module.super.export(key, keys);
	}

	@Override
	public Module export(Set> keys) {
		return Module.super.export(keys);
	}

	@Override
	public  Module rebindExport(Key from, Key to) {
		return Module.super.rebindExport(from, to);
	}

	@Override
	public  Module rebindImport(Key from, Key to) {
		return Module.super.rebindImport(from, to);
	}

	@Override
	public  Module rebindImport(Key from, Binding binding) {
		return Module.super.rebindImport(from, binding);
	}

	@Override
	public Module rebindExports(@NotNull Map, Key> mapping) {
		return Module.super.rebindExports(mapping);
	}

	@Override
	public Module rebindImports(@NotNull Map, Binding> mapping) {
		return Module.super.rebindImports(mapping);
	}

	@Override
	public Module rebindImportKeys(@NotNull Map, Key> mapping) {
		return Module.super.rebindImportKeys(mapping);
	}

	@Override
	public  Module rebindImportDependencies(Key key, Key dependency, Key to) {
		return Module.super.rebindImportDependencies(key, dependency, to);
	}

	@Override
	public  Module rebindImportDependencies(Key key, @NotNull Map, Key> dependencyMapping) {
		return Module.super.rebindImportDependencies(key, dependencyMapping);
	}

	@Override
	public Module rebindImports(BiFunction, Binding, Binding> rebinder) {
		return Module.super.rebindImports(rebinder);
	}

	// endregion

	@Override
	public String toString() {
		Class cls = getClass();
		return ReflectionUtils.getShortName(cls.isAnonymousClass() ? cls.getGenericSuperclass() : cls) +
				"(at " + (location != null ? location : "") + ')';
	}
}