Fix HTML export states, endpoint linking, and dynamic trigger event validation.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -33,7 +33,8 @@ public class TriggerCanonicalizationEnricher implements AnalysisEnricher {
|
|||||||
|
|
||||||
List<TriggerPoint> triggers = result.getMetadata().getTriggers();
|
List<TriggerPoint> triggers = result.getMetadata().getTriggers();
|
||||||
List<TriggerPoint> canonicalTriggers = triggers == null ? null : triggers.stream()
|
List<TriggerPoint> canonicalTriggers = triggers == null ? null : triggers.stream()
|
||||||
.map(trigger -> MachineEnumCanonicalizer.canonicalizeTriggerPoint(trigger, machineTypes))
|
.map(trigger -> MachineEnumCanonicalizer.canonicalizeAndExpandTriggerPoint(
|
||||||
|
trigger, machineTypes, context))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
List<CallChain> callChains = result.getMetadata().getCallChains();
|
List<CallChain> callChains = result.getMetadata().getCallChains();
|
||||||
@@ -43,8 +44,8 @@ public class TriggerCanonicalizationEnricher implements AnalysisEnricher {
|
|||||||
return chain;
|
return chain;
|
||||||
}
|
}
|
||||||
return chain.toBuilder()
|
return chain.toBuilder()
|
||||||
.triggerPoint(MachineEnumCanonicalizer.canonicalizeTriggerPoint(
|
.triggerPoint(MachineEnumCanonicalizer.canonicalizeAndExpandTriggerPoint(
|
||||||
chain.getTriggerPoint(), machineTypes))
|
chain.getTriggerPoint(), machineTypes, context))
|
||||||
.build();
|
.build();
|
||||||
})
|
})
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ public class AnalysisResult {
|
|||||||
}
|
}
|
||||||
click.kamil.springstatemachineexporter.analysis.service.AnalysisResultFinalizer.applyCanonicalization(
|
click.kamil.springstatemachineexporter.analysis.service.AnalysisResultFinalizer.applyCanonicalization(
|
||||||
this,
|
this,
|
||||||
|
null,
|
||||||
new StateMachineTypeResolver.MachineTypes(stateTypeFqn, eventTypeFqn));
|
new StateMachineTypeResolver.MachineTypes(stateTypeFqn, eventTypeFqn));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package click.kamil.springstatemachineexporter.analysis.resolver;
|
package click.kamil.springstatemachineexporter.analysis.resolver;
|
||||||
|
|
||||||
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
||||||
|
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||||
import click.kamil.springstatemachineexporter.model.Event;
|
import click.kamil.springstatemachineexporter.model.Event;
|
||||||
import click.kamil.springstatemachineexporter.model.State;
|
import click.kamil.springstatemachineexporter.model.State;
|
||||||
import click.kamil.springstatemachineexporter.model.Transition;
|
import click.kamil.springstatemachineexporter.model.Transition;
|
||||||
@@ -91,10 +92,73 @@ public final class MachineEnumCanonicalizer {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expands {@code <SYMBOLIC: EnumType.*>} placeholders into concrete package-canonical enum
|
||||||
|
* constants for the owning machine's event type. Non-matching symbolic entries are dropped.
|
||||||
|
*/
|
||||||
|
public static List<String> expandSymbolicPolymorphicEvents(
|
||||||
|
List<String> polymorphicEvents,
|
||||||
|
String machineEventTypeFqn,
|
||||||
|
CodebaseContext context) {
|
||||||
|
if (polymorphicEvents == null || polymorphicEvents.isEmpty()) {
|
||||||
|
return polymorphicEvents;
|
||||||
|
}
|
||||||
|
if (machineEventTypeFqn == null || machineEventTypeFqn.isBlank()) {
|
||||||
|
return polymorphicEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> expanded = new ArrayList<>();
|
||||||
|
for (String pe : polymorphicEvents) {
|
||||||
|
if (pe == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (pe.startsWith("<SYMBOLIC: ") && pe.endsWith(".*>")) {
|
||||||
|
String symbolicType = pe.substring("<SYMBOLIC: ".length(), pe.length() - 3).trim();
|
||||||
|
if (!enumTypesMatch(machineEventTypeFqn, symbolicType)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (context != null) {
|
||||||
|
List<String> enumValues = context.getEnumValues(stripGenerics(machineEventTypeFqn));
|
||||||
|
if (enumValues != null) {
|
||||||
|
for (String value : enumValues) {
|
||||||
|
String parsed = value;
|
||||||
|
if (parsed.contains(".")) {
|
||||||
|
parsed = parsed.substring(parsed.lastIndexOf('.') + 1);
|
||||||
|
}
|
||||||
|
expanded.add(stripGenerics(machineEventTypeFqn) + "." + parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
expanded.add(pe);
|
||||||
|
}
|
||||||
|
return expanded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TriggerPoint canonicalizeAndExpandTriggerPoint(
|
||||||
|
TriggerPoint trigger,
|
||||||
|
StateMachineTypeResolver.MachineTypes machineTypes,
|
||||||
|
CodebaseContext context) {
|
||||||
|
TriggerPoint canonical = canonicalizeTriggerPoint(trigger, machineTypes);
|
||||||
|
if (canonical == null || machineTypes == null || machineTypes.eventTypeFqn() == null) {
|
||||||
|
return canonical;
|
||||||
|
}
|
||||||
|
List<String> expanded = expandSymbolicPolymorphicEvents(
|
||||||
|
canonical.getPolymorphicEvents(), machineTypes.eventTypeFqn(), context);
|
||||||
|
if (expanded == canonical.getPolymorphicEvents()) {
|
||||||
|
return canonical;
|
||||||
|
}
|
||||||
|
return canonical.toBuilder().polymorphicEvents(expanded).build();
|
||||||
|
}
|
||||||
|
|
||||||
public static String qualifyEventIdentifier(String event, String eventTypeFqn) {
|
public static String qualifyEventIdentifier(String event, String eventTypeFqn) {
|
||||||
if (event == null || eventTypeFqn == null || event.isEmpty()) {
|
if (event == null || eventTypeFqn == null || event.isEmpty()) {
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
if (isDynamicTriggerExpression(event)) {
|
||||||
|
return denormalizeCorruptedDynamicEvent(event);
|
||||||
|
}
|
||||||
if (event.startsWith("<SYMBOLIC: ")) {
|
if (event.startsWith("<SYMBOLIC: ")) {
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
@@ -152,9 +216,23 @@ public final class MachineEnumCanonicalizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isWildcardVariable(String eventStr) {
|
private static boolean isWildcardVariable(String eventStr) {
|
||||||
if (eventStr == null) {
|
return isDynamicTriggerExpression(eventStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AST-derived trigger events that must never receive package-canonical enum rewriting
|
||||||
|
* ({@code getType()}, {@code valueOf(...)}, ternary dispatchers, parameter names, etc.).
|
||||||
|
*/
|
||||||
|
public static boolean isDynamicTriggerExpression(String eventStr) {
|
||||||
|
if (eventStr == null || eventStr.isBlank()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (eventStr.startsWith("<SYMBOLIC: ")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (eventStr.contains("${")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (eventStr.equals("event") || eventStr.equals("e")
|
if (eventStr.equals("event") || eventStr.equals("e")
|
||||||
|| eventStr.equals("msg") || eventStr.equals("message")
|
|| eventStr.equals("msg") || eventStr.equals("message")
|
||||||
|| eventStr.equals("payload")) {
|
|| eventStr.equals("payload")) {
|
||||||
@@ -163,7 +241,64 @@ public final class MachineEnumCanonicalizer {
|
|||||||
if (eventStr.contains(".valueOf(") || eventStr.contains("valueOf (")) {
|
if (eventStr.contains(".valueOf(") || eventStr.contains("valueOf (")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return eventStr.contains(" ? ") && eventStr.contains(" : ");
|
if (eventStr.contains(" ? ") && eventStr.contains(" : ")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (eventStr.contains("(") || eventStr.contains(")")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (Character.isLowerCase(eventStr.charAt(0)) && !eventStr.contains(".")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverts mistaken package-prefixing on dynamic {@code Type.valueOf(...)} trigger expressions.
|
||||||
|
*/
|
||||||
|
public static String denormalizeCorruptedDynamicEvent(String value) {
|
||||||
|
if (value == null || !value.contains(".valueOf(")) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return value.replaceAll("(?:[a-z][a-z0-9_]*\\.)+([A-Z][A-Za-z0-9_]*)\\.valueOf\\(", "$1.valueOf(");
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum TriggerEventKind {
|
||||||
|
CANONICAL_ENUM,
|
||||||
|
DYNAMIC_EXPRESSION,
|
||||||
|
SYMBOLIC,
|
||||||
|
LIFECYCLE,
|
||||||
|
OTHER
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TriggerEventKind classifyTriggerEvent(String event) {
|
||||||
|
if (event == null || event.isBlank()) {
|
||||||
|
return TriggerEventKind.OTHER;
|
||||||
|
}
|
||||||
|
if (event.startsWith("<SYMBOLIC: ")) {
|
||||||
|
return TriggerEventKind.SYMBOLIC;
|
||||||
|
}
|
||||||
|
if (click.kamil.springstatemachineexporter.analysis.model.LifecycleTriggerMarkers.isLifecycle(event)) {
|
||||||
|
return TriggerEventKind.LIFECYCLE;
|
||||||
|
}
|
||||||
|
if (isDynamicTriggerExpression(event)) {
|
||||||
|
return TriggerEventKind.DYNAMIC_EXPRESSION;
|
||||||
|
}
|
||||||
|
if (event.contains(".")) {
|
||||||
|
String constant = constantName(event);
|
||||||
|
if (constant.matches("[A-Z_][A-Z0-9_]*")) {
|
||||||
|
return TriggerEventKind.CANONICAL_ENUM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TriggerEventKind.OTHER;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasCorruptedPackageDynamicEvent(String event) {
|
||||||
|
if (event == null || !event.contains(".valueOf(")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String denormalized = denormalizeCorruptedDynamicEvent(event);
|
||||||
|
return !event.equals(denormalized);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<State> canonicalizeStates(List<State> states, String stateTypeFqn) {
|
private static List<State> canonicalizeStates(List<State> states, String stateTypeFqn) {
|
||||||
@@ -257,6 +392,9 @@ public final class MachineEnumCanonicalizer {
|
|||||||
if (value == null || value.isBlank() || enumTypeFqn == null || enumTypeFqn.isBlank()) {
|
if (value == null || value.isBlank() || enumTypeFqn == null || enumTypeFqn.isBlank()) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
if (isDynamicTriggerExpression(value)) {
|
||||||
|
return denormalizeCorruptedDynamicEvent(value);
|
||||||
|
}
|
||||||
value = resolvePlaceholder(value);
|
value = resolvePlaceholder(value);
|
||||||
if (value == null || value.isBlank()) {
|
if (value == null || value.isBlank()) {
|
||||||
return value;
|
return value;
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public final class AnalysisResultFinalizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
persistMachineTypes(result, machineTypes);
|
persistMachineTypes(result, machineTypes);
|
||||||
applyCanonicalization(result, machineTypes);
|
applyCanonicalizationFields(result, context, machineTypes);
|
||||||
|
|
||||||
if (context != null) {
|
if (context != null) {
|
||||||
AnalysisCanonicalFormValidator.enforce(result, context);
|
AnalysisCanonicalFormValidator.enforce(result, context);
|
||||||
@@ -66,17 +66,25 @@ public final class AnalysisResultFinalizer {
|
|||||||
public static void applyCanonicalization(
|
public static void applyCanonicalization(
|
||||||
AnalysisResult result,
|
AnalysisResult result,
|
||||||
StateMachineTypeResolver.MachineTypes machineTypes) {
|
StateMachineTypeResolver.MachineTypes machineTypes) {
|
||||||
|
applyCanonicalization(result, null, machineTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void applyCanonicalization(
|
||||||
|
AnalysisResult result,
|
||||||
|
CodebaseContext context,
|
||||||
|
StateMachineTypeResolver.MachineTypes machineTypes) {
|
||||||
if (result == null || machineTypes == null) {
|
if (result == null || machineTypes == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (machineTypes.stateTypeFqn() == null && machineTypes.eventTypeFqn() == null) {
|
if (machineTypes.stateTypeFqn() == null && machineTypes.eventTypeFqn() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
applyCanonicalizationFields(result, machineTypes);
|
applyCanonicalizationFields(result, context, machineTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void applyCanonicalizationFields(
|
private static void applyCanonicalizationFields(
|
||||||
AnalysisResult result,
|
AnalysisResult result,
|
||||||
|
CodebaseContext context,
|
||||||
StateMachineTypeResolver.MachineTypes machineTypes) {
|
StateMachineTypeResolver.MachineTypes machineTypes) {
|
||||||
MachineEnumCanonicalizer.canonicalizeTransitions(result.getTransitions(), machineTypes);
|
MachineEnumCanonicalizer.canonicalizeTransitions(result.getTransitions(), machineTypes);
|
||||||
|
|
||||||
@@ -91,8 +99,8 @@ public final class AnalysisResultFinalizer {
|
|||||||
|
|
||||||
if (result.getMetadata() != null) {
|
if (result.getMetadata() != null) {
|
||||||
CodebaseMetadata metadata = result.getMetadata();
|
CodebaseMetadata metadata = result.getMetadata();
|
||||||
List<TriggerPoint> triggers = canonicalizeTriggers(metadata.getTriggers(), machineTypes);
|
List<TriggerPoint> triggers = canonicalizeTriggers(metadata.getTriggers(), context, machineTypes);
|
||||||
List<CallChain> callChains = canonicalizeCallChains(metadata.getCallChains(), machineTypes);
|
List<CallChain> callChains = canonicalizeCallChains(metadata.getCallChains(), context, machineTypes);
|
||||||
result.setMetadata(CodebaseMetadata.builder()
|
result.setMetadata(CodebaseMetadata.builder()
|
||||||
.triggers(triggers)
|
.triggers(triggers)
|
||||||
.entryPoints(metadata.getEntryPoints())
|
.entryPoints(metadata.getEntryPoints())
|
||||||
@@ -104,17 +112,20 @@ public final class AnalysisResultFinalizer {
|
|||||||
|
|
||||||
private static List<TriggerPoint> canonicalizeTriggers(
|
private static List<TriggerPoint> canonicalizeTriggers(
|
||||||
List<TriggerPoint> triggers,
|
List<TriggerPoint> triggers,
|
||||||
|
CodebaseContext context,
|
||||||
StateMachineTypeResolver.MachineTypes machineTypes) {
|
StateMachineTypeResolver.MachineTypes machineTypes) {
|
||||||
if (triggers == null) {
|
if (triggers == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return triggers.stream()
|
return triggers.stream()
|
||||||
.map(trigger -> MachineEnumCanonicalizer.canonicalizeTriggerPoint(trigger, machineTypes))
|
.map(trigger -> MachineEnumCanonicalizer.canonicalizeAndExpandTriggerPoint(
|
||||||
|
trigger, machineTypes, context))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<CallChain> canonicalizeCallChains(
|
private static List<CallChain> canonicalizeCallChains(
|
||||||
List<CallChain> callChains,
|
List<CallChain> callChains,
|
||||||
|
CodebaseContext context,
|
||||||
StateMachineTypeResolver.MachineTypes machineTypes) {
|
StateMachineTypeResolver.MachineTypes machineTypes) {
|
||||||
if (callChains == null) {
|
if (callChains == null) {
|
||||||
return null;
|
return null;
|
||||||
@@ -123,7 +134,8 @@ public final class AnalysisResultFinalizer {
|
|||||||
.map(chain -> {
|
.map(chain -> {
|
||||||
TriggerPoint trigger = chain.getTriggerPoint() == null
|
TriggerPoint trigger = chain.getTriggerPoint() == null
|
||||||
? null
|
? null
|
||||||
: MachineEnumCanonicalizer.canonicalizeTriggerPoint(chain.getTriggerPoint(), machineTypes);
|
: MachineEnumCanonicalizer.canonicalizeAndExpandTriggerPoint(
|
||||||
|
chain.getTriggerPoint(), machineTypes, context);
|
||||||
List<MatchedTransition> matched = canonicalizeMatchedTransitions(
|
List<MatchedTransition> matched = canonicalizeMatchedTransitions(
|
||||||
chain.getMatchedTransitions(), machineTypes);
|
chain.getMatchedTransitions(), machineTypes);
|
||||||
return chain.toBuilder()
|
return chain.toBuilder()
|
||||||
|
|||||||
@@ -185,6 +185,7 @@ public final class AnalysisCanonicalFormValidator {
|
|||||||
String stateTypeFqn = preferTypeFqn(trigger.getStateTypeFqn(), machineTypes.stateTypeFqn());
|
String stateTypeFqn = preferTypeFqn(trigger.getStateTypeFqn(), machineTypes.stateTypeFqn());
|
||||||
|
|
||||||
requireCanonical(prefix + ".event", trigger.getEvent(), eventTypeFqn, violations);
|
requireCanonical(prefix + ".event", trigger.getEvent(), eventTypeFqn, violations);
|
||||||
|
validateTriggerEventForm(prefix + ".event", trigger.getEvent(), violations);
|
||||||
requireCanonical(prefix + ".sourceState", trigger.getSourceState(), stateTypeFqn, violations);
|
requireCanonical(prefix + ".sourceState", trigger.getSourceState(), stateTypeFqn, violations);
|
||||||
|
|
||||||
if (trigger.getPolymorphicEvents() != null) {
|
if (trigger.getPolymorphicEvents() != null) {
|
||||||
@@ -215,6 +216,7 @@ public final class AnalysisCanonicalFormValidator {
|
|||||||
String eventTypeFqn = preferTypeFqn(trigger.getEventTypeFqn(), machineTypes.eventTypeFqn());
|
String eventTypeFqn = preferTypeFqn(trigger.getEventTypeFqn(), machineTypes.eventTypeFqn());
|
||||||
String stateTypeFqn = preferTypeFqn(trigger.getStateTypeFqn(), machineTypes.stateTypeFqn());
|
String stateTypeFqn = preferTypeFqn(trigger.getStateTypeFqn(), machineTypes.stateTypeFqn());
|
||||||
requireCanonical(triggerPrefix + ".event", trigger.getEvent(), eventTypeFqn, violations);
|
requireCanonical(triggerPrefix + ".event", trigger.getEvent(), eventTypeFqn, violations);
|
||||||
|
validateTriggerEventForm(triggerPrefix + ".event", trigger.getEvent(), violations);
|
||||||
requireCanonical(triggerPrefix + ".sourceState", trigger.getSourceState(), stateTypeFqn, violations);
|
requireCanonical(triggerPrefix + ".sourceState", trigger.getSourceState(), stateTypeFqn, violations);
|
||||||
if (trigger.getPolymorphicEvents() != null) {
|
if (trigger.getPolymorphicEvents() != null) {
|
||||||
for (int j = 0; j < trigger.getPolymorphicEvents().size(); j++) {
|
for (int j = 0; j < trigger.getPolymorphicEvents().size(); j++) {
|
||||||
@@ -258,4 +260,16 @@ public final class AnalysisCanonicalFormValidator {
|
|||||||
violations.add(new Violation(path, value, expected));
|
violations.add(new Violation(path, value, expected));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void validateTriggerEventForm(String path, String event, List<Violation> violations) {
|
||||||
|
if (event == null || event.isBlank()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (MachineEnumCanonicalizer.hasCorruptedPackageDynamicEvent(event)) {
|
||||||
|
violations.add(new Violation(
|
||||||
|
path,
|
||||||
|
event,
|
||||||
|
MachineEnumCanonicalizer.denormalizeCorruptedDynamicEvent(event)));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,10 +63,10 @@ public class Dot implements StateMachineExporter {
|
|||||||
// Start/End nodes
|
// Start/End nodes
|
||||||
sb.append(" _start [shape=circle, label=\"\", fillcolor=black, width=0.1];\n");
|
sb.append(" _start [shape=circle, label=\"\", fillcolor=black, width=0.1];\n");
|
||||||
for (String start : startStates) {
|
for (String start : startStates) {
|
||||||
sb.append(" _start -> ").append(simplify(start)).append(";\n");
|
sb.append(" _start -> ").append(simplify(options.formatStateLabel(start))).append(";\n");
|
||||||
}
|
}
|
||||||
for (String end : endStates) {
|
for (String end : endStates) {
|
||||||
sb.append(" ").append(simplify(end)).append(" [fillcolor=lightgray];\n");
|
sb.append(" ").append(simplify(options.formatStateLabel(end))).append(" [fillcolor=lightgray];\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transitions
|
// Transitions
|
||||||
|
|||||||
@@ -30,6 +30,12 @@ public class ExportOptions {
|
|||||||
return format(event.rawName(), event.fullIdentifier(), eventFormat);
|
return format(event.rawName(), event.fullIdentifier(), eventFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Formats a bare state label (e.g. start/end state string) using the same rules as {@link #formatState}. */
|
||||||
|
public String formatStateLabel(String label) {
|
||||||
|
if (label == null) return null;
|
||||||
|
return format(label, label, stateFormat);
|
||||||
|
}
|
||||||
|
|
||||||
private String format(String raw, String fqn, EnumFormat format) {
|
private String format(String raw, String fqn, EnumFormat format) {
|
||||||
if (fqn == null) return raw;
|
if (fqn == null) return raw;
|
||||||
switch (format) {
|
switch (format) {
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ public class PlantUml implements StateMachineExporter {
|
|||||||
sb.append("\n");
|
sb.append("\n");
|
||||||
|
|
||||||
for (String start : startStates) {
|
for (String start : startStates) {
|
||||||
sb.append("[*] --> ").append(simplify(start)).append("\n");
|
sb.append("[*] --> ").append(simplify(options.formatStateLabel(start))).append("\n");
|
||||||
}
|
}
|
||||||
sb.append("\n");
|
sb.append("\n");
|
||||||
|
|
||||||
@@ -143,7 +143,7 @@ public class PlantUml implements StateMachineExporter {
|
|||||||
|
|
||||||
sb.append("\n");
|
sb.append("\n");
|
||||||
for (String end : endStates) {
|
for (String end : endStates) {
|
||||||
sb.append(simplify(end)).append(" --> [*]\n");
|
sb.append(simplify(options.formatStateLabel(end))).append(" --> [*]\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.append("@enduml\n");
|
sb.append("@enduml\n");
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public class Scxml implements StateMachineExporter {
|
|||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
|
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
|
||||||
sb.append("<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" version=\"1.0\" initial=\"");
|
sb.append("<scxml xmlns=\"http://www.w3.org/2005/07/scxml\" version=\"1.0\" initial=\"");
|
||||||
sb.append(startStates.isEmpty() ? "" : simplify(startStates.iterator().next()));
|
sb.append(startStates.isEmpty() ? "" : simplify(options.formatStateLabel(startStates.iterator().next())));
|
||||||
sb.append("\">\n");
|
sb.append("\">\n");
|
||||||
|
|
||||||
Set<String> allStates = new java.util.LinkedHashSet<>();
|
Set<String> allStates = new java.util.LinkedHashSet<>();
|
||||||
@@ -34,8 +34,8 @@ public class Scxml implements StateMachineExporter {
|
|||||||
t.getSourceStates().forEach(s -> allStates.add(options.formatState(s)));
|
t.getSourceStates().forEach(s -> allStates.add(options.formatState(s)));
|
||||||
t.getTargetStates().forEach(s -> allStates.add(options.formatState(s)));
|
t.getTargetStates().forEach(s -> allStates.add(options.formatState(s)));
|
||||||
}
|
}
|
||||||
allStates.addAll(startStates);
|
startStates.forEach(s -> allStates.add(options.formatStateLabel(s)));
|
||||||
allStates.addAll(endStates);
|
endStates.forEach(s -> allStates.add(options.formatStateLabel(s)));
|
||||||
|
|
||||||
for (String state : allStates) {
|
for (String state : allStates) {
|
||||||
sb.append(" <state id=\"").append(simplify(state)).append("\">\n");
|
sb.append(" <state id=\"").append(simplify(state)).append("\">\n");
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ public class ExportService {
|
|||||||
result.getStateTypeFqn(),
|
result.getStateTypeFqn(),
|
||||||
result.getEventTypeFqn(),
|
result.getEventTypeFqn(),
|
||||||
context);
|
context);
|
||||||
AnalysisResultFinalizer.applyCanonicalization(result, machineTypes);
|
AnalysisResultFinalizer.applyCanonicalization(result, context, machineTypes);
|
||||||
enrichmentService.relinkAfterPropertyResolution(result, context, null);
|
enrichmentService.relinkAfterPropertyResolution(result, context, null);
|
||||||
AnalysisResultFinalizer.finalizeResult(result, context, machineTypes);
|
AnalysisResultFinalizer.finalizeResult(result, context, machineTypes);
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,35 @@ class TransitionLinkerEnricherTest {
|
|||||||
assertThat(result.getMetadata().getCallChains().get(0).getMatchedTransitions()).hasSize(1);
|
assertThat(result.getMetadata().getCallChains().get(0).getMatchedTransitions()).hasSize(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldLinkWhenSymbolicPolymorphicEventsExpandToMachineEnumConstants() {
|
||||||
|
Transition payT = new Transition();
|
||||||
|
payT.setSourceStates(List.of(
|
||||||
|
State.of("OrderState.NEW", "com.example.order.OrderState.NEW")));
|
||||||
|
payT.setTargetStates(List.of(
|
||||||
|
State.of("OrderState.PAID", "com.example.order.OrderState.PAID")));
|
||||||
|
payT.setEvent(Event.of("OrderEvent.PAY", "com.example.order.OrderEvent.PAY"));
|
||||||
|
|
||||||
|
CallChain chain = CallChain.builder()
|
||||||
|
.triggerPoint(TriggerPoint.builder()
|
||||||
|
.event("eventString")
|
||||||
|
.polymorphicEvents(List.of("com.example.order.OrderEvent.PAY"))
|
||||||
|
.constraint("\"ORDER\".equalsIgnoreCase(machineType)")
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
AnalysisResult result = AnalysisResult.builder()
|
||||||
|
.name("com.example.config.OrderStateMachineConfiguration")
|
||||||
|
.eventTypeFqn("com.example.order.OrderEvent")
|
||||||
|
.transitions(List.of(payT))
|
||||||
|
.metadata(CodebaseMetadata.builder().callChains(List.of(chain)).build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
enricher.enrich(result, null, null);
|
||||||
|
|
||||||
|
assertThat(result.getMetadata().getCallChains().get(0).getMatchedTransitions()).hasSize(1);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldRespectDispatcherBranchConstraintForMachineDomain() {
|
void shouldRespectDispatcherBranchConstraintForMachineDomain() {
|
||||||
Transition payT = new Transition();
|
Transition payT = new Transition();
|
||||||
|
|||||||
@@ -167,6 +167,50 @@ class MachineEnumCanonicalizerTest {
|
|||||||
assertThat(canonical.rawName()).isEqualTo("OrderState.NEW");
|
assertThat(canonical.rawName()).isEqualTo("OrderState.NEW");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotPackageCanonicalizeDynamicValueOfTriggerExpression() {
|
||||||
|
String dynamic = "MyEvent.valueOf(event)";
|
||||||
|
assertThat(MachineEnumCanonicalizer.classifyTriggerEvent(dynamic))
|
||||||
|
.isEqualTo(MachineEnumCanonicalizer.TriggerEventKind.DYNAMIC_EXPRESSION);
|
||||||
|
assertThat(MachineEnumCanonicalizer.canonicalizeLabel(
|
||||||
|
dynamic, "com.abcd.aaa.MyEvent"))
|
||||||
|
.isEqualTo("MyEvent.valueOf(event)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldDenormalizeCorruptedPackageValueOfTriggerExpression() {
|
||||||
|
String corrupted = "com.abcd.aaa.MyEvent.valueOf(event)";
|
||||||
|
assertThat(MachineEnumCanonicalizer.denormalizeCorruptedDynamicEvent(corrupted))
|
||||||
|
.isEqualTo("MyEvent.valueOf(event)");
|
||||||
|
assertThat(MachineEnumCanonicalizer.hasCorruptedPackageDynamicEvent(corrupted)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldClassifyConcreteEnumTriggerEvents() {
|
||||||
|
assertThat(MachineEnumCanonicalizer.classifyTriggerEvent("com.example.order.OrderEvent.PAY"))
|
||||||
|
.isEqualTo(MachineEnumCanonicalizer.TriggerEventKind.CANONICAL_ENUM);
|
||||||
|
assertThat(MachineEnumCanonicalizer.classifyTriggerEvent("event.getType()"))
|
||||||
|
.isEqualTo(MachineEnumCanonicalizer.TriggerEventKind.DYNAMIC_EXPRESSION);
|
||||||
|
assertThat(MachineEnumCanonicalizer.classifyTriggerEvent("<SYMBOLIC: OrderEvent.*>"))
|
||||||
|
.isEqualTo(MachineEnumCanonicalizer.TriggerEventKind.SYMBOLIC);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExpandSymbolicPolymorphicEventsForMatchingMachineType(@TempDir Path tempDir) throws IOException {
|
||||||
|
writeSampleConfig(tempDir);
|
||||||
|
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
List<String> expanded = MachineEnumCanonicalizer.expandSymbolicPolymorphicEvents(
|
||||||
|
List.of("<SYMBOLIC: OrderEvent.*>", "<SYMBOLIC: DocumentEvent.*>"),
|
||||||
|
"com.example.order.OrderEvent",
|
||||||
|
context);
|
||||||
|
|
||||||
|
assertThat(expanded).containsExactly(
|
||||||
|
"com.example.order.OrderEvent.PAY");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldDedupeStatesByCanonicalFullIdentifier(@TempDir Path tempDir) throws IOException {
|
void shouldDedupeStatesByCanonicalFullIdentifier(@TempDir Path tempDir) throws IOException {
|
||||||
writeSampleConfig(tempDir);
|
writeSampleConfig(tempDir);
|
||||||
|
|||||||
@@ -105,10 +105,49 @@ class ExportPipelineIdentifierConsistencyTest {
|
|||||||
}
|
}
|
||||||
AnalysisResult result = new JsonImportService().importAnalysisResult(json);
|
AnalysisResult result = new JsonImportService().importAnalysisResult(json);
|
||||||
assertNoMixedEnumIdentifiers(result);
|
assertNoMixedEnumIdentifiers(result);
|
||||||
|
assertTriggerEventForms(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void assertTriggerEventForms(AnalysisResult result) {
|
||||||
|
if (result.getMetadata() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (result.getMetadata().getTriggers() != null) {
|
||||||
|
for (TriggerPoint trigger : result.getMetadata().getTriggers()) {
|
||||||
|
assertTriggerEventForm(trigger.getEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result.getMetadata().getCallChains() != null) {
|
||||||
|
for (CallChain chain : result.getMetadata().getCallChains()) {
|
||||||
|
if (chain.getTriggerPoint() != null) {
|
||||||
|
assertTriggerEventForm(chain.getTriggerPoint().getEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertTriggerEventForm(String event) {
|
||||||
|
if (event == null || event.isBlank()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MachineEnumCanonicalizer.TriggerEventKind kind = MachineEnumCanonicalizer.classifyTriggerEvent(event);
|
||||||
|
assertThat(kind)
|
||||||
|
.as("triggerPoint.event must be canonical enum, dynamic expression, symbolic, or lifecycle: %s", event)
|
||||||
|
.isIn(
|
||||||
|
MachineEnumCanonicalizer.TriggerEventKind.CANONICAL_ENUM,
|
||||||
|
MachineEnumCanonicalizer.TriggerEventKind.DYNAMIC_EXPRESSION,
|
||||||
|
MachineEnumCanonicalizer.TriggerEventKind.SYMBOLIC,
|
||||||
|
MachineEnumCanonicalizer.TriggerEventKind.LIFECYCLE);
|
||||||
|
assertThat(MachineEnumCanonicalizer.hasCorruptedPackageDynamicEvent(event))
|
||||||
|
.as("dynamic trigger event must not be package-prefixed: %s", event)
|
||||||
|
.isFalse();
|
||||||
|
if (kind == MachineEnumCanonicalizer.TriggerEventKind.CANONICAL_ENUM) {
|
||||||
|
assertThat(event).contains(".").doesNotContain("(");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void propertyResolutionShouldCanonicalizeCallChainTriggersBeforeRelink(@TempDir Path tempDir) throws Exception {
|
void propertyResolutionShouldCanonicalizeCallChainTriggersBeforeRelink(@TempDir Path tempDir) throws Exception {
|
||||||
writeSampleProject(tempDir);
|
writeSampleProject(tempDir);
|
||||||
|
|||||||
@@ -120,6 +120,57 @@ class AnalysisCanonicalFormValidatorTest {
|
|||||||
.anyMatch(path -> path.contains("matchedTransitions[0].sourceState"));
|
.anyMatch(path -> path.contains("matchedTransitions[0].sourceState"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldFailWhenTriggerEventHasCorruptedPackageValueOfForm() {
|
||||||
|
AnalysisResult result = AnalysisResult.builder()
|
||||||
|
.name("com.example.config.OrderStateMachineConfiguration")
|
||||||
|
.stateTypeFqn("com.example.order.OrderState")
|
||||||
|
.eventTypeFqn("com.example.order.OrderEvent")
|
||||||
|
.metadata(CodebaseMetadata.builder()
|
||||||
|
.triggers(List.of(TriggerPoint.builder()
|
||||||
|
.event("com.example.order.OrderEvent.valueOf(eventString)")
|
||||||
|
.className("com.example.web.OrderController")
|
||||||
|
.methodName("dispatch")
|
||||||
|
.sourceFile("OrderController.java")
|
||||||
|
.build()))
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<AnalysisCanonicalFormValidator.Violation> violations =
|
||||||
|
AnalysisCanonicalFormValidator.validateWithMachineTypes(
|
||||||
|
result,
|
||||||
|
new click.kamil.springstatemachineexporter.analysis.resolver.StateMachineTypeResolver.MachineTypes(
|
||||||
|
"com.example.order.OrderState", "com.example.order.OrderEvent"));
|
||||||
|
|
||||||
|
assertThat(violations)
|
||||||
|
.extracting(AnalysisCanonicalFormValidator.Violation::path)
|
||||||
|
.contains("metadata.triggers[0].event");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldAllowDynamicDispatcherExpressionWithoutPackagePrefix() {
|
||||||
|
AnalysisResult result = AnalysisResult.builder()
|
||||||
|
.name("com.example.config.OrderStateMachineConfiguration")
|
||||||
|
.stateTypeFqn("com.example.order.OrderState")
|
||||||
|
.eventTypeFqn("com.example.order.OrderEvent")
|
||||||
|
.metadata(CodebaseMetadata.builder()
|
||||||
|
.triggers(List.of(TriggerPoint.builder()
|
||||||
|
.event("OrderEvent.valueOf(eventString.toUpperCase())")
|
||||||
|
.className("com.example.web.OrderController")
|
||||||
|
.methodName("dispatch")
|
||||||
|
.sourceFile("OrderController.java")
|
||||||
|
.polymorphicEvents(List.of("com.example.order.OrderEvent.PAY"))
|
||||||
|
.build()))
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertThat(AnalysisCanonicalFormValidator.validateWithMachineTypes(
|
||||||
|
result,
|
||||||
|
new click.kamil.springstatemachineexporter.analysis.resolver.StateMachineTypeResolver.MachineTypes(
|
||||||
|
"com.example.order.OrderState", "com.example.order.OrderEvent")))
|
||||||
|
.noneMatch(v -> v.path().contains(".event"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldSkipValidationForStringStateMachines(@TempDir Path tempDir) throws IOException {
|
void shouldSkipValidationForStringStateMachines(@TempDir Path tempDir) throws IOException {
|
||||||
Path configPkg = tempDir.resolve("com/example/config");
|
Path configPkg = tempDir.resolve("com/example/config");
|
||||||
|
|||||||
@@ -52,4 +52,30 @@ class PlantUmlTest {
|
|||||||
assertThat(result).contains("[list.size() < 5]");
|
assertThat(result).contains("[list.size() < 5]");
|
||||||
assertThat(result).contains("new int[]{1, 2}");
|
assertThat(result).contains("new int[]{1, 2}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldFormatPackageCanonicalStartAndEndStatesLikeTransitionNodes() {
|
||||||
|
PlantUml plantUml = new PlantUml();
|
||||||
|
ExportOptions options = ExportOptions.builder().build();
|
||||||
|
|
||||||
|
State stateA = State.of("State.A", "com.abcd.aaa.State.A");
|
||||||
|
State stateB = State.of("State.B", "com.abcd.aaa.State.B");
|
||||||
|
|
||||||
|
Transition t = new Transition();
|
||||||
|
t.setType(TransitionType.EXTERNAL);
|
||||||
|
t.setSourceStates(List.of(stateA));
|
||||||
|
t.setTargetStates(List.of(stateB));
|
||||||
|
|
||||||
|
String result = plantUml.export(
|
||||||
|
"TestMachine",
|
||||||
|
List.of(t),
|
||||||
|
Set.of("com.abcd.aaa.State.A"),
|
||||||
|
Set.of("com.abcd.aaa.State.B"),
|
||||||
|
options);
|
||||||
|
|
||||||
|
assertThat(result).contains("[*] --> State.A");
|
||||||
|
assertThat(result).contains("State.B --> [*]");
|
||||||
|
assertThat(result).doesNotContain("com.abcd.aaa.State.A");
|
||||||
|
assertThat(result).doesNotContain("abcd.aaa.State.A");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ skinparam ArrowThickness 1
|
|||||||
skinparam dpi 110
|
skinparam dpi 110
|
||||||
skinparam svgLinkTarget _self
|
skinparam svgLinkTarget _self
|
||||||
|
|
||||||
[*] --> ComplexStateMachineConfig.States.STATE1
|
[*] --> States.STATE1
|
||||||
|
|
||||||
state States.STATE16 <<choice>>
|
state States.STATE16 <<choice>>
|
||||||
state States.STATE17 <<choice>>
|
state States.STATE17 <<choice>>
|
||||||
|
|||||||
@@ -124,7 +124,5 @@
|
|||||||
<!-- order=1 -->
|
<!-- order=1 -->
|
||||||
</transition>
|
</transition>
|
||||||
</state>
|
</state>
|
||||||
<state id="STATE1">
|
|
||||||
</state>
|
|
||||||
</scxml>
|
</scxml>
|
||||||
|
|
||||||
|
|||||||
@@ -181,13 +181,25 @@
|
|||||||
"stateMachineId" : null,
|
"stateMachineId" : null,
|
||||||
"sourceState" : null,
|
"sourceState" : null,
|
||||||
"lineNumber" : 87,
|
"lineNumber" : 87,
|
||||||
"polymorphicEvents" : [ "<SYMBOLIC: OrderEvent.*>", "<SYMBOLIC: DocumentEvent.*>", "<SYMBOLIC: UserEvent.*>" ],
|
"polymorphicEvents" : [ "click.kamil.enterprise.machines.document.DocumentEvent.SUBMIT", "click.kamil.enterprise.machines.document.DocumentEvent.APPROVE", "click.kamil.enterprise.machines.document.DocumentEvent.REJECT" ],
|
||||||
"external" : true,
|
"external" : true,
|
||||||
"constraint" : "!(\"ORDER\".equalsIgnoreCase(machineType)) && \"DOCUMENT\".equalsIgnoreCase(machineType)",
|
"constraint" : "!(\"ORDER\".equalsIgnoreCase(machineType)) && \"DOCUMENT\".equalsIgnoreCase(machineType)",
|
||||||
"ambiguous" : false
|
"ambiguous" : false
|
||||||
},
|
},
|
||||||
"contextMachineId" : null,
|
"contextMachineId" : null,
|
||||||
"matchedTransitions" : null
|
"matchedTransitions" : [ {
|
||||||
|
"sourceState" : "click.kamil.enterprise.machines.document.DocumentState.DRAFT",
|
||||||
|
"targetState" : "click.kamil.enterprise.machines.document.DocumentState.IN_REVIEW",
|
||||||
|
"event" : "click.kamil.enterprise.machines.document.DocumentEvent.SUBMIT"
|
||||||
|
}, {
|
||||||
|
"sourceState" : "click.kamil.enterprise.machines.document.DocumentState.IN_REVIEW",
|
||||||
|
"targetState" : "click.kamil.enterprise.machines.document.DocumentState.APPROVED",
|
||||||
|
"event" : "click.kamil.enterprise.machines.document.DocumentEvent.APPROVE"
|
||||||
|
}, {
|
||||||
|
"sourceState" : "click.kamil.enterprise.machines.document.DocumentState.IN_REVIEW",
|
||||||
|
"targetState" : "click.kamil.enterprise.machines.document.DocumentState.DRAFT",
|
||||||
|
"event" : "click.kamil.enterprise.machines.document.DocumentEvent.REJECT"
|
||||||
|
} ]
|
||||||
} ],
|
} ],
|
||||||
"properties" : {
|
"properties" : {
|
||||||
"default" : { }
|
"default" : { }
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ skinparam ArrowThickness 1
|
|||||||
skinparam dpi 110
|
skinparam dpi 110
|
||||||
skinparam svgLinkTarget _self
|
skinparam svgLinkTarget _self
|
||||||
|
|
||||||
[*] --> click.kamil.enterprise.machines.document.DocumentState.DRAFT
|
[*] --> DocumentState.DRAFT
|
||||||
|
|
||||||
|
|
||||||
DocumentState.DRAFT -[#1E90FF,bold]-> DocumentState.IN_REVIEW <<external>> : DocumentEvent.SUBMIT
|
DocumentState.DRAFT -[#1E90FF,bold]-> DocumentState.IN_REVIEW <<external>> : DocumentEvent.SUBMIT
|
||||||
DocumentState.IN_REVIEW -[#1E90FF,bold]-> DocumentState.APPROVED <<external>> : DocumentEvent.APPROVE
|
DocumentState.IN_REVIEW -[#1E90FF,bold]-> DocumentState.APPROVED <<external>> : DocumentEvent.APPROVE
|
||||||
DocumentState.IN_REVIEW -[#1E90FF,bold]-> DocumentState.DRAFT <<external>> : DocumentEvent.REJECT
|
DocumentState.IN_REVIEW -[#1E90FF,bold]-> DocumentState.DRAFT <<external>> : DocumentEvent.REJECT
|
||||||
|
|
||||||
click.kamil.enterprise.machines.document.DocumentState.APPROVED --> [*]
|
DocumentState.APPROVED --> [*]
|
||||||
@enduml
|
@enduml
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,5 @@
|
|||||||
</state>
|
</state>
|
||||||
<state id="APPROVED">
|
<state id="APPROVED">
|
||||||
</state>
|
</state>
|
||||||
<state id="DRAFT">
|
|
||||||
</state>
|
|
||||||
<state id="APPROVED">
|
|
||||||
</state>
|
|
||||||
</scxml>
|
</scxml>
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ skinparam ArrowThickness 1
|
|||||||
skinparam dpi 110
|
skinparam dpi 110
|
||||||
skinparam svgLinkTarget _self
|
skinparam svgLinkTarget _self
|
||||||
|
|
||||||
[*] --> click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.States.STATE1
|
[*] --> States.STATE1
|
||||||
|
|
||||||
state States.STATEY <<choice>>
|
state States.STATEY <<choice>>
|
||||||
state States.STATE16 <<choice>>
|
state States.STATE16 <<choice>>
|
||||||
@@ -89,6 +89,6 @@ States.STATE_EXTRA_1 -[#1E90FF,bold]-> States.STATE_EXTRA_3 <<external>> : Event
|
|||||||
States.STATE_EXTRA_1 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL_2
|
States.STATE_EXTRA_1 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL_2
|
||||||
States.STATE_EXTRA_2 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL_2
|
States.STATE_EXTRA_2 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL_2
|
||||||
|
|
||||||
click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.States.STATEZ --> [*]
|
States.STATEZ --> [*]
|
||||||
@enduml
|
@enduml
|
||||||
|
|
||||||
|
|||||||
@@ -160,9 +160,5 @@
|
|||||||
<state id="STATE_EXTRA_2">
|
<state id="STATE_EXTRA_2">
|
||||||
<transition target="CANCEL" event="Events.EVENT_CANCEL_2"/>
|
<transition target="CANCEL" event="Events.EVENT_CANCEL_2"/>
|
||||||
</state>
|
</state>
|
||||||
<state id="STATE1">
|
|
||||||
</state>
|
|
||||||
<state id="STATEZ">
|
|
||||||
</state>
|
|
||||||
</scxml>
|
</scxml>
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ skinparam ArrowThickness 1
|
|||||||
skinparam dpi 110
|
skinparam dpi 110
|
||||||
skinparam svgLinkTarget _self
|
skinparam svgLinkTarget _self
|
||||||
|
|
||||||
[*] --> click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.States.STATE1
|
[*] --> States.STATE1
|
||||||
|
|
||||||
state States.STATEY <<choice>>
|
state States.STATEY <<choice>>
|
||||||
state States.STATE16 <<choice>>
|
state States.STATE16 <<choice>>
|
||||||
@@ -90,6 +90,6 @@ States.STATE_EXTRA_1_1 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVE
|
|||||||
States.STATE_EXTRA_1_2 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL_2
|
States.STATE_EXTRA_1_2 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL_2
|
||||||
States.STATE_EXTRA_1 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL_2
|
States.STATE_EXTRA_1 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL_2
|
||||||
|
|
||||||
click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.States.STATEZ --> [*]
|
States.STATEZ --> [*]
|
||||||
@enduml
|
@enduml
|
||||||
|
|
||||||
|
|||||||
@@ -163,9 +163,5 @@
|
|||||||
<state id="STATE_EXTRA_1">
|
<state id="STATE_EXTRA_1">
|
||||||
<transition target="CANCEL" event="Events.EVENT_CANCEL_2"/>
|
<transition target="CANCEL" event="Events.EVENT_CANCEL_2"/>
|
||||||
</state>
|
</state>
|
||||||
<state id="STATE1">
|
|
||||||
</state>
|
|
||||||
<state id="STATEZ">
|
|
||||||
</state>
|
|
||||||
</scxml>
|
</scxml>
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ skinparam ArrowThickness 1
|
|||||||
skinparam dpi 110
|
skinparam dpi 110
|
||||||
skinparam svgLinkTarget _self
|
skinparam svgLinkTarget _self
|
||||||
|
|
||||||
[*] --> ForkJoinStateMachineConfig.States.START
|
[*] --> States.START
|
||||||
|
|
||||||
|
|
||||||
States.START -[#1E90FF,bold]-> States.FORK <<external>> : Events.TO_FORK
|
States.START -[#1E90FF,bold]-> States.FORK <<external>> : Events.TO_FORK
|
||||||
@@ -33,6 +33,6 @@ States.REGION1_STATE2 -[#8A2BE2,bold]-> States.JOIN <<join>>
|
|||||||
States.REGION2_STATE2 -[#8A2BE2,bold]-> States.JOIN <<join>>
|
States.REGION2_STATE2 -[#8A2BE2,bold]-> States.JOIN <<join>>
|
||||||
States.JOIN -[#1E90FF,bold]-> States.END <<external>> : Events.TO_END
|
States.JOIN -[#1E90FF,bold]-> States.END <<external>> : Events.TO_END
|
||||||
|
|
||||||
ForkJoinStateMachineConfig.States.END --> [*]
|
States.END --> [*]
|
||||||
@enduml
|
@enduml
|
||||||
|
|
||||||
|
|||||||
@@ -24,9 +24,5 @@
|
|||||||
</state>
|
</state>
|
||||||
<state id="END">
|
<state id="END">
|
||||||
</state>
|
</state>
|
||||||
<state id="START">
|
|
||||||
</state>
|
|
||||||
<state id="END">
|
|
||||||
</state>
|
|
||||||
</scxml>
|
</scxml>
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ skinparam ArrowThickness 1
|
|||||||
skinparam dpi 110
|
skinparam dpi 110
|
||||||
skinparam svgLinkTarget _self
|
skinparam svgLinkTarget _self
|
||||||
|
|
||||||
[*] --> click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.States.STATE1
|
[*] --> States.STATE1
|
||||||
|
|
||||||
state States.STATEY <<choice>>
|
state States.STATEY <<choice>>
|
||||||
state States.STATE9 <<choice>>
|
state States.STATE9 <<choice>>
|
||||||
@@ -53,6 +53,6 @@ States.STATE3 -[#1E90FF,bold]-> States.STATE_EXTRA_2 <<external>> : Events.EVENT
|
|||||||
States.STATE_EXTRA_2 -[#1E90FF,bold]-> States.STATE_EXTRA_3 <<external>> : Events.EVENT_1_2
|
States.STATE_EXTRA_2 -[#1E90FF,bold]-> States.STATE_EXTRA_3 <<external>> : Events.EVENT_1_2
|
||||||
States.STATE_EXTRA_3 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL
|
States.STATE_EXTRA_3 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL
|
||||||
|
|
||||||
click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.States.STATEZ --> [*]
|
States.STATEZ --> [*]
|
||||||
@enduml
|
@enduml
|
||||||
|
|
||||||
|
|||||||
@@ -71,9 +71,5 @@
|
|||||||
<state id="STATE_EXTRA_3">
|
<state id="STATE_EXTRA_3">
|
||||||
<transition target="CANCEL" event="Events.EVENT_CANCEL"/>
|
<transition target="CANCEL" event="Events.EVENT_CANCEL"/>
|
||||||
</state>
|
</state>
|
||||||
<state id="STATE1">
|
|
||||||
</state>
|
|
||||||
<state id="STATEZ">
|
|
||||||
</state>
|
|
||||||
</scxml>
|
</scxml>
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ skinparam ArrowThickness 1
|
|||||||
skinparam dpi 110
|
skinparam dpi 110
|
||||||
skinparam svgLinkTarget _self
|
skinparam svgLinkTarget _self
|
||||||
|
|
||||||
[*] --> click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.States.STATE1
|
[*] --> States.STATE1
|
||||||
|
|
||||||
state States.STATEY <<choice>>
|
state States.STATEY <<choice>>
|
||||||
state States.STATE9 <<choice>>
|
state States.STATE9 <<choice>>
|
||||||
@@ -54,6 +54,6 @@ States.STATE_EXTRA_1 -[#1E90FF,bold]-> States.STATE_EXTRA_2 <<external>> : Event
|
|||||||
States.STATE_EXTRA_1 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL
|
States.STATE_EXTRA_1 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL
|
||||||
States.STATE_EXTRA_2 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL
|
States.STATE_EXTRA_2 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL
|
||||||
|
|
||||||
click.kamil.examples.statemachine.inheritanceextrafunctionsstate3.States.STATEZ --> [*]
|
States.STATEZ --> [*]
|
||||||
@enduml
|
@enduml
|
||||||
|
|
||||||
|
|||||||
@@ -72,9 +72,5 @@
|
|||||||
<state id="STATE_EXTRA_2">
|
<state id="STATE_EXTRA_2">
|
||||||
<transition target="CANCEL" event="Events.EVENT_CANCEL"/>
|
<transition target="CANCEL" event="Events.EVENT_CANCEL"/>
|
||||||
</state>
|
</state>
|
||||||
<state id="STATE1">
|
|
||||||
</state>
|
|
||||||
<state id="STATEZ">
|
|
||||||
</state>
|
|
||||||
</scxml>
|
</scxml>
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ skinparam ArrowThickness 1
|
|||||||
skinparam dpi 110
|
skinparam dpi 110
|
||||||
skinparam svgLinkTarget _self
|
skinparam svgLinkTarget _self
|
||||||
|
|
||||||
[*] --> click.kamil.examples.statemachine.enumstate.OrderStates.SUBMITTED
|
[*] --> OrderStates.SUBMITTED
|
||||||
|
|
||||||
state OrderStates.PAID1 <<choice>>
|
state OrderStates.PAID1 <<choice>>
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ OrderStates.PAID2 -[#1E90FF,bold]-> OrderStates.CANCELED <<external>>
|
|||||||
OrderStates.PAID3 -[#1E90FF,bold]-> OrderStates.CANCELED <<external>>
|
OrderStates.PAID3 -[#1E90FF,bold]-> OrderStates.CANCELED <<external>>
|
||||||
OrderStates.PAID2 -[#1E90FF,bold]-> OrderStates.PAID2 <<external>> : OrderEvents.ABCD / λ, action2
|
OrderStates.PAID2 -[#1E90FF,bold]-> OrderStates.PAID2 <<external>> : OrderEvents.ABCD / λ, action2
|
||||||
|
|
||||||
click.kamil.examples.statemachine.enumstate.OrderStates.CANCELED --> [*]
|
OrderStates.CANCELED --> [*]
|
||||||
click.kamil.examples.statemachine.enumstate.OrderStates.FULFILLED --> [*]
|
OrderStates.FULFILLED --> [*]
|
||||||
@enduml
|
@enduml
|
||||||
|
|
||||||
|
|||||||
@@ -37,11 +37,5 @@
|
|||||||
</state>
|
</state>
|
||||||
<state id="HAPPEN">
|
<state id="HAPPEN">
|
||||||
</state>
|
</state>
|
||||||
<state id="SUBMITTED">
|
|
||||||
</state>
|
|
||||||
<state id="CANCELED">
|
|
||||||
</state>
|
|
||||||
<state id="FULFILLED">
|
|
||||||
</state>
|
|
||||||
</scxml>
|
</scxml>
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ skinparam ArrowThickness 1
|
|||||||
skinparam dpi 110
|
skinparam dpi 110
|
||||||
skinparam svgLinkTarget _self
|
skinparam svgLinkTarget _self
|
||||||
|
|
||||||
[*] --> click.kamil.examples.statemachine.inheritancestate.States.STATE1
|
[*] --> States.STATE1
|
||||||
|
|
||||||
state States.STATEY <<choice>>
|
state States.STATEY <<choice>>
|
||||||
state States.STATE16 <<choice>>
|
state States.STATE16 <<choice>>
|
||||||
@@ -78,6 +78,6 @@ States.STATE8 -[#FF6347,bold]-> States.STATE9 <<choice_type>> : [guardVarEquals(
|
|||||||
States.STATE8 -[#FF6347,bold]-> States.STATE10 <<choice_type>> : (order=1)
|
States.STATE8 -[#FF6347,bold]-> States.STATE10 <<choice_type>> : (order=1)
|
||||||
States.STATE5 -[#1E90FF,bold]-> States.STATE_EXTRA_1 <<external>> : Events.EVENTX
|
States.STATE5 -[#1E90FF,bold]-> States.STATE_EXTRA_1 <<external>> : Events.EVENTX
|
||||||
|
|
||||||
click.kamil.examples.statemachine.inheritancestate.States.STATEZ --> [*]
|
States.STATEZ --> [*]
|
||||||
@enduml
|
@enduml
|
||||||
|
|
||||||
|
|||||||
@@ -140,9 +140,5 @@
|
|||||||
</state>
|
</state>
|
||||||
<state id="STATE_EXTRA_1">
|
<state id="STATE_EXTRA_1">
|
||||||
</state>
|
</state>
|
||||||
<state id="STATE1">
|
|
||||||
</state>
|
|
||||||
<state id="STATEZ">
|
|
||||||
</state>
|
|
||||||
</scxml>
|
</scxml>
|
||||||
|
|
||||||
|
|||||||
@@ -158,13 +158,21 @@
|
|||||||
"stateMachineId" : null,
|
"stateMachineId" : null,
|
||||||
"sourceState" : null,
|
"sourceState" : null,
|
||||||
"lineNumber" : 81,
|
"lineNumber" : 81,
|
||||||
"polymorphicEvents" : [ "<SYMBOLIC: OrderEvent.*>", "<SYMBOLIC: DocumentEvent.*>", "<SYMBOLIC: UserEvent.*>" ],
|
"polymorphicEvents" : [ "click.kamil.enterprise.machines.order.OrderEvent.PAY", "click.kamil.enterprise.machines.order.OrderEvent.SHIP" ],
|
||||||
"external" : true,
|
"external" : true,
|
||||||
"constraint" : "\"ORDER\".equalsIgnoreCase(machineType)",
|
"constraint" : "\"ORDER\".equalsIgnoreCase(machineType)",
|
||||||
"ambiguous" : false
|
"ambiguous" : false
|
||||||
},
|
},
|
||||||
"contextMachineId" : null,
|
"contextMachineId" : null,
|
||||||
"matchedTransitions" : null
|
"matchedTransitions" : [ {
|
||||||
|
"sourceState" : "click.kamil.enterprise.machines.order.OrderState.NEW",
|
||||||
|
"targetState" : "click.kamil.enterprise.machines.order.OrderState.PENDING",
|
||||||
|
"event" : "click.kamil.enterprise.machines.order.OrderEvent.PAY"
|
||||||
|
}, {
|
||||||
|
"sourceState" : "click.kamil.enterprise.machines.order.OrderState.PENDING",
|
||||||
|
"targetState" : "click.kamil.enterprise.machines.order.OrderState.SHIPPED",
|
||||||
|
"event" : "click.kamil.enterprise.machines.order.OrderEvent.SHIP"
|
||||||
|
} ]
|
||||||
} ],
|
} ],
|
||||||
"properties" : {
|
"properties" : {
|
||||||
"default" : { }
|
"default" : { }
|
||||||
|
|||||||
@@ -21,12 +21,12 @@ skinparam ArrowThickness 1
|
|||||||
skinparam dpi 110
|
skinparam dpi 110
|
||||||
skinparam svgLinkTarget _self
|
skinparam svgLinkTarget _self
|
||||||
|
|
||||||
[*] --> click.kamil.enterprise.machines.order.OrderState.NEW
|
[*] --> OrderState.NEW
|
||||||
|
|
||||||
|
|
||||||
OrderState.NEW -[#1E90FF,bold]-> OrderState.PENDING <<external>> : OrderEvent.PAY / λ
|
OrderState.NEW -[#1E90FF,bold]-> OrderState.PENDING <<external>> : OrderEvent.PAY / λ
|
||||||
OrderState.PENDING -[#1E90FF,bold]-> OrderState.SHIPPED <<external>> : OrderEvent.SHIP
|
OrderState.PENDING -[#1E90FF,bold]-> OrderState.SHIPPED <<external>> : OrderEvent.SHIP
|
||||||
|
|
||||||
click.kamil.enterprise.machines.order.OrderState.SHIPPED --> [*]
|
OrderState.SHIPPED --> [*]
|
||||||
@enduml
|
@enduml
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,5 @@
|
|||||||
</state>
|
</state>
|
||||||
<state id="SHIPPED">
|
<state id="SHIPPED">
|
||||||
</state>
|
</state>
|
||||||
<state id="NEW">
|
|
||||||
</state>
|
|
||||||
<state id="SHIPPED">
|
|
||||||
</state>
|
|
||||||
</scxml>
|
</scxml>
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ skinparam ArrowThickness 1
|
|||||||
skinparam dpi 110
|
skinparam dpi 110
|
||||||
skinparam svgLinkTarget _self
|
skinparam svgLinkTarget _self
|
||||||
|
|
||||||
[*] --> click.kamil.examples.statemachine.polymorphic.OrderStates.SUBMITTED
|
[*] --> OrderStates.SUBMITTED
|
||||||
|
|
||||||
|
|
||||||
OrderStates.SUBMITTED -[#1E90FF,bold]-> OrderStates.PAID <<external>> : OrderEvents.PAY
|
OrderStates.SUBMITTED -[#1E90FF,bold]-> OrderStates.PAID <<external>> : OrderEvents.PAY
|
||||||
@@ -29,7 +29,7 @@ OrderStates.PAID -[#1E90FF,bold]-> OrderStates.FULFILLED <<external>> : OrderEve
|
|||||||
OrderStates.SUBMITTED -[#1E90FF,bold]-> OrderStates.CANCELED <<external>> : OrderEvents.CANCEL
|
OrderStates.SUBMITTED -[#1E90FF,bold]-> OrderStates.CANCELED <<external>> : OrderEvents.CANCEL
|
||||||
OrderStates.PAID -[#1E90FF,bold]-> OrderStates.CANCELED <<external>> : OrderEvents.ABCD
|
OrderStates.PAID -[#1E90FF,bold]-> OrderStates.CANCELED <<external>> : OrderEvents.ABCD
|
||||||
|
|
||||||
click.kamil.examples.statemachine.polymorphic.OrderStates.CANCELED --> [*]
|
OrderStates.CANCELED --> [*]
|
||||||
click.kamil.examples.statemachine.polymorphic.OrderStates.FULFILLED --> [*]
|
OrderStates.FULFILLED --> [*]
|
||||||
@enduml
|
@enduml
|
||||||
|
|
||||||
|
|||||||
@@ -12,11 +12,5 @@
|
|||||||
</state>
|
</state>
|
||||||
<state id="CANCELED">
|
<state id="CANCELED">
|
||||||
</state>
|
</state>
|
||||||
<state id="SUBMITTED">
|
|
||||||
</state>
|
|
||||||
<state id="CANCELED">
|
|
||||||
</state>
|
|
||||||
<state id="FULFILLED">
|
|
||||||
</state>
|
|
||||||
</scxml>
|
</scxml>
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ skinparam ArrowThickness 1
|
|||||||
skinparam dpi 110
|
skinparam dpi 110
|
||||||
skinparam svgLinkTarget _self
|
skinparam svgLinkTarget _self
|
||||||
|
|
||||||
[*] --> click.kamil.examples.statemachine.simple.OrderStates.SUBMITTED
|
[*] --> OrderStates.SUBMITTED
|
||||||
|
|
||||||
state OrderStates.SUBMITTED <<choice>>
|
state OrderStates.SUBMITTED <<choice>>
|
||||||
state OrderStates.PAID <<choice>>
|
state OrderStates.PAID <<choice>>
|
||||||
@@ -41,7 +41,7 @@ OrderStates.PAID2 -[#1E90FF,bold]-> OrderStates.CANCELED <<external>>
|
|||||||
OrderStates.PAID3 -[#1E90FF,bold]-> OrderStates.CANCELED <<external>>
|
OrderStates.PAID3 -[#1E90FF,bold]-> OrderStates.CANCELED <<external>>
|
||||||
OrderStates.PAID1 -[#1E90FF,bold]-> OrderStates.PAID1 <<external>> : OrderEvents.ABCD / λ, action2
|
OrderStates.PAID1 -[#1E90FF,bold]-> OrderStates.PAID1 <<external>> : OrderEvents.ABCD / λ, action2
|
||||||
|
|
||||||
click.kamil.examples.statemachine.simple.OrderStates.CANCELED --> [*]
|
OrderStates.CANCELED --> [*]
|
||||||
click.kamil.examples.statemachine.simple.OrderStates.FULFILLED --> [*]
|
OrderStates.FULFILLED --> [*]
|
||||||
@enduml
|
@enduml
|
||||||
|
|
||||||
|
|||||||
@@ -42,11 +42,5 @@
|
|||||||
</state>
|
</state>
|
||||||
<state id="HAPPEN">
|
<state id="HAPPEN">
|
||||||
</state>
|
</state>
|
||||||
<state id="SUBMITTED">
|
|
||||||
</state>
|
|
||||||
<state id="CANCELED">
|
|
||||||
</state>
|
|
||||||
<state id="FULFILLED">
|
|
||||||
</state>
|
|
||||||
</scxml>
|
</scxml>
|
||||||
|
|
||||||
|
|||||||
@@ -21,14 +21,14 @@ skinparam ArrowThickness 1
|
|||||||
skinparam dpi 110
|
skinparam dpi 110
|
||||||
skinparam svgLinkTarget _self
|
skinparam svgLinkTarget _self
|
||||||
|
|
||||||
[*] --> click.kamil.examples.statemachine.layered.document.DocumentState.DRAFT
|
[*] --> DocumentState.DRAFT
|
||||||
|
|
||||||
|
|
||||||
DocumentState.DRAFT -[#1E90FF,bold]-> DocumentState.SUBMITTED <<external>> : DocumentTransitionEvent.SUBMIT
|
DocumentState.DRAFT -[#1E90FF,bold]-> DocumentState.SUBMITTED <<external>> : DocumentTransitionEvent.SUBMIT
|
||||||
DocumentState.SUBMITTED -[#1E90FF,bold]-> DocumentState.APPROVED <<external>> : DocumentTransitionEvent.APPROVE
|
DocumentState.SUBMITTED -[#1E90FF,bold]-> DocumentState.APPROVED <<external>> : DocumentTransitionEvent.APPROVE
|
||||||
DocumentState.SUBMITTED -[#1E90FF,bold]-> DocumentState.REJECTED <<external>> : DocumentTransitionEvent.REJECT
|
DocumentState.SUBMITTED -[#1E90FF,bold]-> DocumentState.REJECTED <<external>> : DocumentTransitionEvent.REJECT
|
||||||
|
|
||||||
click.kamil.examples.statemachine.layered.document.DocumentState.REJECTED --> [*]
|
DocumentState.REJECTED --> [*]
|
||||||
click.kamil.examples.statemachine.layered.document.DocumentState.APPROVED --> [*]
|
DocumentState.APPROVED --> [*]
|
||||||
@enduml
|
@enduml
|
||||||
|
|
||||||
|
|||||||
@@ -11,11 +11,5 @@
|
|||||||
</state>
|
</state>
|
||||||
<state id="REJECTED">
|
<state id="REJECTED">
|
||||||
</state>
|
</state>
|
||||||
<state id="DRAFT">
|
|
||||||
</state>
|
|
||||||
<state id="REJECTED">
|
|
||||||
</state>
|
|
||||||
<state id="APPROVED">
|
|
||||||
</state>
|
|
||||||
</scxml>
|
</scxml>
|
||||||
|
|
||||||
|
|||||||
@@ -21,14 +21,14 @@ skinparam ArrowThickness 1
|
|||||||
skinparam dpi 110
|
skinparam dpi 110
|
||||||
skinparam svgLinkTarget _self
|
skinparam svgLinkTarget _self
|
||||||
|
|
||||||
[*] --> click.kamil.examples.statemachine.layered.order.OrderState.NEW
|
[*] --> OrderState.NEW
|
||||||
|
|
||||||
|
|
||||||
OrderState.NEW -[#1E90FF,bold]-> OrderState.PAID <<external>> : OrderTransitionEvent.PAY
|
OrderState.NEW -[#1E90FF,bold]-> OrderState.PAID <<external>> : OrderTransitionEvent.PAY
|
||||||
OrderState.PAID -[#1E90FF,bold]-> OrderState.SHIPPED <<external>> : OrderTransitionEvent.SHIP
|
OrderState.PAID -[#1E90FF,bold]-> OrderState.SHIPPED <<external>> : OrderTransitionEvent.SHIP
|
||||||
OrderState.PAID -[#1E90FF,bold]-> OrderState.CANCELLED <<external>> : OrderTransitionEvent.CANCEL
|
OrderState.PAID -[#1E90FF,bold]-> OrderState.CANCELLED <<external>> : OrderTransitionEvent.CANCEL
|
||||||
|
|
||||||
click.kamil.examples.statemachine.layered.order.OrderState.SHIPPED --> [*]
|
OrderState.SHIPPED --> [*]
|
||||||
click.kamil.examples.statemachine.layered.order.OrderState.CANCELLED --> [*]
|
OrderState.CANCELLED --> [*]
|
||||||
@enduml
|
@enduml
|
||||||
|
|
||||||
|
|||||||
@@ -11,11 +11,5 @@
|
|||||||
</state>
|
</state>
|
||||||
<state id="CANCELLED">
|
<state id="CANCELLED">
|
||||||
</state>
|
</state>
|
||||||
<state id="NEW">
|
|
||||||
</state>
|
|
||||||
<state id="SHIPPED">
|
|
||||||
</state>
|
|
||||||
<state id="CANCELLED">
|
|
||||||
</state>
|
|
||||||
</scxml>
|
</scxml>
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ skinparam ArrowThickness 1
|
|||||||
skinparam dpi 110
|
skinparam dpi 110
|
||||||
skinparam svgLinkTarget _self
|
skinparam svgLinkTarget _self
|
||||||
|
|
||||||
[*] --> click.kamil.domain.OrderState.NEW
|
[*] --> OrderState.NEW
|
||||||
|
|
||||||
|
|
||||||
OrderState.NEW -[#1E90FF,bold]-> OrderState.PROCESSING <<external>> : OrderEvent.PROCESS
|
OrderState.NEW -[#1E90FF,bold]-> OrderState.PROCESSING <<external>> : OrderEvent.PROCESS
|
||||||
@@ -29,7 +29,7 @@ OrderState.PROCESSING -[#1E90FF,bold]-> OrderState.COMPLETED <<external>> : Orde
|
|||||||
OrderState.NEW -[#1E90FF,bold]-> OrderState.CANCELLED <<external>> : OrderEvent.CANCEL
|
OrderState.NEW -[#1E90FF,bold]-> OrderState.CANCELLED <<external>> : OrderEvent.CANCEL
|
||||||
OrderState.PROCESSING -[#1E90FF,bold]-> OrderState.CANCELLED <<external>> : OrderEvent.CANCEL
|
OrderState.PROCESSING -[#1E90FF,bold]-> OrderState.CANCELLED <<external>> : OrderEvent.CANCEL
|
||||||
|
|
||||||
click.kamil.domain.OrderState.COMPLETED --> [*]
|
OrderState.COMPLETED --> [*]
|
||||||
click.kamil.domain.OrderState.CANCELLED --> [*]
|
OrderState.CANCELLED --> [*]
|
||||||
@enduml
|
@enduml
|
||||||
|
|
||||||
|
|||||||
@@ -12,11 +12,5 @@
|
|||||||
</state>
|
</state>
|
||||||
<state id="CANCELLED">
|
<state id="CANCELLED">
|
||||||
</state>
|
</state>
|
||||||
<state id="NEW">
|
|
||||||
</state>
|
|
||||||
<state id="COMPLETED">
|
|
||||||
</state>
|
|
||||||
<state id="CANCELLED">
|
|
||||||
</state>
|
|
||||||
</scxml>
|
</scxml>
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ skinparam ArrowThickness 1
|
|||||||
skinparam dpi 110
|
skinparam dpi 110
|
||||||
skinparam svgLinkTarget _self
|
skinparam svgLinkTarget _self
|
||||||
|
|
||||||
[*] --> click.kamil.examples.statemachine.inheritanceextrafunctionsstate2.States.STATE1
|
[*] --> States.STATE1
|
||||||
|
|
||||||
state States.STATEY <<choice>>
|
state States.STATEY <<choice>>
|
||||||
state States.STATE16 <<choice>>
|
state States.STATE16 <<choice>>
|
||||||
@@ -89,6 +89,6 @@ States.STATE_EXTRA_1 -[#1E90FF,bold]-> States.STATE_EXTRA_3 <<external>> : Event
|
|||||||
States.STATE_EXTRA_1 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL_2
|
States.STATE_EXTRA_1 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL_2
|
||||||
States.STATE_EXTRA_2 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL_2
|
States.STATE_EXTRA_2 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL_2
|
||||||
|
|
||||||
click.kamil.examples.statemachine.inheritanceextrafunctionsstate2.States.STATEZ --> [*]
|
States.STATEZ --> [*]
|
||||||
@enduml
|
@enduml
|
||||||
|
|
||||||
|
|||||||
@@ -160,9 +160,5 @@
|
|||||||
<state id="STATE_EXTRA_2">
|
<state id="STATE_EXTRA_2">
|
||||||
<transition target="CANCEL" event="Events.EVENT_CANCEL_2"/>
|
<transition target="CANCEL" event="Events.EVENT_CANCEL_2"/>
|
||||||
</state>
|
</state>
|
||||||
<state id="STATE1">
|
|
||||||
</state>
|
|
||||||
<state id="STATEZ">
|
|
||||||
</state>
|
|
||||||
</scxml>
|
</scxml>
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ skinparam ArrowThickness 1
|
|||||||
skinparam dpi 110
|
skinparam dpi 110
|
||||||
skinparam svgLinkTarget _self
|
skinparam svgLinkTarget _self
|
||||||
|
|
||||||
[*] --> click.kamil.examples.statemachine.inheritanceextrafunctionsstate.States.STATE1
|
[*] --> States.STATE1
|
||||||
|
|
||||||
state States.STATEY <<choice>>
|
state States.STATEY <<choice>>
|
||||||
state States.STATE16 <<choice>>
|
state States.STATE16 <<choice>>
|
||||||
@@ -85,6 +85,6 @@ States.STATE5 -[#1E90FF,bold]-> States.STATE_EXTRA_1 <<external>> : Events.EVENT
|
|||||||
States.STATE_EXTRA_1 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL
|
States.STATE_EXTRA_1 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL
|
||||||
States.STATE_EXTRA_1 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL_2
|
States.STATE_EXTRA_1 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL_2
|
||||||
|
|
||||||
click.kamil.examples.statemachine.inheritanceextrafunctionsstate.States.STATEZ --> [*]
|
States.STATEZ --> [*]
|
||||||
@enduml
|
@enduml
|
||||||
|
|
||||||
|
|||||||
@@ -149,9 +149,5 @@
|
|||||||
<transition target="CANCEL" event="Events.EVENT_CANCEL"/>
|
<transition target="CANCEL" event="Events.EVENT_CANCEL"/>
|
||||||
<transition target="CANCEL" event="Events.EVENT_CANCEL_2"/>
|
<transition target="CANCEL" event="Events.EVENT_CANCEL_2"/>
|
||||||
</state>
|
</state>
|
||||||
<state id="STATE1">
|
|
||||||
</state>
|
|
||||||
<state id="STATEZ">
|
|
||||||
</state>
|
|
||||||
</scxml>
|
</scxml>
|
||||||
|
|
||||||
|
|||||||
@@ -151,13 +151,21 @@
|
|||||||
"stateMachineId" : null,
|
"stateMachineId" : null,
|
||||||
"sourceState" : null,
|
"sourceState" : null,
|
||||||
"lineNumber" : 93,
|
"lineNumber" : 93,
|
||||||
"polymorphicEvents" : [ "<SYMBOLIC: OrderEvent.*>", "<SYMBOLIC: DocumentEvent.*>", "<SYMBOLIC: UserEvent.*>" ],
|
"polymorphicEvents" : [ "click.kamil.enterprise.machines.user.UserEvent.REGISTER", "click.kamil.enterprise.machines.user.UserEvent.VERIFY", "click.kamil.enterprise.machines.user.UserEvent.SUSPEND" ],
|
||||||
"external" : true,
|
"external" : true,
|
||||||
"constraint" : "!(\"ORDER\".equalsIgnoreCase(machineType)) && !(\"DOCUMENT\".equalsIgnoreCase(machineType)) && \"USER\".equalsIgnoreCase(machineType)",
|
"constraint" : "!(\"ORDER\".equalsIgnoreCase(machineType)) && !(\"DOCUMENT\".equalsIgnoreCase(machineType)) && \"USER\".equalsIgnoreCase(machineType)",
|
||||||
"ambiguous" : false
|
"ambiguous" : false
|
||||||
},
|
},
|
||||||
"contextMachineId" : null,
|
"contextMachineId" : null,
|
||||||
"matchedTransitions" : null
|
"matchedTransitions" : [ {
|
||||||
|
"sourceState" : "click.kamil.enterprise.machines.user.UserState.GUEST",
|
||||||
|
"targetState" : "click.kamil.enterprise.machines.user.UserState.REGISTERED",
|
||||||
|
"event" : "click.kamil.enterprise.machines.user.UserEvent.REGISTER"
|
||||||
|
}, {
|
||||||
|
"sourceState" : "click.kamil.enterprise.machines.user.UserState.REGISTERED",
|
||||||
|
"targetState" : "click.kamil.enterprise.machines.user.UserState.VERIFIED",
|
||||||
|
"event" : "click.kamil.enterprise.machines.user.UserEvent.VERIFY"
|
||||||
|
} ]
|
||||||
} ],
|
} ],
|
||||||
"properties" : {
|
"properties" : {
|
||||||
"default" : { }
|
"default" : { }
|
||||||
|
|||||||
@@ -21,12 +21,12 @@ skinparam ArrowThickness 1
|
|||||||
skinparam dpi 110
|
skinparam dpi 110
|
||||||
skinparam svgLinkTarget _self
|
skinparam svgLinkTarget _self
|
||||||
|
|
||||||
[*] --> click.kamil.enterprise.machines.user.UserState.GUEST
|
[*] --> UserState.GUEST
|
||||||
|
|
||||||
|
|
||||||
UserState.GUEST -[#1E90FF,bold]-> UserState.REGISTERED <<external>> : UserEvent.REGISTER
|
UserState.GUEST -[#1E90FF,bold]-> UserState.REGISTERED <<external>> : UserEvent.REGISTER
|
||||||
UserState.REGISTERED -[#1E90FF,bold]-> UserState.VERIFIED <<external>> : UserEvent.VERIFY
|
UserState.REGISTERED -[#1E90FF,bold]-> UserState.VERIFIED <<external>> : UserEvent.VERIFY
|
||||||
|
|
||||||
click.kamil.enterprise.machines.user.UserState.VERIFIED --> [*]
|
UserState.VERIFIED --> [*]
|
||||||
@enduml
|
@enduml
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,5 @@
|
|||||||
</state>
|
</state>
|
||||||
<state id="VERIFIED">
|
<state id="VERIFIED">
|
||||||
</state>
|
</state>
|
||||||
<state id="GUEST">
|
|
||||||
</state>
|
|
||||||
<state id="VERIFIED">
|
|
||||||
</state>
|
|
||||||
</scxml>
|
</scxml>
|
||||||
|
|
||||||
|
|||||||
@@ -88,6 +88,31 @@ class HtmlTransitionLinkConsistencyTest {
|
|||||||
assertThat(html).contains("com.example.order.OrderEvent.PAY");
|
assertThat(html).contains("com.example.order.OrderEvent.PAY");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void htmlShouldNotRenderDuplicateStateNodesForPackageCanonicalStartStates(@TempDir Path tempDir) throws Exception {
|
||||||
|
writeSampleProject(tempDir);
|
||||||
|
|
||||||
|
AnalysisResult finalizedShape = AnalysisResult.builder()
|
||||||
|
.name("com.example.config.OrderStateMachineConfiguration")
|
||||||
|
.stateTypeFqn("com.example.order.OrderState")
|
||||||
|
.eventTypeFqn("com.example.order.OrderEvent")
|
||||||
|
.transitions(List.of(shortTransition(
|
||||||
|
"com.example.order.OrderEvent.PAY",
|
||||||
|
"com.example.order.OrderState.NEW",
|
||||||
|
"com.example.order.OrderState.PAID")))
|
||||||
|
.startStates(Set.of("com.example.order.OrderState.NEW"))
|
||||||
|
.endStates(Set.of("com.example.order.OrderState.PAID"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
String html = new HtmlExporter().export(finalizedShape, ExportOptions.builder().build());
|
||||||
|
String svg = extractSvg(html);
|
||||||
|
|
||||||
|
assertThat(svg).contains("OrderState.NEW");
|
||||||
|
assertThat(svg).contains("OrderState.PAID");
|
||||||
|
assertThat(svg).doesNotContain("com.example.order.OrderState.NEW");
|
||||||
|
assertThat(svg).doesNotContain("com.example.order.OrderState.PAID");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void enterpriseAstExportHtmlShouldContainTransitionLinkKeys(@TempDir Path tempDir) throws Exception {
|
void enterpriseAstExportHtmlShouldContainTransitionLinkKeys(@TempDir Path tempDir) throws Exception {
|
||||||
Path enterprise = findProjectRoot().resolve("state_machines/state_machine_enterprise");
|
Path enterprise = findProjectRoot().resolve("state_machines/state_machine_enterprise");
|
||||||
@@ -169,6 +194,15 @@ class HtmlTransitionLinkConsistencyTest {
|
|||||||
""");
|
""");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String extractSvg(String html) {
|
||||||
|
int start = html.indexOf("<svg");
|
||||||
|
int end = html.indexOf("</svg>");
|
||||||
|
if (start < 0 || end < 0) {
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
return html.substring(start, end + "</svg>".length());
|
||||||
|
}
|
||||||
|
|
||||||
private static Path findProjectRoot() {
|
private static Path findProjectRoot() {
|
||||||
Path current = Path.of(".").toAbsolutePath();
|
Path current = Path.of(".").toAbsolutePath();
|
||||||
while (current != null && !Files.exists(current.resolve("settings.gradle"))) {
|
while (current != null && !Files.exists(current.resolve("settings.gradle"))) {
|
||||||
|
|||||||
Reference in New Issue
Block a user