done
This commit is contained in:
262
src/main/java/com/example/statemachinedemo/ExportUtils.java
Normal file
262
src/main/java/com/example/statemachinedemo/ExportUtils.java
Normal file
@@ -0,0 +1,262 @@
|
||||
package com.example.statemachinedemo;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class ExportUtils {
|
||||
|
||||
public static String generatePlantUML(List<Transition> transitions,
|
||||
Set<String> startStates,
|
||||
Set<String> endStates,
|
||||
boolean useLambdaGuards) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("@startuml\n");
|
||||
sb.append("skinparam state {\n");
|
||||
sb.append(" BackgroundColor<<choice>> LightYellow\n");
|
||||
sb.append("}\n\n");
|
||||
|
||||
// 1. Print start states
|
||||
for (String start : startStates) {
|
||||
sb.append("[*] --> ").append(simplify(start)).append("\n");
|
||||
}
|
||||
sb.append("\n");
|
||||
|
||||
// 2. Find all states that have choice transitions originating from them
|
||||
// We'll reroute those transitions through a separate choice pseudostate.
|
||||
Set<String> statesWithChoice = new HashSet<>();
|
||||
for (Transition t : transitions) {
|
||||
if ("withChoice".equals(t.type)) {
|
||||
statesWithChoice.add(simplify(t.sourceState));
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Declare choice pseudostates & connect from original state
|
||||
for (String state : statesWithChoice) {
|
||||
String choiceState = state + "_choice";
|
||||
sb.append("state ").append(choiceState).append(" <<choice>>\n");
|
||||
sb.append(state).append(" --> ").append(choiceState).append("\n");
|
||||
}
|
||||
sb.append("\n");
|
||||
|
||||
Set<String> transitionLines = new HashSet<>();
|
||||
|
||||
// 4. Output transitions:
|
||||
for (Transition t : transitions) {
|
||||
if (t.sourceState == null || t.targetState == null) continue;
|
||||
|
||||
String source = simplify(t.sourceState);
|
||||
String target = simplify(t.targetState);
|
||||
|
||||
String transitionSource = source;
|
||||
|
||||
// Reroute choice transitions through pseudostate
|
||||
if ("withChoice".equals(t.type) && statesWithChoice.contains(source)) {
|
||||
transitionSource = source + "_choice";
|
||||
}
|
||||
|
||||
// Prepare guard text
|
||||
String guardText = "";
|
||||
if (t.guard != null && !t.guard.isBlank()) {
|
||||
guardText = useLambdaGuards ? "lambda"
|
||||
: t.guard.replaceAll("[\\n\\r]+", " ").replaceAll("\\s{2,}", " ").trim();
|
||||
}
|
||||
|
||||
// Build label with event and guard
|
||||
StringBuilder label = new StringBuilder();
|
||||
if (t.event != null && !t.event.isBlank()) {
|
||||
label.append(simplify(t.event));
|
||||
}
|
||||
if (!guardText.isBlank()) {
|
||||
label.append(" [").append(guardText).append("]");
|
||||
}
|
||||
|
||||
String line = transitionSource + " --> " + target + (label.isEmpty() ? "" : (" : " + label));
|
||||
|
||||
// Skip duplicates
|
||||
if (transitionLines.add(line)) {
|
||||
sb.append(line).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
sb.append("\n");
|
||||
|
||||
// 5. Mark end states with transition to [*]
|
||||
for (String end : endStates) {
|
||||
sb.append(simplify(end)).append(" --> [*]\n");
|
||||
}
|
||||
|
||||
sb.append("@enduml\n");
|
||||
return sb.toString();
|
||||
}
|
||||
public static String generateDot(List<Transition> transitions,
|
||||
Set<String> startStates,
|
||||
Set<String> endStates,
|
||||
boolean useLambdaGuards) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("digraph stateMachine {\n");
|
||||
sb.append(" rankdir=LR;\n");
|
||||
sb.append(" node [shape=circle, style=filled, fillcolor=lightgray];\n\n");
|
||||
|
||||
// 1. Fake start node
|
||||
sb.append(" start [shape=point, label=\"\"];\n");
|
||||
|
||||
// 2. Find states with choice transitions
|
||||
Set<String> statesWithChoice = new HashSet<>();
|
||||
for (Transition t : transitions) {
|
||||
if ("withChoice".equals(t.type)) {
|
||||
statesWithChoice.add(simplify(t.sourceState));
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Declare normal states and choice pseudostates
|
||||
Set<String> allStates = new HashSet<>();
|
||||
for (Transition t : transitions) {
|
||||
allStates.add(simplify(t.sourceState));
|
||||
allStates.add(simplify(t.targetState));
|
||||
}
|
||||
|
||||
// Print all normal states (except choice pseudostates for now)
|
||||
for (String state : allStates) {
|
||||
if (!statesWithChoice.contains(state + "_choice")) {
|
||||
String shape = endStates.contains(state) ? "doublecircle" : "circle";
|
||||
sb.append(" ").append(state)
|
||||
.append(" [shape=").append(shape).append(", fillcolor=lightgray];\n");
|
||||
}
|
||||
}
|
||||
sb.append("\n");
|
||||
|
||||
// Declare choice pseudostates as diamonds
|
||||
for (String state : statesWithChoice) {
|
||||
String choiceNode = state + "_choice";
|
||||
sb.append(" ").append(choiceNode)
|
||||
.append(" [shape=diamond, label=\"\", fillcolor=lightyellow];\n");
|
||||
}
|
||||
sb.append("\n");
|
||||
|
||||
// 4. Connect start node(s) to start states
|
||||
for (String start : startStates) {
|
||||
sb.append(" start -> ").append(simplify(start)).append(";\n");
|
||||
}
|
||||
sb.append("\n");
|
||||
|
||||
// 5. Connect normal states to their choice pseudostates
|
||||
for (String state : statesWithChoice) {
|
||||
String choiceNode = state + "_choice";
|
||||
sb.append(" ").append(state).append(" -> ").append(choiceNode).append(";\n");
|
||||
}
|
||||
sb.append("\n");
|
||||
|
||||
// 6. Transitions: reroute withChoice transitions via choice pseudostate
|
||||
for (Transition t : transitions) {
|
||||
if (t.sourceState == null || t.targetState == null) continue;
|
||||
|
||||
String source = simplify(t.sourceState);
|
||||
String target = simplify(t.targetState);
|
||||
String edgeSource = source;
|
||||
|
||||
if ("withChoice".equals(t.type) && statesWithChoice.contains(source)) {
|
||||
edgeSource = source + "_choice";
|
||||
}
|
||||
|
||||
String guardText = "";
|
||||
if (t.guard != null && !t.guard.isBlank()) {
|
||||
guardText = useLambdaGuards ? "lambda"
|
||||
: t.guard.replaceAll("[\\n\\r]+", " ").replaceAll("\\s{2,}", " ").trim();
|
||||
}
|
||||
|
||||
StringBuilder label = new StringBuilder();
|
||||
if (t.event != null && !t.event.isBlank()) {
|
||||
label.append(simplify(t.event));
|
||||
}
|
||||
if (!guardText.isBlank()) {
|
||||
label.append(" [").append(guardText).append("]");
|
||||
}
|
||||
|
||||
String edgeLabel = label.length() > 0 ? " [label=\"" + label.toString() + "\"]" : "";
|
||||
|
||||
sb.append(" ").append(edgeSource).append(" -> ").append(target).append(edgeLabel).append(";\n");
|
||||
}
|
||||
|
||||
sb.append("}\n");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String exportToSCXML(List<Transition> transitions,
|
||||
Set<String> startStates,
|
||||
Set<String> endStates,
|
||||
boolean useLambdaGuards) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
|
||||
sb.append("<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" version=\"1.0\">\n\n");
|
||||
|
||||
// 1. Collect all states
|
||||
Set<String> allStates = new HashSet<>();
|
||||
for (Transition t : transitions) {
|
||||
if (t.sourceState != null) allStates.add(t.sourceState);
|
||||
if (t.targetState != null) allStates.add(t.targetState);
|
||||
}
|
||||
|
||||
// 2. Output <state> elements with transitions
|
||||
for (String state : allStates) {
|
||||
String simpleState = simplify(state);
|
||||
sb.append(" <state id=\"").append(simpleState).append("\">\n");
|
||||
|
||||
// If this state is a start state, add <initial> and transition to it
|
||||
if (startStates.contains(state)) {
|
||||
sb.append(" <initial>\n");
|
||||
sb.append(" <transition target=\"").append(simpleState).append("\"/>\n");
|
||||
sb.append(" </initial>\n");
|
||||
}
|
||||
|
||||
// Add transitions from this state
|
||||
for (Transition t : transitions) {
|
||||
if (state.equals(t.sourceState)) {
|
||||
String target = simplify(t.targetState);
|
||||
if (target == null || target.isEmpty()) continue;
|
||||
|
||||
String event = t.event != null ? simplify(t.event) : null;
|
||||
String guard = t.guard != null && !t.guard.isBlank()
|
||||
? (useLambdaGuards ? "lambda" : t.guard.replaceAll("[\\n\\r]+", " ").trim())
|
||||
: null;
|
||||
|
||||
sb.append(" <transition");
|
||||
if (event != null && !event.isBlank()) sb.append(" event=\"").append(event).append("\"");
|
||||
sb.append(" target=\"").append(target).append("\"");
|
||||
if (guard != null && !guard.isBlank()) sb.append(" cond=\"").append(escapeXml(guard)).append("\"");
|
||||
sb.append("/>\n");
|
||||
}
|
||||
}
|
||||
sb.append(" </state>\n\n");
|
||||
}
|
||||
|
||||
// 3. Add final states (if not already defined as states)
|
||||
for (String end : endStates) {
|
||||
String simpleEnd = simplify(end);
|
||||
if (!allStates.contains(end)) {
|
||||
sb.append(" <final id=\"").append(simpleEnd).append("\"/>\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
sb.append("</scxml>\n");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String escapeXml(String s) {
|
||||
if (s == null) return "";
|
||||
return s.replace("&", "&")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace("\"", """)
|
||||
.replace("'", "'");
|
||||
}
|
||||
|
||||
|
||||
// Helper: extract last enum part
|
||||
private static String simplify(String full) {
|
||||
if (full == null) return "";
|
||||
int dot = full.lastIndexOf('.');
|
||||
return dot >= 0 ? full.substring(dot + 1) : full;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user