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}