org.conqat.lib.commons.graph.GraphDebuggingUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of teamscale-lib-commons Show documentation
Show all versions of teamscale-lib-commons Show documentation
Provides common utility functions
/*
* 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);
}
}