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

io.datakernel.di.core.Multibinder 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.core;

import io.datakernel.di.impl.AbstractCompiledBinding;
import io.datakernel.di.impl.CompiledBinding;
import io.datakernel.di.util.Utils;

import java.util.*;
import java.util.concurrent.atomic.AtomicReferenceArray;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static io.datakernel.di.core.BindingType.TRANSIENT;
import static java.util.stream.Collectors.joining;

/**
 * This is a function that is used to resolve binding conflicts.
 */
@FunctionalInterface
public interface Multibinder {
	Binding multibind(Key key, BindingSet bindings);

	/**
	 * Default multibinder that just throws an exception if there is more than one binding per key.
	 */
	Multibinder ERROR_ON_DUPLICATE = (key, bindings) -> {
		throw new DIException(bindings.getBindings().stream()
				.map(Utils::getLocation)
				.collect(joining("\n\t", "Duplicate bindings for key " + key.getDisplayString() + ":\n\t", "\n")));
	};

	@SuppressWarnings("unchecked")
	static  Multibinder getErrorOnDuplicate() {
		return (Multibinder) ERROR_ON_DUPLICATE;
	}

	/**
	 * Multibinder that returns a binding that applies given reducing function to set of instances provided by all conflicting bindings.
	 */
	static  Multibinder ofReducer(BiFunction, Stream, T> reducerFunction) {
		return (key, bindings) ->
				new Binding<>(bindings.getBindings().stream().map(Binding::getDependencies).flatMap(Collection::stream).collect(Collectors.toSet()),
						(compiledBindings, threadsafe, scope, slot) -> {
							final CompiledBinding[] conflictedBindings = bindings.getBindings().stream()
									.map(Binding::getCompiler)
									.map(bindingCompiler -> bindingCompiler.compile(compiledBindings, true, scope, null))
									.toArray(CompiledBinding[]::new);

							return slot == null || bindings.getType() == TRANSIENT ?
									new CompiledBinding() {
										@SuppressWarnings("unchecked")
										@Override
										public T getInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
											return reducerFunction.apply(key, Arrays.stream(conflictedBindings)
													.map(binding -> (T) binding.getInstance(scopedInstances, synchronizedScope)));
										}
									} :
									new AbstractCompiledBinding(scope, slot) {
										@SuppressWarnings("unchecked")
										@Override
										protected T doCreateInstance(AtomicReferenceArray[] scopedInstances, int synchronizedScope) {
											return reducerFunction.apply(key, Arrays.stream(conflictedBindings)
													.map(binding -> (T) binding.getInstance(scopedInstances, synchronizedScope)));
										}
									};
						});
	}

	/**
	 * @see #ofReducer
	 */
	@SuppressWarnings("OptionalGetWithoutIsPresent")
	static  Multibinder ofBinaryOperator(BinaryOperator binaryOperator) {
		return ofReducer(($, stream) -> stream.reduce(binaryOperator).get());
	}

	Multibinder> TO_SET = ofReducer((key, stream) -> {
		Set result = new HashSet<>();
		stream.forEach(result::addAll);
		return result;
	});

	/**
	 * Multibinder that returns a binding for a merged set of sets provided by all conflicting bindings.
	 */
	@SuppressWarnings("unchecked")
	static  Multibinder> toSet() {
		return (Multibinder) TO_SET;
	}

	Multibinder> TO_MAP = ofReducer((key, stream) -> {
		Map result = new HashMap<>();
		stream.forEach(map ->
				map.forEach((k, v) ->
						result.merge(k, v, ($, $2) -> {
							throw new DIException("Duplicate key " + k + " while merging maps for key " + key.getDisplayString());
						})));
		return result;
	});

	/**
	 * Multibinder that returns a binding for a merged map of maps provided by all conflicting bindings.
	 *
	 * @throws DIException on map merge conflicts
	 */
	@SuppressWarnings("unchecked")
	static  Multibinder> toMap() {
		return (Multibinder) TO_MAP;
	}

	/**
	 * Combines all multibinders into one by their type and returns universal multibinder for any key from the map, falling back
	 * to {@link #ERROR_ON_DUPLICATE} when map contains no multibinder for a given key.
	 */
	@SuppressWarnings("unchecked")
	static Multibinder combinedMultibinder(Map, Multibinder> multibinders) {
		return (key, bindings) ->
				((Multibinder) multibinders.getOrDefault(key, ERROR_ON_DUPLICATE)).multibind(key, bindings);
	}
}