• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

LearnLib / automatalib / 13138848026

04 Feb 2025 02:53PM UTC coverage: 92.108% (+2.2%) from 89.877%
13138848026

push

github

mtf90
[maven-release-plugin] prepare release automatalib-0.12.0

16609 of 18032 relevant lines covered (92.11%)

1.7 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.91
/serialization/dot/src/main/java/net/automatalib/serialization/dot/GraphDOT.java
1
/* Copyright (C) 2013-2025 TU Dortmund University
2
 * This file is part of AutomataLib <https://automatalib.net>.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package net.automatalib.serialization.dot;
17

18
import java.io.Flushable;
19
import java.io.IOException;
20
import java.util.ArrayList;
21
import java.util.Arrays;
22
import java.util.Collection;
23
import java.util.HashMap;
24
import java.util.HashSet;
25
import java.util.List;
26
import java.util.Locale;
27
import java.util.Map;
28
import java.util.Set;
29

30
import net.automatalib.automaton.Automaton;
31
import net.automatalib.automaton.graph.TransitionEdge;
32
import net.automatalib.common.util.mapping.MutableMapping;
33
import net.automatalib.common.util.string.StringUtil;
34
import net.automatalib.graph.Graph;
35
import net.automatalib.graph.UndirectedGraph;
36
import net.automatalib.graph.concept.GraphViewable;
37
import net.automatalib.visualization.VisualizationHelper;
38
import net.automatalib.visualization.VisualizationHelper.CommonAttrs;
39
import net.automatalib.visualization.VisualizationHelper.NodeAttrs;
40

41
/**
42
 * Methods for rendering a {@link Graph} or {@link Automaton} in the GraphVIZ DOT format.
43
 */
