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

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

Go to download

DataKernel has an extremely lightweight DI with ground-breaking design principles. It supports nested scopes, singletons, object factories, modules and plugins which allow to transform graph of dependencies at startup time without any reflection.

The newest version!
package io.datakernel.di.module;

import io.datakernel.di.core.*;
import io.datakernel.di.impl.BindingLocator;
import io.datakernel.di.util.Trie;
import org.jetbrains.annotations.Nullable;

import java.util.*;
import java.util.Map.Entry;
import java.util.function.BiFunction;

import static io.datakernel.di.core.BindingType.COMMON;
import static io.datakernel.di.core.Name.uniqueName;
import static io.datakernel.di.core.Scope.UNSCOPED;
import static io.datakernel.di.util.Utils.*;
import static java.util.Collections.emptyMap;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toMap;

/**
 * This class contains a set of utilities for working with {@link Module modules}.
 */
public final class Modules {
	static final Module EMPTY = new SimpleModule(Trie.leaf(emptyMap()), emptyMap(), emptyMap(), emptyMap());

	/**
	 * Combines multiple modules into one.
	 */
	public static Module combine(Collection modules) {
		if (modules.size() == 1) {
			return modules.iterator().next();
		}
		Trie, BindingSet>> bindings = Trie.merge(bindingMultimapMerger(), new HashMap<>(), modules.stream().map(Module::getBindings));

		Map>> bindingTransformers = new HashMap<>();
		Map, Set>> bindingGenerators = new HashMap<>();
		Map, Multibinder> multibinders = new HashMap<>();

		for (Module module : modules) {
			combineMultimap(bindingTransformers, module.getBindingTransformers());
			combineMultimap(bindingGenerators, module.getBindingGenerators());
			mergeMultibinders(multibinders, module.getMultibinders());
		}

		return new SimpleModule(bindings, bindingTransformers, bindingGenerators, multibinders);
	}

	/**
	 * @see #combine(Collection)
	 */
	public static Module combine(Module... modules) {
		return modules.length == 0 ? Module.empty() : modules.length == 1 ? modules[0] : combine(Arrays.asList(modules));
	}

	/**
	 * Consecutively overrides each of the given modules with the next one after it and returns the accumulated result.
	 */
	public static Module override(List modules) {
		return modules.stream().reduce(Module.empty(), Modules::override);
	}

	/**
	 * @see #combine(Collection)
	 */
	public static Module override(Module... modules) {
		return override(Arrays.asList(modules));
	}

	/**
	 * This method creates a module that has bindings, transformers, generators and multibinders from first module
	 * replaced with bindings, transformers, generators and multibinders from the second module.
	 */
	public static Module override(Module into, Module replacements) {
		Trie, BindingSet>> bindings = Trie.merge(Map::putAll, new HashMap<>(), into.getBindings(), replacements.getBindings());

		Map>> bindingTransformers = new HashMap<>(into.getBindingTransformers());
		bindingTransformers.putAll(replacements.getBindingTransformers());

		Map, Set>> bindingGenerators = new HashMap<>(into.getBindingGenerators());
		bindingGenerators.putAll(replacements.getBindingGenerators());

		Map, Multibinder> multibinders = new HashMap<>(into.getMultibinders());
		multibinders.putAll(replacements.getMultibinders());

		return new SimpleModule(bindings, bindingTransformers, bindingGenerators, multibinders);
	}

	/**
	 * Creates a module with all trie nodes merged into one and placed at root.
	 * Basically, any scopes are ignored.
	 * This is useful for some tests.
	 */
	public static Module ignoreScopes(Module from) {
		Map, BindingSet> bindings = new HashMap<>();
		Map, Scope[]> scopes = new HashMap<>();
		from.getBindings().dfs(UNSCOPED, (scope, localBindings) ->
				localBindings.forEach((k, b) -> {
					bindings.merge(k, b, ($, $2) -> {
						Scope[] alreadyThere = scopes.get(k);
						String where = alreadyThere.length == 0 ? "in root" : "in scope " + getScopeDisplayString(alreadyThere);
						throw new IllegalStateException("Duplicate key " + k + ", already defined " + where + " and in scope " + getScopeDisplayString(scope));
					});
					scopes.put(k, scope);
				}));
		return new SimpleModule(Trie.leaf(bindings), from.getBindingTransformers(), from.getBindingGenerators(), from.getMultibinders());
	}

	static Module export(Module module, Set> exportedKeys) {
		Set> originalKeys = new HashSet<>();
		module.getBindings().dfs(multimap -> originalKeys.addAll(multimap.keySet()));

		Set> missing = new HashSet<>(exportedKeys);
		missing.removeAll(originalKeys);

		if (!missing.isEmpty()) {
			throw new DIException(missing.stream()
					.map(Key::getDisplayString)
					.collect(joining(", ", "Exporting keys ", " that were not provided by the module")));
		}

		return doRebind(module,
				originalKeys.stream()
						.filter(originalKey -> !exportedKeys.contains(originalKey))
						.collect(toMap(identity(), originalKey ->
								Key.ofType(originalKey.getType(), uniqueName(originalKey.getName())))));
	}

