net.automatalib.util.graphs.dot.GraphDOT Maven / Gradle / Ivy
Show all versions of automata-util Show documentation
/* Copyright (C) 2013 TU Dortmund
* This file is part of AutomataLib, http://www.automatalib.net/.
*
* AutomataLib is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License version 3.0 as published by the Free Software Foundation.
*
* AutomataLib is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with AutomataLib; if not, see
* http://www.gnu.de/documents/lgpl.en.html.
*/
package net.automatalib.util.graphs.dot;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.Flushable;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.automatalib.AutomataLibSettings;
import net.automatalib.automata.Automaton;
import net.automatalib.automata.graphs.TransitionEdge;
import net.automatalib.commons.dotutil.DOT;
import net.automatalib.commons.util.mappings.MutableMapping;
import net.automatalib.commons.util.strings.StringUtil;
import net.automatalib.graphs.Graph;
import net.automatalib.graphs.UndirectedGraph;
import net.automatalib.graphs.concepts.GraphViewable;
import net.automatalib.graphs.dot.AggregateDOTHelper;
import net.automatalib.graphs.dot.DefaultDOTHelper;
import net.automatalib.graphs.dot.GraphDOTHelper;
import net.automatalib.util.automata.Automata;
/**
* Methods for rendering a {@link Graph} or {@link Automaton} in the GraphVIZ DOT format.
*
* This class does not take care of actually processing the generated DOT data. For this,
* please take a look at the automata-commons-dotutil artifact.
*
* @author Malte Isberner
*
*/
public abstract class GraphDOT {
static {
AutomataLibSettings settings = AutomataLibSettings.getInstance();
String dotExePath = settings.getProperty("dot.exe.dir");
String dotExeName = settings.getProperty("dot.exe.name", "dot");
String dotExe = dotExeName;
if (dotExePath != null) {
Path dotBasePath = FileSystems.getDefault().getPath(dotExePath);
Path resolvedDotPath = dotBasePath.resolve(dotExeName);
dotExe = resolvedDotPath.toString();
}
DOT.setDotExe(dotExe);
}
public static boolean isDotUsable() {
return DOT.checkUsable();
}
public static void write(GraphViewable gv, Appendable a) throws IOException {
Graph,?> graph = gv.graphView();
write(graph, a);
}
/**
* Renders a {@link Graph} in the GraphVIZ DOT format.
* @param graph the graph to render
* @param a the appendable to write to.
* @throws IOException if writing to a fails.
*/
@SafeVarargs
public static void write(Graph graph,
Appendable a, GraphDOTHelper ...additionalHelpers) throws IOException {
GraphDOTHelper helper = graph.getGraphDOTHelper();
writeRaw(graph, helper, a, additionalHelpers);
}
/**
* Renders an {@link Automaton} in the GraphVIZ DOT format.
*
* @param automaton the automaton to render.
* @param helper the helper to use for rendering
* @param inputAlphabet the input alphabet to consider
* @param a the appendable to write to.
* @throws IOException if writing to a fails.
*/
@SafeVarargs
public static void write(Automaton automaton,
GraphDOTHelper> helper,
Collection extends I> inputAlphabet,
Appendable a, GraphDOTHelper> ...additionalHelpers) throws IOException {
Graph> ag = Automata.asGraph(automaton, inputAlphabet);
writeRaw(ag, helper, a, additionalHelpers);
}
/**
* Renders an {@link Automaton} in the GraphVIZ DOT format.
*
* @param automaton the automaton to render.
* @param inputAlphabet the input alphabet to consider
* @param a the appendable to write to
* @throws IOException if writing to a fails
*/
@SafeVarargs
public static void write(Automaton automaton,
Collection extends I> inputAlphabet,
Appendable a, GraphDOTHelper> ...additionalHelpers) throws IOException {
write(automaton.transitionGraphView(inputAlphabet), a, additionalHelpers);
}
@SafeVarargs
public static void writeRaw(Graph graph, GraphDOTHelper helper, Appendable a, GraphDOTHelper ...additionalHelpers) throws IOException {
List> helpers = new ArrayList<>(additionalHelpers.length + 1);
helpers.add(helper);
helpers.addAll(Arrays.asList(additionalHelpers));
writeRaw(graph, a, helpers);
}
public static void writeRaw(Graph graph, Appendable a, List> helpers) throws IOException {
AggregateDOTHelper aggHelper = new AggregateDOTHelper<>(helpers);
writeRaw(graph, aggHelper, a);
}
/**
* Renders a {@link Graph} in the GraphVIZ DOT format.
*
* @param graph the graph to render
* @param dotHelper the helper to use for rendering
* @param a the appendable to write to
* @throws IOException if writing to a fails
*/
public static void writeRaw(Graph graph,
GraphDOTHelper dotHelper,
Appendable a) throws IOException {
if(dotHelper == null)
dotHelper = new DefaultDOTHelper();
boolean directed = true;
if(graph instanceof UndirectedGraph)
directed = false;
if(directed)
a.append("di");
a.append("graph g {\n");
Map props = new HashMap<>();
dotHelper.getGlobalNodeProperties(props);
if(!props.isEmpty()) {
a.append('\t').append("node");
appendParams(props, a);
a.append(";\n");
}
props.clear();
dotHelper.getGlobalEdgeProperties(props);
if(!props.isEmpty()) {
a.append('\t').append("edge");
appendParams(props, a);
a.append(";\n");
}
dotHelper.writePreamble(a);
a.append('\n');
MutableMapping nodeNames = graph.createStaticNodeMapping();
int i = 0;
for(N node : graph) {
props.clear();
if(!dotHelper.getNodeProperties(node, props))
continue;
String id = "s" + i++;
a.append('\t').append(id);
appendParams(props, a);
a.append(";\n");
nodeNames.put(node, id);
}
for(N node : graph) {
String srcId = nodeNames.get(node);
if(srcId == null)
continue;
Collection extends E> outEdges = graph.getOutgoingEdges(node);
if(outEdges.isEmpty())
continue;
for(E e : outEdges) {
N tgt = graph.getTarget(e);
String tgtId = nodeNames.get(tgt);
if(tgtId == null)
continue;
if(!directed && tgtId.compareTo(srcId) < 0)
continue;
props.clear();
if(!dotHelper.getEdgeProperties(node, e, tgt, props))
continue;
a.append('\t').append(srcId).append(' ');
if(directed)
a.append("-> ");
else
a.append("-- ");
a.append(tgtId);
appendParams(props, a);
a.append(";\n");
}
}
a.append('\n');
dotHelper.writePostamble(nodeNames, a);
a.append("}\n");
if (a instanceof Flushable) {
((Flushable) a).flush();
}
}
public static void writeToFileRaw(Graph graph,
GraphDOTHelper dotHelper,
File file) throws IOException {
try(BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
writeRaw(graph, dotHelper, writer);
}
}
private static void appendParams(Map params, Appendable a)
throws IOException {
if(params == null || params.isEmpty())
return;
a.append(" [");
boolean first = true;
for(Map.Entry e : params.entrySet()) {
if(first)
first = false;
else
a.append(' ');
String key = e.getKey();
String value = e.getValue();
a.append(e.getKey()).append("=");
// HTML labels have to be enclosed in <> instead of ""
if(key.equals(GraphDOTHelper.CommonAttrs.LABEL) && value.toUpperCase().startsWith(""))
a.append('<').append(value.substring(6)).append('>');
else
StringUtil.enquote(e.getValue(), a);
}
a.append(']');
}
}