GraphvizExporter.java
package org.eu.autogex.export;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.eu.autogex.core.State;
import org.eu.autogex.models.DFA;
import org.eu.autogex.models.ENFA;
import org.eu.autogex.models.NFA;
/**
* Utility class for exporting automata to the Graphviz DOT language format. This allows for easy
* visual representation of DFA, NFA, and ENFA models.
*/
public class GraphvizExporter {
private static final String EPSILON_LABEL = "ε";
private GraphvizExporter() {
throw new UnsupportedOperationException("Utility class cannot be instantiated");
}
/**
* Exports a DFA to a DOT format string.
*
* @param dfa The Deterministic Finite Automaton.
* @return The DOT language representation.
*/
public static String toDot(DFA dfa) {
StringBuilder sb = buildDotHeader(dfa.getInitialState(), dfa.getFinalStates());
for (Map.Entry<State, Map<Character, State>> entry : dfa.getTransitionTable().entrySet()) {
State source = entry.getKey();
for (Map.Entry<Character, State> transition : entry.getValue().entrySet()) {
appendTransition(sb, source, transition.getKey().toString(), transition.getValue());
}
}
return closeDot(sb);
}
/**
* Exports an NFA to a DOT format string.
*
* @param nfa The Non-Deterministic Finite Automaton.
* @return The DOT language representation.
*/
public static String toDot(NFA nfa) {
StringBuilder sb = buildDotHeader(nfa.getInitialState(), nfa.getFinalStates());
for (Map.Entry<State, Map<Character, Set<State>>> entry :
nfa.getTransitionTable().entrySet()) {
State source = entry.getKey();
for (Map.Entry<Character, Set<State>> transition : entry.getValue().entrySet()) {
for (State target : transition.getValue()) {
appendTransition(sb, source, transition.getKey().toString(), target);
}
}
}
return closeDot(sb);
}
/**
* Exports an ENFA to a DOT format string. Epsilon transitions (null keys) are represented with
* the 'ε' symbol.
*
* @param enfa The Epsilon-NFA.
* @return The DOT language representation.
*/
public static String toDot(ENFA enfa) {
StringBuilder sb = buildDotHeader(enfa.getInitialState(), enfa.getFinalStates());
for (Map.Entry<State, Map<Character, Set<State>>> entry :
enfa.getTransitionTable().entrySet()) {
State source = entry.getKey();
for (Map.Entry<Character, Set<State>> transition : entry.getValue().entrySet()) {
String label =
transition.getKey() == null
? EPSILON_LABEL
: transition.getKey().toString();
for (State target : transition.getValue()) {
appendTransition(sb, source, label, target);
}
}
}
return closeDot(sb);
}
// --- Private Helper Methods ---
private static StringBuilder buildDotHeader(State initialState, Set<State> finalStates) {
StringBuilder sb = new StringBuilder();
sb.append("digraph Automaton {\n");
sb.append(" rankdir=LR;\n"); // Left-to-Right orientation
// Define final states appearance (Double Circle)
if (!finalStates.isEmpty()) {
String finalStatesList =
finalStates.stream()
.map(s -> "\"" + escapeDotString(s.getName()) + "\"")
.collect(Collectors.joining(" "));
sb.append(" node [shape = doublecircle]; ").append(finalStatesList).append(";\n");
}
// Reset to default node appearance (Single Circle)
sb.append(" node [shape = circle];\n");
// Invisible entry node to point to the initial state
sb.append(" __start0 [shape=none, label=\"\"];\n");
if (initialState != null) {
sb.append(" __start0 -> \"")
.append(escapeDotString(initialState.getName()))
.append("\";\n");
}
return sb;
}
private static void appendTransition(
StringBuilder sb, State source, String label, State target) {
sb.append(" \"")
.append(escapeDotString(source.getName()))
.append("\" -> \"")
.append(escapeDotString(target.getName()))
.append("\" [label=\"")
.append(escapeDotString(label))
.append("\"];\n");
}
private static String escapeDotString(String text) {
if (text == null) return "";
return text.replace("\\", "\\\\").replace("\"", "\\\"");
}
private static String closeDot(StringBuilder sb) {
sb.append("}\n");
return sb.toString();
}
}