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

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

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.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.*;

/**
 * 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, Set>>> bindings = Trie.merge(multimapMerger(), 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, Set>>> 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, Set>> 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, Set>>> bindings = renamed.getBindings();
		Map, Set>> localBindings = new HashMap<>(bindings.get());

		rebinds.forEach((k, b) -> {
			Set> bindingSet = localBindings.computeIfAbsent(originalToNew.get(k), $ -> new HashSet<>());
			bindingSet.clear();
			bindingSet.add(b);
		});

		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 -> transformMultimapValues(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 ->
								transformMultimap(bindingsMap,
										key -> originalToNew.getOrDefault(key, key),
										(key, binding) -> {
											if (isKeySet(key)) {
												binding = binding.mapInstance(set ->
														((Set>) set).stream()
																.map(k -> originalToNew.getOrDefault(k, k))
																.collect(toSet()));
											}
											return 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);
						})));
	}
}