44
public final class GraphDOT {
45

46
    private static final String INITIAL_LABEL = "__start";
47
    private static final String HTML_START_TAG = "<HTML>";
48
    private static final String HTML_END_TAG = "</HTML>";
49

50
    private GraphDOT() {}
51

52
    public static void write(GraphViewable gv, Appendable a) throws IOException {
53
        Graph<?, ?> graph = gv.graphView();
2✔
54
        write(graph, a);
2✔
55
    }
2✔
56

57
    /**
58
     * Renders an {@link Automaton} in the GraphVIZ DOT format.
59
     *
60
     * @param automaton
61
     *         the automaton to render
62
     * @param inputAlphabet
63
     *         the input alphabet to consider
64
     * @param a
65
     *         the appendable to write to
66
     * @param <S>
67
     *         state type
68
     * @param <I>
69
     *         input symbol type
70
     * @param <T>
71
     *         transition type
72
     *
73
     * @throws IOException
74
     *         if writing to {@code a} fails
75
     */
76
    public static <S, I, T> void write(Automaton<S, I, T> automaton,
77
                                       Collection<? extends I> inputAlphabet,
78
                                       Appendable a) throws IOException {
79
        write(automaton.transitionGraphView(inputAlphabet), a);
2✔
80
    }
2✔
81

82
    /**
83
     * Renders an {@link Automaton} in the GraphVIZ DOT format.
84
     *
85
     * @param automaton
86
     *         the automaton to render
87
     * @param inputAlphabet
88
     *         the input alphabet to consider
89
     * @param a
90
     *         the appendable to write to
91
     * @param additionalHelpers
92
     *         additional helpers for providing visualization properties
93
     * @param <S>
94
     *         state type
95
     * @param <I>
96
     *         input symbol type
97
     * @param <T>
98
     *         transition type
99
     *
100
     * @throws IOException
101
     *         if writing to {@code a} fails
102
     */
103
    @SafeVarargs
104
    public static <S, I, T> void write(Automaton<S, I, T> automaton,
105
                                       Collection<? extends I> inputAlphabet,
106
                                       Appendable a,
107
                                       VisualizationHelper<S, ? super TransitionEdge<I, T>>... additionalHelpers)
108
            throws IOException {
109
        write(automaton, inputAlphabet, a, Arrays.asList(additionalHelpers));
1✔
110
    }
1✔
111

112
    /**
113
     * Renders an {@link Automaton} in the GraphVIZ DOT format.
114
     *
115
     * @param automaton
116
     *         the automaton to render
117
     * @param inputAlphabet
118
     *         the input alphabet to consider
119
     * @param a
120
     *         the appendable to write to
121
     * @param additionalHelpers
122
     *         additional helpers for providing visualization properties
123
     * @param <S>
124
     *         state type
125
     * @param <I>
126
     *         input symbol type
127
     * @param <T>
128
     *         transition type
129
     *
130
     * @throws IOException
131
     *         if writing to {@code a} fails
132
     */
133
    public static <S, I, T> void write(Automaton<S, I, T> automaton,
134
                                       Collection<? extends I> inputAlphabet,
135
                                       Appendable a,
136
                                       List<VisualizationHelper<S, ? super TransitionEdge<I, T>>> additionalHelpers)
137
            throws IOException {
138
        write(automaton.transitionGraphView(inputAlphabet), a, additionalHelpers);
1✔
139
    }
1✔
140

141
    /**
142
     * Renders a {@link Graph} in the GraphVIZ DOT format.
143
     *
144
     * @param graph
145
     *         the graph to render
146
     * @param a
147
     *         the appendable to write to
148
     * @param <N>
149
     *         node type
150
     * @param <E>
151
     *         edge type
152
     *
153
     * @throws IOException
154
     *         if writing to {@code a} fails
155
     */
156
    public static <N, E> void write(Graph<N, E> graph, Appendable a) throws IOException {
157
        writeRaw(graph, a, toDOTVisualizationHelper(graph.getVisualizationHelper()));
2✔
158
    }
2✔
159

160
    /**
161
     * Renders a {@link Graph} in the GraphVIZ DOT format.
162
     *
163
     * @param graph
164
     *         the graph to render
165
     * @param a
166
     *         the appendable to write to
167
     * @param additionalHelpers
168
     *         additional helpers for providing visualization properties
169
     * @param <N>
170
     *         node type
171
     * @param <E>
172
     *         edge type
173
     *
174
     * @throws IOException
175
     *         if writing to {@code a} fails
176
     */
177
    @SafeVarargs
178
    public static <N, E> void write(Graph<N, E> graph,
179
                                    Appendable a,
180
                                    VisualizationHelper<N, ? super E>... additionalHelpers) throws IOException {
181
        write(graph, a, Arrays.asList(additionalHelpers));
2✔
182
    }
2✔
183

184
    /**
185
     * Renders a {@link Graph} in the GraphVIZ DOT format.
186
     *
187
     * @param graph
188
     *         the graph to render
189
     * @param a
190
     *         the appendable to write to
191
     * @param additionalHelpers
192
     *         additional helpers for providing visualization properties
193
     * @param <N>
194
     *         node type
195
     * @param <E>
196
     *         edge type
197
     *
198
     * @throws IOException
199
     *         if writing to {@code a} fails.
200
     */
201
    public static <N, E> void write(Graph<N, E> graph,
202
                                    Appendable a,
203
                                    List<VisualizationHelper<N, ? super E>> additionalHelpers) throws IOException {
204

205
        final List<VisualizationHelper<N, ? super E>> helpers = new ArrayList<>(additionalHelpers.size() + 1);
2✔
206

207
        helpers.add(graph.getVisualizationHelper());
2✔
208
        helpers.addAll(additionalHelpers);
2✔
209

210
        writeRaw(graph, a, toDOTVisualizationHelper(helpers));
2✔
211
    }
2✔
212

213
    /**
214
     * Renders a list of {@link Graph}s as clusters (subgraphs) in the GraphVIZ DOT format. Note that any markup
215
     * information for each cluster must be provided by the respective graph's
216
     * {@link Graph#getVisualizationHelper() visualization helper}.
217
     *
218
     * @param graphs
219
     *         the graphs to render
220
     * @param a
221
     *         the appendable to write to.
222
     *
223
     * @throws IOException
224
     *         if writing to {@code a} fails.
225
     */
226
    public static void write(List<Graph<?, ?>> graphs, Appendable a) throws IOException {
227

228
        boolean directed = false;
2✔
229

230
        for (Graph<?, ?> g : graphs) {
2✔
231
            // one directed graph is enough to require arrow tips
232
            if (!(g instanceof UndirectedGraph)) {
2✔
233
                directed = true;
2✔
234
                break;
2✔
235
            }
236
        }
×
237

238
        writeRawHeader(a, directed);
2✔
239

240
        int clusterId = 0;
2✔
241
        for (Graph<?, ?> g : graphs) {
2✔
242
            final String idPrefix = "c" + clusterId + '_';
2✔
243

244
            a.append(System.lineSeparator())
2✔
245
             .append("subgraph cluster")
2✔
246
             .append(Integer.toString(clusterId))
2✔
247
             .append(" {")
2✔
248
             .append(System.lineSeparator());
2✔
249

250
            @SuppressWarnings("unchecked")
251
            final Graph<Object, Object> graph = (Graph<Object, Object>) g;
2✔
252

253
            writeRawBody(graph,
2✔
254
                         a,
255
                         toDOTVisualizationHelper(graph.getVisualizationHelper()),
2✔
256
                         !(graph instanceof UndirectedGraph),
257
                         idPrefix);
258
            a.append('}').append(System.lineSeparator());
2✔
259

260
            clusterId++;
2✔
261
        }
2✔
262

263
        writeRawFooter(a);
2✔
264
    }
2✔
265

266
    /**
267
     * Renders a {@link Graph} in the GraphVIZ DOT format.
268
     *
269
     * @param graph
270
     *         the graph to render
271
     * @param a
272
     *         the appendable to write to
273
     * @param dotHelper
274
     *         the helper to use for rendering.
275
     *
276
     * @throws IOException
277
     *         if writing to {@code a} fails
278
     */
279
    private static <N, E> void writeRaw(Graph<N, E> graph, Appendable a, DOTVisualizationHelper<N, ? super E> dotHelper)
280
            throws IOException {
281

282
        final boolean directed = !(graph instanceof UndirectedGraph);
2✔
283

284
        writeRawHeader(a, directed);
2✔
285
        writeRawBody(graph, a, dotHelper, directed, "");
2✔
286
        writeRawFooter(a);
2✔
287

288
        if (a instanceof Flushable) {
2✔
289
            ((Flushable) a).flush();
2✔
290
        }
291
    }
2✔
292

293
    private static void writeRawHeader(Appendable a, boolean directed) throws IOException {
294
        if (directed) {
2✔
295
            a.append("di");
2✔
296
        }
297

298
        a.append("graph g {").append(System.lineSeparator());
2✔
299
    }
2✔
300

301
    private static <N, E> void writeRawBody(Graph<N, E> graph,
302
                                            Appendable a,
303
                                            DOTVisualizationHelper<N, ? super E> dotHelper,
304
                                            boolean directed,
305
                                            String idPrefix) throws IOException {
306

307
        Map<String, String> props = new HashMap<>();
2✔
308

309
        dotHelper.getGlobalNodeProperties(props);
2✔
310
        if (!props.isEmpty()) {
2✔
311
            a.append('\t').append("node");
2✔
312
            appendParams(props, a);
2✔
313
            a.append(';').append(System.lineSeparator());
2✔
314
        }
315

316
        props.clear();
2✔
317
        dotHelper.getGlobalEdgeProperties(props);
2✔
318
        if (!props.isEmpty()) {
2✔
319
            a.append('\t').append("edge");
2✔
320
            appendParams(props, a);
2✔
321
            a.append(';').append(System.lineSeparator());
2✔
322
        }
323

324
        dotHelper.writePreamble(a);
2✔
325
        a.append(System.lineSeparator());
2✔
326

327
        MutableMapping<N, String> nodeNames = graph.createStaticNodeMapping();
2✔
328
        Set<String> initialNodes = new HashSet<>();
2✔
329

330
        int i = 0;
2✔
331

332
        for (N node : graph) {
2✔
333
            props.clear();
2✔
334
            if (!dotHelper.getNodeProperties(node, props)) {
2✔
335
                continue;
2✔
336
            }
337
            String id = idPrefix + "s" + i++;
2✔
338

339
            // remove potential attributes that are no valid DOT attributes
340
            if (Boolean.parseBoolean(props.remove(NodeAttrs.INITIAL))) {
2✔
341
                initialNodes.add(id);
2✔
342
            }
343

344
            a.append('\t').append(id);
2✔
345
            appendParams(props, a);
2✔
346
            a.append(';').append(System.lineSeparator());
2✔
347
            nodeNames.put(node, id);
2✔
348
        }
2✔
349

350
        for (N node : graph) {
2✔
351
            String srcId = nodeNames.get(node);
2✔
352
            if (srcId == null) {
2✔
353
                continue;
2✔
354
            }
355
            Collection<E> outEdges = graph.getOutgoingEdges(node);
2✔
356
            if (outEdges.isEmpty()) {
2✔
357
                continue;
2✔
358
            }
359
            for (E e : outEdges) {
2✔
360
                N tgt = graph.getTarget(e);
2✔
361
                String tgtId = nodeNames.get(tgt);
2✔
362
                if (tgtId == null) {
2✔
363
                    continue;
×
364
                }
365

366
                if (!directed && tgtId.compareTo(srcId) < 0) {
2✔
367
                    continue;
×
368
                }
369

370
                props.clear();
2✔
371
                if (!dotHelper.getEdgeProperties(node, e, tgt, props)) {
2✔
372
                    continue;
×
373
                }
374

375
                a.append('\t').append(srcId).append(' ');
2✔
376
                if (directed) {
2✔
377
                    a.append("-> ");
2✔
378
                } else {
379
                    a.append("-- ");
×
380
                }
381
                a.append(tgtId);
2✔
382
                appendParams(props, a);
2✔
383
                a.append(';').append(System.lineSeparator());
2✔
384
            }
2✔
385
        }
2✔
386

387
        if (!initialNodes.isEmpty()) {
2✔
388
            a.append(System.lineSeparator());
2✔
389
            renderInitialArrowTip(initialNodes, idPrefix, a);
2✔
390
        }
391

392
        a.append(System.lineSeparator());
2✔
393
        dotHelper.writePostamble(a);
2✔
394
    }
2✔
395

396
    private static void writeRawFooter(Appendable a) throws IOException {
397
        a.append('}').append(System.lineSeparator());
2✔
398
    }
2✔
399

400
    private static void appendParams(Map<String, String> params, Appendable a) throws IOException {
401
        if (params.isEmpty()) {
2✔
402
            return;
2✔
403
        }
404
        a.append(" [");
2✔
405
        boolean first = true;
2✔
406
        for (Map.Entry<String, String> e : params.entrySet()) {
2✔
407
            if (first) {
2✔
408
                first = false;
2✔
409
            } else {
410
                a.append(' ');
2✔
411
            }
412
            String key = e.getKey();
2✔
413
            String value = e.getValue();
2✔
414
            a.append(e.getKey()).append("=");
2✔
415
            if (CommonAttrs.LABEL.equals(key)) {
2✔
416
                // HTML labels have to be enclosed in <> instead of ""
417
                final String upperCase = value.toUpperCase(Locale.ROOT);
2✔
418
                if (upperCase.startsWith(HTML_START_TAG)) {
2✔
419
                    a.append('<');
2✔
420
                    if (upperCase.endsWith(HTML_END_TAG)) {
2✔
421
                        a.append(value.substring(HTML_START_TAG.length(), value.length() - HTML_END_TAG.length()));
2✔
422
                    } else {
423
                        a.append(value.substring(HTML_START_TAG.length()));
2✔
424
                    }
425
                    a.append('>');
2✔
426
                } else {
427
                    StringUtil.enquote(e.getValue(), a);
2✔
428
                }
429
            } else {
2✔
430
                StringUtil.enquote(e.getValue(), a);
2✔
431
            }
432
        }
2✔
433
        a.append(']');
2✔
434
    }
2✔
435

436
    private static void renderInitialArrowTip(Set<String> initialNodes, String idPrefix, Appendable a)
437
            throws IOException {
438

439
        int i = 0;
2✔
440
        for (String init : initialNodes) {
2✔
441
            a.append(initialLabel(idPrefix, i))
2✔
442
             .append(" [label=\"\" shape=\"none\" width=\"0\" height=\"0\"];")
2✔
443
             .append(System.lineSeparator())
2✔
444
             .append(initialLabel(idPrefix, i++))
2✔
445
             .append(" -> ")
2✔
446
             .append(init)
2✔
447
             .append(';')
2✔
448
             .append(System.lineSeparator());
2✔
449
        }
2✔
450
    }
2✔
451

452
    public static String initialLabel(int n) {
453
        return initialLabel("", n);
2✔
454
    }
455

456
    public static String initialLabel(String idPrefix, int n) {
457
        return idPrefix + INITIAL_LABEL + n;
2✔
458
    }
459

460
    public static <N, E> DOTVisualizationHelper<N, E> toDOTVisualizationHelper(VisualizationHelper<N, E> helper) {
461
        if (helper instanceof DOTVisualizationHelper) {
2✔
462
            return (DOTVisualizationHelper<N, E>) helper;
2✔
463
        }
464

465
        return new DefaultDOTVisualizationHelper<>(helper);
2✔
466
    }
467

468
    public static <N, E> DOTVisualizationHelper<N, E> toDOTVisualizationHelper(List<VisualizationHelper<N, ? super E>> helpers) {
469

470
        final List<DOTVisualizationHelper<N, ? super E>> convertedHelpers = new ArrayList<>(helpers.size());
2✔
471

472
        for (VisualizationHelper<N, ? super E> h : helpers) {
2✔
473
            convertedHelpers.add(toDOTVisualizationHelper(h));
2✔
474
        }
2✔
475

476
        return new AggregateDOTVisualizationHelper<>(convertedHelpers);
2✔
477
    }
478
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc