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

org.conqat.lib.commons.graph.GraphDebuggingUtils Maven / Gradle / Ivy

There is a newer version: 2024.7.2
Show newest version
/*
 * Copyright (c) CQSE GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.conqat.lib.commons.graph;

import static org.conqat.lib.commons.collections.CollectionUtils.map;
import static org.conqat.lib.commons.string.StringUtils.concat;

import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

import org.conqat.lib.commons.collections.IIdProvider;
import org.conqat.lib.commons.collections.IdManager;
import org.conqat.lib.commons.visitor.IMeshWalker;

/**
 * Useful methods for debugging arbitrary graphs.
 */
public class GraphDebuggingUtils {

	/**
	 * Returns a string representation of the graph. The individual nodes are represented using their
	 * toString() method. This is useful for debugging.
	 * 
	 * @param root
	 *            the root node of the graph to visualize.
	 * @param optionalIdProvider
	 *            an optional IdProvider that provides integer IDs for all Nodes. If {@code null}, we
	 *            will use an IdManager. The IDs are assumed to be stable (the same on each query of the
	 *            same node).
	 * @param walkers
	 *            the mesh walkers used to traverse the graph.
	 */
	@SafeVarargs
	public static  String getString(NodeT root,
			IIdProvider optionalIdProvider, IMeshWalker... walkers) throws X {
		return getString(root, optionalIdProvider, true, walkers);
	}

	/**
	 * Returns a human-readable string representation of the graph. The individual nodes are only
	 * represented as integers. This is useful for debugging.
	 * 
	 * @param root
	 *            the root node of the graph to visualize.
	 * @param optionalIdProvider
	 *            an optional IdProvider that provides integer IDs for all Nodes. If {@code null}, we
	 *            will use an IdManager. The IDs are assumed to be stable (the same on each query of the
	 *            same node).
	 * @param walkers
	 *            the mesh walkers used to traverse the graph.
	 */
	@SafeVarargs
	public static  String getShortString(NodeT root,
			IIdProvider optionalIdProvider, IMeshWalker... walkers) throws X {
		return getString(root, optionalIdProvider, false, walkers);
	}

	/**
	 * Returns a string representation of the graph.
	 * 
	 * @param root
	 *            the root node of the graph to visualize.
	 * @param optionalIdProvider
	 *            an optional IdProvider that provides integer IDs for all Nodes. If {@code null}, we
	 *            will use an IdManager. The IDs are assumed to be stable (the same on each query of the
	 *            same node).
	 * @param detailed
	 *            if true, each node's toString() representation is included in the output.
	 * @param walkers
	 *            the mesh walkers used to traverse different classes of edges found in the graph.
	 */
	@SafeVarargs
	private static  String getString(NodeT root,
			IIdProvider optionalIdProvider, boolean detailed, IMeshWalker... walkers)
			throws X {
		IIdProvider idProvider = getIdProvider(optionalIdProvider);

		INodeRenderer nodeRenderer = node -> {
			StringBuilder rendering = new StringBuilder();
			rendering.append(idProvider.obtainId(node));
			if (detailed) {
				rendering.append(": { ");
				rendering.append(node);
				rendering.append(" }");
			}
			return rendering.toString();
		};
		IEdgeRenderer edgeRenderer = (node, edgeClass, adjacentNodes) -> " --> "
				+ concat(map(adjacentNodes, idProvider::obtainId), ", ") + "\n";

		return renderGraph(root, nodeRenderer, edgeRenderer, walkers);
	}

	/**
	 * Returns a string representation of the graph in Dot format suitable for rendering with Graphviz.
	 *
	 * @param root
	 *            the root node of the graph to visualize.
	 * @param optionalIdProvider
	 *            an optional IdProvider that provides integer IDs for all Nodes. If {@code null}, we
	 *            will use an IdManager. The IDs are assumed to be stable (the same on each query of the
	 *            same node).
	 * @param walkers
	 *            the mesh walkers used to traverse different classes of edges found in the graph.
	 * 
	 * @see The DOT Language
	 */
	@SafeVarargs
	public static  String getDotString(NodeT root,
			IIdProvider optionalIdProvider, IMeshWalker... walkers) throws X {
		IIdProvider idProvider = getIdProvider(optionalIdProvider);

		INodeRenderer nodeRenderer = node -> idProvider.obtainId(node) + //
				" [label = " + toQuotedString(node.toString()) + "];\n";
		IEdgeRenderer edgeRenderer = (node, edgeClass, adjacentNodes) -> idProvider.obtainId(node) + " -> " + //
				"{ " + concat(map(adjacentNodes, idProvider::obtainId), " ") + " }" + " [color=\""
				+ EDGE_COLORS[edgeClass] + "\"];\n";

		return "digraph {\n" + renderGraph(root, nodeRenderer, edgeRenderer, walkers) + "}\n";
	}

	/** Colors for different classes of edges */
	private static final String[] EDGE_COLORS = { "black", "blue", "red", "green" };

	private static  IIdProvider getIdProvider(IIdProvider optionalIdProvider) {
		return Optional.ofNullable(optionalIdProvider).orElseGet(IdManager::new);
	}

	@SafeVarargs
	private static  String renderGraph(NodeT root, INodeRenderer nodeRenderer,
			IEdgeRenderer edgeRenderer, IMeshWalker... walkers) throws X {
		StringBuilder graph = new StringBuilder();

		Deque todo = new ArrayDeque<>(Collections.singleton(root));
		Set addedNodes = new HashSet<>();
		while (!todo.isEmpty()) {
			NodeT nodeToAdd = todo.poll();
			if (!addedNodes.contains(nodeToAdd)) {
				graph.append(nodeRenderer.render(nodeToAdd));

				for (int walkerIndex = 0; walkerIndex < walkers.length; walkerIndex++) {
					IMeshWalker walker = walkers[walkerIndex];
					Collection adjacentElements = walker.getAdjacentElements(nodeToAdd);
					graph.append(edgeRenderer.render(nodeToAdd, walkerIndex, adjacentElements));
					todo.addAll(adjacentElements);
				}

				addedNodes.add(nodeToAdd);
			}
		}

		return graph.toString();
	}

	/**
	 * Produces a double-quoted string as defined by the
	 * DOT language.
	 */
	private static String toQuotedString(String string) {
		return '"' + string.replace("\"", "\\\"") + '"';
	}

	@FunctionalInterface
	private interface INodeRenderer {

		/** Renders the given node. */
		String render(NodeT node);
	}

	@FunctionalInterface
	private interface IEdgeRenderer {

		/**
		 * Renders the edges (of the given class) from the node to any adjacent nodes.
		 */
		String render(NodeT node, int edgeClass, Collection adjacentNodes);
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy