001/* Copyright (C) 2013 TU Dortmund 002 * This file is part of AutomataLib, http://www.automatalib.net/. 003 * 004 * AutomataLib is free software; you can redistribute it and/or 005 * modify it under the terms of the GNU Lesser General Public 006 * License version 3.0 as published by the Free Software Foundation. 007 * 008 * AutomataLib is distributed in the hope that it will be useful, 009 * but WITHOUT ANY WARRANTY; without even the implied warranty of 010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 011 * Lesser General Public License for more details. 012 * 013 * You should have received a copy of the GNU Lesser General Public 014 * License along with AutomataLib; if not, see 015 * http://www.gnu.de/documents/lgpl.en.html. 016 */ 017package net.automatalib.util.graphs.dot; 018 019import java.io.BufferedWriter; 020import java.io.File; 021import java.io.FileWriter; 022import java.io.IOException; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.Collection; 026import java.util.HashMap; 027import java.util.List; 028import java.util.Map; 029 030import net.automatalib.automata.Automaton; 031import net.automatalib.automata.dot.DOTPlottableAutomaton; 032import net.automatalib.automata.dot.DefaultDOTHelperAutomaton; 033import net.automatalib.automata.graphs.TransitionEdge; 034import net.automatalib.commons.util.mappings.MutableMapping; 035import net.automatalib.commons.util.strings.StringUtil; 036import net.automatalib.graphs.Graph; 037import net.automatalib.graphs.UndirectedGraph; 038import net.automatalib.graphs.dot.AggregateDOTHelper; 039import net.automatalib.graphs.dot.DOTPlottableGraph; 040import net.automatalib.graphs.dot.DefaultDOTHelper; 041import net.automatalib.graphs.dot.GraphDOTHelper; 042import net.automatalib.util.automata.Automata; 043 044 045/** 046 * Methods for rendering a {@link Graph} or {@link Automaton} in the GraphVIZ DOT format. 047 * <p> 048 * This class does not take care of actually processing the generated DOT data. For this, 049 * please take a look at the <tt>automata-commons-dotutil</tt> artifact. 050 * 051 * @author Malte Isberner <malte.isberner@gmail.com> 052 * 053 */ 054public abstract class GraphDOT { 055 056 /** 057 * Renders a {@link DOTPlottableGraph} in the GraphVIZ DOT format. 058 * @param graph the graph to render 059 * @param a the appendable to write to. 060 * @throws IOException if writing to <tt>a</tt> fails. 061 */ 062 @SafeVarargs 063 public static <N,E> void write(DOTPlottableGraph<N, E> graph, 064 Appendable a, GraphDOTHelper<N,? super E> ...additionalHelpers) throws IOException { 065 GraphDOTHelper<N,? super E> helper = graph.getGraphDOTHelper(); 066 write(graph, helper, a, additionalHelpers); 067 } 068 069 /** 070 * Renders an {@link Automaton} in the GraphVIZ DOT format. 071 * 072 * @param automaton the automaton to render. 073 * @param helper the helper to use for rendering 074 * @param inputAlphabet the input alphabet to consider 075 * @param a the appendable to write to. 076 * @throws IOException if writing to <tt>a</tt> fails. 077 */ 078 @SafeVarargs 079 public static <S,I,T> void write(Automaton<S,I,T> automaton, 080 GraphDOTHelper<S, ? super TransitionEdge<I,T>> helper, 081 Collection<? extends I> inputAlphabet, 082 Appendable a, GraphDOTHelper<S,? super TransitionEdge<I,T>> ...additionalHelpers) throws IOException { 083 Graph<S,TransitionEdge<I,T>> ag = Automata.asGraph(automaton, inputAlphabet); 084 write(ag, helper, a, additionalHelpers); 085 } 086 087 /** 088 * Renders an {@link Automaton} in the GraphVIZ DOT format. 089 * 090 * @param automaton the automaton to render. 091 * @param inputAlphabet the input alphabet to consider 092 * @param a the appendable to write to 093 * @throws IOException if writing to <tt>a</tt> fails 094 */ 095 @SafeVarargs 096 public static <S,I,T> void write(Automaton<S,I,T> automaton, 097 Collection<? extends I> inputAlphabet, 098 Appendable a, GraphDOTHelper<S,? super TransitionEdge<I,T>> ...additionalHelpers) throws IOException { 099 GraphDOTHelper<S,? super TransitionEdge<I,T>> helper; 100 if(automaton instanceof DOTPlottableAutomaton) { 101 DOTPlottableAutomaton<S,I,T> dp = (DOTPlottableAutomaton<S,I,T>)automaton; 102 helper = dp.getDOTHelper(); 103 } 104 else 105 helper = new DefaultDOTHelperAutomaton<S, I, T, Automaton<S,I,T>>(automaton); 106 107 write(automaton, helper, inputAlphabet, a, additionalHelpers); 108 } 109 110 @SafeVarargs 111 public static <S,I,T> void write(DOTPlottableAutomaton<S, I, T> automaton, Appendable a, GraphDOTHelper<S,? super TransitionEdge<I,T>> ...additionalHelpers) throws IOException { 112 write(automaton, automaton.getDOTHelper(), automaton.getInputAlphabet(), a, additionalHelpers); 113 } 114 115 116 /** 117 * Renders a {@link Graph} in the GraphVIZ DOT format. 118 * 119 * @param graph the graph to render 120 * @param a the appendable to write to 121 * @throws IOException if writing to <tt>a</tt> fails 122 */ 123 @SuppressWarnings("unchecked") 124 @SafeVarargs 125 public static <N,E> void write(Graph<N,E> graph, 126 Appendable a, GraphDOTHelper<N,? super E> ...additionalHelpers) throws IOException { 127 GraphDOTHelper<N, ? super E> helper = null; 128 if(graph instanceof DOTPlottableGraph) { 129 DOTPlottableGraph<N,E> plottable = (DOTPlottableGraph<N,E>)graph; 130 helper = plottable.getGraphDOTHelper(); 131 } 132 else 133 helper = (GraphDOTHelper<N,? super E>)DefaultDOTHelper.getInstance(); 134 write(graph, helper, a, additionalHelpers); 135 } 136 137 @SafeVarargs 138 public static <N,E> void write(Graph<N,E> graph, GraphDOTHelper<N, ? super E> helper, Appendable a, GraphDOTHelper<N, ? super E> ...additionalHelpers) throws IOException { 139 List<GraphDOTHelper<N,? super E>> helpers = new ArrayList<>(additionalHelpers.length + 1); 140 helpers.add(helper); 141 helpers.addAll(Arrays.asList(additionalHelpers)); 142 143 write(graph, a, helpers); 144 } 145 146 public static <N,E> void write(Graph<N,E> graph, Appendable a, List<GraphDOTHelper<N,? super E>> helpers) throws IOException { 147 AggregateDOTHelper<N, E> aggHelper = new AggregateDOTHelper<>(helpers); 148 write(graph, aggHelper, a); 149 } 150 151 /** 152 * Renders a {@link Graph} in the GraphVIZ DOT format. 153 * 154 * @param graph the graph to render 155 * @param dotHelper the helper to use for rendering 156 * @param a the appendable to write to 157 * @throws IOException if writing to <tt>a</tt> fails 158 */ 159 public static <N,E> void write(Graph<N, E> graph, 160 GraphDOTHelper<N,? super E> dotHelper, 161 Appendable a) throws IOException { 162 163 if(dotHelper == null) 164 dotHelper = new DefaultDOTHelper<N, E>(); 165 166 boolean directed = true; 167 if(graph instanceof UndirectedGraph) 168 directed = false; 169 170 if(directed) 171 a.append("di"); 172 a.append("graph g {\n"); 173 174 dotHelper.writePreamble(a); 175 a.append('\n'); 176 177 Map<String,String> props = new HashMap<>(); 178 179 MutableMapping<N,String> nodeNames = graph.createStaticNodeMapping(); 180 181 int i = 0; 182 183 for(N node : graph) { 184 props.clear(); 185 if(!dotHelper.getNodeProperties(node, props)) 186 continue; 187 String id = "s" + i++; 188 a.append('\t').append(id); 189 appendParams(props, a); 190 a.append(";\n"); 191 nodeNames.put(node, id); 192 } 193 194 for(N node : graph) { 195 String srcId = nodeNames.get(node); 196 if(srcId == null) 197 continue; 198 Collection<E> outEdges = graph.getOutgoingEdges(node); 199 if(outEdges == null || outEdges.isEmpty()) 200 continue; 201 for(E e : outEdges) { 202 N tgt = graph.getTarget(e); 203 String tgtId = nodeNames.get(tgt); 204 if(tgtId == null) 205 continue; 206 207 if(!directed && tgtId.compareTo(srcId) < 0) 208 continue; 209 210 props.clear(); 211 if(!dotHelper.getEdgeProperties(node, e, tgt, props)) 212 continue; 213 214 a.append('\t').append(srcId).append(' '); 215 if(directed) 216 a.append("-> "); 217 else 218 a.append("-- "); 219 a.append(tgtId); 220 appendParams(props, a); 221 a.append(";\n"); 222 } 223 } 224 225 a.append('\n'); 226 dotHelper.writePostamble(nodeNames, a); 227 a.append("}\n"); 228 } 229 230 public static <N,E> void writeToFile(Graph<N,E> graph, 231 GraphDOTHelper<N,E> dotHelper, 232 File file) throws IOException { 233 234 try(BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { 235 write(graph, dotHelper, writer); 236 } 237 } 238 239 240 private static void appendParams(Map<String,String> params, Appendable a) 241 throws IOException { 242 if(params == null || params.isEmpty()) 243 return; 244 a.append(" ["); 245 boolean first = true; 246 for(Map.Entry<String,String> e : params.entrySet()) { 247 if(first) 248 first = false; 249 else 250 a.append(' '); 251 String key = e.getKey(); 252 String value = e.getValue(); 253 a.append(e.getKey()).append("="); 254 // HTML labels have to be enclosed in <> instead of "" 255 if(key.equals(GraphDOTHelper.LABEL) && value.startsWith("<HTML>")) 256 a.append('<').append(value.substring(6)).append('>'); 257 else 258 StringUtil.enquote(e.getValue(), a); 259 } 260 a.append(']'); 261 } 262}