	static Module rebindExports(Module module, Map, Key> originalToNew) {
		Set> originalKeys = new HashSet<>();

		module.getBindings().dfs(multimap -> originalKeys.addAll(multimap.keySet()));

		if (originalToNew.keySet().stream().noneMatch(originalKeys::contains)) {
			return module;
		}
		return doRebind(module, originalToNew);
	}

	@SuppressWarnings("unchecked")
	static Module rebindImports(Module module, Map, Binding> rebinds) {
		Map, Key> originalToNew = new HashMap<>();

		rebinds.forEach((k, b) -> {
			Key priv = (Key) k.named(uniqueName(k.getName()));
			originalToNew.put(k, priv);
		});

		Module renamed = doRebind(module, originalToNew);
		Trie, BindingSet>> bindings = renamed.getBindings();
		Map, BindingSet> localBindings = new HashMap<>(bindings.get());

		rebinds.forEach((k, b) -> {
			Set> bindingSet = new HashSet<>();
			bindingSet.add(b);
			localBindings.put(originalToNew.get(k), new BindingSet(bindingSet, COMMON));
		});

		return Module.of(Trie.of(localBindings, bindings.getChildren()), renamed.getBindingTransformers(), renamed.getBindingGenerators(), renamed.getMultibinders());
	}

	static Module rebindImportKeys(Module module, Map, Key> mapping) {
		return rebindImports(module, mapping.entrySet().stream().collect(toMap(Entry::getKey, e -> Binding.to(e.getValue()))));
	}

	@SuppressWarnings("unchecked")
	static Module rebindImports(Module module, BiFunction, Binding, Binding> rebinder) {
		return new SimpleModule(
				module.getBindings().map(bindingsMap -> transformBindingMultimapValues(bindingsMap, rebinder)),
				transformMultimapValues(module.getBindingTransformers(),
						(priority, bindingTransformer) ->
								(bindings, scope, key, binding) -> {
									Binding transformed = ((BindingTransformer) bindingTransformer).transform(bindings, scope, key, binding);
									if (transformed == binding) return binding;
									return (Binding) rebinder.apply(key, transformed);
								}),
				transformMultimapValues(module.getBindingGenerators(),
						(clazz, bindingGenerator) ->
								(provider, scope, key) -> {
									Binding generated = ((BindingGenerator) bindingGenerator).generate(provider, scope, key);
									return generated != null ? (Binding) rebinder.apply(key, generated) : null;
								}),
				module.getMultibinders());
	}

	private static  Binding rebindMatchedDependencies(Binding binding, Map, Key> originalToNew) {
		return binding.rebindDependencies(
				binding.getDependencies()
						.stream()
						.map(Dependency::getKey)
						.filter(originalToNew::containsKey)
						.collect(toMap(identity(), originalToNew::get)));
	}

	@SuppressWarnings("unchecked")
	private static Module doRebind(Module module, Map, Key> originalToNew) {
		Map, Key> newToOriginal = originalToNew.entrySet().stream().collect(toMap(Entry::getValue, Entry::getKey));
		return new SimpleModule(
				module.getBindings()
						.map(bindingsMap ->
								transformBindingMultimap(bindingsMap,
										key -> originalToNew.getOrDefault(key, key),
										(key, binding) -> rebindMatchedDependencies(binding, originalToNew))),
				transformMultimapValues(module.getBindingTransformers(),
						($, transformer) ->
								(bindings, scope, key, binding) -> {
									Binding transformed = ((BindingTransformer) transformer).transform(
											new BindingLocator() {
												@Override
												public @Nullable  Binding get(Key key) {
													return (Binding) bindings.get(originalToNew.getOrDefault(key, key));
												}
											},
											scope,
											(Key) newToOriginal.getOrDefault(key, key),
											binding);
									return transformed.equals(binding) ?
											binding :
											rebindMatchedDependencies(transformed, originalToNew);
								}),
				transformMultimapValues(module.getBindingGenerators(),
						($, generator) ->
								(bindings, scope, key) -> {
									Binding binding = ((BindingGenerator) generator).generate(
											new BindingLocator() {
												@Override
												public @Nullable  Binding get(Key key) {
													return (Binding) bindings.get(originalToNew.getOrDefault(key, key));
												}
											},
											scope,
											(Key) newToOriginal.getOrDefault(key, key));
									return binding != null ?
											rebindMatchedDependencies(binding, originalToNew) :
											null;
								}),
				module.getMultibinders()
						.entrySet()
						.stream()
						.collect(toMap(e -> originalToNew.getOrDefault(e.getKey(), e.getKey()), e -> {
							Multibinder value = e.getValue();
							if (!originalToNew.containsKey(e.getKey())) {
								return value;
							}
							return (key, bindings) -> rebindMatchedDependencies(((Multibinder) value).multibind(key, bindings), originalToNew);
						})));
	}
}