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

io.datakernel.di.util.Utils 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.util;

import io.datakernel.di.core.*;
import org.jetbrains.annotations.Nullable;

import java.util.*;
import java.util.Map.Entry;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.UnaryOperator;
import java.util.stream.Collector;

import static io.datakernel.di.core.BindingType.EAGER;
import static io.datakernel.di.core.BindingType.TRANSIENT;
import static io.datakernel.di.core.Scope.UNSCOPED;
import static java.util.Collections.singleton;
import static java.util.stream.Collectors.*;

public final class Utils {

	private static final BiConsumer, BindingSet>, Map, BindingSet>> BINDING_MULTIMAP_MERGER =
			(into, from) -> from.forEach((k, v) -> into.merge(k, v, (first, second) -> BindingSet.merge(k, first, second)));

	public static BiConsumer, BindingSet>, Map, BindingSet>> bindingMultimapMerger() {
		return BINDING_MULTIMAP_MERGER;
	}

	public static  T[] next(T[] items, T item) {
		T[] next = Arrays.copyOf(items, items.length + 1);
		next[items.length] = item;
		return next;
	}

	public static String getScopeDisplayString(Scope[] scope) {
		return Arrays.stream(scope).map(Scope::getDisplayString).collect(joining("->", "()", ""));
	}

	public static void mergeMultibinders(Map, Multibinder> into, Map, Multibinder> from) {
		from.forEach((k, v) -> into.merge(k, v, (oldResolver, newResolver) -> {
			if (!oldResolver.equals(newResolver)) {
				throw new DIException("More than one multibinder per key");
			}
			return oldResolver;
		}));
	}

	public static  void combineMultimap(Map> accumulator, Map> multimap) {
		multimap.forEach((key, set) -> accumulator.computeIfAbsent(key, $ -> new HashSet<>()).addAll(set));
	}

	public static  Set union(Set first, Set second) {
		Set result = new HashSet<>((first.size() + second.size()) * 4 / 3 + 1);
		result.addAll(first);
		result.addAll(second);
		return result;
	}

	public static  Map override(Map into, Map from) {
		Map result = new HashMap<>((from.size() + into.size()) * 4 / 3 + 1);
		result.putAll(from);
		result.putAll(into);
		return result;
	}

	public static  Collector>> toMultimap(Function keyMapper,
			Function valueMapper) {
		return toMap(keyMapper, t -> singleton(valueMapper.apply(t)), Utils::union);
	}

	public static  Map> transformMultimapValues(Map> multimap, BiFunction fn) {
		return transformMultimap(multimap, Function.identity(), fn);
	}

	public static  Map> transformMultimap(Map> multimap, Function fnKey, BiFunction fnValue) {
		return multimap.entrySet()
				.stream()
				.collect(toMap(
						entry -> fnKey.apply(entry.getKey()),
						entry -> entry.getValue()
								.stream()
								.map(v -> fnValue.apply(entry.getKey(), v))
								.collect(toSet())));
	}

	public static Map, BindingSet> transformBindingMultimapValues(Map, BindingSet> multimap, BiFunction, Binding, Binding> fn) {
		return transformBindingMultimap(multimap, UnaryOperator.identity(), fn);
	}

	@SuppressWarnings("unchecked")
	public static Map, BindingSet> transformBindingMultimap(Map, BindingSet> multimap, UnaryOperator> fnKey, BiFunction, Binding, Binding> fnValue) {
		return multimap.entrySet()
				.stream()
				.collect(toMap(
						entry -> fnKey.apply(entry.getKey()),
						entry -> {
							BindingSet bindingSet = entry.getValue();
							return new BindingSet(
									bindingSet
											.getBindings()
											.stream()
											.map(v -> fnValue.apply(entry.getKey(), v))
											.collect(toSet()),
									bindingSet.getType());
						}));
	}

	public static  Map squash(Map> multimap, BiFunction, V> squasher) {
		return multimap.entrySet().stream()
				.collect(toMap(Entry::getKey, e -> squasher.apply(e.getKey(), e.getValue())));
	}

	public static void checkArgument(boolean condition, String message) {
		if (!condition) {
			throw new IllegalArgumentException(message);
		}
	}

	public static void checkState(boolean condition, String message) {
		if (!condition) {
			throw new IllegalStateException(message);
		}
	}

	public static String getLocation(@Nullable Binding binding) {
		LocationInfo location = binding != null ? binding.getLocation() : null;
		return "at " + (location != null ? location.toString() : "");
	}

	/**
	 * A shortcut for printing the result of {@link #makeGraphVizGraph} into the standard output.
	 */
	public static void printGraphVizGraph(Trie, BindingInfo>> trie) {
//		System.out.println("https://somegraphvizurl/#" + URLEncoder.encode(makeGraphVizGraph(trie), "utf-8").replaceAll("\\+", "%20"));
		System.out.println(makeGraphVizGraph(trie));
	}

