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

io.datakernel.di.util.Utils Maven / Gradle / Ivy

package io.datakernel.di.util;

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

import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
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.stream.Collector;

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>, Map>> MULTIMAP_MERGER =
			(into, from) -> from.forEach((k, v) -> into.computeIfAbsent(k, $ -> new HashSet<>()).addAll(v));

	@SuppressWarnings("unchecked")
	public static  BiConsumer>, Map>> multimapMerger() {
		return (BiConsumer>, Map>>) (BiConsumer) 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 scope.length != 0 ? 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> toMultimap(Map map) {
		return map.entrySet().stream().collect(toMap(Map.Entry::getKey, entry -> singleton(entry.getValue())));
	}

	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 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 boolean isKeySet(Key key) {
		if (Types.getRawType(key.getType()) != Set.class) {
			return false;
		}
		Name name = key.getName();
		if (name == null || !name.isMarkedBy(KeySetAnnotation.class)) {
			return false;
		}
		Key param = key.getTypeParameter(0);
		if (Types.getRawType(param.getType()) != Key.class) {
			return false;
		}
		Type subparam = param.getTypeParameter(0).getType();
		if (!(subparam instanceof WildcardType)) {
			return false;
		}
		WildcardType wildcard = (WildcardType) subparam;
		if (wildcard.getLowerBounds().length != 0) {
			return false;
		}
		if (wildcard.getUpperBounds().length != 1) {
			return false;
		}
		return wildcard.getUpperBounds()[0] == Object.class;
	}

	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, Binding>> 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, Binding>> 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, Binding>> 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, Binding>>> entry : trie.getChildren().entrySet()) {
			writeNodes(next(scope, entry.getKey()), entry.getValue(), known, indent + '\t', scopeCount, sb);
		}

		Set> leafs = new HashSet<>();

		for (Entry, Binding> entry : trie.get().entrySet()) {
			Key key = entry.getKey();
			if (entry.getValue().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("\"];\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, Binding>> trie, Set>> known, StringBuilder sb) {
		String scopePath = getScopeId(scope);

		for (Entry, Binding> 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 = "\"" + scopePath + depKey.toString().replace("\"", "\\\"") + '"';

					if (known.add(ScopedValue.of(scope, 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('"');
				}
				if (!dependency.isRequired()) {
					sb.append(" [style=dashed]");
				}
				sb.append(";\n");
			}
		}
		for (Entry, Binding>>> 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