	/**
	 * Makes a GraphViz graph representation of the binding graph.
	 * Scopes are grouped nicely into subgraph boxes and dependencies are properly drawn from lower to upper scopes.
	 */
	public static String makeGraphVizGraph(Trie, BindingInfo>> trie) {
		StringBuilder sb = new StringBuilder();
		sb.append("digraph {\n	rankdir=BT;\n");
		Set>> known = new HashSet<>();
		writeNodes(UNSCOPED, trie, known, "", new int[]{0}, sb);
		writeEdges(UNSCOPED, trie, known, sb);
		sb.append("}\n");
		return sb.toString();
	}

	private static void writeNodes(Scope[] scope, Trie, BindingInfo>> trie, Set>> known, String indent, int[] scopeCount, StringBuilder sb) {
		if (scope != UNSCOPED) {
			sb.append('\n').append(indent)
					.append("subgraph cluster_").append(scopeCount[0]++).append(" {\n")
					.append(indent).append("\tlabel=\"").append(scope[scope.length - 1].getDisplayString().replace("\"", "\\\"")).append("\"\n");
		}

		for (Entry, BindingInfo>>> entry : trie.getChildren().entrySet()) {
			writeNodes(next(scope, entry.getKey()), entry.getValue(), known, indent + '\t', scopeCount, sb);
		}

		Set> leafs = new HashSet<>();

		for (Entry, BindingInfo> entry : trie.get().entrySet()) {
			Key key = entry.getKey();
			BindingInfo bindingInfo = entry.getValue();

			if (bindingInfo.getDependencies().size() == 0) {
				leafs.add(key);
			}
			known.add(ScopedValue.of(scope, key));
			sb.append(indent)
					.append('\t')
					.append('"').append(getScopeId(scope)).append(key.toString().replace("\"", "\\\"")).append('"')
					.append(" [label=\"").append(key.getDisplayString().replace("\"", "\\\""))
					.append("\"")
					.append(bindingInfo.getType() == TRANSIENT ? " style=dotted" : bindingInfo.getType() == EAGER ? " style=bold" : "")
					.append("];\n");
		}

		if (!leafs.isEmpty()) {
			sb.append(leafs.stream()
					.map(key -> '"' + getScopeId(scope) + key.toString().replace("\"", "\\\"") + '"')
					.collect(joining(" ", '\n' + indent + "\t{ rank=same; ", " }\n")));
			if (scope == UNSCOPED) {
				sb.append('\n');
			}
		}

		if (scope != UNSCOPED) {
			sb.append(indent).append("}\n\n");
		}
	}

	private static void writeEdges(Scope[] scope, Trie, BindingInfo>> trie, Set>> known, StringBuilder sb) {
		String scopePath = getScopeId(scope);

		for (Entry, BindingInfo> entry : trie.get().entrySet()) {
			String key = "\"" + scopePath + entry.getKey().toString().replace("\"", "\\\"") + "\"";
			for (Dependency dependency : entry.getValue().getDependencies()) {
				Key depKey = dependency.getKey();

				Scope[] depScope = scope;
				while (!known.contains(ScopedValue.of(depScope, depKey)) && depScope.length != 0) {
					depScope = Arrays.copyOfRange(depScope, 0, depScope.length - 1);
				}

				if (depScope.length == 0) {
					String dep = "\"" + getScopeId(depScope) + depKey.toString().replace("\"", "\\\"") + '"';

					if (known.add(ScopedValue.of(depScope, depKey))) {
						sb.append('\t')
								.append(dep)
								.append(" [label=\"")
								.append(depKey.getDisplayString().replace("\"", "\\\""))
								.append("\" style=dashed, color=")
								.append(dependency.isRequired() ? "red" : "orange")
								.append("];\n");
					}
					sb.append('\t').append(key).append(" -> ").append(dep);
				} else {
					sb.append('\t').append(key).append(" -> \"").append(getScopeId(depScope)).append(depKey.toString().replace("\"", "\\\"")).append('"');
				}
				sb.append(" [");
				if (!dependency.isRequired()) {
					sb.append("style=dashed,");
				}
				if (dependency.isImplicit()) {
					sb.append("color=gray");
				}
				sb.append("];\n");
			}
		}
		for (Entry, BindingInfo>>> entry : trie.getChildren().entrySet()) {
			writeEdges(next(scope, entry.getKey()), entry.getValue(), known, sb);
		}
	}

	private static String getScopeId(Scope[] scope) {
		return Arrays.stream(scope).map(Scope::toString).collect(joining("->", "()->", "")).replace("\"", "\\\"");
	}

	public static int getKeyDisplayCenter(Key key) {
		Name name = key.getName();
		int nameOffset = name != null ? name.getDisplayString().length() + 1 : 0;
		return nameOffset + (key.getDisplayString().length() - nameOffset) / 2;
	}

	public static String drawCycle(Key[] cycle) {
		int offset = getKeyDisplayCenter(cycle[0]);
		String cycleString = Arrays.stream(cycle).map(Key::getDisplayString).collect(joining(" -> ", "\t", ""));
		String indent = new String(new char[offset]).replace('\0', ' ');
		String line = new String(new char[cycleString.length() - offset]).replace('\0', '-');
		return cycleString + " -,\n\t" + indent + "^" + line + "'";
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy