29 Commits

Author SHA1 Message Date
9f651e7dc9 heuristics classpath 2026-06-21 07:51:25 +02:00
e06429cb02 heuristic call graph 2026-06-21 07:33:01 +02:00
9cf2d063b3 matching egnine extraction 2026-06-21 07:20:38 +02:00
073df21395 heauristic bean resolution 2026-06-21 06:55:55 +02:00
f9e6247eb3 fix 6 2026-06-20 13:20:05 +02:00
76ac143370 more fixes 5 2026-06-20 13:11:54 +02:00
9ca8b2fe37 more fixes 4 2026-06-20 12:31:11 +02:00
56cdf96f2d more fixes 3 2026-06-20 12:21:11 +02:00
c37aa92c78 more fixes 2 2026-06-20 07:57:03 +02:00
3ac057618f more fixes 2026-06-20 07:21:19 +02:00
d59f4ac166 fix attempt 1 2026-06-20 07:04:44 +02:00
19547d3c5f better html tooltip styling 2026-06-20 06:16:12 +02:00
ab37eb5d40 call graph fix + tests 2026-06-20 05:51:42 +02:00
bf9208d529 better enricher attempt 2026-06-19 21:21:44 +02:00
7077214c81 better enricher 2026-06-19 20:56:14 +02:00
344e295106 enricher 2026-06-19 19:18:23 +02:00
d6b1571f18 test1 2026-06-19 17:45:57 +02:00
e617fb1754 update 1 test 2026-06-19 17:37:08 +02:00
06dc0ba641 update 1 2026-06-19 17:33:32 +02:00
ba412b905e moar 2026-06-19 17:25:01 +02:00
198c5d830e test 2026-06-19 17:23:27 +02:00
a383de5a5f fix html attempt 2 2026-06-19 17:19:26 +02:00
ef9ebe3512 fix html 2026-06-19 17:11:34 +02:00
b480dc83ef transition enricher rabbit 2026-06-19 09:22:27 +02:00
0a23daae05 transition enricher 2026-06-19 09:06:37 +02:00
e894566112 transition enricher 2026-06-19 07:44:54 +02:00
8d4ba0697e transition enricher 2026-06-18 23:02:39 +02:00
d93d36e8ad html exporter fixed 2026-06-18 22:06:57 +02:00
5894303510 fqn names of events and states 2026-06-18 22:02:42 +02:00
134 changed files with 8003 additions and 950 deletions

22
InspectGolden.java Normal file
View File

@@ -0,0 +1,22 @@
import java.nio.file.*;
import java.util.*;
import click.kamil.springstatemachineexporter.service.ExportService;
import click.kamil.springstatemachineexporter.exporter.*;
import click.kamil.springstatemachineexporter.analysis.service.EnrichmentService;
public class InspectGolden {
public static void main(String[] args) throws Exception {
Path inputDir = Path.of("state_machines/complex_state_machine");
Path tempDir = Files.createTempDirectory("golden-update-");
List<StateMachineExporter> exporters = List.of(new PlantUml(), new Dot(), new Scxml(), new JsonExporter());
EnrichmentService enrichmentService = new EnrichmentService(Collections.emptyList());
ExportService exportService = new ExportService(exporters, enrichmentService);
exportService.runExporter(inputDir, tempDir, null, true);
try (var stream = Files.list(tempDir)) {
stream.forEach(System.out::println);
}
}
}

View File

@@ -1,6 +1,5 @@
1.extract from all possible formats (ask chatgpt) 1.extract from all possible formats (ask chatgpt)
[DONE] json
[TODO] xml, maybe yaml [TODO] xml, maybe yaml
2. [PLAN] Support external dependencies (JARs/compiled classes): 2. [PLAN] Support external dependencies (JARs/compiled classes):

4
run_golden.gradle Normal file
View File

@@ -0,0 +1,4 @@
task runGolden(type: JavaExec) {
mainClass = "click.kamil.springstatemachineexporter.GoldenUpdater"
classpath = sourceSets.test.runtimeClasspath
}

View File

@@ -36,6 +36,7 @@ dependencies {
implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1' implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.17.1' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.17.1'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.1' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.17.1'
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.17.1'
compileOnly 'org.projectlombok:lombok:1.18.46' compileOnly 'org.projectlombok:lombok:1.18.46'
annotationProcessor 'org.projectlombok:lombok:1.18.46' annotationProcessor 'org.projectlombok:lombok:1.18.46'
@@ -53,3 +54,14 @@ tasks.named('test') {
useJUnitPlatform() useJUnitPlatform()
systemProperty "updateGolden", System.getProperty("updateGolden") systemProperty "updateGolden", System.getProperty("updateGolden")
} }
task runGolden(type: JavaExec) {
mainClass = "click.kamil.springstatemachineexporter.GoldenUpdater"
classpath = sourceSets.test.runtimeClasspath
}
task runGoldenFixed(type: JavaExec) {
mainClass = "click.kamil.springstatemachineexporter.GoldenUpdater"
classpath = sourceSets.test.runtimeClasspath
workingDir = rootProject.projectDir
}

View File

@@ -14,6 +14,14 @@ import java.util.List;
public class Main { public class Main {
public static void main(String[] args) { public static void main(String[] args) {
// Enable diagnostic mode early before SLF4J initializes
for (String arg : args) {
if ("--debug".equals(arg)) {
System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "debug");
break;
}
}
// Manual DI / Wiring // Manual DI / Wiring
var exporters = List.of(new PlantUml(), new Dot(), new Scxml(), new JsonExporter()); var exporters = List.of(new PlantUml(), new Dot(), new Scxml(), new JsonExporter());
var exportService = new ExportService(exporters); var exportService = new ExportService(exporters);

View File

@@ -0,0 +1,146 @@
package click.kamil.springstatemachineexporter.analysis.enricher;
import click.kamil.springstatemachineexporter.analysis.model.AnalysisResult;
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.analysis.model.CodebaseMetadata;
import click.kamil.springstatemachineexporter.analysis.model.MatchedTransition;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.analysis.service.CodebaseIntelligenceProvider;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import click.kamil.springstatemachineexporter.model.State;
import click.kamil.springstatemachineexporter.model.Transition;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import click.kamil.springstatemachineexporter.analysis.enricher.routing.HeuristicBeanResolutionEngine;
import click.kamil.springstatemachineexporter.analysis.enricher.routing.BeanResolutionEngine;
import click.kamil.springstatemachineexporter.analysis.enricher.matching.EventMatchingEngine;
import click.kamil.springstatemachineexporter.analysis.enricher.matching.HeuristicEventMatchingEngine;
public class TransitionLinkerEnricher implements AnalysisEnricher {
private final BeanResolutionEngine routingEngine = new HeuristicBeanResolutionEngine();
private final EventMatchingEngine matchingEngine = new HeuristicEventMatchingEngine();
@Override
public void enrich(AnalysisResult result, CodebaseContext context, CodebaseIntelligenceProvider intelligence) {
if (result.getMetadata() == null || result.getMetadata().getCallChains() == null) {
return;
}
List<CallChain> updatedChains = new ArrayList<>();
List<Transition> stateMachineTransitions = result.getTransitions();
for (CallChain chain : result.getMetadata().getCallChains()) {
TriggerPoint tp = chain.getTriggerPoint();
if (tp == null || tp.getEvent() == null) {
updatedChains.add(chain);
continue;
}
String triggerEvent = simplify(tp.getEvent());
String triggerSource = tp.getSourceState() != null ? simplify(tp.getSourceState()) : null;
List<MatchedTransition> matched = new ArrayList<>();
for (Transition t : stateMachineTransitions) {
if (t.getEvent() != null && matchingEngine.matches(t.getEvent(), tp)) {
String smEventRaw = t.getEvent().fullIdentifier() != null ? t.getEvent().fullIdentifier() : t.getEvent().rawName();
// Event matches
for (State smSourceState : t.getSourceStates()) {
String smSourceRaw = smSourceState.fullIdentifier() != null ? smSourceState.fullIdentifier() : smSourceState.rawName();
String smSource = simplify(smSourceRaw);
if (triggerSource == null || triggerSource.equals(smSource)) {
if (t.getTargetStates() == null || t.getTargetStates().isEmpty()) {
MatchedTransition mt = MatchedTransition.builder()
.sourceState(smSourceRaw)
.targetState(smSourceRaw)
.event(smEventRaw)
.build();
if (isRoutedToCorrectMachine(chain, result.getName())) {
matched.add(mt);
}
} else {
for (State smTargetState : t.getTargetStates()) {
String targetRaw = smTargetState.fullIdentifier() != null ? smTargetState.fullIdentifier() : smTargetState.rawName();
MatchedTransition mt = MatchedTransition.builder()
.sourceState(smSourceRaw)
.targetState(targetRaw)
.event(smEventRaw)
.build();
if (isRoutedToCorrectMachine(chain, result.getName())) {
matched.add(mt);
}
}
}
}
}
}
}
if (!matched.isEmpty()) {
CallChain newChain = chain.toBuilder().matchedTransitions(matched).build();
updatedChains.add(newChain);
} else {
updatedChains.add(chain);
}
}
// Update the metadata with the new call chains
CodebaseMetadata updatedMetadata = CodebaseMetadata.builder()
.triggers(result.getMetadata().getTriggers())
.entryPoints(result.getMetadata().getEntryPoints())
.callChains(updatedChains)
.properties(result.getMetadata().getProperties())
.build();
result.setMetadata(updatedMetadata);
}
private boolean isRoutedToCorrectMachine(CallChain chain, String currentMachineName) {
return routingEngine.isRoutedToCorrectMachine(chain, currentMachineName);
}
private String simplify(String name) {
if (name == null) return null;
// Strip common suffixes
String simplified = name.replaceAll("(?i)(.*)(Event|Action|Transition|Command)(s)?$", "$1");
if (simplified.isEmpty()) {
simplified = name;
}
// Simplify full identifiers to just the last part (enum name)
simplified = simplified.replaceAll("^.*\\.([A-Z0-9_]+)$", "$1");
// For remaining full caps with underscores (like EVENT_X), keep as is or try to simplify
return simplified;
}
private boolean isGuardedContains(String smEvent, String triggerEvent) {
// Only fire if it looks like a simple identifier (no spaces, no parens)
// Prevents unresolved AST nodes (e.g. "event.getPayload()") from accidentally matching "Event"
if (smEvent.contains(" ") || smEvent.contains("(") || smEvent.contains(")") ||
triggerEvent.contains(" ") || triggerEvent.contains("(") || triggerEvent.contains(")")) {
return false;
}
String shorter = smEvent.length() < triggerEvent.length() ? smEvent : triggerEvent;
String longer = smEvent.length() >= triggerEvent.length() ? smEvent : triggerEvent;
shorter = shorter.toLowerCase();
longer = longer.toLowerCase();
if (longer.contains(shorter)) {
// Guard: If the matching part is very short (< 4 chars), it must be bounded by word separators
// to avoid matching completely unrelated words (e.g., "PAY" matching "PAYMENT" or "E" matching "UPDATE")
if (shorter.length() < 4) {
return longer.matches(".*(^|_|-)" + shorter + "(_|-|$).*");
}
// For longer strings, a straight contains is usually safe enough
return true;
}
return false;
}
}

View File

@@ -0,0 +1,11 @@
package click.kamil.springstatemachineexporter.analysis.enricher.matching;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.model.Event;
public interface EventMatchingEngine {
/**
* Determines whether the given trigger point is a match for the state machine event.
*/
boolean matches(Event stateMachineEvent, TriggerPoint triggerPoint);
}

View File

@@ -0,0 +1,53 @@
package click.kamil.springstatemachineexporter.analysis.enricher.matching;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.model.Event;
import java.util.List;
public class HeuristicEventMatchingEngine implements EventMatchingEngine {
@Override
public boolean matches(Event stateMachineEvent, TriggerPoint triggerPoint) {
if (stateMachineEvent == null || triggerPoint == null || triggerPoint.getEvent() == null) {
return false;
}
String triggerEvent = simplify(triggerPoint.getEvent());
String smEventRaw = stateMachineEvent.fullIdentifier() != null ? stateMachineEvent.fullIdentifier() : stateMachineEvent.rawName();
String smEvent = simplify(smEventRaw);
boolean isWildcard = isWildcardVariable(triggerEvent);
List<String> polyEvents = triggerPoint.getPolymorphicEvents() != null ? triggerPoint.getPolymorphicEvents() : java.util.Collections.emptyList();
boolean hasPolyMatch = false;
for (String pe : polyEvents) {
String simplePe = pe;
if (pe.contains(".")) {
simplePe = pe.substring(pe.lastIndexOf('.') + 1);
}
if (simplePe.equals(smEventRaw) || simplePe.equals(smEvent)) {
hasPolyMatch = true;
break;
}
}
return hasPolyMatch || smEvent.equals(triggerEvent) || (polyEvents.isEmpty() && isWildcard);
}
private boolean isWildcardVariable(String eventStr) {
return eventStr.equals("event") || eventStr.equals("e") ||
eventStr.equals("msg") || eventStr.equals("message") ||
eventStr.equals("payload");
}
private String simplify(String name) {
if (name == null) return null;
String simplified = name.replaceAll("(?i)(.*)(Event|Action|Transition|Command)(s)?$", "$1");
if (simplified.isEmpty()) {
simplified = name;
}
simplified = simplified.replaceAll("^.*\\.([A-Z0-9_]+)$", "$1");
return simplified;
}
}

View File

@@ -0,0 +1,7 @@
package click.kamil.springstatemachineexporter.analysis.enricher.routing;
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
public interface BeanResolutionEngine {
boolean isRoutedToCorrectMachine(CallChain chain, String currentMachineName);
}

View File

@@ -0,0 +1,188 @@
package click.kamil.springstatemachineexporter.analysis.enricher.routing;
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import java.util.HashSet;
import java.util.Set;
public class HeuristicBeanResolutionEngine implements BeanResolutionEngine {
@Override
public boolean isRoutedToCorrectMachine(CallChain chain, String currentMachineName) {
String targetVar = chain.getContextMachineId();
if (targetVar == null && chain.getTriggerPoint() != null) {
targetVar = chain.getTriggerPoint().getStateMachineId();
}
String simplifiedMachineName = currentMachineName != null ? currentMachineName.substring(currentMachineName.lastIndexOf('.') + 1).toLowerCase() : "";
if (targetVar != null && !targetVar.isEmpty()) {
targetVar = targetVar.toLowerCase();
if (targetVar.endsWith("statemachine")) {
String prefix = targetVar.substring(0, targetVar.length() - "statemachine".length());
if (!prefix.isEmpty()) {
if (simplifiedMachineName.contains(prefix)) {
return true; // Explicit positive match
} else {
return false; // Explicit negative match
}
}
} else if (simplifiedMachineName.contains(targetVar)) {
return true;
}
}
if (chain.getMethodChain() != null && !chain.getMethodChain().isEmpty() && currentMachineName != null) {
String smPackage = getPackageName(currentMachineName);
String smSimple = getSimpleClassName(currentMachineName);
String smPrefix = getFirstCamelCaseWord(smSimple);
boolean hasPositiveMatch = false;
boolean hasStrongMismatch = false;
for (String method : chain.getMethodChain()) {
String chainClass = getClassNameOnly(method);
String chainPackage = getPackageName(chainClass);
String chainSimple = getSimpleClassName(chainClass);
String chainPrefix = getFirstCamelCaseWord(chainSimple);
if (smPrefix != null && chainPrefix != null && smPrefix.equals(chainPrefix)) {
hasPositiveMatch = true;
}
if (isDomainMatch(smPackage, chainPackage, smPrefix, chainPrefix)) {
hasPositiveMatch = true;
}
if (isDomainMismatch(smPackage, chainPackage, smPrefix, chainPrefix)) {
hasStrongMismatch = true;
}
}
if (hasPositiveMatch) {
return true;
}
if (hasStrongMismatch) {
return false;
}
}
return true;
}
private boolean isDomainMatch(String smPackage, String chainPackage, String smPrefix, String chainPrefix) {
if (smPackage == null || chainPackage == null || smPackage.isEmpty() || chainPackage.isEmpty()) return false;
if (smPackage.startsWith(chainPackage) || chainPackage.startsWith(smPackage)) {
return true;
}
// Find deepest common package root
String[] p1 = smPackage.split("\\.");
String[] p2 = chainPackage.split("\\.");
int matchIdx = -1;
for (int i = 0; i < Math.min(p1.length, p2.length); i++) {
if (p1[i].equals(p2[i])) {
matchIdx = i;
} else {
break;
}
}
// If the state machine's prefix is part of the shared common package,
// then the state machine represents the parent domain of the caller.
if (smPrefix != null && matchIdx >= 0) {
String lowerPrefix = smPrefix.toLowerCase();
for (int i = 0; i <= matchIdx; i++) {
if (p1[i].toLowerCase().contains(lowerPrefix) || lowerPrefix.contains(p1[i].toLowerCase())) {
return true;
}
}
}
return false;
}
private boolean isDomainMismatch(String smPackage, String chainPackage, String smPrefix, String chainPrefix) {
if (smPackage == null || chainPackage == null || smPackage.isEmpty() || chainPackage.isEmpty()) return false;
String[] p1 = smPackage.split("\\.");
String[] p2 = chainPackage.split("\\.");
int matchIdx = -1;
for (int i = 0; i < Math.min(p1.length, p2.length); i++) {
if (p1[i].equals(p2[i])) {
matchIdx = i;
} else {
break;
}
}
Set<String> smDivergentTerms = new HashSet<>();
if (smPrefix != null) smDivergentTerms.add(smPrefix.toLowerCase());
for (int i = matchIdx + 1; i < p1.length; i++) {
smDivergentTerms.add(p1[i].toLowerCase());
}
Set<String> chainDivergentTerms = new HashSet<>();
if (chainPrefix != null) chainDivergentTerms.add(chainPrefix.toLowerCase());
for (int i = matchIdx + 1; i < p2.length; i++) {
chainDivergentTerms.add(p2[i].toLowerCase());
}
if (!smDivergentTerms.isEmpty() && !chainDivergentTerms.isEmpty()) {
Set<String> intersection = new HashSet<>(smDivergentTerms);
intersection.retainAll(chainDivergentTerms);
return intersection.isEmpty();
}
return false;
}
private Set<String> extractDomainTerms(String pkg, String prefix) {
Set<String> terms = new HashSet<>();
if (pkg != null && !pkg.isEmpty()) {
String[] segments = pkg.split("\\.");
// Take segments from index 2 onwards if length > 2
int start = segments.length > 2 ? 2 : 0;
for (int i = start; i < segments.length; i++) {
terms.add(segments[i].toLowerCase());
}
}
if (prefix != null && !prefix.isEmpty()) {
terms.add(prefix.toLowerCase());
}
return terms;
}
private String getPackageName(String fqn) {
if (fqn == null || !fqn.contains(".")) return "";
return fqn.substring(0, fqn.lastIndexOf('.'));
}
private String getSimpleClassName(String fqn) {
if (fqn == null) return "";
if (!fqn.contains(".")) return fqn;
return fqn.substring(fqn.lastIndexOf('.') + 1);
}
private String getClassNameOnly(String methodFqn) {
if (methodFqn == null) return "";
String clean = methodFqn;
if (clean.contains("(")) {
clean = clean.substring(0, clean.indexOf('('));
}
if (clean.contains(".")) {
clean = clean.substring(0, clean.lastIndexOf('.'));
}
return clean;
}
private String getFirstCamelCaseWord(String simpleName) {
if (simpleName == null || simpleName.isEmpty()) return null;
String[] words = simpleName.split("(?<!^)(?=[A-Z])");
if (words.length > 0) {
return words[0];
}
return null;
}
}

View File

@@ -60,7 +60,7 @@ public class AnalysisResult {
if (transitions != null) { if (transitions != null) {
for (var t : transitions) { for (var t : transitions) {
if (t.getEvent() != null) { if (t.getEvent() != null) {
t.setEvent(resolver.resolveValue(t.getEvent(), properties)); t.setEvent(click.kamil.springstatemachineexporter.model.Event.of(t.getEvent().rawName(), resolver.resolveValue(t.getEvent().fullIdentifier(), properties)));
} }
// Resolve states within transitions // Resolve states within transitions
t.setSourceStates(t.getSourceStates().stream() t.setSourceStates(t.getSourceStates().stream()

View File

@@ -8,11 +8,13 @@ import lombok.extern.jackson.Jacksonized;
import java.util.List; import java.util.List;
@Data @Data
@Builder @Builder(toBuilder = true)
@Jacksonized @Jacksonized
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public class CallChain { public class CallChain {
private final EntryPoint entryPoint; private final EntryPoint entryPoint;
private final List<String> methodChain; // e.g., ["Controller.submit", "Service.process", "Service.send"] private final List<String> methodChain; // e.g., ["Controller.submit", "Service.process", "Service.send"]
private final TriggerPoint triggerPoint; private final TriggerPoint triggerPoint;
private final String contextMachineId;
private final List<MatchedTransition> matchedTransitions;
} }

View File

@@ -0,0 +1,11 @@
package click.kamil.springstatemachineexporter.analysis.model;
import lombok.Data;
import java.util.List;
@Data
public class CallEdge {
private final String targetMethod;
private final List<String> arguments;
}

View File

@@ -13,5 +13,7 @@ import java.util.List;
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public class LibraryHint { public class LibraryHint {
private final String methodFqn; // e.g., "com.thirdparty.Workflow.send" private final String methodFqn; // e.g., "com.thirdparty.Workflow.send"
private final String event; // The event it triggers private final String event; // The event it triggers (static)
private final Integer eventArgumentIndex; // e.g., 0 to extract from the 0th argument
private final String eventArgumentMethod; // e.g., "getType" to extract from argument method call
} }

View File

@@ -0,0 +1,16 @@
package click.kamil.springstatemachineexporter.analysis.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Builder;
import lombok.Data;
import lombok.extern.jackson.Jacksonized;
@Data
@Builder
@Jacksonized
@JsonIgnoreProperties(ignoreUnknown = true)
public class MatchedTransition {
private final String sourceState;
private final String targetState;
private final String event;
}

View File

@@ -18,5 +18,7 @@ public class TriggerPoint {
private final String sourceFile; private final String sourceFile;
private final String sourceModule; private final String sourceModule;
private final String stateMachineId; // Optional: to link to a specific SM instance private final String stateMachineId; // Optional: to link to a specific SM instance
private final String sourceState; // Optional: if we can determine the expected current state
private final int lineNumber; private final int lineNumber;
private final java.util.List<String> polymorphicEvents; // NEW: stores concrete events resolved via deep polymorphism
} }

View File

@@ -48,12 +48,56 @@ public class ConstantResolver {
return resolveInfix(infix, context, visited); return resolveInfix(infix, context, visited);
} }
if (expr instanceof QualifiedName qn) { if (expr instanceof QualifiedName qn) {
return resolveManual(qn, context, visited); String val = resolveManual(qn, context, visited);
return val != null ? val : qn.toString();
} }
if (expr instanceof SimpleName sn) { if (expr instanceof SimpleName sn) {
return resolveManual(sn, context, visited); return resolveManual(sn, context, visited);
} }
if (expr instanceof MethodInvocation mi) {
if ((mi.getName().getIdentifier().equals("name") || mi.getName().getIdentifier().equals("toString")) && mi.arguments().isEmpty()) {
String val = resolveInternal(mi.getExpression(), context, visited);
if (val != null) {
// If it was resolved as a fully qualified enum string like "MyEvents.ORDER_PAID", strip it
if (val.contains(".")) {
return val.substring(val.lastIndexOf('.') + 1);
}
return val;
}
// Fallback for unresolved AST nodes
if (mi.getExpression() instanceof QualifiedName qn) {
return qn.getName().getIdentifier();
} else if (mi.getExpression() instanceof SimpleName sn) {
return sn.getIdentifier();
}
}
IMethodBinding mb = mi.resolveMethodBinding();
if (mb != null) {
ITypeBinding returnType = mb.getReturnType();
if (returnType != null) {
java.util.List<String> values = context.getEnumValues(returnType.getQualifiedName());
if (values != null && !values.isEmpty()) {
return "ENUM_SET:" + String.join(",", values);
}
}
} else {
TypeDeclaration td = findEnclosingType(mi);
if (td != null) {
MethodDeclaration md = context.findMethodDeclaration(td, mi.getName().getIdentifier(), true);
if (md != null && md.getReturnType2() != null) {
java.util.List<String> values = context.getEnumValues(md.getReturnType2().toString());
if (values != null && !values.isEmpty()) {
return "ENUM_SET:" + String.join(",", values);
}
}
}
}
}
return null; return null;
} }

View File

@@ -105,25 +105,32 @@ public class PropertyResolver {
} }
private Map<String, String> loadYaml(Path path) { private Map<String, String> loadYaml(Path path) {
// Placeholder for future YAML support Map<String, String> props = new HashMap<>();
log.warn("YAML parsing not fully implemented yet for {}", path); try {
return new HashMap<>(); com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper(new com.fasterxml.jackson.dataformat.yaml.YAMLFactory());
Map<String, Object> map = mapper.readValue(path.toFile(), new com.fasterxml.jackson.core.type.TypeReference<Map<String, Object>>() {});
flattenYaml("", map, props);
} catch (IOException e) {
log.warn("Failed to load YAML from {}", path);
}
return props;
}
@SuppressWarnings("unchecked")
private void flattenYaml(String prefix, Map<String, Object> map, Map<String, String> result) {
if (map == null) return;
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = prefix.isEmpty() ? entry.getKey() : prefix + "." + entry.getKey();
Object value = entry.getValue();
if (value instanceof Map) {
flattenYaml(key, (Map<String, Object>) value, result);
} else if (value != null) {
result.put(key, value.toString());
}
}
} }
public String resolveValue(String placeholder, Map<String, String> properties) { public String resolveValue(String placeholder, Map<String, String> properties) {
if (placeholder == null || !placeholder.contains("${")) return placeholder; return click.kamil.springstatemachineexporter.analysis.util.PlaceholderResolver.resolve(placeholder, properties);
// Very basic placeholder extraction: ${key} or ${key:default}
String content = placeholder.substring(placeholder.indexOf("${") + 2, placeholder.lastIndexOf("}"));
String key = content;
String defaultValue = null;
if (content.contains(":")) {
int colonIndex = content.indexOf(":");
key = content.substring(0, colonIndex);
defaultValue = content.substring(colonIndex + 1);
}
return properties.getOrDefault(key, defaultValue);
} }
} }

View File

@@ -1,253 +0,0 @@
package click.kamil.springstatemachineexporter.analysis.service;
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.jdt.core.dom.*;
import java.util.*;
@Slf4j
public class CallGraphBuilder {
private final CodebaseContext context;
private final ConstantResolver constantResolver;
private String currentMethodFqn;
private Map<String, List<CallEdge>> graph;
@lombok.Data
private static class CallEdge {
private final String targetMethod;
private final List<String> arguments;
}
public CallGraphBuilder(CodebaseContext context) {
this.context = context;
this.constantResolver = new ConstantResolver();
}
public List<CallChain> findChains(List<EntryPoint> entryPoints, List<TriggerPoint> triggers) {
Map<String, List<CallEdge>> callGraph = buildCallGraph();
List<CallChain> chains = new ArrayList<>();
for (EntryPoint ep : entryPoints) {
String startMethod = ep.getClassName() + "." + ep.getMethodName();
for (TriggerPoint tp : triggers) {
String targetMethod = tp.getClassName() + "." + tp.getMethodName();
List<String> path = findPath(startMethod, targetMethod, callGraph, new HashSet<>());
if (path != null) {
TriggerPoint resolvedTp = resolveTriggerPointParameters(tp, path, callGraph);
chains.add(CallChain.builder()
.entryPoint(ep)
.triggerPoint(resolvedTp)
.methodChain(path)
.build());
}
}
}
return chains;
}
private TriggerPoint resolveTriggerPointParameters(TriggerPoint tp, List<String> path, Map<String, List<CallEdge>> callGraph) {
if (path.size() < 2) return tp;
String event = tp.getEvent();
if (event == null || event.isEmpty() || event.contains("\"")) return tp;
TypeDeclaration td = context.getTypeDeclaration(tp.getClassName());
if (td != null) {
MethodDeclaration md = context.findMethodDeclaration(td, tp.getMethodName(), true);
if (md != null) {
int paramIndex = -1;
for (int i = 0; i < md.parameters().size(); i++) {
SingleVariableDeclaration svd = (SingleVariableDeclaration) md.parameters().get(i);
if (svd.getName().getIdentifier().equals(event)) {
paramIndex = i;
break;
}
}
if (paramIndex >= 0) {
String caller = path.get(path.size() - 2);
String target = path.get(path.size() - 1);
List<CallEdge> edges = callGraph.get(caller);
if (edges != null) {
for (CallEdge edge : edges) {
if (edge.getTargetMethod().equals(target) || isHeuristicMatch(edge.getTargetMethod(), target)) {
if (paramIndex < edge.getArguments().size()) {
String resolvedValue = edge.getArguments().get(paramIndex);
if (resolvedValue != null) {
return TriggerPoint.builder()
.event(resolvedValue)
.className(tp.getClassName())
.methodName(tp.getMethodName())
.sourceFile(tp.getSourceFile())
.lineNumber(tp.getLineNumber())
.build();
}
}
}
}
}
}
}
}
return tp;
}
private Map<String, List<CallEdge>> buildCallGraph() {
graph = new HashMap<>();
for (CompilationUnit cu : context.getCompilationUnits()) {
cu.accept(new ASTVisitor() {
@Override
public boolean visit(MethodDeclaration node) {
TypeDeclaration td = findEnclosingType(node);
if (td != null) {
currentMethodFqn = context.getFqn(td) + "." + node.getName().getIdentifier();
}
return super.visit(node);
}
@Override
public boolean visit(MethodInvocation node) {
if (currentMethodFqn != null) {
List<String> calledMethods = resolveCalledMethodsPolymorphic(node);
List<String> args = resolveArguments(node.arguments());
for (String calledMethod : calledMethods) {
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(calledMethod, args));
}
}
return super.visit(node);
}
@Override
public boolean visit(SuperMethodInvocation node) {
if (currentMethodFqn != null) {
String methodName = node.getName().getIdentifier();
TypeDeclaration td = findEnclosingType(node);
if (td != null) {
String superFqn = context.getSuperclassFqn(td);
if (superFqn != null) {
TypeDeclaration superTd = context.getTypeDeclaration(superFqn);
if (superTd != null) {
String calledMethod = resolveMethodInType(superTd, methodName);
if (calledMethod != null) {
List<String> args = resolveArguments(node.arguments());
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(calledMethod, args));
}
}
}
}
}
return super.visit(node);
}
});
}
return graph;
}
private List<String> resolveArguments(List<?> astArguments) {
List<String> args = new ArrayList<>();
for (Object argObj : astArguments) {
Expression expr = (Expression) argObj;
String val = constantResolver.resolve(expr, context);
if (val == null && expr instanceof SimpleName sn) {
val = sn.getIdentifier();
}
args.add(val);
}
return args;
}
private List<String> resolveCalledMethodsPolymorphic(MethodInvocation node) {
String baseCalled = resolveCalledMethod(node);
if (baseCalled == null) return Collections.emptyList();
List<String> allResolved = new ArrayList<>();
allResolved.add(baseCalled);
if (baseCalled.contains(".")) {
String className = baseCalled.substring(0, baseCalled.lastIndexOf('.'));
String methodName = baseCalled.substring(baseCalled.lastIndexOf('.') + 1);
List<String> impls = context.getImplementations(className);
for (String impl : impls) {
allResolved.add(impl + "." + methodName);
}
}
return allResolved;
}
private String resolveCalledMethod(MethodInvocation node) {
Expression receiver = node.getExpression();
String methodName = node.getName().getIdentifier();
if (receiver == null) {
TypeDeclaration td = findEnclosingType(node);
if (td != null) {
return resolveMethodInType(td, methodName);
}
return null;
}
ITypeBinding binding = receiver.resolveTypeBinding();
if (binding != null) {
return binding.getQualifiedName() + "." + methodName;
}
if (receiver instanceof SimpleName sn) {
String receiverName = sn.getIdentifier();
return receiverName + "." + methodName;
}
return null;
}
private String resolveMethodInType(TypeDeclaration td, String methodName) {
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
if (md != null) {
TypeDeclaration declaringTd = findEnclosingType(md);
return context.getFqn(declaringTd) + "." + methodName;
}
return null;
}
private List<String> findPath(String start, String target, Map<String, List<CallEdge>> graph, Set<String> visited) {
if (start.equals(target)) return new ArrayList<>(List.of(start));
if (!visited.add(start)) return null;
List<CallEdge> neighbors = graph.get(start);
if (neighbors != null) {
for (CallEdge edge : neighbors) {
String neighbor = edge.getTargetMethod();
if (neighbor.equals(target) || isHeuristicMatch(neighbor, target)) {
return new ArrayList<>(List.of(start, target));
}
List<String> path = findPath(neighbor, target, graph, visited);
if (path != null) {
path.add(0, start);
return path;
}
}
}
return null;
}
private boolean isHeuristicMatch(String neighbor, String target) {
if (target.endsWith("." + neighbor)) return true;
String simpleTarget = target.contains(".") ? target.substring(target.lastIndexOf('.') + 1) : target;
String simpleNeighbor = neighbor.contains(".") ? neighbor.substring(neighbor.lastIndexOf('.') + 1) : neighbor;
return simpleNeighbor.equals(simpleTarget);
}
private TypeDeclaration findEnclosingType(ASTNode node) {
ASTNode parent = node.getParent();
while (parent != null && !(parent instanceof TypeDeclaration)) {
parent = parent.getParent();
}
return (TypeDeclaration) parent;
}
}

View File

@@ -0,0 +1,11 @@
package click.kamil.springstatemachineexporter.analysis.service;
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import java.util.List;
public interface CallGraphEngine {
List<CallChain> findChains(List<EntryPoint> entryPoints, List<TriggerPoint> triggers);
}

View File

@@ -0,0 +1,162 @@
package click.kamil.springstatemachineexporter.analysis.service;
import lombok.extern.slf4j.Slf4j;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@Slf4j
public class DynamicClasspathResolver {
public List<String> resolveClasspath(Path projectRoot) {
log.info("Attempting dynamic classpath resolution for project at {}", projectRoot);
if (projectRoot == null || !Files.exists(projectRoot)) {
return Collections.emptyList();
}
if (Files.exists(projectRoot.resolve("pom.xml"))) {
return resolveMaven(projectRoot);
} else if (Files.exists(projectRoot.resolve("build.gradle")) || Files.exists(projectRoot.resolve("build.gradle.kts"))) {
return resolveGradle(projectRoot);
}
log.info("No Maven or Gradle configuration found at {}. Proceeding without dynamic classpath.", projectRoot);
return Collections.emptyList();
}
protected List<String> resolveMaven(Path projectRoot) {
log.info("Maven project detected. Resolving dependencies...");
Path cpFile = null;
try {
cpFile = Files.createTempFile("state_machine_exporter_cp", ".txt");
String mvnCmd = getCommand(projectRoot, "mvnw", "mvn");
if (mvnCmd == null) {
log.warn("Maven executable not found.");
return Collections.emptyList();
}
ProcessBuilder pb = new ProcessBuilder(
mvnCmd,
"-q",
"dependency:build-classpath",
"-DincludeScope=compile",
"-Dmdep.outputFile=" + cpFile.toAbsolutePath().toString()
);
pb.directory(projectRoot.toFile());
runProcess(pb);
if (Files.exists(cpFile)) {
String cpString = Files.readString(cpFile).trim();
if (!cpString.isEmpty()) {
return Arrays.asList(cpString.split(File.pathSeparator));
}
}
} catch (Exception e) {
log.error("Failed to dynamically resolve Maven classpath", e);
} finally {
if (cpFile != null) {
try {
Files.deleteIfExists(cpFile);
} catch (IOException ignored) {}
}
}
return Collections.emptyList();
}
protected List<String> resolveGradle(Path projectRoot) {
log.info("Gradle project detected. Resolving dependencies...");
Path initScript = null;
try {
initScript = Files.createTempFile("state_machine_exporter_init", ".gradle");
String gradleCmd = getCommand(projectRoot, "gradlew", "gradle");
if (gradleCmd == null) {
log.warn("Gradle executable not found.");
return Collections.emptyList();
}
String scriptContent = """
allprojects {
task smePrintClasspath {
doLast {
def cp = []
if (configurations.findByName("compileClasspath")) {
cp = configurations.compileClasspath.files
}
println "SME_CLASSPATH_MARKER:" + cp.join(File.pathSeparator)
}
}
}
""";
Files.writeString(initScript, scriptContent);
ProcessBuilder pb = new ProcessBuilder(
gradleCmd,
"-q",
"-I", initScript.toAbsolutePath().toString(),
"smePrintClasspath"
);
pb.directory(projectRoot.toFile());
String output = runProcessAndGetOutput(pb);
for (String line : output.split("\\r?\\n")) {
if (line.startsWith("SME_CLASSPATH_MARKER:")) {
String cpString = line.substring("SME_CLASSPATH_MARKER:".length()).trim();
if (!cpString.isEmpty()) {
return Arrays.asList(cpString.split(File.pathSeparator));
}
}
}
} catch (Exception e) {
log.error("Failed to dynamically resolve Gradle classpath", e);
} finally {
if (initScript != null) {
try {
Files.deleteIfExists(initScript);
} catch (IOException ignored) {}
}
}
return Collections.emptyList();
}
private String getCommand(Path projectRoot, String wrapper, String systemBinary) {
Path wrapperUnix = projectRoot.resolve(wrapper);
Path wrapperWin = projectRoot.resolve(wrapper + ".bat");
boolean isWin = System.getProperty("os.name").toLowerCase().contains("win");
if (isWin && Files.exists(wrapperWin)) return wrapperWin.toAbsolutePath().toString();
if (!isWin && Files.exists(wrapperUnix)) {
// Need absolute path like ./gradlew
return wrapperUnix.toAbsolutePath().toString();
}
return systemBinary;
}
protected void runProcess(ProcessBuilder pb) throws IOException, InterruptedException {
Process p = pb.start();
p.waitFor();
}
protected String runProcessAndGetOutput(ProcessBuilder pb) throws IOException, InterruptedException {
Process p = pb.start();
StringBuilder out = new StringBuilder();
try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
String line;
while ((line = br.readLine()) != null) {
out.append(line).append("\n");
}
}
p.waitFor();
return out.toString();
}
}

View File

@@ -33,7 +33,8 @@ public class GenericEventDetector {
public boolean visit(MethodInvocation node) { public boolean visit(MethodInvocation node) {
String methodName = node.getName().getIdentifier(); String methodName = node.getName().getIdentifier();
if ("sendEvent".equals(methodName)) { if ("sendEvent".equals(methodName) || "sendEvents".equals(methodName) ||
"sendEventCollect".equals(methodName) || "sendEventMono".equals(methodName)) {
processSendEvent(node, cu, triggers); processSendEvent(node, cu, triggers);
} }
@@ -46,10 +47,12 @@ public class GenericEventDetector {
} }
private void processSendEvent(MethodInvocation node, CompilationUnit cu, List<TriggerPoint> triggers) { private void processSendEvent(MethodInvocation node, CompilationUnit cu, List<TriggerPoint> triggers) {
TriggerPoint trigger = buildTriggerPoint(node, cu, null); List<TriggerPoint> builtTriggers = buildTriggerPoints(node, cu, null);
if (trigger != null) { if (builtTriggers != null) {
log.debug("Successfully built trigger point: {}", trigger.getEvent()); for (TriggerPoint trigger : builtTriggers) {
triggers.add(trigger); log.debug("Successfully built trigger point: {}", trigger.getEvent());
triggers.add(trigger);
}
} }
} }
@@ -61,10 +64,23 @@ public class GenericEventDetector {
for (LibraryHint hint : hints) { for (LibraryHint hint : hints) {
if (calledMethod.equals(hint.getMethodFqn()) || isHintMatch(calledMethod, hint.getMethodFqn())) { if (calledMethod.equals(hint.getMethodFqn()) || isHintMatch(calledMethod, hint.getMethodFqn())) {
TriggerPoint trigger = buildTriggerPoint(node, cu, hint.getEvent()); String eventToUse = hint.getEvent();
if (trigger != null) { if (eventToUse == null && hint.getEventArgumentIndex() != null) {
log.debug("Successfully built synthetic trigger point from hint: {}", trigger.getEvent()); if (node.arguments().size() > hint.getEventArgumentIndex()) {
triggers.add(trigger); Expression argExpr = (Expression) node.arguments().get(hint.getEventArgumentIndex());
eventToUse = argExpr.toString();
if (hint.getEventArgumentMethod() != null && !hint.getEventArgumentMethod().isEmpty()) {
eventToUse = eventToUse + "." + hint.getEventArgumentMethod() + "()";
}
}
}
List<TriggerPoint> builtTriggers = buildTriggerPoints(node, cu, eventToUse);
if (builtTriggers != null) {
for (TriggerPoint trigger : builtTriggers) {
log.debug("Successfully built synthetic trigger point from hint: {}", trigger.getEvent());
triggers.add(trigger);
}
} }
} }
} }
@@ -101,12 +117,12 @@ public class GenericEventDetector {
return receiver.toString() + "." + methodName; return receiver.toString() + "." + methodName;
} }
private TriggerPoint buildTriggerPoint(MethodInvocation node, CompilationUnit cu, String forcedEvent) { private List<TriggerPoint> buildTriggerPoints(MethodInvocation node, CompilationUnit cu, String forcedEvent) {
String eventValue = null; String eventValue = null;
if (forcedEvent != null) { if (forcedEvent != null) {
eventValue = context.resolveString(forcedEvent); eventValue = context.resolveString(forcedEvent);
} else { } else {
if (node.arguments().isEmpty()) return null; if (node.arguments().isEmpty()) return Collections.emptyList();
Expression eventExpr = (Expression) node.arguments().get(0); Expression eventExpr = (Expression) node.arguments().get(0);
// Try MessageBuilder extraction first // Try MessageBuilder extraction first
@@ -122,34 +138,229 @@ public class GenericEventDetector {
MethodDeclaration method = findEnclosingMethod(node); MethodDeclaration method = findEnclosingMethod(node);
TypeDeclaration type = findEnclosingType(node); TypeDeclaration type = findEnclosingType(node);
if (type == null) return null; if (type == null) return Collections.emptyList();
return TriggerPoint.builder() String sourceState = extractSourceState(node);
.event(eventValue)
.className(context.getFqn(type)) List<TriggerPoint> results = new ArrayList<>();
.methodName(method != null ? method.getName().getIdentifier() : "initializer") if (eventValue != null && eventValue.startsWith("ENUM_SET:")) {
.sourceFile(context.getRelativePath(context.getFqn(type))) String[] parts = eventValue.substring(9).split(",");
.lineNumber(cu.getLineNumber(node.getStartPosition())) for (String part : parts) {
.build(); if (!part.trim().isEmpty()) {
results.add(TriggerPoint.builder()
.event(part.trim())
.sourceState(sourceState)
.className(context.getFqn(type))
.methodName(method != null ? method.getName().getIdentifier() : "initializer")
.sourceFile(context.getRelativePath(context.getFqn(type)))
.lineNumber(cu.getLineNumber(node.getStartPosition()))
.build());
}
}
} else {
results.add(TriggerPoint.builder()
.event(eventValue)
.sourceState(sourceState)
.className(context.getFqn(type))
.methodName(method != null ? method.getName().getIdentifier() : "initializer")
.sourceFile(context.getRelativePath(context.getFqn(type)))
.lineNumber(cu.getLineNumber(node.getStartPosition()))
.build());
}
return results;
}
private String extractSourceState(ASTNode node) {
ASTNode current = node;
while (current != null && !(current instanceof MethodDeclaration)) {
ASTNode parent = current.getParent();
// 1. Direct parent is IfStatement wrapper (e.g., if (state == X) { sm.sendEvent() })
if (parent instanceof IfStatement ifStmt) {
// Only consider if our current node is in the 'then' or 'else' part, not the condition
if (ifStmt.getExpression() != current) {
Expression expr = ifStmt.getExpression();
String state = extractStateFromExpression(expr);
if (state != null) return state;
}
}
// 2. Parent is a Block or SwitchStatement: perform sibling traversal
if (parent instanceof Block block) {
String state = extractStateFromSiblings(current, block.statements());
if (state != null) return state;
} else if (parent instanceof SwitchStatement switchStmt) {
String state = extractStateFromSiblings(current, switchStmt.statements());
if (state != null) return state;
}
current = parent;
}
return null;
}
private String extractStateFromSiblings(ASTNode currentNode, List<?> statements) {
int index = statements.indexOf(currentNode);
if (index <= 0) return null; // No previous siblings
// Walk backwards through siblings
for (int i = index - 1; i >= 0; i--) {
Object stmtObj = statements.get(i);
if (stmtObj instanceof SwitchCase switchCase) {
if (!switchCase.expressions().isEmpty()) {
return getSimpleNameString((Expression) switchCase.expressions().get(0));
}
} else if (stmtObj instanceof IfStatement ifStmt) {
// Guard clause detection: check if the 'then' block has a return/throw
if (isGuardClause(ifStmt.getThenStatement())) {
Expression expr = ifStmt.getExpression();
// If it's a guard clause, it usually checks `state != EXPECTED`, so the true state *is* EXPECTED
String state = extractStateFromExpression(expr);
if (state != null) return state;
}
}
}
return null;
}
private boolean isGuardClause(Statement thenStatement) {
if (thenStatement instanceof ReturnStatement || thenStatement instanceof ThrowStatement) {
return true;
}
if (thenStatement instanceof Block block) {
for (Object obj : block.statements()) {
if (obj instanceof ReturnStatement || obj instanceof ThrowStatement) {
return true;
}
}
}
return false;
}
private String extractStateFromExpression(Expression expr) {
if (expr instanceof InfixExpression infix) {
if (infix.getOperator() == InfixExpression.Operator.EQUALS || infix.getOperator() == InfixExpression.Operator.NOT_EQUALS) {
Expression left = infix.getLeftOperand();
Expression right = infix.getRightOperand();
// Ignore null checks entirely
if (left instanceof NullLiteral || right instanceof NullLiteral) {
return null;
}
// Usually one is a method call like getState() or a variable like `state`
// and the other is the constant enum like `OrderState.PENDING` or `"PENDING"`
// If one is a QualifiedName (enum constant) or StringLiteral, it's likely the state
if (left instanceof QualifiedName || left instanceof StringLiteral || left instanceof FieldAccess) {
return getSimpleNameString(left);
}
if (right instanceof QualifiedName || right instanceof StringLiteral || right instanceof FieldAccess) {
return getSimpleNameString(right);
}
// Fallback
return getSimpleNameString(right);
}
} else if (expr instanceof MethodInvocation mi) {
if ("equals".equals(mi.getName().getIdentifier()) && !mi.arguments().isEmpty()) {
return getSimpleNameString((Expression) mi.arguments().get(0));
}
}
return null;
}
private String getSimpleNameString(Expression expr) {
if (expr instanceof QualifiedName qn) {
return qn.getName().getIdentifier();
} else if (expr instanceof FieldAccess fa) {
return fa.getName().getIdentifier();
} else if (expr instanceof StringLiteral sl) {
return sl.getLiteralValue();
}
return expr.toString();
} }
private String extractEventFromMessageBuilder(Expression expr) { private String extractEventFromMessageBuilder(Expression expr) {
if (expr instanceof CastExpression ce) {
return extractEventFromMessageBuilder(ce.getExpression());
}
if (expr instanceof SimpleName sn) {
String varName = sn.getIdentifier();
MethodDeclaration enclosingMethod = findEnclosingMethod(expr);
if (enclosingMethod != null) {
// Find variable declaration
final Expression[] initializer = new Expression[1];
enclosingMethod.accept(new ASTVisitor() {
@Override
public boolean visit(VariableDeclarationFragment node) {
if (node.getName().getIdentifier().equals(varName) && node.getInitializer() != null) {
initializer[0] = node.getInitializer();
}
return super.visit(node);
}
@Override
public boolean visit(Assignment node) {
if (node.getLeftHandSide() instanceof SimpleName asn && asn.getIdentifier().equals(varName)) {
initializer[0] = node.getRightHandSide();
}
return super.visit(node);
}
});
if (initializer[0] != null) {
return extractEventFromMessageBuilder(initializer[0]); // recursive
}
// If it has NO initializer, it might be a method parameter!
for (Object paramObj : enclosingMethod.parameters()) {
if (paramObj instanceof SingleVariableDeclaration svd) {
if (svd.getName().getIdentifier().equals(varName)) {
return varName;
}
}
}
}
}
if (!(expr instanceof MethodInvocation mi)) return null; if (!(expr instanceof MethodInvocation mi)) return null;
// Trace back chain: MessageBuilder.withPayload(event).setHeader(...).build() // Trace back chain: MessageBuilder.withPayload(event).setHeader(...).build()
MethodInvocation current = mi; MethodInvocation current = mi;
while (current != null) { while (current != null) {
String name = current.getName().getIdentifier(); String name = current.getName().getIdentifier();
if ("withPayload".equals(name) && !current.arguments().isEmpty()) { if (("withPayload".equals(name) || "just".equals(name)) && !current.arguments().isEmpty()) {
Expression payloadExpr = (Expression) current.arguments().get(0); Expression payloadExpr = (Expression) current.arguments().get(0);
// If it's Mono.just(msg), where msg is a variable
if ("just".equals(name)) {
String extracted = extractEventFromMessageBuilder(payloadExpr);
if (extracted != null) return extracted;
}
String resolved = constantResolver.resolve(payloadExpr, context); String resolved = constantResolver.resolve(payloadExpr, context);
if (resolved != null) return resolved; if (resolved != null) return resolved;
// If not a constant, it might be a parameter like 'event' // If not a constant, it might be a parameter like 'event'
if (payloadExpr instanceof CastExpression ce) {
payloadExpr = ce.getExpression();
}
if (payloadExpr instanceof SimpleName sn) { if (payloadExpr instanceof SimpleName sn) {
return sn.getIdentifier(); // Return the parameter name so the call graph can fill it String extracted = extractEventFromMessageBuilder(payloadExpr);
if (extracted != null) {
return extracted;
}
return sn.getIdentifier(); // Fall back to returning the parameter name
} }
return payloadExpr.toString(); return payloadExpr.toString();
} else if (current.arguments().isEmpty() && current.getExpression() instanceof SimpleName sn) {
// If the event is obtained by calling a method on a provider/supplier parameter,
// return the provider's name so CallGraphBuilder can trace the lambda argument
String traced = extractEventFromMessageBuilder(sn);
return traced != null ? traced : sn.getIdentifier();
} }
Expression receiver = current.getExpression(); Expression receiver = current.getExpression();

View File

@@ -0,0 +1,967 @@
package click.kamil.springstatemachineexporter.analysis.service;
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.analysis.model.CallEdge;
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.jdt.core.dom.*;
import java.util.*;
@Slf4j
public class HeuristicCallGraphEngine implements CallGraphEngine {
private final CodebaseContext context;
private final ConstantResolver constantResolver;
private String currentMethodFqn;
private Map<String, List<CallEdge>> graph;
public HeuristicCallGraphEngine(CodebaseContext context) {
this.context = context;
this.constantResolver = new ConstantResolver();
}
public List<CallChain> findChains(List<EntryPoint> entryPoints, List<TriggerPoint> triggers) {
Map<String, List<CallEdge>> callGraph = buildCallGraph();
List<CallChain> chains = new ArrayList<>();
for (EntryPoint ep : entryPoints) {
String startMethod = ep.getClassName() + "." + ep.getMethodName();
boolean foundAny = false;
for (TriggerPoint tp : triggers) {
String targetMethod = tp.getClassName() + "." + tp.getMethodName();
List<String> path = findPath(startMethod, targetMethod, callGraph, new HashSet<>());
if (path != null) {
foundAny = true;
TriggerPoint resolvedTp = resolveTriggerPointParameters(tp, path, callGraph);
String contextMachineId = extractContextMachineId(path, callGraph);
chains.add(CallChain.builder()
.entryPoint(ep)
.triggerPoint(resolvedTp)
.methodChain(path)
.contextMachineId(contextMachineId)
.build());
}
}
if (!foundAny && log.isDebugEnabled()) {
log.debug("Entry point {} ({}) did not reach any trigger points. Graph nodes available: {}", ep.getName(), startMethod, callGraph.keySet());
}
}
return chains;
}
private TriggerPoint resolveTriggerPointParameters(TriggerPoint tp, List<String> path, Map<String, List<CallEdge>> callGraph) {
if (path.size() < 2) return tp;
String event = tp.getEvent();
if (event == null || event.isEmpty() || event.contains("\"")) return tp;
String currentParamName = event;
String resolvedValue = event;
String methodSuffix = "";
// Extract method calls like .getType() so we can trace the base parameter
int dotIndex = currentParamName.indexOf('.');
if (dotIndex > 0 && dotIndex + 1 < currentParamName.length()) {
char nextChar = currentParamName.charAt(dotIndex + 1);
if (Character.isLowerCase(nextChar)) {
methodSuffix = currentParamName.substring(dotIndex);
currentParamName = currentParamName.substring(0, dotIndex);
}
}
// Walk backwards up the call chain
for (int i = path.size() - 1; i > 0; i--) {
String target = path.get(i);
String caller = path.get(i - 1);
// Find parameter index in target method
int paramIndex = getParameterIndex(target, currentParamName);
if (paramIndex < 0) {
// Not a parameter. Maybe it's a local variable initialized from a parameter or method call?
String tracedVar = traceLocalVariable(target, currentParamName);
if (tracedVar != null && !tracedVar.equals(currentParamName)) {
// Extract method calls like .getType() from the traced variable
int dotIdx = tracedVar.indexOf('.');
if (dotIdx > 0 && dotIdx + 1 < tracedVar.length() && Character.isLowerCase(tracedVar.charAt(dotIdx + 1))) {
methodSuffix = tracedVar.substring(dotIdx) + methodSuffix;
tracedVar = tracedVar.substring(0, dotIdx);
}
currentParamName = tracedVar;
resolvedValue = tracedVar + methodSuffix;
paramIndex = getParameterIndex(target, currentParamName);
}
}
if (paramIndex < 0) {
break; // Parameter name changed or not found, stop tracing
}
// Find the edge from caller to target to get the argument passed
List<CallEdge> edges = callGraph.get(caller);
boolean found = false;
if (edges != null) {
for (CallEdge edge : edges) {
if (edge.getTargetMethod().equals(target) || isHeuristicMatch(edge.getTargetMethod(), target)) {
if (paramIndex < edge.getArguments().size()) {
String arg = edge.getArguments().get(paramIndex);
if (arg != null) {
// If the argument passed has a method call, extract it
int dotIdx = arg.indexOf('.');
if (dotIdx > 0 && dotIdx + 1 < arg.length() && Character.isLowerCase(arg.charAt(dotIdx + 1))) {
methodSuffix = arg.substring(dotIdx) + methodSuffix;
arg = arg.substring(0, dotIdx);
}
currentParamName = arg;
resolvedValue = arg + methodSuffix;
found = true;
break;
}
}
}
}
}
if (!found) break; // Could not map argument
}
// Final check on the entry method
String entryMethod = path.get(0);
int entryParamIndex = getParameterIndex(entryMethod, currentParamName);
if (entryParamIndex < 0) {
String tracedVar = traceLocalVariable(entryMethod, currentParamName);
if (tracedVar != null && !tracedVar.equals(currentParamName)) {
int dotIdx = tracedVar.indexOf('.');
if (dotIdx > 0 && dotIdx + 1 < tracedVar.length() && Character.isLowerCase(tracedVar.charAt(dotIdx + 1))) {
methodSuffix = tracedVar.substring(dotIdx) + methodSuffix;
tracedVar = tracedVar.substring(0, dotIdx);
}
currentParamName = tracedVar;
resolvedValue = tracedVar + methodSuffix;
}
}
List<String> polymorphicEvents = new ArrayList<>();
if (resolvedValue.matches(".*\\.[a-zA-Z0-9_]+\\(\\)")) {
int lastDot = resolvedValue.lastIndexOf('.');
int firstDot = resolvedValue.indexOf('.');
int openParen = resolvedValue.indexOf('(', lastDot);
if (lastDot > 0 && openParen > lastDot) {
String varName = resolvedValue.substring(0, firstDot);
if (varName.contains("(")) {
varName = null;
}
String methodName = resolvedValue.substring(lastDot + 1, openParen);
String declaredType = null;
String sourceMethod = null;
if (varName != null) {
for (String methodFqn : path) {
declaredType = getVariableDeclaredType(methodFqn, varName);
if (declaredType != null) {
sourceMethod = methodFqn;
break;
}
}
} else {
String baseExpr = resolvedValue.substring(0, lastDot);
if (baseExpr.contains("new ")) {
int newIdx = baseExpr.indexOf("new ");
int openParenBase = baseExpr.indexOf('(', newIdx);
if (openParenBase > newIdx) {
declaredType = baseExpr.substring(newIdx + 4, openParenBase).trim();
if (declaredType.contains("<")) {
declaredType = declaredType.substring(0, declaredType.indexOf('<'));
}
sourceMethod = "inline-instantiation";
}
}
}
if (declaredType != null) {
System.out.println("DEEP TRACE: " + sourceMethod + " " + (varName != null ? varName : "inline") + " -> " + declaredType);
List<String> typesToInspect = new ArrayList<>();
if ("inline-instantiation".equals(sourceMethod)) {
typesToInspect.add(declaredType);
} else {
typesToInspect.add(declaredType);
typesToInspect.addAll(context.getImplementations(declaredType));
}
System.out.println("DEEP TRACE IMPLS: " + typesToInspect);
for (String type : typesToInspect) {
Set<String> visited = new HashSet<>();
TypeDeclaration baseTd = context.getTypeDeclaration(type);
CompilationUnit cuToUse = null;
if (baseTd != null && baseTd.getRoot() instanceof CompilationUnit) {
cuToUse = (CompilationUnit) baseTd.getRoot();
} else {
String firstPathMethod = path.get(0);
String entryClassName = firstPathMethod.substring(0, firstPathMethod.lastIndexOf('.'));
TypeDeclaration entryTd = context.getTypeDeclaration(entryClassName);
if (entryTd != null && entryTd.getRoot() instanceof CompilationUnit) {
cuToUse = (CompilationUnit) entryTd.getRoot();
}
}
List<String> constants = resolveMethodReturnConstant(type, methodName, 0, visited, cuToUse);
System.out.println("DEEP TRACE RETURN: " + type + " -> " + constants);
for (String constant : constants) {
if (!polymorphicEvents.contains(constant)) {
polymorphicEvents.add(constant);
}
}
}
}
}
}
// LAST RESORT FALLBACK: If AST deep trace failed to find a valid ALL_CAPS constant
// (e.g. it returned a field name like 'type', or nothing), we can scrape the resolvedValue string directly
// for string literals or enum-like constants.
boolean hasValidConstant = false;
for (String ev : polymorphicEvents) {
String val = ev.contains(".") ? ev.substring(ev.lastIndexOf('.') + 1) : ev;
if (val.equals(val.toUpperCase()) && val.length() > 2) {
hasValidConstant = true;
break;
}
}
if (!hasValidConstant && resolvedValue.contains("(")) {
java.util.List<String> scraped = new java.util.ArrayList<>();
// Extract "STRING_LITERALS"
java.util.regex.Matcher m1 = java.util.regex.Pattern.compile("\"([^\"]+)\"").matcher(resolvedValue);
while (m1.find()) {
scraped.add(m1.group(1));
}
// Extract ENUM_LIKE_CONSTANTS
java.util.regex.Matcher m2 = java.util.regex.Pattern.compile("\\b([A-Z_]{3,})\\b").matcher(resolvedValue);
while (m2.find()) {
if (!scraped.contains(m2.group(1))) {
scraped.add(m2.group(1));
}
}
if (!scraped.isEmpty()) {
polymorphicEvents.clear();
polymorphicEvents.addAll(scraped);
}
}
// Clean up any remaining invalid AST fallbacks (like 'type', 'event') if we found valid ones
if (polymorphicEvents.size() > 1) {
polymorphicEvents.removeIf(e -> {
String val = e.contains(".") ? e.substring(e.lastIndexOf('.') + 1) : e;
return !val.equals(val.toUpperCase()) || val.length() <= 2;
});
}
if (!resolvedValue.equals(event) || !polymorphicEvents.isEmpty()) {
return TriggerPoint.builder()
.event(resolvedValue)
.className(tp.getClassName())
.methodName(tp.getMethodName())
.sourceFile(tp.getSourceFile())
.lineNumber(tp.getLineNumber())
.polymorphicEvents(polymorphicEvents)
.build();
}
return tp;
}
private int getParameterIndex(String methodFqn, String paramName) {
if (methodFqn == null || !methodFqn.contains(".")) return -1;
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1);
TypeDeclaration td = context.getTypeDeclaration(className);
if (td != null) {
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
if (md != null) {
for (int i = 0; i < md.parameters().size(); i++) {
SingleVariableDeclaration svd = (SingleVariableDeclaration) md.parameters().get(i);
if (svd.getName().getIdentifier().equals(paramName)) {
return i;
}
}
}
}
return -1;
}
private String getVariableDeclaredType(String methodFqn, String varName) {
if (methodFqn == null || !methodFqn.contains(".")) return null;
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1);
TypeDeclaration td = context.getTypeDeclaration(className);
if (td != null) {
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
if (md != null) {
for (Object pObj : md.parameters()) {
SingleVariableDeclaration svd = (SingleVariableDeclaration) pObj;
if (svd.getName().getIdentifier().equals(varName)) {
return svd.getType().toString();
}
}
final String[] foundType = new String[1];
if (md.getBody() != null) {
md.getBody().accept(new ASTVisitor() {
@Override
public boolean visit(VariableDeclarationStatement node) {
for (Object fragObj : node.fragments()) {
VariableDeclarationFragment frag = (VariableDeclarationFragment) fragObj;
if (frag.getName().getIdentifier().equals(varName)) {
foundType[0] = node.getType().toString();
}
}
return super.visit(node);
}
});
}
return foundType[0];
}
}
return null;
}
private List<String> resolveMethodReturnConstant(String className, String methodName, int depth, Set<String> visited, CompilationUnit contextCu) {
if (depth > 20) return Collections.emptyList();
String fqn = className + "." + methodName;
if (!visited.add(fqn)) return Collections.emptyList();
List<String> constants = new ArrayList<>();
TypeDeclaration tempTd = contextCu != null ? context.getTypeDeclaration(className, contextCu) : null;
if (tempTd == null) {
tempTd = context.getTypeDeclaration(className);
}
final TypeDeclaration td = tempTd;
if (td == null) {
System.out.println("DEEP TRACE FAILED TO FIND TD: " + className);
}
if (td != null) {
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
if (md != null && md.getBody() != null) {
md.getBody().accept(new ASTVisitor() {
@Override
public boolean visit(ReturnStatement node) {
Expression retExpr = node.getExpression();
if (retExpr != null) {
boolean handled = false;
if (retExpr instanceof MethodInvocation mi) {
// Follow delegation first
String called = resolveCalledMethod(mi);
System.out.println("DEEP TRACE RESOLVED CALLED: " + called);
if (called != null && called.contains(".")) {
if (visited.contains(called)) {
handled = true;
} else {
String cName = called.substring(0, called.lastIndexOf('.'));
String mName = called.substring(called.lastIndexOf('.') + 1);
TypeDeclaration targetTd = contextCu != null ? context.getTypeDeclaration(cName, contextCu) : context.getTypeDeclaration(cName);
if (targetTd == null) targetTd = context.getTypeDeclaration(cName);
if (targetTd != null && context.findMethodDeclaration(targetTd, mName, true) != null) {
List<String> delegationResult = resolveMethodReturnConstant(cName, mName, depth + 1, visited, contextCu);
constants.addAll(delegationResult);
handled = true;
}
}
}
}
if (!handled) {
String val = constantResolver.resolve(retExpr, context);
if (val != null) {
if (val.startsWith("ENUM_SET:")) {
for (String eVal : val.substring(9).split(",")) {
constants.add(eVal.substring(eVal.lastIndexOf('.') + 1));
}
} else {
constants.add(val);
}
} else if (retExpr instanceof QualifiedName qn) {
constants.add(qn.toString());
} else if (retExpr instanceof SimpleName sn) {
List<String> consts = traceFieldInConstructors(td, sn.getIdentifier(), context, visited);
if (!consts.isEmpty()) {
constants.addAll(consts);
} else {
constants.add(sn.toString());
}
}
}
}
return super.visit(node);
}
});
}
}
visited.remove(fqn);
return constants;
}
private String traceLocalVariable(String methodFqn, String varName) {
if (methodFqn == null || !methodFqn.contains(".")) return null;
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1);
TypeDeclaration td = context.getTypeDeclaration(className);
if (td != null) {
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
if (md != null && md.getBody() != null) {
final Expression[] initializer = new Expression[1];
md.getBody().accept(new ASTVisitor() {
@Override
public boolean visit(VariableDeclarationFragment node) {
if (node.getName().getIdentifier().equals(varName) && node.getInitializer() != null) {
initializer[0] = node.getInitializer();
}
return super.visit(node);
}
@Override
public boolean visit(Assignment node) {
if (node.getLeftHandSide() instanceof SimpleName asn && asn.getIdentifier().equals(varName)) {
initializer[0] = node.getRightHandSide();
}
return super.visit(node);
}
});
if (initializer[0] != null) {
Expression expr = traceVariable(initializer[0]);
if (expr instanceof MethodInvocation mi) {
// Unwrapper logic: If wrapper method is called, extract its arguments recursively
Expression innerMost = unwrapMethodInvocation(mi, 0);
if (innerMost instanceof MethodInvocation innerMi) {
if (innerMi.getExpression() instanceof SimpleName sn) {
return sn.getIdentifier() + "." + innerMi.getName().getIdentifier() + "()";
}
return innerMi.getName().getIdentifier() + "()";
}
if (innerMost instanceof SimpleName sn) {
return sn.getIdentifier();
}
return innerMost.toString();
}
if (expr instanceof SimpleName sn) {
return sn.getIdentifier();
}
return expr.toString();
}
}
}
return null;
}
private Expression unwrapMethodInvocation(MethodInvocation mi, int depth) {
if (depth > 5) return mi;
if (!mi.arguments().isEmpty()) {
Expression arg = (Expression) mi.arguments().get(0);
if (arg instanceof MethodInvocation innerMi) {
return unwrapMethodInvocation(innerMi, depth + 1);
}
return arg;
}
return mi;
}
private String extractContextMachineId(List<String> path, Map<String, List<CallEdge>> callGraph) {
for (String node : path) {
List<CallEdge> edges = callGraph.get(node);
if (edges != null) {
for (CallEdge edge : edges) {
String target = edge.getTargetMethod();
if (target != null && (target.contains(".restore") || target.contains(".read"))) {
// Persister signatures usually like: restore(stateMachine, contextObj)
if (edge.getArguments().size() >= 2) {
return edge.getArguments().get(1); // The contextObj / machineId
}
}
}
}
}
return null;
}
private MethodDeclaration findEnclosingMethod(ASTNode node) {
ASTNode parent = node.getParent();
while (parent != null && !(parent instanceof MethodDeclaration)) {
parent = parent.getParent();
}
return (MethodDeclaration) parent;
}
private Map<String, List<CallEdge>> buildCallGraph() {
graph = new HashMap<>();
for (CompilationUnit cu : context.getCompilationUnits()) {
cu.accept(new ASTVisitor() {
@Override
public boolean visit(MethodInvocation node) {
MethodDeclaration md = findEnclosingMethod(node);
if (md != null) {
TypeDeclaration td = findEnclosingType(md);
if (td != null) {
String currentMethodFqn = context.getFqn(td) + "." + md.getName().getIdentifier();
List<String> calledMethods = resolveCalledMethodsPolymorphic(node);
List<String> args = resolveArguments(node.arguments());
for (String calledMethod : calledMethods) {
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(calledMethod, args));
}
for (Object argObj : node.arguments()) {
if (argObj instanceof ExpressionMethodReference emr) {
String typeName = emr.getExpression().toString();
if ("this".equals(typeName) || "super".equals(typeName)) {
TypeDeclaration td2 = findEnclosingType(node);
if (td2 != null) {
String refMethod = resolveMethodInType(td2, emr.getName().getIdentifier());
if (refMethod != null) {
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(refMethod, args));
}
}
} else {
String fallbackTypeFqn = null;
ITypeBinding binding = emr.getExpression().resolveTypeBinding();
if (binding != null) {
fallbackTypeFqn = binding.getQualifiedName();
} else if (emr.getExpression() instanceof SimpleName sn) {
fallbackTypeFqn = resolveReceiverTypeFallback(sn);
}
if (fallbackTypeFqn != null) {
String calledMethod = fallbackTypeFqn + "." + emr.getName().getIdentifier();
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(calledMethod, args));
List<String> impls = context.getImplementations(fallbackTypeFqn);
for (String impl : impls) {
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(impl + "." + emr.getName().getIdentifier(), args));
}
}
}
}
}
}
}
return super.visit(node);
}
@Override
public boolean visit(SuperMethodInvocation node) {
MethodDeclaration md = findEnclosingMethod(node);
if (md != null) {
TypeDeclaration tdOuter = findEnclosingType(md);
if (tdOuter != null) {
String currentMethodFqn = context.getFqn(tdOuter) + "." + md.getName().getIdentifier();
String methodName = node.getName().getIdentifier();
TypeDeclaration td = findEnclosingType(node);
if (td != null) {
String superFqn = context.getSuperclassFqn(td);
if (superFqn != null) {
TypeDeclaration superTd = context.getTypeDeclaration(superFqn);
String calledMethod = null;
if (superTd != null) {
calledMethod = resolveMethodInType(superTd, methodName);
}
if (calledMethod == null) {
calledMethod = superFqn + "." + methodName;
}
List<String> args = resolveArguments(node.arguments());
graph.computeIfAbsent(currentMethodFqn, k -> new ArrayList<>()).add(new CallEdge(calledMethod, args));
}
}
}
}
return super.visit(node);
}
});
}
return graph;
}
private List<String> resolveArguments(List<?> astArguments) {
List<String> args = new ArrayList<>();
for (Object argObj : astArguments) {
Expression expr = (Expression) argObj;
// Extract from lambda
if (expr instanceof LambdaExpression le) {
ASTNode body = le.getBody();
if (body instanceof Expression bodyExpr) {
expr = bodyExpr;
} else if (body instanceof Block block) {
for (Object stmtObj : block.statements()) {
if (stmtObj instanceof ReturnStatement rs && rs.getExpression() != null) {
expr = rs.getExpression();
break;
}
}
}
}
if (expr instanceof ExpressionMethodReference emr) {
TypeDeclaration td = findEnclosingType(expr);
if (td != null) {
MethodDeclaration md = context.findMethodDeclaration(td, emr.getName().getIdentifier(), true);
if (md != null && md.getBody() != null) {
for (Object stmtObj : md.getBody().statements()) {
if (stmtObj instanceof ReturnStatement rs && rs.getExpression() != null) {
expr = rs.getExpression();
break;
}
}
}
}
}
// Extract from constructor args (e.g., new CustomMessage(OrderEvent.PROCESS, ...))
Expression tracedExpr = traceVariable(expr);
if (tracedExpr instanceof QualifiedName || tracedExpr instanceof ClassInstanceCreation || tracedExpr instanceof StringLiteral || tracedExpr instanceof NumberLiteral) {
expr = tracedExpr; // Only accept trace if it resolves to a pure constant/primitive/constructor
}
if (expr instanceof ClassInstanceCreation cic && !cic.arguments().isEmpty()) {
Expression firstArg = (Expression) cic.arguments().get(0);
String resolved = constantResolver.resolve(firstArg, context);
if (resolved != null) {
expr = firstArg; // Only unwrap if it's actually a constant
}
}
String val = constantResolver.resolve(expr, context);
if (val == null) {
val = expr.toString(); // Fallback to raw string (handles Enums, 'new Class()', etc)
}
args.add(val);
}
return args;
}
private List<String> resolveCalledMethodsPolymorphic(MethodInvocation node) {
String baseCalled = resolveCalledMethod(node);
if (baseCalled == null) return Collections.emptyList();
List<String> allResolved = new ArrayList<>();
allResolved.add(baseCalled);
if (baseCalled.contains(".")) {
String className = baseCalled.substring(0, baseCalled.lastIndexOf('.'));
String methodName = baseCalled.substring(baseCalled.lastIndexOf('.') + 1);
List<String> impls = context.getImplementations(className);
for (String impl : impls) {
allResolved.add(impl + "." + methodName);
}
}
return allResolved;
}
private String resolveCalledMethod(MethodInvocation node) {
Expression receiver = node.getExpression();
String methodName = node.getName().getIdentifier();
if (receiver == null) {
TypeDeclaration td = findEnclosingType(node);
if (td != null) {
return resolveMethodInType(td, methodName);
}
return null;
}
ITypeBinding binding = receiver.resolveTypeBinding();
if (binding != null) {
return binding.getQualifiedName() + "." + methodName;
}
if (receiver instanceof ThisExpression) {
TypeDeclaration td = findEnclosingType(node);
if (td != null) {
return context.getFqn(td) + "." + methodName;
}
}
if (receiver instanceof SimpleName sn) {
String fallbackTypeFqn = resolveReceiverTypeFallback(sn);
if (fallbackTypeFqn != null) {
return fallbackTypeFqn + "." + methodName;
}
String receiverName = sn.getIdentifier();
return receiverName + "." + methodName;
}
return null;
}
private String resolveReceiverTypeFallback(SimpleName receiverNameNode) {
String varName = receiverNameNode.getIdentifier();
// 1. Check local variables in enclosing method
MethodDeclaration enclosingMethod = findEnclosingMethod(receiverNameNode);
if (enclosingMethod != null) {
// Check parameters
for (Object paramObj : enclosingMethod.parameters()) {
SingleVariableDeclaration svd = (SingleVariableDeclaration) paramObj;
if (svd.getName().getIdentifier().equals(varName)) {
return resolveTypeToFqn(svd.getType(), receiverNameNode);
}
}
// Check method body (local variables)
if (enclosingMethod.getBody() != null) {
Type[] foundType = new Type[1];
enclosingMethod.getBody().accept(new ASTVisitor() {
@Override
public boolean visit(VariableDeclarationStatement node) {
for (Object fragObj : node.fragments()) {
VariableDeclarationFragment frag = (VariableDeclarationFragment) fragObj;
if (frag.getName().getIdentifier().equals(varName)) {
foundType[0] = node.getType();
}
}
return super.visit(node);
}
});
if (foundType[0] != null) {
return resolveTypeToFqn(foundType[0], receiverNameNode);
}
}
}
// 2. Check fields in enclosing class
TypeDeclaration enclosingType = findEnclosingType(receiverNameNode);
if (enclosingType != null) {
for (FieldDeclaration field : enclosingType.getFields()) {
for (Object fragObj : field.fragments()) {
VariableDeclarationFragment frag = (VariableDeclarationFragment) fragObj;
if (frag.getName().getIdentifier().equals(varName)) {
return resolveTypeToFqn(field.getType(), receiverNameNode);
}
}
}
}
return null;
}
private String resolveTypeToFqn(Type type, ASTNode contextNode) {
if (type == null) return null;
String simpleName;
if (type.isSimpleType()) {
simpleName = ((SimpleType) type).getName().getFullyQualifiedName();
} else if (type.isParameterizedType()) {
return resolveTypeToFqn(((ParameterizedType) type).getType(), contextNode);
} else {
simpleName = type.toString();
}
CompilationUnit cu = (CompilationUnit) contextNode.getRoot();
TypeDeclaration td = context.getTypeDeclaration(simpleName, cu);
if (td != null) {
return context.getFqn(td);
}
// Fallback to import matching if CodebaseContext doesn't know it (e.g., external library)
for (Object impObj : cu.imports()) {
ImportDeclaration imp = (ImportDeclaration) impObj;
String impName = imp.getName().getFullyQualifiedName();
if (impName.endsWith("." + simpleName)) {
return impName;
}
}
return simpleName;
}
private String resolveMethodInType(TypeDeclaration td, String methodName) {
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
if (md != null) {
TypeDeclaration declaringTd = findEnclosingType(md);
return context.getFqn(declaringTd) + "." + methodName;
}
return null;
}
private List<String> findPath(String start, String target, Map<String, List<CallEdge>> graph, Set<String> visited) {
if (start.equals(target)) return new ArrayList<>(List.of(start));
if (!visited.add(start)) return null; // Path-scoped cycle detection
List<CallEdge> neighbors = graph.get(start);
if (neighbors != null) {
for (CallEdge edge : neighbors) {
String neighbor = edge.getTargetMethod();
if (neighbor.equals(target) || isHeuristicMatch(neighbor, target)) {
visited.remove(start);
return new ArrayList<>(List.of(start, target));
}
List<String> path = findPath(neighbor, target, graph, visited);
if (path != null) {
path.add(0, start);
visited.remove(start);
return path;
}
}
}
if (log.isDebugEnabled()) {
log.debug("Path search dead-end at {} when looking for {}", start, target);
}
visited.remove(start);
return null;
}
private boolean isHeuristicMatch(String neighbor, String target) {
if (target.endsWith("." + neighbor)) return true;
String simpleTarget = target.contains(".") ? target.substring(target.lastIndexOf('.') + 1) : target;
String simpleNeighbor = neighbor.contains(".") ? neighbor.substring(neighbor.lastIndexOf('.') + 1) : neighbor;
return simpleNeighbor.equals(simpleTarget);
}
private TypeDeclaration findEnclosingType(ASTNode node) {
ASTNode parent = node.getParent();
while (parent != null && !(parent instanceof TypeDeclaration)) {
parent = parent.getParent();
}
return (TypeDeclaration) parent;
}
private Expression traceVariable(Expression expr) {
if (expr instanceof SimpleName sn) {
String varName = sn.getIdentifier();
MethodDeclaration enclosingMethod = findEnclosingMethod(expr);
if (enclosingMethod != null) {
final Expression[] initializer = new Expression[1];
enclosingMethod.accept(new ASTVisitor() {
@Override
public boolean visit(VariableDeclarationFragment node) {
if (node.getName().getIdentifier().equals(varName) && node.getInitializer() != null) {
initializer[0] = node.getInitializer();
}
return super.visit(node);
}
@Override
public boolean visit(Assignment node) {
if (node.getLeftHandSide() instanceof SimpleName asn && asn.getIdentifier().equals(varName)) {
initializer[0] = node.getRightHandSide();
}
return super.visit(node);
}
});
if (initializer[0] != null) {
return traceVariable(initializer[0]);
}
}
}
return expr;
}
private List<String> traceFieldInConstructors(TypeDeclaration td, String fieldName, CodebaseContext context, Set<String> visited) {
List<String> results = new ArrayList<>();
// 1. Check Field Declarations
for (org.eclipse.jdt.core.dom.FieldDeclaration fd : td.getFields()) {
for (Object fragObj : fd.fragments()) {
org.eclipse.jdt.core.dom.VariableDeclarationFragment frag = (org.eclipse.jdt.core.dom.VariableDeclarationFragment) fragObj;
if (frag.getName().getIdentifier().equals(fieldName) && frag.getInitializer() != null) {
Expression right = frag.getInitializer();
if (right instanceof MethodInvocation mi) {
String calledName = mi.getName().getIdentifier();
String targetClass = context.getFqn(td);
CompilationUnit cu = td.getRoot() instanceof CompilationUnit ? (CompilationUnit) td.getRoot() : null;
results.addAll(resolveMethodReturnConstant(targetClass, calledName, 0, new HashSet<>(visited), cu));
} else {
String val = constantResolver.resolve(right, context);
if (val != null) results.add(val);
}
}
}
}
org.eclipse.jdt.core.dom.ASTVisitor assignmentVisitor = new org.eclipse.jdt.core.dom.ASTVisitor() {
@Override
public boolean visit(Assignment node) {
Expression left = node.getLeftHandSide();
if ((left instanceof SimpleName && ((SimpleName) left).getIdentifier().equals(fieldName)) ||
(left instanceof FieldAccess && ((FieldAccess) left).getName().getIdentifier().equals(fieldName))) {
Expression right = node.getRightHandSide();
if (right instanceof MethodInvocation mi) {
String calledName = mi.getName().getIdentifier();
String targetClass = context.getFqn(td);
if (mi.getExpression() != null && mi.resolveMethodBinding() != null && mi.resolveMethodBinding().getDeclaringClass() != null) {
targetClass = mi.resolveMethodBinding().getDeclaringClass().getQualifiedName();
} else if (mi.getExpression() instanceof SimpleName) {
String exprName = ((SimpleName) mi.getExpression()).getIdentifier();
CompilationUnit cu = td.getRoot() instanceof CompilationUnit ? (CompilationUnit) td.getRoot() : null;
TypeDeclaration targetTd = context.resolveStaticImport(exprName, cu);
if (targetTd != null) {
targetClass = context.getFqn(targetTd);
}
}
CompilationUnit cu = td.getRoot() instanceof CompilationUnit ? (CompilationUnit) td.getRoot() : null;
results.addAll(resolveMethodReturnConstant(targetClass, calledName, 0, new HashSet<>(visited), cu));
} else {
String val = constantResolver.resolve(right, context);
if (val != null) results.add(val);
}
}
return super.visit(node);
}
@Override
public boolean visit(ConstructorInvocation node) {
for (Object argObj : node.arguments()) {
Expression arg = (Expression) argObj;
String val = constantResolver.resolve(arg, context);
if (val != null) {
if (val.startsWith("ENUM_SET:")) {
for (String eVal : val.substring(9).split(",")) {
results.add(eVal.substring(eVal.lastIndexOf('.') + 1));
}
} else {
results.add(val);
}
}
}
return super.visit(node);
}
@Override
public boolean visit(SuperConstructorInvocation node) {
for (Object argObj : node.arguments()) {
Expression arg = (Expression) argObj;
String val = constantResolver.resolve(arg, context);
if (val != null) {
if (val.startsWith("ENUM_SET:")) {
for (String eVal : val.substring(9).split(",")) {
results.add(eVal.substring(eVal.lastIndexOf('.') + 1));
}
} else {
results.add(val);
}
}
}
return super.visit(node);
}
};
// 2. Check Constructors
for (MethodDeclaration md : td.getMethods()) {
if (md.isConstructor() && md.getBody() != null) {
md.getBody().accept(assignmentVisitor);
}
}
// 3. Check Initializers
for (Object bodyDecl : td.bodyDeclarations()) {
if (bodyDecl instanceof org.eclipse.jdt.core.dom.Initializer init && init.getBody() != null) {
init.getBody().accept(assignmentVisitor);
}
}
return results;
}
}

View File

@@ -21,9 +21,10 @@ public class JdtIntelligenceProvider implements CodebaseIntelligenceProvider {
private final GenericEventDetector eventDetector; private final GenericEventDetector eventDetector;
private final SpringMvcDetector mvcDetector; private final SpringMvcDetector mvcDetector;
private final MessagingDetector messagingDetector; private final MessagingDetector messagingDetector;
private final CallGraphBuilder callGraphBuilder; private final CallGraphEngine callGraphEngine;
private final LifecycleDetector lifecycleDetector; private final LifecycleDetector lifecycleDetector;
private final InterceptorDetector interceptorDetector; private final InterceptorDetector interceptorDetector;
private final SpringComponentDetector componentDetector;
public JdtIntelligenceProvider(CodebaseContext context, Path rootDir) { public JdtIntelligenceProvider(CodebaseContext context, Path rootDir) {
this.context = context; this.context = context;
@@ -31,9 +32,10 @@ public class JdtIntelligenceProvider implements CodebaseIntelligenceProvider {
this.eventDetector = new GenericEventDetector(context, constantResolver, context.getLibraryHints()); this.eventDetector = new GenericEventDetector(context, constantResolver, context.getLibraryHints());
this.mvcDetector = new SpringMvcDetector(context, constantResolver); this.mvcDetector = new SpringMvcDetector(context, constantResolver);
this.messagingDetector = new MessagingDetector(context); this.messagingDetector = new MessagingDetector(context);
this.callGraphBuilder = new CallGraphBuilder(context); this.callGraphEngine = new HeuristicCallGraphEngine(context);
this.lifecycleDetector = new LifecycleDetector(context); this.lifecycleDetector = new LifecycleDetector(context);
this.interceptorDetector = new InterceptorDetector(context); this.interceptorDetector = new InterceptorDetector(context);
this.componentDetector = new SpringComponentDetector(context);
} }
@Override @Override
@@ -58,6 +60,7 @@ public class JdtIntelligenceProvider implements CodebaseIntelligenceProvider {
allEntryPoints.addAll(mvcDetector.detect(cu)); allEntryPoints.addAll(mvcDetector.detect(cu));
allEntryPoints.addAll(messagingDetector.detect(cu)); allEntryPoints.addAll(messagingDetector.detect(cu));
allEntryPoints.addAll(interceptorDetector.detect(cu)); allEntryPoints.addAll(interceptorDetector.detect(cu));
allEntryPoints.addAll(componentDetector.detect(cu));
} }
log.info("Found {} entry points (including interceptors and listeners) in total", allEntryPoints.size()); log.info("Found {} entry points (including interceptors and listeners) in total", allEntryPoints.size());
return allEntryPoints; return allEntryPoints;
@@ -66,7 +69,7 @@ public class JdtIntelligenceProvider implements CodebaseIntelligenceProvider {
@Override @Override
public List<CallChain> findCallChains(List<EntryPoint> entryPoints, List<TriggerPoint> triggers) { public List<CallChain> findCallChains(List<EntryPoint> entryPoints, List<TriggerPoint> triggers) {
log.info("JdtIntelligenceProvider searching for call chains between {} entry points and {} triggers", entryPoints.size(), triggers.size()); log.info("JdtIntelligenceProvider searching for call chains between {} entry points and {} triggers", entryPoints.size(), triggers.size());
return callGraphBuilder.findChains(entryPoints, triggers); return callGraphEngine.findChains(entryPoints, triggers);
} }
@Override @Override

View File

@@ -71,6 +71,7 @@ public class MessagingDetector {
.name(protocolName + ": " + destination) .name(protocolName + ": " + destination)
.className(context.getFqn((TypeDeclaration) method.getParent())) .className(context.getFqn((TypeDeclaration) method.getParent()))
.methodName(method.getName().getIdentifier()) .methodName(method.getName().getIdentifier())
.sourceFile(context.getRelativePath(context.getFqn((TypeDeclaration) method.getParent())))
.metadata(metadata) .metadata(metadata)
.parameters(extractParameters(method)) .parameters(extractParameters(method))
.build()); .build());

View File

@@ -0,0 +1,117 @@
package click.kamil.springstatemachineexporter.analysis.service;
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import lombok.RequiredArgsConstructor;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.MemberValuePair;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RequiredArgsConstructor
public class SpringComponentDetector {
private final CodebaseContext context;
public List<EntryPoint> detect(CompilationUnit cu) {
List<EntryPoint> entryPoints = new ArrayList<>();
cu.accept(new ASTVisitor() {
@Override
public boolean visit(MethodDeclaration node) {
if (!(node.getParent() instanceof TypeDeclaration)) {
return super.visit(node);
}
TypeDeclaration parentTd = (TypeDeclaration) node.getParent();
for (Object modifier : node.modifiers()) {
if (modifier instanceof Annotation annotation) {
String typeName = annotation.getTypeName().getFullyQualifiedName();
if (typeName.endsWith("Scheduled")) {
Map<String, String> meta = new HashMap<>();
String cron = extractAnnotationValue(annotation, "cron");
if (!cron.isEmpty())
meta.put("cron", cron);
String fixedRate = extractAnnotationValue(annotation, "fixedRate");
if (!fixedRate.isEmpty())
meta.put("fixedRate", fixedRate);
String fixedDelay = extractAnnotationValue(annotation, "fixedDelay");
if (!fixedDelay.isEmpty())
meta.put("fixedDelay", fixedDelay);
entryPoints.add(EntryPoint.builder()
.type(EntryPoint.Type.CUSTOM)
.name("@Scheduled: " + node.getName().getIdentifier())
.className(context.getFqn(parentTd))
.methodName(node.getName().getIdentifier())
.sourceFile(context.getRelativePath(context.getFqn(parentTd)))
.metadata(meta)
.build());
} else if (typeName.endsWith("EventListener")) {
Map<String, String> meta = new HashMap<>();
String classes = extractAnnotationValue(annotation, "classes");
if (!classes.isEmpty())
meta.put("classes", classes);
String condition = extractAnnotationValue(annotation, "condition");
if (!condition.isEmpty())
meta.put("condition", condition);
entryPoints.add(EntryPoint.builder()
.type(EntryPoint.Type.CUSTOM)
.name("@EventListener: " + node.getName().getIdentifier())
.className(context.getFqn(parentTd))
.methodName(node.getName().getIdentifier())
.sourceFile(context.getRelativePath(context.getFqn(parentTd)))
.metadata(meta)
.build());
} else if (typeName.endsWith("Around") || typeName.endsWith("Before") || typeName.endsWith("After") || typeName.endsWith("AfterReturning") || typeName.endsWith("AfterThrowing")) {
Map<String, String> meta = new HashMap<>();
String value = extractAnnotationValue(annotation, "value");
if (!value.isEmpty())
meta.put("pointcut", value);
else {
String pointcut = extractAnnotationValue(annotation, "pointcut");
if (!pointcut.isEmpty())
meta.put("pointcut", pointcut);
}
entryPoints.add(EntryPoint.builder()
.type(EntryPoint.Type.CUSTOM)
.name("@" + annotation.getTypeName().getFullyQualifiedName() + ": " + node.getName().getIdentifier())
.className(context.getFqn(parentTd))
.methodName(node.getName().getIdentifier())
.sourceFile(context.getRelativePath(context.getFqn(parentTd)))
.metadata(meta)
.build());
}
}
}
return super.visit(node);
}
});
return entryPoints;
}
private String extractAnnotationValue(Annotation annotation, String memberName) {
if (annotation instanceof SingleMemberAnnotation sma) {
if ("value".equals(memberName)) {
return sma.getValue().toString();
}
} else if (annotation instanceof NormalAnnotation na) {
for (Object pairObj : na.values()) {
MemberValuePair pair = (MemberValuePair) pairObj;
if (pair.getName().getIdentifier().equals(memberName)) {
return pair.getValue().toString();
}
}
}
return "";
}
}

View File

@@ -4,6 +4,7 @@ import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver
import click.kamil.springstatemachineexporter.model.Action; import click.kamil.springstatemachineexporter.model.Action;
import click.kamil.springstatemachineexporter.model.Guard; import click.kamil.springstatemachineexporter.model.Guard;
import click.kamil.springstatemachineexporter.model.State; import click.kamil.springstatemachineexporter.model.State;
import click.kamil.springstatemachineexporter.model.Event;
import click.kamil.springstatemachineexporter.model.Transition; import click.kamil.springstatemachineexporter.model.Transition;
import click.kamil.springstatemachineexporter.model.TransitionType; import click.kamil.springstatemachineexporter.model.TransitionType;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext; import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
@@ -213,12 +214,33 @@ public class AstTransitionParser {
Expression resolved = resolveArg((Expression) args.get(0), argsMap); Expression resolved = resolveArg((Expression) args.get(0), argsMap);
String eventValue = constantResolver.resolve(resolved, context); String eventValue = constantResolver.resolve(resolved, context);
if (eventValue != null) { if (eventValue != null) {
t.setEvent(eventValue); t.setEvent(Event.of(resolved.toString(), eventValue));
} else if (resolved instanceof QualifiedName qn) {
String qualifier = qn.getQualifier().toString();
String name = qn.getName().getIdentifier();
TypeDeclaration td = context.getTypeDeclaration(qualifier, cu);
if (td != null) {
t.setEvent(Event.of(resolved.toString(), context.getFqn(td) + "." + name));
} else {
t.setEvent(Event.of(resolved.toString(), qn.getFullyQualifiedName()));
}
} else if (resolved instanceof SimpleName sn) {
String name = sn.getIdentifier();
String full = name;
for (Object impObj : cu.imports()) {
ImportDeclaration imp = (ImportDeclaration) impObj;
String impName = imp.getName().getFullyQualifiedName();
if (impName.endsWith("." + name)) {
full = impName;
break;
}
}
t.setEvent(Event.of(resolved.toString(), full));
} else { } else {
t.setEvent(QuotedExpression.of(resolved).toStringWithoutQuotes()); t.setEvent(Event.of(QuotedExpression.of(resolved).toStringWithoutQuotes()));
} }
} }
case "guard" -> { case "guard", "guardExpression" -> {
Expression resolved = resolveArg((Expression) args.get(0), argsMap); Expression resolved = resolveArg((Expression) args.get(0), argsMap);
parseGuard(resolved, t, cu, context); parseGuard(resolved, t, cu, context);
} }
@@ -226,6 +248,10 @@ public class AstTransitionParser {
Expression resolved = resolveArg((Expression) args.get(0), argsMap); Expression resolved = resolveArg((Expression) args.get(0), argsMap);
parseAction(resolved, t, cu, context); parseAction(resolved, t, cu, context);
} }
case "timer", "timerOnce" -> {
Expression resolved = resolveArg((Expression) args.get(0), argsMap);
t.setEvent(Event.of(methodName + "(" + resolved.toString() + ")", methodName + "(" + resolved.toString() + ")"));
}
} }
} }
return t; return t;

View File

@@ -170,6 +170,7 @@ public class CodebaseContext {
private final List<CompilationUnit> allCus = new ArrayList<>(); private final List<CompilationUnit> allCus = new ArrayList<>();
private final Map<String, List<String>> interfaceToImpls = new HashMap<>(); private final Map<String, List<String>> interfaceToImpls = new HashMap<>();
private final Map<String, List<String>> enumValues = new HashMap<>();
public void scan(Set<Path> rootDirs, Set<String> customIgnorePatterns) throws IOException { public void scan(Set<Path> rootDirs, Set<String> customIgnorePatterns) throws IOException {
this.allProperties = propertyResolver.resolveAllProperties(rootDirs); this.allProperties = propertyResolver.resolveAllProperties(rootDirs);
@@ -188,6 +189,8 @@ public class CodebaseContext {
for (Object type : cu.types()) { for (Object type : cu.types()) {
if (type instanceof TypeDeclaration td) { if (type instanceof TypeDeclaration td) {
indexType(td, packageName, javaFile); indexType(td, packageName, javaFile);
} else if (type instanceof EnumDeclaration ed) {
indexEnum(ed, packageName, javaFile);
} }
} }
} }
@@ -210,9 +213,18 @@ public class CodebaseContext {
} }
// Inheritance Mapping // Inheritance Mapping
for (Object itf : td.superInterfaceTypes()) { // Track implementations (for both interfaces and superclasses)
String itfName = itf.toString(); if (td.superInterfaceTypes() != null) {
interfaceToImpls.computeIfAbsent(itfName, k -> new ArrayList<>()).add(fqn); for (Object itfObj : td.superInterfaceTypes()) {
Type itf = (Type) itfObj;
String itfName = extractTypeName(itf);
interfaceToImpls.computeIfAbsent(itfName, k -> new ArrayList<>()).add(fqn);
}
}
Type superclass = td.getSuperclassType();
if (superclass != null) {
String superName = extractTypeName(superclass);
interfaceToImpls.computeIfAbsent(superName, k -> new ArrayList<>()).add(fqn);
} }
// Recursively index nested types // Recursively index nested types
@@ -224,15 +236,67 @@ public class CodebaseContext {
} }
public List<String> getImplementations(String interfaceName) { public List<String> getImplementations(String interfaceName) {
Set<String> allImpls = new HashSet<>();
collectImplementations(interfaceName, allImpls, new HashSet<>());
return new ArrayList<>(allImpls);
}
private void collectImplementations(String typeName, Set<String> results, Set<String> visited) {
if (!visited.add(typeName)) return;
// Try direct match // Try direct match
List<String> impls = interfaceToImpls.get(interfaceName); List<String> directImpls = interfaceToImpls.get(typeName);
if (impls != null) return impls;
// Try FQN match if input was simple name // Try FQN match if input was simple name
String fqn = simpleNameToFqn.get(interfaceName); if (directImpls == null) {
if (fqn != null) return interfaceToImpls.getOrDefault(fqn, Collections.emptyList()); String fqn = simpleNameToFqn.get(typeName);
if (fqn != null) {
directImpls = interfaceToImpls.get(fqn);
}
}
return Collections.emptyList(); // Try simple name match if input was FQN
if (directImpls == null && typeName.contains(".")) {
String simpleName = typeName.substring(typeName.lastIndexOf('.') + 1);
directImpls = interfaceToImpls.get(simpleName);
}
if (directImpls != null) {
for (String impl : directImpls) {
results.add(impl);
// Recursively find implementations of the implementation (e.g., subclasses of an abstract class)
collectImplementations(impl, results, visited);
}
}
}
private void indexEnum(EnumDeclaration ed, String parentFqn, Path javaFile) {
String simpleName = ed.getName().getIdentifier();
String fqn = parentFqn.isEmpty() ? simpleName : parentFqn + "." + simpleName;
classes.put(fqn, (CompilationUnit) ed.getRoot());
classPaths.put(fqn, javaFile);
List<String> values = new ArrayList<>();
for (Object o : ed.enumConstants()) {
if (o instanceof EnumConstantDeclaration ecd) {
values.add(fqn + "." + ecd.getName().getIdentifier());
}
}
enumValues.put(fqn, values);
if (!simpleNameToFqn.containsKey(simpleName)) {
simpleNameToFqn.put(simpleName, fqn);
}
}
public List<String> getEnumValues(String fqnOrSimpleName) {
if (fqnOrSimpleName == null) return null;
List<String> values = enumValues.get(fqnOrSimpleName);
if (values != null) return values;
String fqn = simpleNameToFqn.get(fqnOrSimpleName);
if (fqn != null) return enumValues.get(fqn);
// Also try stripping array or generic types if any (e.g. MyEnum[])
return null;
} }
public State resolveState(Expression expr, CompilationUnit cu) { public State resolveState(Expression expr, CompilationUnit cu) {

View File

@@ -32,6 +32,9 @@ public class FileUtils {
return paths return paths
.filter(p -> Files.isRegularFile(p) && p.toString().endsWith(extension)) .filter(p -> Files.isRegularFile(p) && p.toString().endsWith(extension))
.filter(p -> matchers.stream().noneMatch(m -> m.matches(p))) .filter(p -> matchers.stream().noneMatch(m -> m.matches(p)))
.map(Path::toAbsolutePath)
.map(Path::normalize)
.distinct()
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
} }

View File

@@ -48,11 +48,28 @@ public class ExporterCommand implements Callable<Integer> {
@Option(names = {"-p", "--profiles"}, description = "Spring profiles to activate for property resolution.", split = ",") @Option(names = {"-p", "--profiles"}, description = "Spring profiles to activate for property resolution.", split = ",")
private List<String> activeProfiles; private List<String> activeProfiles;
@Option(names = {"--event"}, description = "Format for events when they are enums: fn (fullName, default), fqn (fully qualified name), sn (short name)", defaultValue = "fn")
private click.kamil.springstatemachineexporter.exporter.EnumFormat eventFormat;
@Option(names = {"--state"}, description = "Format for states when they are enums: fn (fullName, default), fqn (fully qualified name), sn (short name)", defaultValue = "fn")
private click.kamil.springstatemachineexporter.exporter.EnumFormat stateFormat;
@Option(names = {"--debug"}, description = "Enable diagnostic mode to show unresolved call chains and detailed analysis logging.", defaultValue = "false")
private boolean debug;
@Option(names = {"--resolve-classpath"}, description = "Automatically run Maven/Gradle to resolve full classpath dependencies for perfect AST bindings.", defaultValue = "false")
private boolean resolveClasspath;
@Override @Override
public Integer call() throws Exception { public Integer call() throws Exception {
var out = spec.commandLine().getOut(); var out = spec.commandLine().getOut();
var err = spec.commandLine().getErr(); var err = spec.commandLine().getErr();
if (debug) {
System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "debug");
out.println(CommandLine.Help.Ansi.AUTO.string("@|bold,cyan Diagnostic mode enabled|@"));
}
if (inputDir == null && jsonFile == null) { if (inputDir == null && jsonFile == null) {
inputDir = Path.of("."); inputDir = Path.of(".");
} }
@@ -71,9 +88,9 @@ public class ExporterCommand implements Callable<Integer> {
try { try {
List<String> profiles = activeProfiles != null ? activeProfiles : java.util.Collections.emptyList(); List<String> profiles = activeProfiles != null ? activeProfiles : java.util.Collections.emptyList();
if (jsonFile != null) { if (jsonFile != null) {
exportService.runJsonExporter(jsonFile, outputDir, selectedFormats, profiles); exportService.runJsonExporter(jsonFile, outputDir, selectedFormats, profiles, eventFormat, stateFormat);
} else { } else {
exportService.runExporter(inputDir, outputDir, selectedFormats, renderChoicesAsDiamonds, profiles); exportService.runExporter(inputDir, outputDir, selectedFormats, renderChoicesAsDiamonds, profiles, null, null, eventFormat, stateFormat, resolveClasspath);
} }
out.println(CommandLine.Help.Ansi.AUTO.string("%n@|bold,green SUCCESS:|@ All state machines have been exported to " + outputDir.toAbsolutePath())); out.println(CommandLine.Help.Ansi.AUTO.string("%n@|bold,green SUCCESS:|@ All state machines have been exported to " + outputDir.toAbsolutePath()));
return 0; return 0;

View File

@@ -41,7 +41,7 @@ public class Dot implements StateMachineExporter {
for (Transition t : transitions) { for (Transition t : transitions) {
if (t.getType() == TransitionType.CHOICE && t.getSourceStates() != null) { if (t.getType() == TransitionType.CHOICE && t.getSourceStates() != null) {
for (State source : t.getSourceStates()) { for (State source : t.getSourceStates()) {
String simplified = simplify(source.toString()); String simplified = simplify(options.formatState(source));
statesWithChoice.add(simplified); statesWithChoice.add(simplified);
if (!choiceColorMap.containsKey(simplified)) { if (!choiceColorMap.containsKey(simplified)) {
choiceColorMap.put(simplified, CHOICE_COLORS[colorIndex % CHOICE_COLORS.length]); choiceColorMap.put(simplified, CHOICE_COLORS[colorIndex % CHOICE_COLORS.length]);
@@ -86,11 +86,11 @@ public class Dot implements StateMachineExporter {
} }
for (State rawSourceState : t.getSourceStates()) { for (State rawSourceState : t.getSourceStates()) {
String rawSource = rawSourceState.toString(); String rawSource = options.formatState(rawSourceState);
String source = simplify(rawSource); String source = simplify(rawSource);
for (State rawTargetState : targets) { for (State rawTargetState : targets) {
String rawTarget = rawTargetState.toString(); String rawTarget = options.formatState(rawTargetState);
String target = simplify(rawTarget); String target = simplify(rawTarget);
String edgeSource = source; String edgeSource = source;
@@ -100,8 +100,11 @@ public class Dot implements StateMachineExporter {
// Label: event [guard] / actions // Label: event [guard] / actions
StringBuilder label = new StringBuilder(); StringBuilder label = new StringBuilder();
if (t.getEvent() != null && !t.getEvent().isBlank()) { if (t.getEvent() != null) {
label.append(escapeDotString(t.getEvent())); String eventStr = options.formatEvent(t.getEvent());
if (eventStr != null && !eventStr.isBlank()) {
label.append(escapeDotString(eventStr));
}
} }
// Guard // Guard

View File

@@ -0,0 +1,5 @@
package click.kamil.springstatemachineexporter.exporter;
public enum EnumFormat {
fn, fqn, sn
}

View File

@@ -12,4 +12,37 @@ public class ExportOptions {
boolean renderChoicesAsDiamonds = true; boolean renderChoicesAsDiamonds = true;
@Builder.Default @Builder.Default
boolean embedIdentifiers = false; boolean embedIdentifiers = false;
@Builder.Default
boolean includeMetadataPane = true;
@Builder.Default
EnumFormat eventFormat = EnumFormat.fn;
@Builder.Default
EnumFormat stateFormat = EnumFormat.fn;
public String formatState(click.kamil.springstatemachineexporter.model.State state) {
if (state == null) return null;
return format(state.rawName(), state.fullIdentifier(), stateFormat);
}
public String formatEvent(click.kamil.springstatemachineexporter.model.Event event) {
if (event == null) return null;
return format(event.rawName(), event.fullIdentifier(), eventFormat);
}
private String format(String raw, String fqn, EnumFormat format) {
if (fqn == null) return raw;
switch (format) {
case fqn: return fqn;
case sn: return fqn.substring(fqn.lastIndexOf('.') + 1);
case fn:
int lastDot = fqn.lastIndexOf('.');
if (lastDot > 0) {
int prevDot = fqn.lastIndexOf('.', lastDot - 1);
return prevDot > 0 ? fqn.substring(prevDot + 1) : fqn;
}
return fqn;
default: return raw;
}
}
} }

View File

@@ -63,7 +63,7 @@ public class PlantUml implements StateMachineExporter {
for (Transition t : transitions) { for (Transition t : transitions) {
if (t.getType() == TransitionType.CHOICE && t.getSourceStates() != null) { if (t.getType() == TransitionType.CHOICE && t.getSourceStates() != null) {
for (State source : t.getSourceStates()) { for (State source : t.getSourceStates()) {
statesWithChoice.add(simplify(source.toString())); statesWithChoice.add(simplify(options.formatState(source)));
} }
} }
} }
@@ -77,7 +77,7 @@ public class PlantUml implements StateMachineExporter {
Set<String> junctionStates = transitions.stream() Set<String> junctionStates = transitions.stream()
.filter(t -> t.getType() == TransitionType.JUNCTION) .filter(t -> t.getType() == TransitionType.JUNCTION)
.flatMap(t -> t.getSourceStates().stream()) .flatMap(t -> t.getSourceStates().stream())
.map(s -> simplify(s.toString())) .map(s -> simplify(options.formatState(s)))
.collect(Collectors.toCollection(java.util.LinkedHashSet::new)); .collect(Collectors.toCollection(java.util.LinkedHashSet::new));
for (String junction : junctionStates) { for (String junction : junctionStates) {
if (options.isRenderChoicesAsDiamonds()) { if (options.isRenderChoicesAsDiamonds()) {
@@ -96,16 +96,16 @@ public class PlantUml implements StateMachineExporter {
List<String> targets; List<String> targets;
if (t.getTargetStates() == null || t.getTargetStates().isEmpty()) { if (t.getTargetStates() == null || t.getTargetStates().isEmpty()) {
if (!allowNoTarget) { if (!allowNoTarget) {
targets = t.getSourceStates().stream().map(s -> s.toString()).toList(); targets = t.getSourceStates().stream().map(s -> options.formatState(s)).toList();
} else { } else {
continue; continue;
} }
} else { } else {
targets = t.getTargetStates().stream().map(s -> s.toString()).toList(); targets = t.getTargetStates().stream().map(s -> options.formatState(s)).toList();
} }
for (State rawSourceState : t.getSourceStates()) { for (State rawSourceState : t.getSourceStates()) {
String source = simplify(rawSourceState.toString()); String source = simplify(options.formatState(rawSourceState));
for (String rawTarget : targets) { for (String rawTarget : targets) {
String target = simplify(rawTarget); String target = simplify(rawTarget);
@@ -116,19 +116,23 @@ public class PlantUml implements StateMachineExporter {
sb.append(" <<").append(styleClass).append(">>"); sb.append(" <<").append(styleClass).append(">>");
if (t.getEvent() != null && !t.getEvent().isBlank()) {
sb.append(" <<e_").append(normalize(t.getEvent())).append(">>");
}
String label = buildLabel(t, options.isUseLambdaGuards()); String label = buildLabel(t, options);
if (options.isEmbedIdentifiers()) { if (options.isEmbedIdentifiers()) {
String linkId = t.getEvent() != null ? "link_" + normalize(t.getEvent()) : "link_anon_" + normalize(source) + "_" + normalize(target); String eventStr = t.getEvent() != null ? options.formatEvent(t.getEvent()) : null;
// Force a label even if empty to ensure the link group exists in SVG String linkId = eventStr != null && !eventStr.isBlank() ? "link_" + normalize(source) + "__" + normalize(eventStr) : "link_anon_" + normalize(source) + "_" + normalize(target);
String displayLabel = label.isEmpty() ? " " : label;
// Brackets [...] in the label break PlantUML link syntax [[url label]], sb.append(" : [[#").append(linkId).append(" ");
// so we strip them for the interactive identification label. if (!label.isEmpty()) {
String safeLabel = displayLabel.replaceAll("[\\[\\]]", ""); String sanitized = label.replace("[", "&#91;")
sb.append(" : ").append("[[#").append(linkId).append(" ").append(safeLabel).append("]]"); .replace("]", "&#93;")
.replace("<", "&lt;")
.replace(">", "&gt;");
sb.append(sanitized);
} else {
sb.append(".");
}
sb.append("]]");
} else if (!label.isEmpty()) { } else if (!label.isEmpty()) {
sb.append(" : ").append(label); sb.append(" : ").append(label);
} }
@@ -191,14 +195,17 @@ public class PlantUml implements StateMachineExporter {
return state.replaceAll("[^a-zA-Z0-9_.]", ""); return state.replaceAll("[^a-zA-Z0-9_.]", "");
} }
private String buildLabel(Transition t, boolean useLambdaGuards) { private String buildLabel(Transition t, ExportOptions options) {
List<String> parts = new ArrayList<>(); List<String> parts = new ArrayList<>();
if (t.getEvent() != null && !t.getEvent().isBlank()) { if (t.getEvent() != null) {
parts.add(t.getEvent()); String eventStr = options.formatEvent(t.getEvent());
if (eventStr != null && !eventStr.isBlank()) {
parts.add(eventStr);
}
} }
if (t.getGuard() != null) { if (t.getGuard() != null) {
String g = t.getGuard().expression(); String g = t.getGuard().expression();
if (useLambdaGuards && t.getGuard().isLambda()) { if (options.isUseLambdaGuards() && t.getGuard().isLambda()) {
parts.add("[" + LAMBDA + "]"); parts.add("[" + LAMBDA + "]");
} else { } else {
parts.add("[" + g.replaceAll("[\\n\\r]+", " ").replaceAll("\\s{2,}", " ").trim() + "]"); parts.add("[" + g.replaceAll("[\\n\\r]+", " ").replaceAll("\\s{2,}", " ").trim() + "]");
@@ -206,7 +213,7 @@ public class PlantUml implements StateMachineExporter {
} }
if (t.getActions() != null && !t.getActions().isEmpty()) { if (t.getActions() != null && !t.getActions().isEmpty()) {
parts.add("/ " + t.getActions().stream() parts.add("/ " + t.getActions().stream()
.map(a -> (useLambdaGuards && a.isLambda()) ? LAMBDA : a.expression().replaceAll("[\\n\\r]+", " ").replaceAll("\\s{2,}", " ").trim()) .map(a -> (options.isUseLambdaGuards() && a.isLambda()) ? LAMBDA : a.expression().replaceAll("[\\n\\r]+", " ").replaceAll("\\s{2,}", " ").trim())
.collect(Collectors.joining(", "))); .collect(Collectors.joining(", ")));
} }
Optional.ofNullable(t.getOrder()) Optional.ofNullable(t.getOrder())

View File

@@ -31,8 +31,8 @@ public class Scxml implements StateMachineExporter {
Set<String> allStates = new java.util.LinkedHashSet<>(); Set<String> allStates = new java.util.LinkedHashSet<>();
for (Transition t : transitions) { for (Transition t : transitions) {
t.getSourceStates().forEach(s -> allStates.add(s.toString())); t.getSourceStates().forEach(s -> allStates.add(options.formatState(s)));
t.getTargetStates().forEach(s -> allStates.add(s.toString())); t.getTargetStates().forEach(s -> allStates.add(options.formatState(s)));
} }
allStates.addAll(startStates); allStates.addAll(startStates);
allStates.addAll(endStates); allStates.addAll(endStates);
@@ -41,7 +41,7 @@ public class Scxml implements StateMachineExporter {
sb.append(" <state id=\"").append(simplify(state)).append("\">\n"); sb.append(" <state id=\"").append(simplify(state)).append("\">\n");
for (Transition t : transitions) { for (Transition t : transitions) {
if (t.getSourceStates() == null || t.getSourceStates().stream().noneMatch(s -> s.toString().equals(state))) if (t.getSourceStates() == null || t.getSourceStates().stream().noneMatch(s -> options.formatState(s).equals(state)))
continue; continue;
boolean allowNoTarget = t.getType() == TransitionType.JOIN || t.getType() == TransitionType.FORK; boolean allowNoTarget = t.getType() == TransitionType.JOIN || t.getType() == TransitionType.FORK;
@@ -58,7 +58,7 @@ public class Scxml implements StateMachineExporter {
} }
for (State target : targets) { for (State target : targets) {
sb.append(renderTransition(t, target, options.isUseLambdaGuards())); sb.append(renderTransition(t, target, options));
} }
} }
sb.append(" </state>\n"); sb.append(" </state>\n");
@@ -73,15 +73,18 @@ public class Scxml implements StateMachineExporter {
return ".scxml.xml"; return ".scxml.xml";
} }
private String renderTransition(Transition t, State target, boolean useLambdaGuards) { private String renderTransition(Transition t, State target, ExportOptions options) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(" <transition target=\"").append(simplify(target.toString())).append("\""); sb.append(" <transition target=\"").append(simplify(options.formatState(target))).append("\"");
if (t.getEvent() != null && !t.getEvent().isBlank()) { if (t.getEvent() != null) {
sb.append(" event=\"").append(escapeXml(t.getEvent())).append("\""); String eventStr = options.formatEvent(t.getEvent());
if (eventStr != null && !eventStr.isBlank()) {
sb.append(" event=\"").append(escapeXml(eventStr)).append("\"");
}
} }
String guard = getGuardText(t, useLambdaGuards); String guard = getGuardText(t, options.isUseLambdaGuards());
if (!guard.isBlank()) { if (!guard.isBlank()) {
sb.append(" cond=\"").append(escapeXml(guard)).append("\""); sb.append(" cond=\"").append(escapeXml(guard)).append("\"");
} }

View File

@@ -0,0 +1,16 @@
package click.kamil.springstatemachineexporter.model;
public record Event(String rawName, String fullIdentifier) {
public static Event of(String name) {
return new Event(name, name);
}
public static Event of(String raw, String full) {
return new Event(raw, full);
}
@Override
public String toString() {
return fullIdentifier != null ? fullIdentifier : rawName;
}
}

View File

@@ -14,7 +14,7 @@ public class Transition {
private TransitionType type; private TransitionType type;
private List<State> sourceStates = new ArrayList<>(); private List<State> sourceStates = new ArrayList<>();
private List<State> targetStates = new ArrayList<>(); private List<State> targetStates = new ArrayList<>();
private String event; private Event event;
private Guard guard; private Guard guard;
private List<Action> actions = new ArrayList<>(); private List<Action> actions = new ArrayList<>();

View File

@@ -44,7 +44,8 @@ public class ExportService {
new TriggerEnricher(), new TriggerEnricher(),
new EntryPointEnricher(), new EntryPointEnricher(),
new PropertyEnricher(), new PropertyEnricher(),
new CallChainEnricher() new CallChainEnricher(),
new click.kamil.springstatemachineexporter.analysis.enricher.TransitionLinkerEnricher()
))); )));
} }
@@ -52,14 +53,22 @@ public class ExportService {
public static final Set<String> STATE_MACHINE_RETURN_TYPES = Set.of("StateMachine", "StateMachineFactory", "StateMachineModelFactory"); public static final Set<String> STATE_MACHINE_RETURN_TYPES = Set.of("StateMachine", "StateMachineFactory", "StateMachineModelFactory");
public void runExporter(Path inputDir, Path outputDir, List<String> selectedFormats, boolean renderChoicesAsDiamonds) throws IOException { public void runExporter(Path inputDir, Path outputDir, List<String> selectedFormats, boolean renderChoicesAsDiamonds) throws IOException {
runExporter(inputDir, outputDir, selectedFormats, renderChoicesAsDiamonds, Collections.emptyList()); runExporter(inputDir, outputDir, selectedFormats, renderChoicesAsDiamonds, Collections.emptyList(), null, null, click.kamil.springstatemachineexporter.exporter.EnumFormat.fn, click.kamil.springstatemachineexporter.exporter.EnumFormat.fn, false);
} }
public void runExporter(Path inputDir, Path outputDir, List<String> selectedFormats, boolean renderChoicesAsDiamonds, List<String> activeProfiles) throws IOException { public void runExporter(Path inputDir, Path outputDir, List<String> selectedFormats, boolean renderChoicesAsDiamonds, List<String> activeProfiles) throws IOException {
runExporter(inputDir, outputDir, selectedFormats, renderChoicesAsDiamonds, activeProfiles, null, null); runExporter(inputDir, outputDir, selectedFormats, renderChoicesAsDiamonds, activeProfiles, null, null, click.kamil.springstatemachineexporter.exporter.EnumFormat.fn, click.kamil.springstatemachineexporter.exporter.EnumFormat.fn, false);
} }
public void runExporter(Path inputDir, Path outputDir, List<String> selectedFormats, boolean renderChoicesAsDiamonds, List<String> activeProfiles, Path flowsOverride, String machineFilter) throws IOException { public void runExporter(Path inputDir, Path outputDir, List<String> selectedFormats, boolean renderChoicesAsDiamonds, List<String> activeProfiles, Path flowsOverride, String machineFilter) throws IOException {
runExporter(inputDir, outputDir, selectedFormats, renderChoicesAsDiamonds, activeProfiles, flowsOverride, machineFilter, click.kamil.springstatemachineexporter.exporter.EnumFormat.fn, click.kamil.springstatemachineexporter.exporter.EnumFormat.fn, false);
}
public void runExporter(Path inputDir, Path outputDir, List<String> selectedFormats, boolean renderChoicesAsDiamonds, List<String> activeProfiles, click.kamil.springstatemachineexporter.exporter.EnumFormat eventFormat, click.kamil.springstatemachineexporter.exporter.EnumFormat stateFormat) throws IOException {
runExporter(inputDir, outputDir, selectedFormats, renderChoicesAsDiamonds, activeProfiles, null, null, eventFormat, stateFormat, false);
}
public void runExporter(Path inputDir, Path outputDir, List<String> selectedFormats, boolean renderChoicesAsDiamonds, List<String> activeProfiles, Path flowsOverride, String machineFilter, click.kamil.springstatemachineexporter.exporter.EnumFormat eventFormat, click.kamil.springstatemachineexporter.exporter.EnumFormat stateFormat, boolean resolveClasspath) throws IOException {
CodebaseContext context = new CodebaseContext(); CodebaseContext context = new CodebaseContext();
context.setActiveProfiles(activeProfiles); context.setActiveProfiles(activeProfiles);
@@ -85,6 +94,18 @@ public class ExportService {
.collect(Collectors.toList()); .collect(Collectors.toList());
log.info("Setting JDT sourcepath: {}", sourcepaths); log.info("Setting JDT sourcepath: {}", sourcepaths);
context.setSourcepath(sourcepaths); context.setSourcepath(sourcepaths);
if (resolveClasspath) {
click.kamil.springstatemachineexporter.analysis.service.DynamicClasspathResolver classpathResolver = new click.kamil.springstatemachineexporter.analysis.service.DynamicClasspathResolver();
List<String> dynamicClasspath = classpathResolver.resolveClasspath(projectRoot);
if (!dynamicClasspath.isEmpty()) {
context.setClasspath(dynamicClasspath);
log.info("Injected {} external JARs into JDT ASTParser", dynamicClasspath.size());
}
} else {
log.info("Dynamic classpath resolution disabled. Use --resolve-classpath to enable.");
}
context.setResolveBindings(true); context.setResolveBindings(true);
Path hintsFile = inputDir.resolve("hints.json"); Path hintsFile = inputDir.resolve("hints.json");
@@ -116,21 +137,25 @@ public class ExportService {
context.scan(allPaths, Collections.emptySet()); context.scan(allPaths, Collections.emptySet());
CodebaseIntelligenceProvider intelligence = new JdtIntelligenceProvider(context, inputDir); CodebaseIntelligenceProvider intelligence = new JdtIntelligenceProvider(context, inputDir);
exportAll(context, intelligence, outputDir, selectedFormats, renderChoicesAsDiamonds, flows, machineFilter, activeProfiles != null ? activeProfiles : Collections.emptyList()); exportAll(context, intelligence, outputDir, selectedFormats, renderChoicesAsDiamonds, flows, machineFilter, activeProfiles != null ? activeProfiles : Collections.emptyList(), eventFormat, stateFormat);
} }
public void runJsonExporter(Path jsonFile, Path outputDir, List<String> selectedFormats) throws IOException { public void runJsonExporter(Path jsonFile, Path outputDir, List<String> selectedFormats) throws IOException {
runJsonExporter(jsonFile, outputDir, selectedFormats, Collections.emptyList()); runJsonExporter(jsonFile, outputDir, selectedFormats, Collections.emptyList(), click.kamil.springstatemachineexporter.exporter.EnumFormat.fn, click.kamil.springstatemachineexporter.exporter.EnumFormat.fn);
} }
public void runJsonExporter(Path jsonFile, Path outputDir, List<String> selectedFormats, List<String> activeProfiles) throws IOException { public void runJsonExporter(Path jsonFile, Path outputDir, List<String> selectedFormats, List<String> activeProfiles) throws IOException {
runJsonExporter(jsonFile, outputDir, selectedFormats, activeProfiles, click.kamil.springstatemachineexporter.exporter.EnumFormat.fn, click.kamil.springstatemachineexporter.exporter.EnumFormat.fn);
}
public void runJsonExporter(Path jsonFile, Path outputDir, List<String> selectedFormats, List<String> activeProfiles, click.kamil.springstatemachineexporter.exporter.EnumFormat eventFormat, click.kamil.springstatemachineexporter.exporter.EnumFormat stateFormat) throws IOException {
JsonImportService jsonImportService = new JsonImportService(); JsonImportService jsonImportService = new JsonImportService();
AnalysisResult result = jsonImportService.importAnalysisResult(jsonFile); AnalysisResult result = jsonImportService.importAnalysisResult(jsonFile);
// Apply property resolution pass if placeholders exist // Apply property resolution pass if placeholders exist
resolveProperties(result, activeProfiles); resolveProperties(result, activeProfiles);
generateOutputs(outputDir, result, selectedFormats); generateOutputs(outputDir, result, selectedFormats, eventFormat, stateFormat);
} }
private void resolveProperties(AnalysisResult result, List<String> activeProfiles) { private void resolveProperties(AnalysisResult result, List<String> activeProfiles) {
@@ -154,7 +179,7 @@ public class ExportService {
result.applyResolution(merged); result.applyResolution(merged);
} }
private void exportAll(CodebaseContext context, CodebaseIntelligenceProvider intelligence, Path outputDir, List<String> selectedFormats, boolean renderChoicesAsDiamonds, List<BusinessFlow> flows, String machineFilter, List<String> activeProfiles) throws IOException { private void exportAll(CodebaseContext context, CodebaseIntelligenceProvider intelligence, Path outputDir, List<String> selectedFormats, boolean renderChoicesAsDiamonds, List<BusinessFlow> flows, String machineFilter, List<String> activeProfiles, click.kamil.springstatemachineexporter.exporter.EnumFormat eventFormat, click.kamil.springstatemachineexporter.exporter.EnumFormat stateFormat) throws IOException {
Set<String> processedLocations = new HashSet<>(); Set<String> processedLocations = new HashSet<>();
// 1. Find entry point classes (annotated or extending adapter) // 1. Find entry point classes (annotated or extending adapter)
@@ -163,7 +188,7 @@ public class ExportService {
String fqn = context.getFqn(td); String fqn = context.getFqn(td);
if (machineFilter != null && !fqn.contains(machineFilter)) continue; if (machineFilter != null && !fqn.contains(machineFilter)) continue;
if (processedLocations.add(fqn)) { if (processedLocations.add(fqn)) {
processEntryPoint(td, outputDir, context, intelligence, selectedFormats, renderChoicesAsDiamonds, flows, activeProfiles); processEntryPoint(td, outputDir, context, intelligence, selectedFormats, renderChoicesAsDiamonds, flows, activeProfiles, eventFormat, stateFormat);
} }
} }
@@ -177,12 +202,12 @@ public class ExportService {
if (machineFilter != null && !uniqueId.contains(machineFilter)) continue; if (machineFilter != null && !uniqueId.contains(machineFilter)) continue;
if (processedLocations.add(uniqueId)) { if (processedLocations.add(uniqueId)) {
processBeanMethod(m, outputDir, context, intelligence, selectedFormats, renderChoicesAsDiamonds, flows, activeProfiles); processBeanMethod(m, outputDir, context, intelligence, selectedFormats, renderChoicesAsDiamonds, flows, activeProfiles, eventFormat, stateFormat);
} }
} }
} }
private void processEntryPoint(TypeDeclaration td, Path outputDir, CodebaseContext context, CodebaseIntelligenceProvider intelligence, List<String> selectedFormats, boolean renderChoicesAsDiamonds, List<BusinessFlow> flows, List<String> activeProfiles) throws IOException { private void processEntryPoint(TypeDeclaration td, Path outputDir, CodebaseContext context, CodebaseIntelligenceProvider intelligence, List<String> selectedFormats, boolean renderChoicesAsDiamonds, List<BusinessFlow> flows, List<String> activeProfiles, click.kamil.springstatemachineexporter.exporter.EnumFormat eventFormat, click.kamil.springstatemachineexporter.exporter.EnumFormat stateFormat) throws IOException {
String className = context.getFqn(td); String className = context.getFqn(td);
log.info("Processing state machine config: {}", className); log.info("Processing state machine config: {}", className);
@@ -200,6 +225,11 @@ public class ExportService {
Set<String> endStates = TransitionStateUtils.findEndStates(transitions, endStatesAst); Set<String> endStates = TransitionStateUtils.findEndStates(transitions, endStatesAst);
Set<click.kamil.springstatemachineexporter.model.State> allStates = TransitionStateUtils.findAllStates(transitions, initialStatesAst, endStatesAst); Set<click.kamil.springstatemachineexporter.model.State> allStates = TransitionStateUtils.findAllStates(transitions, initialStatesAst, endStatesAst);
if (allStates.isEmpty() && transitions.isEmpty()) {
log.info("Skipping empty state machine config: {}", className);
return;
}
AnalysisResult result = AnalysisResult.builder() AnalysisResult result = AnalysisResult.builder()
.name(className) .name(className)
.states(allStates) .states(allStates)
@@ -214,10 +244,10 @@ public class ExportService {
resolveProperties(result, activeProfiles); resolveProperties(result, activeProfiles);
generateOutputs(outputDir, result, selectedFormats); generateOutputs(outputDir, result, selectedFormats, eventFormat, stateFormat);
} }
private void processBeanMethod(MethodDeclaration m, Path outputDir, CodebaseContext context, CodebaseIntelligenceProvider intelligence, List<String> selectedFormats, boolean renderChoicesAsDiamonds, List<BusinessFlow> flows, List<String> activeProfiles) throws IOException { private void processBeanMethod(MethodDeclaration m, Path outputDir, CodebaseContext context, CodebaseIntelligenceProvider intelligence, List<String> selectedFormats, boolean renderChoicesAsDiamonds, List<BusinessFlow> flows, List<String> activeProfiles, click.kamil.springstatemachineexporter.exporter.EnumFormat eventFormat, click.kamil.springstatemachineexporter.exporter.EnumFormat stateFormat) throws IOException {
TypeDeclaration parentClass = (TypeDeclaration) m.getParent(); TypeDeclaration parentClass = (TypeDeclaration) m.getParent();
String parentFqn = context.getFqn(parentClass); String parentFqn = context.getFqn(parentClass);
String methodName = m.getName().getIdentifier(); String methodName = m.getName().getIdentifier();
@@ -231,6 +261,11 @@ public class ExportService {
Set<String> endStates = TransitionStateUtils.findEndStates(transitions, null); Set<String> endStates = TransitionStateUtils.findEndStates(transitions, null);
Set<click.kamil.springstatemachineexporter.model.State> allStates = TransitionStateUtils.findAllStates(transitions, null, null); Set<click.kamil.springstatemachineexporter.model.State> allStates = TransitionStateUtils.findAllStates(transitions, null, null);
if (allStates.isEmpty() && transitions.isEmpty()) {
log.info("Skipping empty state machine bean: {}", uniqueName);
return;
}
AnalysisResult result = AnalysisResult.builder() AnalysisResult result = AnalysisResult.builder()
.name(uniqueName) .name(uniqueName)
.states(allStates) .states(allStates)
@@ -245,10 +280,10 @@ public class ExportService {
resolveProperties(result, activeProfiles); resolveProperties(result, activeProfiles);
generateOutputs(outputDir, result, selectedFormats); generateOutputs(outputDir, result, selectedFormats, eventFormat, stateFormat);
} }
private void generateOutputs(Path outputDir, AnalysisResult result, List<String> selectedFormats) throws IOException { private void generateOutputs(Path outputDir, AnalysisResult result, List<String> selectedFormats, click.kamil.springstatemachineexporter.exporter.EnumFormat eventFormat, click.kamil.springstatemachineexporter.exporter.EnumFormat stateFormat) throws IOException {
if (selectedFormats == null || selectedFormats.isEmpty()) { if (selectedFormats == null || selectedFormats.isEmpty()) {
return; return;
} }
@@ -259,6 +294,8 @@ public class ExportService {
ExportOptions options = ExportOptions.builder() ExportOptions options = ExportOptions.builder()
.renderChoicesAsDiamonds(result.isRenderChoicesAsDiamonds()) .renderChoicesAsDiamonds(result.isRenderChoicesAsDiamonds())
.eventFormat(eventFormat)
.stateFormat(stateFormat)
.build(); .build();
for (StateMachineExporter exporter : exporters) { for (StateMachineExporter exporter : exporters) {

View File

@@ -23,8 +23,7 @@ import java.util.List;
public class GoldenUpdater { public class GoldenUpdater {
private static final List<StateMachineExporter> exporters = List.of(new PlantUml(), new Dot(), new Scxml(), new JsonExporter()); private static final List<StateMachineExporter> exporters = List.of(new PlantUml(), new Dot(), new Scxml(), new JsonExporter());
private static final EnrichmentService enrichmentService = new EnrichmentService(Collections.emptyList()); private static final ExportService exportService = new ExportService(exporters);
private static final ExportService exportService = new ExportService(exporters, enrichmentService);
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
List<TestScenario> scenarios = provideTestScenarios(); List<TestScenario> scenarios = provideTestScenarios();
@@ -34,7 +33,7 @@ public class GoldenUpdater {
System.out.println("Updating golden files for: " + scenario.name()); System.out.println("Updating golden files for: " + scenario.name());
Path tempDir = Files.createTempDirectory("golden-update-"); Path tempDir = Files.createTempDirectory("golden-update-");
try { try {
exportService.runExporter(scenario.inputPath(), tempDir, null, true); exportService.runExporter(scenario.inputPath(), tempDir, List.of("puml", "dot", "scxml", "json"), true);
List<Path> generatedDirs; List<Path> generatedDirs;
try (var stream = Files.list(tempDir)) { try (var stream = Files.list(tempDir)) {

View File

@@ -137,6 +137,12 @@ public class RegressionTest {
root.resolve("state_machines/maven_multi_module/core-module"), root.resolve("state_machines/maven_multi_module/core-module"),
Path.of("src/test/resources/golden/MavenOrderStateMachine"), Path.of("src/test/resources/golden/MavenOrderStateMachine"),
"MavenOrderStateMachine" "MavenOrderStateMachine"
),
new TestScenario(
"Complex Multi-Module Sample",
root.resolve("state_machines/complex_multi_module_sm"),
Path.of("src/test/resources/golden/StateMachineConfig"),
"StateMachineConfig"
) )
); );
} }

View File

@@ -4,7 +4,7 @@ import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint; import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint; import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver; import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver;
import click.kamil.springstatemachineexporter.analysis.service.CallGraphBuilder; import click.kamil.springstatemachineexporter.analysis.service.HeuristicCallGraphEngine;
import click.kamil.springstatemachineexporter.analysis.service.GenericEventDetector; import click.kamil.springstatemachineexporter.analysis.service.GenericEventDetector;
import click.kamil.springstatemachineexporter.analysis.service.SpringMvcDetector; import click.kamil.springstatemachineexporter.analysis.service.SpringMvcDetector;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext; import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
@@ -76,7 +76,7 @@ public class InheritedEventDetectionTest {
assertThat(tp.getEvent()).isEqualTo("event"); assertThat(tp.getEvent()).isEqualTo("event");
// Build Call Chains // Build Call Chains
CallGraphBuilder callGraphBuilder = new CallGraphBuilder(context); HeuristicCallGraphEngine callGraphBuilder = new HeuristicCallGraphEngine(context);
List<CallChain> chains = callGraphBuilder.findChains(entryPoints, triggers); List<CallChain> chains = callGraphBuilder.findChains(entryPoints, triggers);
assertThat(chains).describedAs("Should link ChildController.trigger to BaseController.send").hasSize(1); assertThat(chains).describedAs("Should link ChildController.trigger to BaseController.send").hasSize(1);

View File

@@ -0,0 +1,411 @@
package click.kamil.springstatemachineexporter.analysis.enricher;
import click.kamil.springstatemachineexporter.analysis.model.AnalysisResult;
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.analysis.model.CodebaseMetadata;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.model.Event;
import click.kamil.springstatemachineexporter.model.State;
import click.kamil.springstatemachineexporter.model.Transition;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
class TransitionLinkerEnricherTest {
private final TransitionLinkerEnricher enricher = new TransitionLinkerEnricher();
@Test
void shouldLinkTransitionByEventOnly() {
Transition t1 = new Transition();
t1.setSourceStates(List.of(State.of("NEW", "NEW")));
t1.setTargetStates(List.of(State.of("PAID", "PAID")));
t1.setEvent(Event.of("PAY", "PAY"));
Transition t2 = new Transition();
t2.setSourceStates(List.of(State.of("FAILED", "FAILED")));
t2.setTargetStates(List.of(State.of("PAID", "PAID")));
t2.setEvent(Event.of("PAY", "PAY"));
CallChain chain = CallChain.builder()
.triggerPoint(TriggerPoint.builder().event("PAY").build())
.build();
AnalysisResult result = AnalysisResult.builder()
.transitions(List.of(t1, t2))
.metadata(CodebaseMetadata.builder().callChains(List.of(chain)).build())
.build();
enricher.enrich(result, null, null);
CallChain updatedChain = result.getMetadata().getCallChains().get(0);
assertThat(updatedChain.getMatchedTransitions()).hasSize(2);
assertThat(updatedChain.getMatchedTransitions())
.extracting("sourceState")
.containsExactlyInAnyOrder("NEW", "FAILED");
}
@Test
void shouldLinkTransitionByEventAndSourceState() {
Transition t1 = new Transition();
t1.setSourceStates(List.of(State.of("NEW", "NEW")));
t1.setTargetStates(List.of(State.of("PAID", "PAID")));
t1.setEvent(Event.of("PAY", "PAY"));
Transition t2 = new Transition();
t2.setSourceStates(List.of(State.of("FAILED", "FAILED")));
t2.setTargetStates(List.of(State.of("PAID", "PAID")));
t2.setEvent(Event.of("PAY", "PAY"));
CallChain chain = CallChain.builder()
.triggerPoint(TriggerPoint.builder().event("PAY").sourceState("NEW").build())
.build();
AnalysisResult result = AnalysisResult.builder()
.transitions(List.of(t1, t2))
.metadata(CodebaseMetadata.builder().callChains(List.of(chain)).build())
.build();
enricher.enrich(result, null, null);
CallChain updatedChain = result.getMetadata().getCallChains().get(0);
assertThat(updatedChain.getMatchedTransitions()).hasSize(1);
assertThat(updatedChain.getMatchedTransitions().get(0).getSourceState()).isEqualTo("NEW");
assertThat(updatedChain.getMatchedTransitions().get(0).getTargetState()).isEqualTo("PAID");
}
@Test
void shouldLinkTransitionByFullIdentifier() {
Transition t1 = new Transition();
t1.setSourceStates(List.of(State.of("\"NEW\"", "NEW")));
t1.setTargetStates(List.of(State.of("\"PAID\"", "PAID")));
// This simulates how Spring Config usually has Event as OrderEvents.PAY but its resolved string is "PAY_ORDER"
t1.setEvent(Event.of("OrderEvents.PAY", "PAY_ORDER"));
CallChain chain = CallChain.builder()
.triggerPoint(TriggerPoint.builder().event("PAY_ORDER").sourceState("NEW").build())
.build();
AnalysisResult result = AnalysisResult.builder()
.transitions(List.of(t1))
.metadata(CodebaseMetadata.builder().callChains(List.of(chain)).build())
.build();
enricher.enrich(result, null, null);
CallChain updatedChain = result.getMetadata().getCallChains().get(0);
assertThat(updatedChain.getMatchedTransitions()).hasSize(1);
assertThat(updatedChain.getMatchedTransitions().get(0).getSourceState()).isEqualTo("NEW");
assertThat(updatedChain.getMatchedTransitions().get(0).getTargetState()).isEqualTo("PAID");
assertThat(updatedChain.getMatchedTransitions().get(0).getEvent()).isEqualTo("PAY_ORDER");
}
@Test
void shouldHandleUnknownEvents() {
CallChain chain = CallChain.builder()
.triggerPoint(TriggerPoint.builder().event(null).build()) // Dynamic or unresolved event
.build();
AnalysisResult result = AnalysisResult.builder()
.transitions(List.of(new Transition()))
.metadata(CodebaseMetadata.builder().callChains(List.of(chain)).build())
.build();
enricher.enrich(result, null, null);
CallChain updatedChain = result.getMetadata().getCallChains().get(0);
assertThat(updatedChain.getMatchedTransitions()).isNull();
}
@Test
void shouldMatchWildcardVariable() {
Transition t1 = new Transition();
t1.setSourceStates(List.of(State.of("NEW", "NEW")));
t1.setTargetStates(List.of(State.of("PAID", "PAID")));
t1.setEvent(Event.of("PAY", "PAY"));
CallChain chain = CallChain.builder()
.triggerPoint(TriggerPoint.builder().event("event").build())
.contextMachineId("testMachine")
.build();
AnalysisResult result = AnalysisResult.builder()
.name("testMachine")
.transitions(List.of(t1))
.metadata(CodebaseMetadata.builder().callChains(List.of(chain)).build())
.build();
enricher.enrich(result, null, null);
CallChain updatedChain = result.getMetadata().getCallChains().get(0);
assertThat(updatedChain.getMatchedTransitions()).hasSize(1);
}
@Test
void shouldMatchBaseEventDespiteScrapedPolyEvents() {
Transition t1 = new Transition();
t1.setSourceStates(List.of(State.of("NEW", "NEW")));
t1.setTargetStates(List.of(State.of("PAID", "PAID")));
// State machine uses the base event generically
t1.setEvent(Event.of("BaseEvent", "BaseEvent"));
// Trigger point uses "BaseEvent", but polyEvents scraped some implementation/constants
CallChain chain = CallChain.builder()
.triggerPoint(TriggerPoint.builder()
.event("BaseEvent")
.polymorphicEvents(List.of("EVENT_A", "EVENT_B"))
.build())
.contextMachineId("testMachine")
.build();
AnalysisResult result = AnalysisResult.builder()
.name("testMachine")
.transitions(List.of(t1))
.metadata(CodebaseMetadata.builder().callChains(List.of(chain)).build())
.build();
enricher.enrich(result, null, null);
// It should match because smEvent matches triggerEvent directly, overriding the failed poly search
CallChain updatedChain = result.getMetadata().getCallChains().get(0);
assertThat(updatedChain.getMatchedTransitions()).hasSize(1);
}
@Test
void shouldNotMatchArbitraryGetXMethodWildcard() {
Transition t1 = new Transition();
t1.setSourceStates(List.of(State.of("NEW", "NEW")));
t1.setTargetStates(List.of(State.of("PAID", "PAID")));
t1.setEvent(Event.of("PAY", "PAY"));
CallChain chain = CallChain.builder()
.triggerPoint(TriggerPoint.builder().event("richEvent.getId()").build())
.contextMachineId("testMachine")
.build();
AnalysisResult result = AnalysisResult.builder()
.name("testMachine")
.transitions(List.of(t1))
.metadata(CodebaseMetadata.builder().callChains(List.of(chain)).build())
.build();
enricher.enrich(result, null, null);
CallChain updatedChain = result.getMetadata().getCallChains().get(0);
assertThat(updatedChain.getMatchedTransitions()).isNull();
}
@Test
void shouldAllowWildcardWhenContextMachineIdIsMissing() {
Transition t1 = new Transition();
t1.setSourceStates(List.of(State.of("NEW", "NEW")));
t1.setTargetStates(List.of(State.of("PAID", "PAID")));
t1.setEvent(Event.of("PAY", "PAY"));
CallChain chain = CallChain.builder()
.triggerPoint(TriggerPoint.builder().event("event").build())
// NO contextMachineId or stateMachineId
.build();
AnalysisResult result = AnalysisResult.builder()
.name("testMachine")
.transitions(List.of(t1))
.metadata(CodebaseMetadata.builder().callChains(List.of(chain)).build())
.build();
enricher.enrich(result, null, null);
CallChain updatedChain = result.getMetadata().getCallChains().get(0);
assertThat(updatedChain.getMatchedTransitions()).hasSize(1);
}
@Test
void shouldNotFalsePositiveOnSubstringContains() {
// This test proves that the heuristic is entirely removed and false positives are eliminated
Transition t1 = new Transition();
t1.setSourceStates(List.of(State.of("NEW", "NEW")));
t1.setTargetStates(List.of(State.of("PAID", "PAID")));
t1.setEvent(Event.of("PAY", "PAY"));
// Trigger point sends "PAYMENT" which contains "PAY". Under the old heuristics, this would falsely match!
CallChain chain = CallChain.builder()
.triggerPoint(TriggerPoint.builder().event("PAYMENT").build())
.build();
AnalysisResult result = AnalysisResult.builder()
.transitions(List.of(t1))
.metadata(CodebaseMetadata.builder().callChains(List.of(chain)).build())
.build();
enricher.enrich(result, null, null);
// It should NOT match, because "PAYMENT" != "PAY" strictly.
CallChain updatedChain = result.getMetadata().getCallChains().get(0);
assertThat(updatedChain.getMatchedTransitions()).isNullOrEmpty();
}
@Test
void shouldFilterCrossStateMachineRoutingByVertical() {
Transition t1 = new Transition();
t1.setSourceStates(List.of(State.of("NEW", "NEW")));
t1.setTargetStates(List.of(State.of("ASSEMBLED", "ASSEMBLED")));
t1.setEvent(Event.of("ASSEMBLE", "ASSEMBLE"));
CallChain chain = CallChain.builder()
.triggerPoint(TriggerPoint.builder().event("ASSEMBLE").build())
.methodChain(List.of(
"com.example.electronics.ElectronicsOrderController.post()",
"com.example.electronics.ElectronicsOrderService.processOrderEvent()"
))
.build();
// Testing against Electronics SM - should match
AnalysisResult resultElectronics = AnalysisResult.builder()
.name("com.example.electronics.ElectronicsOwnDeliveryStateMachineConfiguration")
.transitions(List.of(t1))
.metadata(CodebaseMetadata.builder().callChains(List.of(chain)).build())
.build();
enricher.enrich(resultElectronics, null, null);
CallChain updatedChainElec = resultElectronics.getMetadata().getCallChains().get(0);
assertThat(updatedChainElec.getMatchedTransitions()).hasSize(1);
// Testing against ComputerStore SM - should NOT match because chain has Electronics service
AnalysisResult resultComputer = AnalysisResult.builder()
.name("com.example.computerstore.OwnDeliveryStateMachineConfiguration") // the non-electronics one
.transitions(List.of(t1))
.metadata(CodebaseMetadata.builder().callChains(List.of(chain)).build())
.build();
enricher.enrich(resultComputer, null, null);
CallChain updatedChainComp = resultComputer.getMetadata().getCallChains().get(0);
assertThat(updatedChainComp.getMatchedTransitions()).isNullOrEmpty();
}
@Test
void shouldAllowSharedServicesToRouteToMultipleStateMachines() {
Transition t1 = new Transition();
t1.setSourceStates(List.of(State.of("NEW", "NEW")));
t1.setTargetStates(List.of(State.of("PROCESSED", "PROCESSED")));
t1.setEvent(Event.of("PROCESS", "PROCESS"));
// Chain contains NO vertical prefix (e.g. CommonOrderService)
CallChain chain = CallChain.builder()
.triggerPoint(TriggerPoint.builder().event("PROCESS").build())
.methodChain(List.of(
"com.example.CommonOrderController.post()",
"com.example.CommonOrderService.processOrderEvent()"
))
.build();
AnalysisResult resultElectronics = AnalysisResult.builder()
.name("com.example.electronics.ElectronicsOwnDeliveryStateMachineConfiguration")
.transitions(List.of(t1))
.metadata(CodebaseMetadata.builder().callChains(List.of(chain)).build())
.build();
enricher.enrich(resultElectronics, null, null);
CallChain updatedChainElec = resultElectronics.getMetadata().getCallChains().get(0);
// Should match Electronics because it's a shared service (no computerstore service in path)
assertThat(updatedChainElec.getMatchedTransitions()).hasSize(1);
AnalysisResult resultComputer = AnalysisResult.builder()
.name("com.example.computerstore.OwnDeliveryStateMachineConfiguration")
.transitions(List.of(t1))
.metadata(CodebaseMetadata.builder().callChains(List.of(chain)).build())
.build();
enricher.enrich(resultComputer, null, null);
CallChain updatedChainComp = resultComputer.getMetadata().getCallChains().get(0);
// Should ALSO match ComputerStore because it's a shared service
assertThat(updatedChainComp.getMatchedTransitions()).hasSize(1);
}
@Test
void shouldFilterCrossStateMachineRoutingWithMultipleVerticals() {
Transition t1 = new Transition();
t1.setSourceStates(List.of(State.of("NEW", "NEW")));
t1.setTargetStates(List.of(State.of("ASSEMBLED", "ASSEMBLED")));
t1.setEvent(Event.of("ASSEMBLE", "ASSEMBLE"));
// Chain 1: Electronics
CallChain chainElec = CallChain.builder()
.triggerPoint(TriggerPoint.builder().event("ASSEMBLE").build())
.methodChain(List.of("com.example.electronics.ElectronicsOrderController.post()"))
.build();
// Chain 2: Furniture
CallChain chainFurn = CallChain.builder()
.triggerPoint(TriggerPoint.builder().event("ASSEMBLE").build())
.methodChain(List.of("com.example.furniture.FurnitureOrderController.post()"))
.build();
// Chain 3: Groceries
CallChain chainGroc = CallChain.builder()
.triggerPoint(TriggerPoint.builder().event("ASSEMBLE").build())
.methodChain(List.of("com.example.groceries.GroceriesOrderController.post()"))
.build();
// SM 1: Electronics
AnalysisResult resElec = AnalysisResult.builder()
.name("com.example.electronics.ElectronicsOwnDeliveryStateMachineConfiguration")
.transitions(List.of(t1))
.metadata(CodebaseMetadata.builder().callChains(List.of(chainElec, chainFurn, chainGroc)).build())
.build();
// SM 2: Furniture
AnalysisResult resFurn = AnalysisResult.builder()
.name("com.example.furniture.FurnitureOwnDeliveryStateMachineConfiguration")
.transitions(List.of(t1))
.metadata(CodebaseMetadata.builder().callChains(List.of(chainElec, chainFurn, chainGroc)).build())
.build();
// SM 3: Groceries
AnalysisResult resGroc = AnalysisResult.builder()
.name("com.example.groceries.GroceriesStateMachineConfiguration")
.transitions(List.of(t1))
.metadata(CodebaseMetadata.builder().callChains(List.of(chainElec, chainFurn, chainGroc)).build())
.build();
// SM 4: Computer Store (The catch-all base one in computerstore package)
AnalysisResult resComp = AnalysisResult.builder()
.name("com.example.computerstore.OwnDeliveryStateMachineConfiguration")
.transitions(List.of(t1))
.metadata(CodebaseMetadata.builder().callChains(List.of(chainElec, chainFurn, chainGroc)).build())
.build();
// Test Electronics SM - Should only keep Electronics Chain
enricher.enrich(resElec, null, null);
assertThat(resElec.getMetadata().getCallChains().get(0).getMatchedTransitions()).hasSize(1); // Elec
assertThat(resElec.getMetadata().getCallChains().get(1).getMatchedTransitions()).isNullOrEmpty(); // Furn
assertThat(resElec.getMetadata().getCallChains().get(2).getMatchedTransitions()).isNullOrEmpty(); // Groc
// Test Furniture SM - Should only keep Furniture Chain
enricher.enrich(resFurn, null, null);
assertThat(resFurn.getMetadata().getCallChains().get(0).getMatchedTransitions()).isNullOrEmpty(); // Elec
assertThat(resFurn.getMetadata().getCallChains().get(1).getMatchedTransitions()).hasSize(1); // Furn
assertThat(resFurn.getMetadata().getCallChains().get(2).getMatchedTransitions()).isNullOrEmpty(); // Groc
// Test Groceries SM - Should only keep Groceries Chain
enricher.enrich(resGroc, null, null);
assertThat(resGroc.getMetadata().getCallChains().get(0).getMatchedTransitions()).isNullOrEmpty(); // Elec
assertThat(resGroc.getMetadata().getCallChains().get(1).getMatchedTransitions()).isNullOrEmpty(); // Furn
assertThat(resGroc.getMetadata().getCallChains().get(2).getMatchedTransitions()).hasSize(1); // Groc
// Test ComputerStore SM - Should reject ALL because none belong to computerstore
enricher.enrich(resComp, null, null);
assertThat(resComp.getMetadata().getCallChains().get(0).getMatchedTransitions()).isNullOrEmpty(); // Elec
assertThat(resComp.getMetadata().getCallChains().get(1).getMatchedTransitions()).isNullOrEmpty(); // Furn
assertThat(resComp.getMetadata().getCallChains().get(2).getMatchedTransitions()).isNullOrEmpty(); // Groc
}
}

View File

@@ -0,0 +1,103 @@
package click.kamil.springstatemachineexporter.analysis.enricher.routing;
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.*;
@Tag("heuristic_bean_resolution")
public class HeuristicBeanResolutionEngineTest {
private final HeuristicBeanResolutionEngine engine = new HeuristicBeanResolutionEngine();
@Test
public void testDeepPackageDivergenceMismatch() {
// This is the bug case: sharing a deep root, but diverging into different domains
CallChain chain = CallChain.builder()
.methodChain(Arrays.asList("com.acme.corp.division.project.orders.OrderService.pay()"))
.build();
String machineName = "com.acme.corp.division.project.payments.PaymentStateMachine";
boolean result = engine.isRoutedToCorrectMachine(chain, machineName);
// They share com.acme.corp.division.project, but diverge into orders vs payments
// This should be a strong mismatch, so it should return false
assertFalse(result, "Deep package divergence into different domains should be rejected");
}
@Test
public void testSameDomainMatch() {
CallChain chain = CallChain.builder()
.methodChain(Arrays.asList("com.acme.ecommerce.orders.OrderService.process()"))
.build();
String machineName = "com.acme.ecommerce.orders.OrderStateMachine";
boolean result = engine.isRoutedToCorrectMachine(chain, machineName);
// Same exact domain, should match
assertTrue(result, "Same domain should be accepted");
}
@Test
public void testSubPackageMatch() {
CallChain chain = CallChain.builder()
.methodChain(Arrays.asList("com.acme.ecommerce.orders.impl.OrderServiceImpl.process()"))
.build();
String machineName = "com.acme.ecommerce.orders.OrderStateMachine";
boolean result = engine.isRoutedToCorrectMachine(chain, machineName);
// Subpackage should match
assertTrue(result, "Sub-packages of the same domain should be accepted");
}
@Test
public void testSharedDomainTermMatch() {
CallChain chain = CallChain.builder()
.methodChain(Arrays.asList("com.acme.orders.web.OrderController.submit()"))
.build();
String machineName = "com.acme.orders.service.OrderStateMachine";
boolean result = engine.isRoutedToCorrectMachine(chain, machineName);
// Different subpackages, but they share the "order" domain term and common prefix
assertTrue(result, "Divergent packages that share a domain term should be accepted");
}
@Test
public void testExplicitTargetVariableMatch() {
CallChain chain = CallChain.builder()
.methodChain(Arrays.asList("com.acme.common.GlobalTrigger.fire()"))
.contextMachineId("paymentStateMachine") // Explicitly targets payment
.build();
String machineName = "com.acme.corp.payments.PaymentStateMachine";
boolean result = engine.isRoutedToCorrectMachine(chain, machineName);
// Mismatched domains, but explicit variable targeting works
assertTrue(result, "Explicit variable target matching the machine name should be accepted");
}
@Test
public void testExplicitTargetVariableMismatch() {
CallChain chain = CallChain.builder()
.methodChain(Arrays.asList("com.acme.common.GlobalTrigger.fire()"))
.contextMachineId("paymentStateMachine") // Explicitly targets payment
.build();
String machineName = "com.acme.corp.orders.OrderStateMachine"; // But this is Order
boolean result = engine.isRoutedToCorrectMachine(chain, machineName);
// Explicit variable targeting completely conflicts
assertFalse(result, "Explicit variable target mismatching the machine name should be rejected");
}
}

View File

@@ -0,0 +1,60 @@
package click.kamil.springstatemachineexporter.analysis.resolver;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
class PropertyResolverTest {
@Test
void testYamlPropertiesAreLoadedAndFlattened(@TempDir Path tempDir) throws IOException {
Path applicationYml = tempDir.resolve("application.yml");
Files.writeString(applicationYml, """
messaging:
sqs:
integration-system1:
callback:
queue: "integration-system1-queue"
""");
PropertyResolver resolver = new PropertyResolver();
Map<String, String> props = resolver.resolveProperties(tempDir);
assertThat(props).containsEntry("messaging.sqs.integration-system1.callback.queue", "integration-system1-queue");
}
@Test
void testPropertiesFilesAreLoaded(@TempDir Path tempDir) throws IOException {
Path applicationProps = tempDir.resolve("application.properties");
Files.writeString(applicationProps, "messaging.sqs.integration-system1.callback.queue=integration-system1-queue-props");
PropertyResolver resolver = new PropertyResolver();
Map<String, String> props = resolver.resolveProperties(tempDir);
assertThat(props).containsEntry("messaging.sqs.integration-system1.callback.queue", "integration-system1-queue-props");
}
@Test
void testPlaceholderResolution() {
PropertyResolver resolver = new PropertyResolver();
Map<String, String> properties = Map.of("messaging.sqs.integration-system1.callback.queue", "integration-system1-queue");
String resolved = resolver.resolveValue("SQS: ${messaging.sqs.integration-system1.callback.queue}", properties);
assertThat(resolved).isEqualTo("SQS: integration-system1-queue");
}
@Test
void testPlaceholderResolutionWithDefault() {
PropertyResolver resolver = new PropertyResolver();
Map<String, String> properties = Map.of();
String resolved = resolver.resolveValue("SQS: ${messaging.sqs.queue:default-queue}", properties);
assertThat(resolved).isEqualTo("SQS: default-queue");
}
}

View File

@@ -0,0 +1,78 @@
package click.kamil.springstatemachineexporter.analysis.service;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@Tag("dynamic_classpath_resolver")
class DynamicClasspathResolverTest {
@Test
void shouldResolveMavenClasspathDynamically(@TempDir Path tempDir) throws IOException {
// Create dummy pom.xml
Files.createFile(tempDir.resolve("pom.xml"));
// Mock the resolver so it doesn't actually run maven, but writes the expected output file
DynamicClasspathResolver resolver = new DynamicClasspathResolver() {
@Override
protected void runProcess(ProcessBuilder pb) throws IOException {
// Pretend maven executed and generated the cp.txt
String cp = "/fake/m2/repo/spring-core.jar" + File.pathSeparator + "/fake/m2/repo/spring-context.jar";
String outputFileArg = pb.command().stream()
.filter(arg -> arg.startsWith("-Dmdep.outputFile="))
.findFirst()
.orElseThrow();
Path outputFile = Path.of(outputFileArg.substring("-Dmdep.outputFile=".length()));
Files.writeString(outputFile, cp);
}
};
List<String> classpath = resolver.resolveClasspath(tempDir);
assertThat(classpath).containsExactly(
"/fake/m2/repo/spring-core.jar",
"/fake/m2/repo/spring-context.jar"
);
}
@Test
void shouldResolveGradleClasspathDynamically(@TempDir Path tempDir) throws IOException {
// Create dummy build.gradle
Files.createFile(tempDir.resolve("build.gradle"));
DynamicClasspathResolver resolver = new DynamicClasspathResolver() {
@Override
protected String runProcessAndGetOutput(ProcessBuilder pb) {
// Pretend gradle executed and printed the marker
String cp = "/fake/gradle/caches/spring-core.jar" + File.pathSeparator + "/fake/gradle/caches/spring-context.jar";
return "Some noisy gradle output\n" +
"SME_CLASSPATH_MARKER:" + cp + "\n" +
"BUILD SUCCESSFUL\n";
}
};
List<String> classpath = resolver.resolveClasspath(tempDir);
assertThat(classpath).containsExactly(
"/fake/gradle/caches/spring-core.jar",
"/fake/gradle/caches/spring-context.jar"
);
}
@Test
void shouldReturnEmptyListIfNoBuildFileFound(@TempDir Path tempDir) {
DynamicClasspathResolver resolver = new DynamicClasspathResolver();
List<String> classpath = resolver.resolveClasspath(tempDir);
assertThat(classpath).isEmpty();
}
}

View File

@@ -0,0 +1,132 @@
package click.kamil.springstatemachineexporter.analysis.service;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@Tag("control_flow_sibling_avoidance")
class GenericEventDetectorControlFlowTest {
@Test
void shouldDetectStateFromDirectIfWrapper(@TempDir Path tempDir) throws IOException {
String source = """
package com.example;
public class OrderService {
private StateMachine sm;
public void processOrder(OrderState state) {
if (state == OrderState.PENDING) {
sm.sendEvent(OrderEvent.PAY);
}
}
}
class StateMachine {
public void sendEvent(OrderEvent e) {}
}
enum OrderState { PENDING }
enum OrderEvent { PAY }
""";
Files.writeString(tempDir.resolve("OrderService.java"), source);
CodebaseContext context = new CodebaseContext();
context.scan(tempDir);
GenericEventDetector detector = new GenericEventDetector(context, new ConstantResolver(), Collections.emptyList());
List<TriggerPoint> triggers = detector.detect((org.eclipse.jdt.core.dom.CompilationUnit) context.getTypeDeclaration("com.example.OrderService").getRoot());
assertThat(triggers).hasSize(1);
TriggerPoint tp = triggers.get(0);
assertThat(tp.getEvent()).isEqualTo("OrderEvent.PAY");
assertThat(tp.getSourceState()).isEqualTo("PENDING");
}
@Test
void shouldDetectStateFromGuardClauseSibling(@TempDir Path tempDir) throws IOException {
String source = """
package com.example;
public class OrderService {
private StateMachine sm;
public void processOrder(OrderState state) {
if (state != OrderState.PENDING) {
return;
}
sm.sendEvent(OrderEvent.PAY);
}
}
class StateMachine {
public void sendEvent(OrderEvent e) {}
}
enum OrderState { PENDING }
enum OrderEvent { PAY }
""";
Files.writeString(tempDir.resolve("OrderService.java"), source);
CodebaseContext context = new CodebaseContext();
context.scan(tempDir);
GenericEventDetector detector = new GenericEventDetector(context, new ConstantResolver(), Collections.emptyList());
List<TriggerPoint> triggers = detector.detect((org.eclipse.jdt.core.dom.CompilationUnit) context.getTypeDeclaration("com.example.OrderService").getRoot());
assertThat(triggers).hasSize(1);
TriggerPoint tp = triggers.get(0);
assertThat(tp.getEvent()).isEqualTo("OrderEvent.PAY");
assertThat(tp.getSourceState()).isEqualTo("PENDING");
}
@Test
void shouldDetectStateFromSwitchCaseSibling(@TempDir Path tempDir) throws IOException {
String source = """
package com.example;
public class OrderService {
private StateMachine sm;
public void processOrder(OrderState state) {
switch (state) {
case PENDING:
sm.sendEvent(OrderEvent.PAY);
break;
case PAID:
sm.sendEvent(OrderEvent.SHIP);
break;
}
}
}
class StateMachine {
public void sendEvent(OrderEvent e) {}
}
enum OrderState { PENDING, PAID }
enum OrderEvent { PAY, SHIP }
""";
Files.writeString(tempDir.resolve("OrderService.java"), source);
CodebaseContext context = new CodebaseContext();
context.scan(tempDir);
GenericEventDetector detector = new GenericEventDetector(context, new ConstantResolver(), Collections.emptyList());
List<TriggerPoint> triggers = detector.detect((org.eclipse.jdt.core.dom.CompilationUnit) context.getTypeDeclaration("com.example.OrderService").getRoot());
assertThat(triggers).hasSize(2);
TriggerPoint payTrigger = triggers.stream().filter(t -> t.getEvent().equals("OrderEvent.PAY")).findFirst().get();
assertThat(payTrigger.getSourceState()).isEqualTo("PENDING");
TriggerPoint shipTrigger = triggers.stream().filter(t -> t.getEvent().equals("OrderEvent.SHIP")).findFirst().get();
assertThat(shipTrigger.getSourceState()).isEqualTo("PAID");
}
}

View File

@@ -0,0 +1,114 @@
package click.kamil.springstatemachineexporter.analysis.service;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class GenericEventDetectorTest {
@Test
void testMessageBuilderVariableAssignment(@TempDir Path tempDir) throws IOException {
Path dir = tempDir.resolve("com/example");
Files.createDirectories(dir);
Files.writeString(dir.resolve("MyService.java"),
"package com.example;\n" +
"import org.springframework.messaging.support.MessageBuilder;\n" +
"import org.springframework.messaging.Message;\n" +
"import org.springframework.statemachine.StateMachine;\n" +
"public class MyService {\n" +
" private StateMachine<String, String> stateMachine;\n" +
" private void a(MyInterface myEvent) {\n" +
" Message<MyInterface> msg = MessageBuilder.withPayload(myEvent).setHeader(\"k\", \"v\").build();\n" +
" stateMachine.sendEvent(Mono.just(msg));\n" +
" }\n" +
"}\n");
CodebaseContext context = new CodebaseContext();
context.scan(tempDir);
GenericEventDetector detector = new GenericEventDetector(context, new ConstantResolver(), List.of());
CompilationUnit cu = context.getCompilationUnits().iterator().next();
List<TriggerPoint> triggers = detector.detect(cu);
assertThat(triggers).hasSize(1);
assertThat(triggers.get(0).getEvent()).isEqualTo("myEvent");
assertThat(triggers.get(0).getMethodName()).isEqualTo("a");
}
@Test
void testMessageBuilderIntermediateVariable(@TempDir Path tempDir) throws IOException {
Path dir = tempDir.resolve("com/example");
Files.createDirectories(dir);
Files.writeString(dir.resolve("MyService.java"),
"package com.example;\n" +
"import org.springframework.messaging.support.MessageBuilder;\n" +
"import org.springframework.messaging.Message;\n" +
"import org.springframework.statemachine.StateMachine;\n" +
"public class MyService {\n" +
" private StateMachine<String, String> stateMachine;\n" +
" private void a(MyInterface myEvent) {\n" +
" MessageBuilder<MyInterface> messageBuilder = MessageBuilder.withPayload(myEvent).setHeader(\"k\", \"v\");\n" +
" stateMachine.sendEvent(messageBuilder.build());\n" +
" }\n" +
"}\n");
CodebaseContext context = new CodebaseContext();
context.scan(tempDir);
GenericEventDetector detector = new GenericEventDetector(context, new ConstantResolver(), List.of());
CompilationUnit cu = context.getCompilationUnits().iterator().next();
List<TriggerPoint> triggers = detector.detect(cu);
assertThat(triggers).hasSize(1);
assertThat(triggers.get(0).getEvent()).isEqualTo("myEvent");
assertThat(triggers.get(0).getMethodName()).isEqualTo("a");
}
@Test
void testEnumGetterUnionExtraction(@TempDir Path tempDir) throws IOException {
Path dir = tempDir.resolve("com/example");
Files.createDirectories(dir);
Files.writeString(dir.resolve("MyEnum.java"),
"package com.example;\n" +
"public enum MyEnum {\n" +
" STATE_A, STATE_B;\n" +
"}\n");
Files.writeString(dir.resolve("MyService.java"),
"package com.example;\n" +
"import org.springframework.statemachine.StateMachine;\n" +
"public class MyService {\n" +
" private StateMachine<String, String> stateMachine;\n" +
" public MyEnum getEvent() { return MyEnum.STATE_A; }\n" + // Pretend it returns the enum
" public void trigger() {\n" +
" stateMachine.sendEvent(this.getEvent());\n" +
" }\n" +
"}\n");
CodebaseContext context = new CodebaseContext();
context.scan(tempDir);
GenericEventDetector detector = new GenericEventDetector(context, new ConstantResolver(), List.of());
CompilationUnit serviceCu = context.getCompilationUnits().stream()
.filter(cu -> cu.getJavaElement() == null || cu.getJavaElement().getElementName().contains("MyService"))
.findFirst().orElseThrow();
List<TriggerPoint> triggers = detector.detect(serviceCu);
System.out.println("TRIGGERS: " + triggers);
assertThat(triggers).hasSize(2);
assertThat(triggers).anyMatch(t -> t.getEvent().equals("com.example.MyEnum.STATE_A"));
assertThat(triggers).anyMatch(t -> t.getEvent().equals("com.example.MyEnum.STATE_B"));
}
}

View File

@@ -47,9 +47,7 @@ class AstTransitionParserTest {
.isEqualTo(TransitionType.EXTERNAL); .isEqualTo(TransitionType.EXTERNAL);
assertThat(transition.getSourceStates()).extracting(State::toString).containsExactly("CREATED"); assertThat(transition.getSourceStates()).extracting(State::toString).containsExactly("CREATED");
assertThat(transition.getTargetStates()).extracting(State::toString).containsExactly("PAID"); assertThat(transition.getTargetStates()).extracting(State::toString).containsExactly("PAID");
assertThat(transition) assertThat(transition.getEvent().toString()).isEqualTo("PAY");
.extracting(Transition::getEvent)
.isEqualTo("PAY");
} }
@Test @Test
@@ -211,8 +209,8 @@ class AstTransitionParserTest {
List<Transition> transitions = AstTransitionParser.parseTransitions(method, context); List<Transition> transitions = AstTransitionParser.parseTransitions(method, context);
assertThat(transitions).hasSize(2); assertThat(transitions).hasSize(2);
assertThat(transitions.get(0).getEvent()).isEqualTo("E1"); assertThat(transitions.get(0).getEvent().toString()).isEqualTo("E1");
assertThat(transitions.get(1).getEvent()).isEqualTo("E2"); assertThat(transitions.get(1).getEvent().toString()).isEqualTo("E2");
} }
@Test @Test

View File

@@ -58,8 +58,8 @@ class StateMachineAggregatorTest {
List<Transition> transitions = aggregator.aggregateTransitions(childTd); List<Transition> transitions = aggregator.aggregateTransitions(childTd);
assertThat(transitions).hasSize(2); assertThat(transitions).hasSize(2);
assertThat(transitions).anyMatch(t -> t.getEvent().equals("BASE_E1")); assertThat(transitions).anyMatch(t -> t.getEvent().toString().equals("BASE_E1"));
assertThat(transitions).anyMatch(t -> t.getEvent().equals("CHILD_E1")); assertThat(transitions).anyMatch(t -> t.getEvent().toString().equals("CHILD_E1"));
} }
@Test @Test
@@ -93,7 +93,7 @@ class StateMachineAggregatorTest {
List<Transition> transitions = aggregator.aggregateTransitions(childTd); List<Transition> transitions = aggregator.aggregateTransitions(childTd);
assertThat(transitions).hasSize(1); assertThat(transitions).hasSize(1);
assertThat(transitions.get(0).getEvent()).isEqualTo("OVERRIDDEN_E1"); assertThat(transitions.get(0).getEvent().toString()).isEqualTo("OVERRIDDEN_E1");
} }
@Test @Test
@@ -119,7 +119,7 @@ class StateMachineAggregatorTest {
assertThat(transitions).hasSize(1); assertThat(transitions).hasSize(1);
assertThat(transitions.get(0).getSourceStates()).extracting(State::toString).containsExactly("START"); assertThat(transitions.get(0).getSourceStates()).extracting(State::toString).containsExactly("START");
assertThat(transitions.get(0).getTargetStates()).extracting(State::toString).containsExactly("END"); assertThat(transitions.get(0).getTargetStates()).extracting(State::toString).containsExactly("END");
assertThat(transitions.get(0).getEvent()).isEqualTo("GO"); assertThat(transitions.get(0).getEvent().toString()).isEqualTo("GO");
} }
@Test @Test
@@ -143,8 +143,8 @@ class StateMachineAggregatorTest {
List<Transition> transitions = aggregator.aggregateTransitions(td); List<Transition> transitions = aggregator.aggregateTransitions(td);
assertThat(transitions).hasSize(2); assertThat(transitions).hasSize(2);
assertThat(transitions).anyMatch(t -> t.getEvent().equals("E1")); assertThat(transitions).anyMatch(t -> t.getEvent().toString().equals("E1"));
assertThat(transitions).anyMatch(t -> t.getEvent().equals("E2")); assertThat(transitions).anyMatch(t -> t.getEvent().toString().equals("E2"));
} }
@Test @Test
@@ -241,9 +241,9 @@ class StateMachineAggregatorTest {
List<Transition> transitions = aggregator.aggregateTransitions(childTd); List<Transition> transitions = aggregator.aggregateTransitions(childTd);
assertThat(transitions).hasSize(3); assertThat(transitions).hasSize(3);
assertThat(transitions).anyMatch(t -> t.getEvent().equals("GP_E1")); assertThat(transitions).anyMatch(t -> t.getEvent().toString().equals("GP_E1"));
assertThat(transitions).anyMatch(t -> t.getEvent().equals("P_E1")); assertThat(transitions).anyMatch(t -> t.getEvent().toString().equals("P_E1"));
assertThat(transitions).anyMatch(t -> t.getEvent().equals("C_E1")); assertThat(transitions).anyMatch(t -> t.getEvent().toString().equals("C_E1"));
} }
@Test @Test
@@ -267,8 +267,8 @@ class StateMachineAggregatorTest {
List<Transition> transitions = aggregator.aggregateTransitions(td); List<Transition> transitions = aggregator.aggregateTransitions(td);
assertThat(transitions).hasSize(2); assertThat(transitions).hasSize(2);
assertThat(transitions).anyMatch(t -> t.getEvent().equals("E1")); assertThat(transitions).anyMatch(t -> t.getEvent().toString().equals("E1"));
assertThat(transitions).anyMatch(t -> t.getEvent().equals("E2")); assertThat(transitions).anyMatch(t -> t.getEvent().toString().equals("E2"));
} }
@Test @Test
@@ -381,7 +381,7 @@ class StateMachineAggregatorTest {
// It might not find ExternalTransitions.addCommon unless it's static or we handle imports. // It might not find ExternalTransitions.addCommon unless it's static or we handle imports.
// Let's see if it works or if we need to improve the aggregator. // Let's see if it works or if we need to improve the aggregator.
assertThat(transitions).hasSize(1); assertThat(transitions).hasSize(1);
assertThat(transitions.get(0).getEvent()).isEqualTo("EXT_E1"); assertThat(transitions.get(0).getEvent().toString()).isEqualTo("EXT_E1");
} }
@Test @Test
@@ -408,8 +408,8 @@ class StateMachineAggregatorTest {
// Should not crash and should find both transitions // Should not crash and should find both transitions
assertThat(transitions).hasSize(2); assertThat(transitions).hasSize(2);
assertThat(transitions).anyMatch(t -> t.getEvent().equals("E1")); assertThat(transitions).anyMatch(t -> t.getEvent().toString().equals("E1"));
assertThat(transitions).anyMatch(t -> t.getEvent().equals("E2")); assertThat(transitions).anyMatch(t -> t.getEvent().toString().equals("E2"));
} }
@Test @Test
@@ -434,7 +434,7 @@ class StateMachineAggregatorTest {
List<Transition> transitions = aggregator.aggregateTransitions(td); List<Transition> transitions = aggregator.aggregateTransitions(td);
assertThat(transitions).hasSize(1); assertThat(transitions).hasSize(1);
assertThat(transitions.get(0).getEvent()).isEqualTo("TRY_E1"); assertThat(transitions.get(0).getEvent().toString()).isEqualTo("TRY_E1");
} }
@Test @Test
@@ -536,8 +536,8 @@ class StateMachineAggregatorTest {
List<Transition> transitions = aggregator.aggregateTransitions(td); List<Transition> transitions = aggregator.aggregateTransitions(td);
assertThat(transitions).hasSize(2); assertThat(transitions).hasSize(2);
assertThat(transitions).anyMatch(t -> t.getEvent().equals("E1")); assertThat(transitions).anyMatch(t -> t.getEvent().toString().equals("E1"));
assertThat(transitions).anyMatch(t -> t.getEvent().equals("E2")); assertThat(transitions).anyMatch(t -> t.getEvent().toString().equals("E2"));
} }
@Test @Test
@@ -585,7 +585,7 @@ class StateMachineAggregatorTest {
List<Transition> transitions = aggregator.aggregateTransitions(td); List<Transition> transitions = aggregator.aggregateTransitions(td);
assertThat(transitions).hasSize(1); assertThat(transitions).hasSize(1);
assertThat(transitions.get(0).getEvent()).isEqualTo("E1"); assertThat(transitions.get(0).getEvent().toString()).isEqualTo("E1");
} }
private void writeClass(String name, String source) throws IOException { private void writeClass(String name, String source) throws IOException {

View File

@@ -20,7 +20,7 @@ class JsonExporterTest {
transition.setType(TransitionType.EXTERNAL); transition.setType(TransitionType.EXTERNAL);
transition.setSourceStates(List.of(State.of("S1"))); transition.setSourceStates(List.of(State.of("S1")));
transition.setTargetStates(List.of(State.of("S2"))); transition.setTargetStates(List.of(State.of("S2")));
transition.setEvent("E1"); transition.setEvent(click.kamil.springstatemachineexporter.model.Event.of("E1"));
AnalysisResult result = AnalysisResult.builder() AnalysisResult result = AnalysisResult.builder()
.name("TestSM") .name("TestSM")
@@ -33,7 +33,7 @@ class JsonExporterTest {
assertThat(json) assertThat(json)
.contains("\"name\" : \"TestSM\"") .contains("\"name\" : \"TestSM\"")
.contains("\"event\" : \"E1\"") .contains("\"rawName\" : \"E1\"")
.contains("\"rawName\" : \"S1\"") .contains("\"rawName\" : \"S1\"")
.contains("\"rawName\" : \"S2\""); .contains("\"rawName\" : \"S2\"");
} }

View File

@@ -0,0 +1,55 @@
package click.kamil.springstatemachineexporter.exporter;
import click.kamil.springstatemachineexporter.model.State;
import click.kamil.springstatemachineexporter.model.Transition;
import click.kamil.springstatemachineexporter.model.TransitionType;
import click.kamil.springstatemachineexporter.model.Guard;
import click.kamil.springstatemachineexporter.model.Action;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Set;
import static org.assertj.core.api.Assertions.assertThat;
class PlantUmlTest {
@Test
void testExportSanitizesLabels() {
PlantUml plantUml = new PlantUml();
ExportOptions options = ExportOptions.builder()
.embedIdentifiers(true)
.useLambdaGuards(false)
.build();
State state1 = State.of("State1");
State state2 = State.of("State2");
Transition t = new Transition();
t.setType(TransitionType.EXTERNAL);
t.setSourceStates(List.of(state1));
t.setTargetStates(List.of(state2));
// Add a guard and action containing problematic characters
t.setGuard(Guard.of("list.size() < 5", false, null, null, null, null));
t.setActions(List.of(Action.of("doSomething(new int[]{1, 2})", false, null, null, null, null)));
String result = plantUml.export(
"TestMachine",
List.of(t),
Set.of("State1"),
Set.of("State2"),
options
);
// Verify that < and > are replaced with HTML entities
assertThat(result).doesNotContain("< 5");
assertThat(result).contains("&lt; 5");
// Verify that [ and ] are replaced with HTML entities inside the link
assertThat(result).doesNotContain("[list.size() &lt; 5]");
assertThat(result).doesNotContain("new int[]{1, 2}");
assertThat(result).contains("&#91;list.size() &lt; 5&#93;");
assertThat(result).contains("new int&#91;&#93;{1, 2}");
}
}

View File

@@ -27,7 +27,7 @@ public class DeferredProfileResolutionTest {
assertThat(result.getStartStates()).containsExactly("START_RESOLVED"); assertThat(result.getStartStates()).containsExactly("START_RESOLVED");
Transition t = result.getTransitions().get(0); Transition t = result.getTransitions().get(0);
assertThat(t.getEvent()).isEqualTo("RESOLVED_EVENT"); assertThat(t.getEvent().fullIdentifier()).isEqualTo("RESOLVED_EVENT");
assertThat(t.getSourceStates().get(0).fullIdentifier()).isEqualTo("START_RESOLVED"); assertThat(t.getSourceStates().get(0).fullIdentifier()).isEqualTo("START_RESOLVED");
} }
@@ -53,7 +53,7 @@ public class DeferredProfileResolutionTest {
State s2 = State.of("END", "END"); State s2 = State.of("END", "END");
Transition t1 = new Transition(); Transition t1 = new Transition();
t1.setEvent("${app.event.name:RESOLVED_EVENT}"); t1.setEvent(click.kamil.springstatemachineexporter.model.Event.of("${app.event.name:RESOLVED_EVENT}"));
t1.setSourceStates(List.of(s1)); t1.setSourceStates(List.of(s1));
t1.setTargetStates(List.of(s2)); t1.setTargetStates(List.of(s2));

View File

@@ -0,0 +1,55 @@
package click.kamil.springstatemachineexporter.service;
import click.kamil.springstatemachineexporter.exporter.StateMachineExporter;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
class ExportServiceEmptyStateTest {
@TempDir
Path tempDir;
@Test
void shouldSkipEmptyStateMachineConfigurations() throws Exception {
// Arrange
Path inputDir = tempDir.resolve("input");
Files.createDirectories(inputDir);
Path outputDir = tempDir.resolve("output");
Files.createDirectories(outputDir);
String code = """
package com.example;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
@Configuration
@EnableStateMachine
public class EmptyStateMachineConfig extends StateMachineConfigurerAdapter<String, String> {
// No configurers overridden, meaning 0 states and 0 transitions parsed
}
""";
Files.writeString(inputDir.resolve("EmptyStateMachineConfig.java"), code);
ExportService exportService = new ExportService(Collections.emptyList()); // No exporters needed, checking if it generates anything (it generates JSON natively)
// Act
exportService.runExporter(inputDir, outputDir, List.of("plantuml"), true);
// Assert
// The output directory should have no files created in it
long fileCount = Files.walk(outputDir)
.filter(Files::isRegularFile)
.count();
assertThat(fileCount).isZero();
}
}

View File

@@ -23,7 +23,7 @@ class JsonImportServiceTest {
"type" : "EXTERNAL", "type" : "EXTERNAL",
"sourceStates" : [ { "rawName" : "S1", "fullIdentifier" : "S1" } ], "sourceStates" : [ { "rawName" : "S1", "fullIdentifier" : "S1" } ],
"targetStates" : [ { "rawName" : "S2", "fullIdentifier" : "S2" } ], "targetStates" : [ { "rawName" : "S2", "fullIdentifier" : "S2" } ],
"event" : "E1" "event" : { "rawName" : "E1", "fullIdentifier" : "E1" }
} ], } ],
"startStates" : [ "S1" ], "startStates" : [ "S1" ],
"endStates" : [ "S2" ] "endStates" : [ "S2" ]
@@ -36,7 +36,7 @@ class JsonImportServiceTest {
assertThat(model.getName()).isEqualTo("ImportedSM"); assertThat(model.getName()).isEqualTo("ImportedSM");
assertThat(model.getTransitions()).hasSize(1); assertThat(model.getTransitions()).hasSize(1);
assertThat(model.getTransitions().get(0).getEvent()).isEqualTo("E1"); assertThat(model.getTransitions().get(0).getEvent().rawName()).isEqualTo("E1");
assertThat(model.getStartStates()).containsExactly("S1"); assertThat(model.getStartStates()).containsExactly("S1");
assertThat(model.getEndStates()).containsExactly("S2"); assertThat(model.getEndStates()).containsExactly("S2");
} }

View File

@@ -22,7 +22,10 @@
"rawName" : "States.STATE2", "rawName" : "States.STATE2",
"fullIdentifier" : "States.STATE2" "fullIdentifier" : "States.STATE2"
} ], } ],
"event" : "Events.EVENT1", "event" : {
"rawName" : "Events.EVENT1",
"fullIdentifier" : "Events.EVENT1"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -36,7 +39,10 @@
"rawName" : "States.STATE3", "rawName" : "States.STATE3",
"fullIdentifier" : "States.STATE3" "fullIdentifier" : "States.STATE3"
} ], } ],
"event" : "Events.EVENT2", "event" : {
"rawName" : "Events.EVENT2",
"fullIdentifier" : "Events.EVENT2"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -50,7 +56,10 @@
"rawName" : "States.STATE4", "rawName" : "States.STATE4",
"fullIdentifier" : "States.STATE4" "fullIdentifier" : "States.STATE4"
} ], } ],
"event" : "Events.EVENT3", "event" : {
"rawName" : "Events.EVENT3",
"fullIdentifier" : "Events.EVENT3"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -64,7 +73,10 @@
"rawName" : "States.STATE5", "rawName" : "States.STATE5",
"fullIdentifier" : "States.STATE5" "fullIdentifier" : "States.STATE5"
} ], } ],
"event" : "Events.EVENT4", "event" : {
"rawName" : "Events.EVENT4",
"fullIdentifier" : "Events.EVENT4"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -78,7 +90,10 @@
"rawName" : "States.STATE6", "rawName" : "States.STATE6",
"fullIdentifier" : "States.STATE6" "fullIdentifier" : "States.STATE6"
} ], } ],
"event" : "Events.EVENT5", "event" : {
"rawName" : "Events.EVENT5",
"fullIdentifier" : "Events.EVENT5"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -92,7 +107,10 @@
"rawName" : "States.STATE7", "rawName" : "States.STATE7",
"fullIdentifier" : "States.STATE7" "fullIdentifier" : "States.STATE7"
} ], } ],
"event" : "Events.EVENT6", "event" : {
"rawName" : "Events.EVENT6",
"fullIdentifier" : "Events.EVENT6"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -106,7 +124,10 @@
"rawName" : "States.STATE8", "rawName" : "States.STATE8",
"fullIdentifier" : "States.STATE8" "fullIdentifier" : "States.STATE8"
} ], } ],
"event" : "Events.EVENT7", "event" : {
"rawName" : "Events.EVENT7",
"fullIdentifier" : "Events.EVENT7"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -120,7 +141,10 @@
"rawName" : "States.STATE9", "rawName" : "States.STATE9",
"fullIdentifier" : "States.STATE9" "fullIdentifier" : "States.STATE9"
} ], } ],
"event" : "Events.EVENT8", "event" : {
"rawName" : "Events.EVENT8",
"fullIdentifier" : "Events.EVENT8"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -134,7 +158,10 @@
"rawName" : "States.STATE10", "rawName" : "States.STATE10",
"fullIdentifier" : "States.STATE10" "fullIdentifier" : "States.STATE10"
} ], } ],
"event" : "Events.EVENT9", "event" : {
"rawName" : "Events.EVENT9",
"fullIdentifier" : "Events.EVENT9"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -148,7 +175,10 @@
"rawName" : "States.STATE11", "rawName" : "States.STATE11",
"fullIdentifier" : "States.STATE11" "fullIdentifier" : "States.STATE11"
} ], } ],
"event" : "Events.EVENT10", "event" : {
"rawName" : "Events.EVENT10",
"fullIdentifier" : "Events.EVENT10"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -162,7 +192,10 @@
"rawName" : "States.STATE12", "rawName" : "States.STATE12",
"fullIdentifier" : "States.STATE12" "fullIdentifier" : "States.STATE12"
} ], } ],
"event" : "Events.EVENT11", "event" : {
"rawName" : "Events.EVENT11",
"fullIdentifier" : "Events.EVENT11"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -176,7 +209,10 @@
"rawName" : "States.STATE13", "rawName" : "States.STATE13",
"fullIdentifier" : "States.STATE13" "fullIdentifier" : "States.STATE13"
} ], } ],
"event" : "Events.EVENT12", "event" : {
"rawName" : "Events.EVENT12",
"fullIdentifier" : "Events.EVENT12"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -190,7 +226,10 @@
"rawName" : "States.STATE14", "rawName" : "States.STATE14",
"fullIdentifier" : "States.STATE14" "fullIdentifier" : "States.STATE14"
} ], } ],
"event" : "Events.EVENT13", "event" : {
"rawName" : "Events.EVENT13",
"fullIdentifier" : "Events.EVENT13"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -204,7 +243,10 @@
"rawName" : "States.STATE15", "rawName" : "States.STATE15",
"fullIdentifier" : "States.STATE15" "fullIdentifier" : "States.STATE15"
} ], } ],
"event" : "Events.EVENT14", "event" : {
"rawName" : "Events.EVENT14",
"fullIdentifier" : "Events.EVENT14"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -218,7 +260,10 @@
"rawName" : "States.STATE16", "rawName" : "States.STATE16",
"fullIdentifier" : "States.STATE16" "fullIdentifier" : "States.STATE16"
} ], } ],
"event" : "Events.EVENT15", "event" : {
"rawName" : "Events.EVENT15",
"fullIdentifier" : "Events.EVENT15"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

After

Width:  |  Height:  |  Size: 157 KiB

View File

@@ -34,21 +34,21 @@ state States.STATE14 <<choice>>
state States.STATE9 <<choice>> state States.STATE9 <<choice>>
state States.STATE8 <<choice>> state States.STATE8 <<choice>>
States.STATE1 -[#1E90FF,bold]-> States.STATE2 <<external>> <<e_Events_EVENT1>> : Events.EVENT1 States.STATE1 -[#1E90FF,bold]-> States.STATE2 <<external>> : Events.EVENT1
States.STATE2 -[#1E90FF,bold]-> States.STATE3 <<external>> <<e_Events_EVENT2>> : Events.EVENT2 States.STATE2 -[#1E90FF,bold]-> States.STATE3 <<external>> : Events.EVENT2
States.STATE3 -[#1E90FF,bold]-> States.STATE4 <<external>> <<e_Events_EVENT3>> : Events.EVENT3 States.STATE3 -[#1E90FF,bold]-> States.STATE4 <<external>> : Events.EVENT3
States.STATE4 -[#1E90FF,bold]-> States.STATE5 <<external>> <<e_Events_EVENT4>> : Events.EVENT4 States.STATE4 -[#1E90FF,bold]-> States.STATE5 <<external>> : Events.EVENT4
States.STATE5 -[#1E90FF,bold]-> States.STATE6 <<external>> <<e_Events_EVENT5>> : Events.EVENT5 States.STATE5 -[#1E90FF,bold]-> States.STATE6 <<external>> : Events.EVENT5
States.STATE6 -[#1E90FF,bold]-> States.STATE7 <<external>> <<e_Events_EVENT6>> : Events.EVENT6 States.STATE6 -[#1E90FF,bold]-> States.STATE7 <<external>> : Events.EVENT6
States.STATE7 -[#1E90FF,bold]-> States.STATE8 <<external>> <<e_Events_EVENT7>> : Events.EVENT7 States.STATE7 -[#1E90FF,bold]-> States.STATE8 <<external>> : Events.EVENT7
States.STATE8 -[#1E90FF,bold]-> States.STATE9 <<external>> <<e_Events_EVENT8>> : Events.EVENT8 States.STATE8 -[#1E90FF,bold]-> States.STATE9 <<external>> : Events.EVENT8
States.STATE9 -[#1E90FF,bold]-> States.STATE10 <<external>> <<e_Events_EVENT9>> : Events.EVENT9 States.STATE9 -[#1E90FF,bold]-> States.STATE10 <<external>> : Events.EVENT9
States.STATE10 -[#1E90FF,bold]-> States.STATE11 <<external>> <<e_Events_EVENT10>> : Events.EVENT10 States.STATE10 -[#1E90FF,bold]-> States.STATE11 <<external>> : Events.EVENT10
States.STATE11 -[#1E90FF,bold]-> States.STATE12 <<external>> <<e_Events_EVENT11>> : Events.EVENT11 States.STATE11 -[#1E90FF,bold]-> States.STATE12 <<external>> : Events.EVENT11
States.STATE12 -[#1E90FF,bold]-> States.STATE13 <<external>> <<e_Events_EVENT12>> : Events.EVENT12 States.STATE12 -[#1E90FF,bold]-> States.STATE13 <<external>> : Events.EVENT12
States.STATE13 -[#1E90FF,bold]-> States.STATE14 <<external>> <<e_Events_EVENT13>> : Events.EVENT13 States.STATE13 -[#1E90FF,bold]-> States.STATE14 <<external>> : Events.EVENT13
States.STATE14 -[#1E90FF,bold]-> States.STATE15 <<external>> <<e_Events_EVENT14>> : Events.EVENT14 States.STATE14 -[#1E90FF,bold]-> States.STATE15 <<external>> : Events.EVENT14
States.STATE15 -[#1E90FF,bold]-> States.STATE16 <<external>> <<e_Events_EVENT15>> : Events.EVENT15 States.STATE15 -[#1E90FF,bold]-> States.STATE16 <<external>> : Events.EVENT15
States.STATE16 -[#FF6347,bold]-> States.STATE17 <<choice_type>> : [guardVarEquals("value1")] (order=0) States.STATE16 -[#FF6347,bold]-> States.STATE17 <<choice_type>> : [guardVarEquals("value1")] (order=0)
States.STATE16 -[#FF6347,bold]-> States.STATE18 <<choice_type>> : [guardVarEquals("value2")] (order=1) States.STATE16 -[#FF6347,bold]-> States.STATE18 <<choice_type>> : [guardVarEquals("value2")] (order=1)
States.STATE16 -[#FF6347,bold]-> States.STATE19 <<choice_type>> : (order=2) States.STATE16 -[#FF6347,bold]-> States.STATE19 <<choice_type>> : (order=2)

View File

@@ -7,7 +7,9 @@
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/api/SecurityInterceptor.java", "sourceFile" : "src/main/java/click/kamil/examples/enterprise/api/SecurityInterceptor.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 17 "sourceState" : null,
"lineNumber" : 17,
"polymorphicEvents" : null
}, { }, {
"event" : "PLACE_ORDER", "event" : "PLACE_ORDER",
"className" : "click.kamil.examples.enterprise.service.OrderServiceImpl", "className" : "click.kamil.examples.enterprise.service.OrderServiceImpl",
@@ -15,7 +17,9 @@
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/service/OrderServiceImpl.java", "sourceFile" : "src/main/java/click/kamil/examples/enterprise/service/OrderServiceImpl.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 16 "sourceState" : null,
"lineNumber" : 16,
"polymorphicEvents" : null
}, { }, {
"event" : "CANCEL_ORDER", "event" : "CANCEL_ORDER",
"className" : "click.kamil.examples.enterprise.service.OrderServiceImpl", "className" : "click.kamil.examples.enterprise.service.OrderServiceImpl",
@@ -23,7 +27,9 @@
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/service/OrderServiceImpl.java", "sourceFile" : "src/main/java/click/kamil/examples/enterprise/service/OrderServiceImpl.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 21 "sourceState" : null,
"lineNumber" : 21,
"polymorphicEvents" : null
}, { }, {
"event" : "PAY_ORDER", "event" : "PAY_ORDER",
"className" : "click.kamil.examples.enterprise.service.ReactivePaymentService", "className" : "click.kamil.examples.enterprise.service.ReactivePaymentService",
@@ -31,7 +37,9 @@
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/service/ReactivePaymentService.java", "sourceFile" : "src/main/java/click/kamil/examples/enterprise/service/ReactivePaymentService.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 18 "sourceState" : null,
"lineNumber" : 18,
"polymorphicEvents" : null
}, { }, {
"event" : "SHIP_ORDER", "event" : "SHIP_ORDER",
"className" : "click.kamil.examples.enterprise.messaging.ShippingJmsListener", "className" : "click.kamil.examples.enterprise.messaging.ShippingJmsListener",
@@ -39,7 +47,9 @@
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/messaging/ShippingJmsListener.java", "sourceFile" : "src/main/java/click/kamil/examples/enterprise/messaging/ShippingJmsListener.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 17 "sourceState" : null,
"lineNumber" : 17,
"polymorphicEvents" : null
}, { }, {
"event" : "RETURN_ORDER", "event" : "RETURN_ORDER",
"className" : "click.kamil.examples.enterprise.messaging.ReturnsRabbitListener", "className" : "click.kamil.examples.enterprise.messaging.ReturnsRabbitListener",
@@ -47,7 +57,9 @@
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/messaging/ReturnsRabbitListener.java", "sourceFile" : "src/main/java/click/kamil/examples/enterprise/messaging/ReturnsRabbitListener.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 17 "sourceState" : null,
"lineNumber" : 17,
"polymorphicEvents" : null
} ], } ],
"entryPoints" : [ { "entryPoints" : [ {
"type" : "REST", "type" : "REST",
@@ -131,7 +143,7 @@
"name" : "JMS: shipping.queue", "name" : "JMS: shipping.queue",
"className" : "click.kamil.examples.enterprise.messaging.ShippingJmsListener", "className" : "click.kamil.examples.enterprise.messaging.ShippingJmsListener",
"methodName" : "onShippingReady", "methodName" : "onShippingReady",
"sourceFile" : null, "sourceFile" : "src/main/java/click/kamil/examples/enterprise/messaging/ShippingJmsListener.java",
"metadata" : { "metadata" : {
"protocol" : "JMS", "protocol" : "JMS",
"destination" : "shipping.queue" "destination" : "shipping.queue"
@@ -146,7 +158,7 @@
"name" : "RABBIT: returns.queue", "name" : "RABBIT: returns.queue",
"className" : "click.kamil.examples.enterprise.messaging.ReturnsRabbitListener", "className" : "click.kamil.examples.enterprise.messaging.ReturnsRabbitListener",
"methodName" : "onReturn", "methodName" : "onReturn",
"sourceFile" : null, "sourceFile" : "src/main/java/click/kamil/examples/enterprise/messaging/ReturnsRabbitListener.java",
"metadata" : { "metadata" : {
"protocol" : "RABBIT", "protocol" : "RABBIT",
"destination" : "returns.queue" "destination" : "returns.queue"
@@ -178,8 +190,16 @@
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/service/OrderServiceImpl.java", "sourceFile" : "src/main/java/click/kamil/examples/enterprise/service/OrderServiceImpl.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 16 "sourceState" : null,
} "lineNumber" : 16,
"polymorphicEvents" : null
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "NEW",
"targetState" : "CHECK_AVAILABILITY",
"event" : "PLACE_ORDER"
} ]
}, { }, {
"entryPoint" : { "entryPoint" : {
"type" : "REST", "type" : "REST",
@@ -205,8 +225,16 @@
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/service/OrderServiceImpl.java", "sourceFile" : "src/main/java/click/kamil/examples/enterprise/service/OrderServiceImpl.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 21 "sourceState" : null,
} "lineNumber" : 21,
"polymorphicEvents" : null
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "PAID",
"targetState" : "CANCELLED",
"event" : "CANCEL_ORDER"
} ]
}, { }, {
"entryPoint" : { "entryPoint" : {
"type" : "CUSTOM", "type" : "CUSTOM",
@@ -227,8 +255,12 @@
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/api/SecurityInterceptor.java", "sourceFile" : "src/main/java/click/kamil/examples/enterprise/api/SecurityInterceptor.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 17 "sourceState" : null,
} "lineNumber" : 17,
"polymorphicEvents" : null
},
"contextMachineId" : null,
"matchedTransitions" : null
}, { }, {
"entryPoint" : { "entryPoint" : {
"type" : "REST", "type" : "REST",
@@ -254,15 +286,23 @@
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/service/ReactivePaymentService.java", "sourceFile" : "src/main/java/click/kamil/examples/enterprise/service/ReactivePaymentService.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 18 "sourceState" : null,
} "lineNumber" : 18,
"polymorphicEvents" : null
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "PENDING_PAYMENT",
"targetState" : "PAID",
"event" : "PAY_ORDER"
} ]
}, { }, {
"entryPoint" : { "entryPoint" : {
"type" : "JMS", "type" : "JMS",
"name" : "JMS: shipping.queue", "name" : "JMS: shipping.queue",
"className" : "click.kamil.examples.enterprise.messaging.ShippingJmsListener", "className" : "click.kamil.examples.enterprise.messaging.ShippingJmsListener",
"methodName" : "onShippingReady", "methodName" : "onShippingReady",
"sourceFile" : null, "sourceFile" : "src/main/java/click/kamil/examples/enterprise/messaging/ShippingJmsListener.java",
"metadata" : { "metadata" : {
"protocol" : "JMS", "protocol" : "JMS",
"destination" : "shipping.queue" "destination" : "shipping.queue"
@@ -281,15 +321,23 @@
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/messaging/ShippingJmsListener.java", "sourceFile" : "src/main/java/click/kamil/examples/enterprise/messaging/ShippingJmsListener.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 17 "sourceState" : null,
} "lineNumber" : 17,
"polymorphicEvents" : null
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "PAID",
"targetState" : "SHIPPED",
"event" : "SHIP_ORDER"
} ]
}, { }, {
"entryPoint" : { "entryPoint" : {
"type" : "RABBIT", "type" : "RABBIT",
"name" : "RABBIT: returns.queue", "name" : "RABBIT: returns.queue",
"className" : "click.kamil.examples.enterprise.messaging.ReturnsRabbitListener", "className" : "click.kamil.examples.enterprise.messaging.ReturnsRabbitListener",
"methodName" : "onReturn", "methodName" : "onReturn",
"sourceFile" : null, "sourceFile" : "src/main/java/click/kamil/examples/enterprise/messaging/ReturnsRabbitListener.java",
"metadata" : { "metadata" : {
"protocol" : "RABBIT", "protocol" : "RABBIT",
"destination" : "returns.queue" "destination" : "returns.queue"
@@ -308,8 +356,16 @@
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/messaging/ReturnsRabbitListener.java", "sourceFile" : "src/main/java/click/kamil/examples/enterprise/messaging/ReturnsRabbitListener.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 17 "sourceState" : null,
} "lineNumber" : 17,
"polymorphicEvents" : null
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "DELIVERED",
"targetState" : "RETURNED",
"event" : "RETURN_ORDER"
} ]
} ], } ],
"properties" : { "properties" : {
"default" : { } "default" : { }
@@ -328,7 +384,10 @@
"rawName" : "\"CHECK_AVAILABILITY\"", "rawName" : "\"CHECK_AVAILABILITY\"",
"fullIdentifier" : "CHECK_AVAILABILITY" "fullIdentifier" : "CHECK_AVAILABILITY"
} ], } ],
"event" : "PLACE_ORDER", "event" : {
"rawName" : "OrderEvents.PLACE",
"fullIdentifier" : "PLACE_ORDER"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -377,7 +436,10 @@
"rawName" : "\"PAID\"", "rawName" : "\"PAID\"",
"fullIdentifier" : "PAID" "fullIdentifier" : "PAID"
} ], } ],
"event" : "PAY_ORDER", "event" : {
"rawName" : "OrderEvents.PAY",
"fullIdentifier" : "PAY_ORDER"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -391,7 +453,10 @@
"rawName" : "\"SHIPPED\"", "rawName" : "\"SHIPPED\"",
"fullIdentifier" : "SHIPPED" "fullIdentifier" : "SHIPPED"
} ], } ],
"event" : "SHIP_ORDER", "event" : {
"rawName" : "OrderEvents.SHIP",
"fullIdentifier" : "SHIP_ORDER"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -405,7 +470,10 @@
"rawName" : "\"DELIVERED\"", "rawName" : "\"DELIVERED\"",
"fullIdentifier" : "DELIVERED" "fullIdentifier" : "DELIVERED"
} ], } ],
"event" : "FINALIZE", "event" : {
"rawName" : "\"FINALIZE\"",
"fullIdentifier" : "FINALIZE"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -419,7 +487,10 @@
"rawName" : "\"CANCELLED\"", "rawName" : "\"CANCELLED\"",
"fullIdentifier" : "CANCELLED" "fullIdentifier" : "CANCELLED"
} ], } ],
"event" : "CANCEL_ORDER", "event" : {
"rawName" : "OrderEvents.CANCEL",
"fullIdentifier" : "CANCEL_ORDER"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -433,7 +504,10 @@
"rawName" : "\"RETURNED\"", "rawName" : "\"RETURNED\"",
"fullIdentifier" : "RETURNED" "fullIdentifier" : "RETURNED"
} ], } ],
"event" : "RETURN_ORDER", "event" : {
"rawName" : "OrderEvents.RETURN",
"fullIdentifier" : "RETURN_ORDER"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -25,14 +25,14 @@ skinparam svgLinkTarget _self
state CHECK_AVAILABILITY <<choice>> state CHECK_AVAILABILITY <<choice>>
NEW -[#1E90FF,bold]-> CHECK_AVAILABILITY <<external>> <<e_PLACE_ORDER>> : PLACE_ORDER NEW -[#1E90FF,bold]-> CHECK_AVAILABILITY <<external>> : PLACE_ORDER
CHECK_AVAILABILITY -[#FF6347,bold]-> PENDING_PAYMENT <<choice_type>> : [λ] (order=0) CHECK_AVAILABILITY -[#FF6347,bold]-> PENDING_PAYMENT <<choice_type>> : [λ] (order=0)
CHECK_AVAILABILITY -[#FF6347,bold]-> CANCELLED <<choice_type>> : (order=1) CHECK_AVAILABILITY -[#FF6347,bold]-> CANCELLED <<choice_type>> : (order=1)
PENDING_PAYMENT -[#1E90FF,bold]-> PAID <<external>> <<e_PAY_ORDER>> : PAY_ORDER PENDING_PAYMENT -[#1E90FF,bold]-> PAID <<external>> : PAY_ORDER
PAID -[#1E90FF,bold]-> SHIPPED <<external>> <<e_SHIP_ORDER>> : SHIP_ORDER PAID -[#1E90FF,bold]-> SHIPPED <<external>> : SHIP_ORDER
SHIPPED -[#1E90FF,bold]-> DELIVERED <<external>> <<e_FINALIZE>> : FINALIZE SHIPPED -[#1E90FF,bold]-> DELIVERED <<external>> : FINALIZE
PAID -[#1E90FF,bold]-> CANCELLED <<external>> <<e_CANCEL_ORDER>> : CANCEL_ORDER PAID -[#1E90FF,bold]-> CANCELLED <<external>> : CANCEL_ORDER
DELIVERED -[#1E90FF,bold]-> RETURNED <<external>> <<e_RETURN_ORDER>> : RETURN_ORDER DELIVERED -[#1E90FF,bold]-> RETURNED <<external>> : RETURN_ORDER
CANCELLED --> [*] CANCELLED --> [*]
DELIVERED --> [*] DELIVERED --> [*]

View File

@@ -7,7 +7,9 @@
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/OrderController.java", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/OrderController.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 34 "sourceState" : null,
"lineNumber" : 34,
"polymorphicEvents" : null
}, { }, {
"event" : "AUDIT_EVENT", "event" : "AUDIT_EVENT",
"className" : "click.kamil.examples.statemachine.extended.web.AuditInterceptor", "className" : "click.kamil.examples.statemachine.extended.web.AuditInterceptor",
@@ -15,7 +17,9 @@
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/AuditInterceptor.java", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/AuditInterceptor.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 16 "sourceState" : null,
"lineNumber" : 16,
"polymorphicEvents" : null
}, { }, {
"event" : "EXTERNAL_TRIGGER", "event" : "EXTERNAL_TRIGGER",
"className" : "click.kamil.examples.statemachine.extended.service.OrderService", "className" : "click.kamil.examples.statemachine.extended.service.OrderService",
@@ -23,7 +27,9 @@
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/OrderService.java", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/OrderService.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 19 "sourceState" : null,
"lineNumber" : 19,
"polymorphicEvents" : null
}, { }, {
"event" : "SUBMIT_EVENT", "event" : "SUBMIT_EVENT",
"className" : "click.kamil.examples.statemachine.extended.service.OrderService", "className" : "click.kamil.examples.statemachine.extended.service.OrderService",
@@ -31,7 +37,9 @@
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/OrderService.java", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/OrderService.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 20 "sourceState" : null,
"lineNumber" : 20,
"polymorphicEvents" : null
}, { }, {
"event" : "CANCEL_EVENT", "event" : "CANCEL_EVENT",
"className" : "click.kamil.examples.statemachine.extended.service.OrderService", "className" : "click.kamil.examples.statemachine.extended.service.OrderService",
@@ -39,7 +47,9 @@
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/OrderService.java", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/OrderService.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 25 "sourceState" : null,
"lineNumber" : 25,
"polymorphicEvents" : null
}, { }, {
"event" : "[LIFECYCLE:RESTORE]", "event" : "[LIFECYCLE:RESTORE]",
"className" : "click.kamil.examples.statemachine.extended.service.OrderService", "className" : "click.kamil.examples.statemachine.extended.service.OrderService",
@@ -47,7 +57,9 @@
"sourceFile" : null, "sourceFile" : null,
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 29 "sourceState" : null,
"lineNumber" : 29,
"polymorphicEvents" : null
}, { }, {
"event" : "REACTIVE_EVENT", "event" : "REACTIVE_EVENT",
"className" : "click.kamil.examples.statemachine.extended.service.ReactiveOrderService", "className" : "click.kamil.examples.statemachine.extended.service.ReactiveOrderService",
@@ -55,7 +67,9 @@
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ReactiveOrderService.java", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ReactiveOrderService.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 18 "sourceState" : null,
"lineNumber" : 18,
"polymorphicEvents" : null
} ], } ],
"entryPoints" : [ { "entryPoints" : [ {
"type" : "REST", "type" : "REST",
@@ -152,8 +166,16 @@
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/OrderService.java", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/OrderService.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 19 "sourceState" : null,
} "lineNumber" : 19,
"polymorphicEvents" : null
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "START",
"targetState" : "PROCESSING",
"event" : "EXTERNAL_TRIGGER"
} ]
}, { }, {
"entryPoint" : { "entryPoint" : {
"type" : "REST", "type" : "REST",
@@ -175,8 +197,16 @@
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/OrderService.java", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/OrderService.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 20 "sourceState" : null,
} "lineNumber" : 20,
"polymorphicEvents" : null
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "START",
"targetState" : "PROCESSING",
"event" : "SUBMIT_EVENT"
} ]
}, { }, {
"entryPoint" : { "entryPoint" : {
"type" : "REST", "type" : "REST",
@@ -198,8 +228,16 @@
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/OrderService.java", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/OrderService.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 25 "sourceState" : null,
} "lineNumber" : 25,
"polymorphicEvents" : null
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "PROCESSING",
"targetState" : "CANCELLED",
"event" : "CANCEL_EVENT"
} ]
}, { }, {
"entryPoint" : { "entryPoint" : {
"type" : "REST", "type" : "REST",
@@ -221,8 +259,16 @@
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/OrderController.java", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/OrderController.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 34 "sourceState" : null,
} "lineNumber" : 34,
"polymorphicEvents" : null
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "PROCESSING",
"targetState" : "COMPLETED",
"event" : "FINISH"
} ]
}, { }, {
"entryPoint" : { "entryPoint" : {
"type" : "REST", "type" : "REST",
@@ -248,8 +294,12 @@
"sourceFile" : null, "sourceFile" : null,
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 29 "sourceState" : null,
} "lineNumber" : 29,
"polymorphicEvents" : null
},
"contextMachineId" : "orderId",
"matchedTransitions" : null
}, { }, {
"entryPoint" : { "entryPoint" : {
"type" : "REST", "type" : "REST",
@@ -275,8 +325,16 @@
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ReactiveOrderService.java", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ReactiveOrderService.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 18 "sourceState" : null,
} "lineNumber" : 18,
"polymorphicEvents" : null
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "START",
"targetState" : "PROCESSING",
"event" : "REACTIVE_EVENT"
} ]
}, { }, {
"entryPoint" : { "entryPoint" : {
"type" : "CUSTOM", "type" : "CUSTOM",
@@ -297,8 +355,16 @@
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/AuditInterceptor.java", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/AuditInterceptor.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 16 "sourceState" : null,
} "lineNumber" : 16,
"polymorphicEvents" : null
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "START",
"targetState" : "START",
"event" : "AUDIT_EVENT"
} ]
} ], } ],
"properties" : { "properties" : {
"default" : { "default" : {
@@ -322,7 +388,10 @@
"rawName" : "\"PROCESSING\"", "rawName" : "\"PROCESSING\"",
"fullIdentifier" : "PROCESSING" "fullIdentifier" : "PROCESSING"
} ], } ],
"event" : "SUBMIT_EVENT", "event" : {
"rawName" : "MyEvents.SUBMIT",
"fullIdentifier" : "SUBMIT_EVENT"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -336,7 +405,10 @@
"rawName" : "\"COMPLETED\"", "rawName" : "\"COMPLETED\"",
"fullIdentifier" : "COMPLETED" "fullIdentifier" : "COMPLETED"
} ], } ],
"event" : "FINISH", "event" : {
"rawName" : "\"FINISH\"",
"fullIdentifier" : "FINISH"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -350,7 +422,10 @@
"rawName" : "\"CANCELLED\"", "rawName" : "\"CANCELLED\"",
"fullIdentifier" : "CANCELLED" "fullIdentifier" : "CANCELLED"
} ], } ],
"event" : "CANCEL_EVENT", "event" : {
"rawName" : "MyEvents.CANCEL",
"fullIdentifier" : "CANCEL_EVENT"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -364,7 +439,10 @@
"rawName" : "\"PROCESSING\"", "rawName" : "\"PROCESSING\"",
"fullIdentifier" : "PROCESSING" "fullIdentifier" : "PROCESSING"
} ], } ],
"event" : "REACTIVE_EVENT", "event" : {
"rawName" : "\"REACTIVE_EVENT\"",
"fullIdentifier" : "REACTIVE_EVENT"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -378,7 +456,10 @@
"rawName" : "\"START\"", "rawName" : "\"START\"",
"fullIdentifier" : "START" "fullIdentifier" : "START"
} ], } ],
"event" : "AUDIT_EVENT", "event" : {
"rawName" : "\"AUDIT_EVENT\"",
"fullIdentifier" : "AUDIT_EVENT"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -392,7 +473,10 @@
"rawName" : "\"PROCESSING\"", "rawName" : "\"PROCESSING\"",
"fullIdentifier" : "PROCESSING" "fullIdentifier" : "PROCESSING"
} ], } ],
"event" : "EXTERNAL_TRIGGER", "event" : {
"rawName" : "\"EXTERNAL_TRIGGER\"",
"fullIdentifier" : "EXTERNAL_TRIGGER"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -24,12 +24,12 @@ skinparam svgLinkTarget _self
[*] --> INIT_STATE [*] --> INIT_STATE
START -[#1E90FF,bold]-> PROCESSING <<external>> <<e_SUBMIT_EVENT>> : SUBMIT_EVENT START -[#1E90FF,bold]-> PROCESSING <<external>> : SUBMIT_EVENT
PROCESSING -[#1E90FF,bold]-> COMPLETED <<external>> <<e_FINISH>> : FINISH PROCESSING -[#1E90FF,bold]-> COMPLETED <<external>> : FINISH
PROCESSING -[#1E90FF,bold]-> CANCELLED <<external>> <<e_CANCEL_EVENT>> : CANCEL_EVENT PROCESSING -[#1E90FF,bold]-> CANCELLED <<external>> : CANCEL_EVENT
START -[#1E90FF,bold]-> PROCESSING <<external>> <<e_REACTIVE_EVENT>> : REACTIVE_EVENT START -[#1E90FF,bold]-> PROCESSING <<external>> : REACTIVE_EVENT
START -[#1E90FF,bold]-> START <<external>> <<e_AUDIT_EVENT>> : AUDIT_EVENT START -[#1E90FF,bold]-> START <<external>> : AUDIT_EVENT
START -[#1E90FF,bold]-> PROCESSING <<external>> <<e_EXTERNAL_TRIGGER>> : EXTERNAL_TRIGGER START -[#1E90FF,bold]-> PROCESSING <<external>> : EXTERNAL_TRIGGER
CANCELLED --> [*] CANCELLED --> [*]
COMPLETED --> [*] COMPLETED --> [*]

View File

@@ -7,7 +7,9 @@
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/OrderController.java", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/OrderController.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 34 "sourceState" : null,
"lineNumber" : 34,
"polymorphicEvents" : null
}, { }, {
"event" : "AUDIT_EVENT", "event" : "AUDIT_EVENT",
"className" : "click.kamil.examples.statemachine.extended.web.AuditInterceptor", "className" : "click.kamil.examples.statemachine.extended.web.AuditInterceptor",
@@ -15,7 +17,9 @@
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/AuditInterceptor.java", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/AuditInterceptor.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 16 "sourceState" : null,
"lineNumber" : 16,
"polymorphicEvents" : null
}, { }, {
"event" : "EXTERNAL_TRIGGER", "event" : "EXTERNAL_TRIGGER",
"className" : "click.kamil.examples.statemachine.extended.service.OrderService", "className" : "click.kamil.examples.statemachine.extended.service.OrderService",
@@ -23,7 +27,9 @@
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/OrderService.java", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/OrderService.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 19 "sourceState" : null,
"lineNumber" : 19,
"polymorphicEvents" : null
}, { }, {
"event" : "SUBMIT_EVENT", "event" : "SUBMIT_EVENT",
"className" : "click.kamil.examples.statemachine.extended.service.OrderService", "className" : "click.kamil.examples.statemachine.extended.service.OrderService",
@@ -31,7 +37,9 @@
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/OrderService.java", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/OrderService.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 20 "sourceState" : null,
"lineNumber" : 20,
"polymorphicEvents" : null
}, { }, {
"event" : "CANCEL_EVENT", "event" : "CANCEL_EVENT",
"className" : "click.kamil.examples.statemachine.extended.service.OrderService", "className" : "click.kamil.examples.statemachine.extended.service.OrderService",
@@ -39,7 +47,9 @@
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/OrderService.java", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/OrderService.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 25 "sourceState" : null,
"lineNumber" : 25,
"polymorphicEvents" : null
}, { }, {
"event" : "[LIFECYCLE:RESTORE]", "event" : "[LIFECYCLE:RESTORE]",
"className" : "click.kamil.examples.statemachine.extended.service.OrderService", "className" : "click.kamil.examples.statemachine.extended.service.OrderService",
@@ -47,7 +57,9 @@
"sourceFile" : null, "sourceFile" : null,
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 29 "sourceState" : null,
"lineNumber" : 29,
"polymorphicEvents" : null
}, { }, {
"event" : "REACTIVE_EVENT", "event" : "REACTIVE_EVENT",
"className" : "click.kamil.examples.statemachine.extended.service.ReactiveOrderService", "className" : "click.kamil.examples.statemachine.extended.service.ReactiveOrderService",
@@ -55,7 +67,9 @@
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ReactiveOrderService.java", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ReactiveOrderService.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 18 "sourceState" : null,
"lineNumber" : 18,
"polymorphicEvents" : null
} ], } ],
"entryPoints" : [ { "entryPoints" : [ {
"type" : "REST", "type" : "REST",
@@ -152,8 +166,16 @@
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/OrderService.java", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/OrderService.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 19 "sourceState" : null,
} "lineNumber" : 19,
"polymorphicEvents" : null
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "START",
"targetState" : "PROCESSING",
"event" : "EXTERNAL_TRIGGER"
} ]
}, { }, {
"entryPoint" : { "entryPoint" : {
"type" : "REST", "type" : "REST",
@@ -175,8 +197,16 @@
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/OrderService.java", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/OrderService.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 20 "sourceState" : null,
} "lineNumber" : 20,
"polymorphicEvents" : null
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "START",
"targetState" : "PROCESSING",
"event" : "SUBMIT_EVENT"
} ]
}, { }, {
"entryPoint" : { "entryPoint" : {
"type" : "REST", "type" : "REST",
@@ -198,8 +228,16 @@
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/OrderService.java", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/OrderService.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 25 "sourceState" : null,
} "lineNumber" : 25,
"polymorphicEvents" : null
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "PROCESSING",
"targetState" : "CANCELLED",
"event" : "CANCEL_EVENT"
} ]
}, { }, {
"entryPoint" : { "entryPoint" : {
"type" : "REST", "type" : "REST",
@@ -221,8 +259,16 @@
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/OrderController.java", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/OrderController.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 34 "sourceState" : null,
} "lineNumber" : 34,
"polymorphicEvents" : null
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "PROCESSING",
"targetState" : "COMPLETED",
"event" : "FINISH"
} ]
}, { }, {
"entryPoint" : { "entryPoint" : {
"type" : "REST", "type" : "REST",
@@ -248,8 +294,12 @@
"sourceFile" : null, "sourceFile" : null,
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 29 "sourceState" : null,
} "lineNumber" : 29,
"polymorphicEvents" : null
},
"contextMachineId" : "orderId",
"matchedTransitions" : null
}, { }, {
"entryPoint" : { "entryPoint" : {
"type" : "REST", "type" : "REST",
@@ -275,8 +325,16 @@
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ReactiveOrderService.java", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ReactiveOrderService.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 18 "sourceState" : null,
} "lineNumber" : 18,
"polymorphicEvents" : null
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "START",
"targetState" : "PROCESSING",
"event" : "REACTIVE_EVENT"
} ]
}, { }, {
"entryPoint" : { "entryPoint" : {
"type" : "CUSTOM", "type" : "CUSTOM",
@@ -297,8 +355,16 @@
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/AuditInterceptor.java", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/AuditInterceptor.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 16 "sourceState" : null,
} "lineNumber" : 16,
"polymorphicEvents" : null
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "START",
"targetState" : "START",
"event" : "AUDIT_EVENT"
} ]
} ], } ],
"properties" : { "properties" : {
"default" : { "default" : {
@@ -322,7 +388,10 @@
"rawName" : "\"PROCESSING\"", "rawName" : "\"PROCESSING\"",
"fullIdentifier" : "PROCESSING" "fullIdentifier" : "PROCESSING"
} ], } ],
"event" : "SUBMIT_EVENT", "event" : {
"rawName" : "MyEvents.SUBMIT",
"fullIdentifier" : "SUBMIT_EVENT"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -336,7 +405,10 @@
"rawName" : "\"COMPLETED\"", "rawName" : "\"COMPLETED\"",
"fullIdentifier" : "COMPLETED" "fullIdentifier" : "COMPLETED"
} ], } ],
"event" : "FINISH", "event" : {
"rawName" : "\"FINISH\"",
"fullIdentifier" : "FINISH"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -350,7 +422,10 @@
"rawName" : "\"CANCELLED\"", "rawName" : "\"CANCELLED\"",
"fullIdentifier" : "CANCELLED" "fullIdentifier" : "CANCELLED"
} ], } ],
"event" : "CANCEL_EVENT", "event" : {
"rawName" : "MyEvents.CANCEL",
"fullIdentifier" : "CANCEL_EVENT"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -364,7 +439,10 @@
"rawName" : "\"PROCESSING\"", "rawName" : "\"PROCESSING\"",
"fullIdentifier" : "PROCESSING" "fullIdentifier" : "PROCESSING"
} ], } ],
"event" : "REACTIVE_EVENT", "event" : {
"rawName" : "\"REACTIVE_EVENT\"",
"fullIdentifier" : "REACTIVE_EVENT"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -378,7 +456,10 @@
"rawName" : "\"START\"", "rawName" : "\"START\"",
"fullIdentifier" : "START" "fullIdentifier" : "START"
} ], } ],
"event" : "AUDIT_EVENT", "event" : {
"rawName" : "\"AUDIT_EVENT\"",
"fullIdentifier" : "AUDIT_EVENT"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -392,7 +473,10 @@
"rawName" : "\"PROCESSING\"", "rawName" : "\"PROCESSING\"",
"fullIdentifier" : "PROCESSING" "fullIdentifier" : "PROCESSING"
} ], } ],
"event" : "EXTERNAL_TRIGGER", "event" : {
"rawName" : "\"EXTERNAL_TRIGGER\"",
"fullIdentifier" : "EXTERNAL_TRIGGER"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -24,12 +24,12 @@ skinparam svgLinkTarget _self
[*] --> PROD_INITIAL [*] --> PROD_INITIAL
START -[#1E90FF,bold]-> PROCESSING <<external>> <<e_SUBMIT_EVENT>> : SUBMIT_EVENT START -[#1E90FF,bold]-> PROCESSING <<external>> : SUBMIT_EVENT
PROCESSING -[#1E90FF,bold]-> COMPLETED <<external>> <<e_FINISH>> : FINISH PROCESSING -[#1E90FF,bold]-> COMPLETED <<external>> : FINISH
PROCESSING -[#1E90FF,bold]-> CANCELLED <<external>> <<e_CANCEL_EVENT>> : CANCEL_EVENT PROCESSING -[#1E90FF,bold]-> CANCELLED <<external>> : CANCEL_EVENT
START -[#1E90FF,bold]-> PROCESSING <<external>> <<e_REACTIVE_EVENT>> : REACTIVE_EVENT START -[#1E90FF,bold]-> PROCESSING <<external>> : REACTIVE_EVENT
START -[#1E90FF,bold]-> START <<external>> <<e_AUDIT_EVENT>> : AUDIT_EVENT START -[#1E90FF,bold]-> START <<external>> : AUDIT_EVENT
START -[#1E90FF,bold]-> PROCESSING <<external>> <<e_EXTERNAL_TRIGGER>> : EXTERNAL_TRIGGER START -[#1E90FF,bold]-> PROCESSING <<external>> : EXTERNAL_TRIGGER
CANCELLED --> [*] CANCELLED --> [*]
COMPLETED --> [*] COMPLETED --> [*]

View File

@@ -22,7 +22,10 @@
"rawName" : "States.STATE2", "rawName" : "States.STATE2",
"fullIdentifier" : "States.STATE2" "fullIdentifier" : "States.STATE2"
} ], } ],
"event" : "Events.EVENT1", "event" : {
"rawName" : "Events.EVENT1",
"fullIdentifier" : "Events.EVENT1"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -36,7 +39,10 @@
"rawName" : "States.STATE3", "rawName" : "States.STATE3",
"fullIdentifier" : "States.STATE3" "fullIdentifier" : "States.STATE3"
} ], } ],
"event" : "Events.EVENT2", "event" : {
"rawName" : "Events.EVENT2",
"fullIdentifier" : "Events.EVENT2"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -50,7 +56,10 @@
"rawName" : "States.STATE4", "rawName" : "States.STATE4",
"fullIdentifier" : "States.STATE4" "fullIdentifier" : "States.STATE4"
} ], } ],
"event" : "Events.EVENT3", "event" : {
"rawName" : "Events.EVENT3",
"fullIdentifier" : "Events.EVENT3"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -64,7 +73,10 @@
"rawName" : "States.STATE5", "rawName" : "States.STATE5",
"fullIdentifier" : "States.STATE5" "fullIdentifier" : "States.STATE5"
} ], } ],
"event" : "Events.EVENT4", "event" : {
"rawName" : "Events.EVENT4",
"fullIdentifier" : "Events.EVENT4"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -78,7 +90,10 @@
"rawName" : "States.STATE6", "rawName" : "States.STATE6",
"fullIdentifier" : "States.STATE6" "fullIdentifier" : "States.STATE6"
} ], } ],
"event" : "Events.EVENT5", "event" : {
"rawName" : "Events.EVENT5",
"fullIdentifier" : "Events.EVENT5"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -92,7 +107,10 @@
"rawName" : "States.STATE7", "rawName" : "States.STATE7",
"fullIdentifier" : "States.STATE7" "fullIdentifier" : "States.STATE7"
} ], } ],
"event" : "Events.EVENT6", "event" : {
"rawName" : "Events.EVENT6",
"fullIdentifier" : "Events.EVENT6"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -106,7 +124,10 @@
"rawName" : "States.STATE8", "rawName" : "States.STATE8",
"fullIdentifier" : "States.STATE8" "fullIdentifier" : "States.STATE8"
} ], } ],
"event" : "Events.EVENT7", "event" : {
"rawName" : "Events.EVENT7",
"fullIdentifier" : "Events.EVENT7"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -120,7 +141,10 @@
"rawName" : "States.STATE9", "rawName" : "States.STATE9",
"fullIdentifier" : "States.STATE9" "fullIdentifier" : "States.STATE9"
} ], } ],
"event" : "Events.EVENT8", "event" : {
"rawName" : "Events.EVENT8",
"fullIdentifier" : "Events.EVENT8"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -134,7 +158,10 @@
"rawName" : "States.STATE10", "rawName" : "States.STATE10",
"fullIdentifier" : "States.STATE10" "fullIdentifier" : "States.STATE10"
} ], } ],
"event" : "Events.EVENT9", "event" : {
"rawName" : "Events.EVENT9",
"fullIdentifier" : "Events.EVENT9"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -148,7 +175,10 @@
"rawName" : "States.STATE11", "rawName" : "States.STATE11",
"fullIdentifier" : "States.STATE11" "fullIdentifier" : "States.STATE11"
} ], } ],
"event" : "Events.EVENT10", "event" : {
"rawName" : "Events.EVENT10",
"fullIdentifier" : "Events.EVENT10"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -162,7 +192,10 @@
"rawName" : "States.STATE12", "rawName" : "States.STATE12",
"fullIdentifier" : "States.STATE12" "fullIdentifier" : "States.STATE12"
} ], } ],
"event" : "Events.EVENT11", "event" : {
"rawName" : "Events.EVENT11",
"fullIdentifier" : "Events.EVENT11"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -176,7 +209,10 @@
"rawName" : "States.STATE13", "rawName" : "States.STATE13",
"fullIdentifier" : "States.STATE13" "fullIdentifier" : "States.STATE13"
} ], } ],
"event" : "Events.EVENT12", "event" : {
"rawName" : "Events.EVENT12",
"fullIdentifier" : "Events.EVENT12"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -190,7 +226,10 @@
"rawName" : "States.STATE14", "rawName" : "States.STATE14",
"fullIdentifier" : "States.STATE14" "fullIdentifier" : "States.STATE14"
} ], } ],
"event" : "Events.EVENT13", "event" : {
"rawName" : "Events.EVENT13",
"fullIdentifier" : "Events.EVENT13"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -204,7 +243,10 @@
"rawName" : "States.STATE15", "rawName" : "States.STATE15",
"fullIdentifier" : "States.STATE15" "fullIdentifier" : "States.STATE15"
} ], } ],
"event" : "Events.EVENT14", "event" : {
"rawName" : "Events.EVENT14",
"fullIdentifier" : "Events.EVENT14"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -218,7 +260,10 @@
"rawName" : "States.STATE16", "rawName" : "States.STATE16",
"fullIdentifier" : "States.STATE16" "fullIdentifier" : "States.STATE16"
} ], } ],
"event" : "Events.EVENT15", "event" : {
"rawName" : "Events.EVENT15",
"fullIdentifier" : "Events.EVENT15"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -232,7 +277,10 @@
"rawName" : "States.CANCEL", "rawName" : "States.CANCEL",
"fullIdentifier" : "States.CANCEL" "fullIdentifier" : "States.CANCEL"
} ], } ],
"event" : "Events.EVENT_CANCEL", "event" : {
"rawName" : "Events.EVENT_CANCEL",
"fullIdentifier" : "Events.EVENT_CANCEL"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -246,7 +294,10 @@
"rawName" : "States.CANCEL", "rawName" : "States.CANCEL",
"fullIdentifier" : "States.CANCEL" "fullIdentifier" : "States.CANCEL"
} ], } ],
"event" : "Events.EVENT_CANCEL", "event" : {
"rawName" : "Events.EVENT_CANCEL",
"fullIdentifier" : "Events.EVENT_CANCEL"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -260,7 +311,10 @@
"rawName" : "States.CANCEL", "rawName" : "States.CANCEL",
"fullIdentifier" : "States.CANCEL" "fullIdentifier" : "States.CANCEL"
} ], } ],
"event" : "Events.EVENT_CANCEL", "event" : {
"rawName" : "Events.EVENT_CANCEL",
"fullIdentifier" : "Events.EVENT_CANCEL"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -274,7 +328,10 @@
"rawName" : "States.CANCEL", "rawName" : "States.CANCEL",
"fullIdentifier" : "States.CANCEL" "fullIdentifier" : "States.CANCEL"
} ], } ],
"event" : "Events.EVENT_CANCEL", "event" : {
"rawName" : "Events.EVENT_CANCEL",
"fullIdentifier" : "Events.EVENT_CANCEL"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -288,7 +345,10 @@
"rawName" : "States.CANCEL", "rawName" : "States.CANCEL",
"fullIdentifier" : "States.CANCEL" "fullIdentifier" : "States.CANCEL"
} ], } ],
"event" : "Events.EVENT_CANCEL", "event" : {
"rawName" : "Events.EVENT_CANCEL",
"fullIdentifier" : "Events.EVENT_CANCEL"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -302,7 +362,10 @@
"rawName" : "States.STATEY", "rawName" : "States.STATEY",
"fullIdentifier" : "States.STATEY" "fullIdentifier" : "States.STATEY"
} ], } ],
"event" : "Events.EVENTY", "event" : {
"rawName" : "Events.EVENTY",
"fullIdentifier" : "Events.EVENTY"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -764,7 +827,10 @@
"rawName" : "States.STATE_EXTRA_1", "rawName" : "States.STATE_EXTRA_1",
"fullIdentifier" : "States.STATE_EXTRA_1" "fullIdentifier" : "States.STATE_EXTRA_1"
} ], } ],
"event" : "Events.EVENTX", "event" : {
"rawName" : "Events.EVENTX",
"fullIdentifier" : "Events.EVENTX"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -813,7 +879,10 @@
"rawName" : "States.STATE_EXTRA_3", "rawName" : "States.STATE_EXTRA_3",
"fullIdentifier" : "States.STATE_EXTRA_3" "fullIdentifier" : "States.STATE_EXTRA_3"
} ], } ],
"event" : "Events.EVENTY", "event" : {
"rawName" : "Events.EVENTY",
"fullIdentifier" : "Events.EVENTY"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -827,7 +896,10 @@
"rawName" : "States.CANCEL", "rawName" : "States.CANCEL",
"fullIdentifier" : "States.CANCEL" "fullIdentifier" : "States.CANCEL"
} ], } ],
"event" : "Events.EVENT_CANCEL_2", "event" : {
"rawName" : "Events.EVENT_CANCEL_2",
"fullIdentifier" : "Events.EVENT_CANCEL_2"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -841,7 +913,10 @@
"rawName" : "States.CANCEL", "rawName" : "States.CANCEL",
"fullIdentifier" : "States.CANCEL" "fullIdentifier" : "States.CANCEL"
} ], } ],
"event" : "Events.EVENT_CANCEL_2", "event" : {
"rawName" : "Events.EVENT_CANCEL_2",
"fullIdentifier" : "Events.EVENT_CANCEL_2"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null

Binary file not shown.

Before

Width:  |  Height:  |  Size: 223 KiB

After

Width:  |  Height:  |  Size: 223 KiB

View File

@@ -36,27 +36,27 @@ state States.STATE9 <<choice>>
state States.STATE8 <<choice>> state States.STATE8 <<choice>>
state States.STATE2 <<choice>> state States.STATE2 <<choice>>
States.STATE1 -[#1E90FF,bold]-> States.STATE2 <<external>> <<e_Events_EVENT1>> : Events.EVENT1 States.STATE1 -[#1E90FF,bold]-> States.STATE2 <<external>> : Events.EVENT1
States.STATE2 -[#1E90FF,bold]-> States.STATE3 <<external>> <<e_Events_EVENT2>> : Events.EVENT2 States.STATE2 -[#1E90FF,bold]-> States.STATE3 <<external>> : Events.EVENT2
States.STATE3 -[#1E90FF,bold]-> States.STATE4 <<external>> <<e_Events_EVENT3>> : Events.EVENT3 States.STATE3 -[#1E90FF,bold]-> States.STATE4 <<external>> : Events.EVENT3
States.STATE4 -[#1E90FF,bold]-> States.STATE5 <<external>> <<e_Events_EVENT4>> : Events.EVENT4 States.STATE4 -[#1E90FF,bold]-> States.STATE5 <<external>> : Events.EVENT4
States.STATE5 -[#1E90FF,bold]-> States.STATE6 <<external>> <<e_Events_EVENT5>> : Events.EVENT5 States.STATE5 -[#1E90FF,bold]-> States.STATE6 <<external>> : Events.EVENT5
States.STATE6 -[#1E90FF,bold]-> States.STATE7 <<external>> <<e_Events_EVENT6>> : Events.EVENT6 States.STATE6 -[#1E90FF,bold]-> States.STATE7 <<external>> : Events.EVENT6
States.STATE7 -[#1E90FF,bold]-> States.STATE8 <<external>> <<e_Events_EVENT7>> : Events.EVENT7 States.STATE7 -[#1E90FF,bold]-> States.STATE8 <<external>> : Events.EVENT7
States.STATE8 -[#1E90FF,bold]-> States.STATE9 <<external>> <<e_Events_EVENT8>> : Events.EVENT8 States.STATE8 -[#1E90FF,bold]-> States.STATE9 <<external>> : Events.EVENT8
States.STATE9 -[#1E90FF,bold]-> States.STATE10 <<external>> <<e_Events_EVENT9>> : Events.EVENT9 States.STATE9 -[#1E90FF,bold]-> States.STATE10 <<external>> : Events.EVENT9
States.STATE10 -[#1E90FF,bold]-> States.STATE11 <<external>> <<e_Events_EVENT10>> : Events.EVENT10 States.STATE10 -[#1E90FF,bold]-> States.STATE11 <<external>> : Events.EVENT10
States.STATE11 -[#1E90FF,bold]-> States.STATE12 <<external>> <<e_Events_EVENT11>> : Events.EVENT11 States.STATE11 -[#1E90FF,bold]-> States.STATE12 <<external>> : Events.EVENT11
States.STATE12 -[#1E90FF,bold]-> States.STATE13 <<external>> <<e_Events_EVENT12>> : Events.EVENT12 States.STATE12 -[#1E90FF,bold]-> States.STATE13 <<external>> : Events.EVENT12
States.STATE13 -[#1E90FF,bold]-> States.STATE14 <<external>> <<e_Events_EVENT13>> : Events.EVENT13 States.STATE13 -[#1E90FF,bold]-> States.STATE14 <<external>> : Events.EVENT13
States.STATE14 -[#1E90FF,bold]-> States.STATE15 <<external>> <<e_Events_EVENT14>> : Events.EVENT14 States.STATE14 -[#1E90FF,bold]-> States.STATE15 <<external>> : Events.EVENT14
States.STATE15 -[#1E90FF,bold]-> States.STATE16 <<external>> <<e_Events_EVENT15>> : Events.EVENT15 States.STATE15 -[#1E90FF,bold]-> States.STATE16 <<external>> : Events.EVENT15
States.STATE1 -[#1E90FF,bold]-> States.CANCEL <<external>> <<e_Events_EVENT_CANCEL>> : Events.EVENT_CANCEL States.STATE1 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL
States.STATE2 -[#1E90FF,bold]-> States.CANCEL <<external>> <<e_Events_EVENT_CANCEL>> : Events.EVENT_CANCEL States.STATE2 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL
States.STATE3 -[#1E90FF,bold]-> States.CANCEL <<external>> <<e_Events_EVENT_CANCEL>> : Events.EVENT_CANCEL States.STATE3 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL
States.STATE4 -[#1E90FF,bold]-> States.CANCEL <<external>> <<e_Events_EVENT_CANCEL>> : Events.EVENT_CANCEL States.STATE4 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL
States.STATE5 -[#1E90FF,bold]-> States.CANCEL <<external>> <<e_Events_EVENT_CANCEL>> : Events.EVENT_CANCEL States.STATE5 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL
States.STATE4 -[#1E90FF,bold]-> States.STATEY <<external>> <<e_Events_EVENTY>> : Events.EVENTY States.STATE4 -[#1E90FF,bold]-> States.STATEY <<external>> : Events.EVENTY
States.STATEY -[#FF6347,bold]-> States.STATEX <<choice_type>> : [guardVarEquals("value1")] (order=0) States.STATEY -[#FF6347,bold]-> States.STATEX <<choice_type>> : [guardVarEquals("value1")] (order=0)
States.STATEY -[#FF6347,bold]-> States.STATEZ <<choice_type>> : (order=1) States.STATEY -[#FF6347,bold]-> States.STATEZ <<choice_type>> : (order=1)
States.STATE16 -[#FF6347,bold]-> States.STATE17 <<choice_type>> : [guardVarEquals("value1")] (order=0) States.STATE16 -[#FF6347,bold]-> States.STATE17 <<choice_type>> : [guardVarEquals("value1")] (order=0)
@@ -82,12 +82,12 @@ States.STATE9 -[#FF6347,bold]-> States.STATE7 <<choice_type>> : [guardVarEquals(
States.STATE9 -[#FF6347,bold]-> States.STATE6 <<choice_type>> : (order=2) States.STATE9 -[#FF6347,bold]-> States.STATE6 <<choice_type>> : (order=2)
States.STATE8 -[#FF6347,bold]-> States.STATE9 <<choice_type>> : [guardVarEquals("forward9")] (order=0) States.STATE8 -[#FF6347,bold]-> States.STATE9 <<choice_type>> : [guardVarEquals("forward9")] (order=0)
States.STATE8 -[#FF6347,bold]-> States.STATE10 <<choice_type>> : (order=1) States.STATE8 -[#FF6347,bold]-> States.STATE10 <<choice_type>> : (order=1)
States.STATE2 -[#1E90FF,bold]-> States.STATE_EXTRA_1 <<external>> <<e_Events_EVENTX>> : Events.EVENTX States.STATE2 -[#1E90FF,bold]-> States.STATE_EXTRA_1 <<external>> : Events.EVENTX
States.STATE2 -[#FF6347,bold]-> States.STATE1 <<choice_type>> : [guardEventHeaderEquals("header1","foo")] (order=0) States.STATE2 -[#FF6347,bold]-> States.STATE1 <<choice_type>> : [guardEventHeaderEquals("header1","foo")] (order=0)
States.STATE2 -[#FF6347,bold]-> States.STATE_EXTRA_1 <<choice_type>> : (order=1) States.STATE2 -[#FF6347,bold]-> States.STATE_EXTRA_1 <<choice_type>> : (order=1)
States.STATE_EXTRA_1 -[#1E90FF,bold]-> States.STATE_EXTRA_3 <<external>> <<e_Events_EVENTY>> : Events.EVENTY States.STATE_EXTRA_1 -[#1E90FF,bold]-> States.STATE_EXTRA_3 <<external>> : Events.EVENTY
States.STATE_EXTRA_1 -[#1E90FF,bold]-> States.CANCEL <<external>> <<e_Events_EVENT_CANCEL_2>> : 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>> <<e_Events_EVENT_CANCEL_2>> : Events.EVENT_CANCEL_2 States.STATE_EXTRA_2 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL_2
States.STATEZ --> [*] States.STATEZ --> [*]
@enduml @enduml

View File

@@ -22,7 +22,10 @@
"rawName" : "States.STATE2", "rawName" : "States.STATE2",
"fullIdentifier" : "States.STATE2" "fullIdentifier" : "States.STATE2"
} ], } ],
"event" : "Events.EVENT1", "event" : {
"rawName" : "Events.EVENT1",
"fullIdentifier" : "Events.EVENT1"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -36,7 +39,10 @@
"rawName" : "States.STATE3", "rawName" : "States.STATE3",
"fullIdentifier" : "States.STATE3" "fullIdentifier" : "States.STATE3"
} ], } ],
"event" : "Events.EVENT2", "event" : {
"rawName" : "Events.EVENT2",
"fullIdentifier" : "Events.EVENT2"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -50,7 +56,10 @@
"rawName" : "States.STATE4", "rawName" : "States.STATE4",
"fullIdentifier" : "States.STATE4" "fullIdentifier" : "States.STATE4"
} ], } ],
"event" : "Events.EVENT3", "event" : {
"rawName" : "Events.EVENT3",
"fullIdentifier" : "Events.EVENT3"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -64,7 +73,10 @@
"rawName" : "States.STATE5", "rawName" : "States.STATE5",
"fullIdentifier" : "States.STATE5" "fullIdentifier" : "States.STATE5"
} ], } ],
"event" : "Events.EVENT4", "event" : {
"rawName" : "Events.EVENT4",
"fullIdentifier" : "Events.EVENT4"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -78,7 +90,10 @@
"rawName" : "States.STATE6", "rawName" : "States.STATE6",
"fullIdentifier" : "States.STATE6" "fullIdentifier" : "States.STATE6"
} ], } ],
"event" : "Events.EVENT5", "event" : {
"rawName" : "Events.EVENT5",
"fullIdentifier" : "Events.EVENT5"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -92,7 +107,10 @@
"rawName" : "States.STATE7", "rawName" : "States.STATE7",
"fullIdentifier" : "States.STATE7" "fullIdentifier" : "States.STATE7"
} ], } ],
"event" : "Events.EVENT6", "event" : {
"rawName" : "Events.EVENT6",
"fullIdentifier" : "Events.EVENT6"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -106,7 +124,10 @@
"rawName" : "States.STATE8", "rawName" : "States.STATE8",
"fullIdentifier" : "States.STATE8" "fullIdentifier" : "States.STATE8"
} ], } ],
"event" : "Events.EVENT7", "event" : {
"rawName" : "Events.EVENT7",
"fullIdentifier" : "Events.EVENT7"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -120,7 +141,10 @@
"rawName" : "States.STATE9", "rawName" : "States.STATE9",
"fullIdentifier" : "States.STATE9" "fullIdentifier" : "States.STATE9"
} ], } ],
"event" : "Events.EVENT8", "event" : {
"rawName" : "Events.EVENT8",
"fullIdentifier" : "Events.EVENT8"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -134,7 +158,10 @@
"rawName" : "States.STATE10", "rawName" : "States.STATE10",
"fullIdentifier" : "States.STATE10" "fullIdentifier" : "States.STATE10"
} ], } ],
"event" : "Events.EVENT9", "event" : {
"rawName" : "Events.EVENT9",
"fullIdentifier" : "Events.EVENT9"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -148,7 +175,10 @@
"rawName" : "States.STATE11", "rawName" : "States.STATE11",
"fullIdentifier" : "States.STATE11" "fullIdentifier" : "States.STATE11"
} ], } ],
"event" : "Events.EVENT10", "event" : {
"rawName" : "Events.EVENT10",
"fullIdentifier" : "Events.EVENT10"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -162,7 +192,10 @@
"rawName" : "States.STATE12", "rawName" : "States.STATE12",
"fullIdentifier" : "States.STATE12" "fullIdentifier" : "States.STATE12"
} ], } ],
"event" : "Events.EVENT11", "event" : {
"rawName" : "Events.EVENT11",
"fullIdentifier" : "Events.EVENT11"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -176,7 +209,10 @@
"rawName" : "States.STATE13", "rawName" : "States.STATE13",
"fullIdentifier" : "States.STATE13" "fullIdentifier" : "States.STATE13"
} ], } ],
"event" : "Events.EVENT12", "event" : {
"rawName" : "Events.EVENT12",
"fullIdentifier" : "Events.EVENT12"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -190,7 +226,10 @@
"rawName" : "States.STATE14", "rawName" : "States.STATE14",
"fullIdentifier" : "States.STATE14" "fullIdentifier" : "States.STATE14"
} ], } ],
"event" : "Events.EVENT13", "event" : {
"rawName" : "Events.EVENT13",
"fullIdentifier" : "Events.EVENT13"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -204,7 +243,10 @@
"rawName" : "States.STATE15", "rawName" : "States.STATE15",
"fullIdentifier" : "States.STATE15" "fullIdentifier" : "States.STATE15"
} ], } ],
"event" : "Events.EVENT14", "event" : {
"rawName" : "Events.EVENT14",
"fullIdentifier" : "Events.EVENT14"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -218,7 +260,10 @@
"rawName" : "States.STATE16", "rawName" : "States.STATE16",
"fullIdentifier" : "States.STATE16" "fullIdentifier" : "States.STATE16"
} ], } ],
"event" : "Events.EVENT15", "event" : {
"rawName" : "Events.EVENT15",
"fullIdentifier" : "Events.EVENT15"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -232,7 +277,10 @@
"rawName" : "States.CANCEL", "rawName" : "States.CANCEL",
"fullIdentifier" : "States.CANCEL" "fullIdentifier" : "States.CANCEL"
} ], } ],
"event" : "Events.EVENT_CANCEL", "event" : {
"rawName" : "Events.EVENT_CANCEL",
"fullIdentifier" : "Events.EVENT_CANCEL"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -246,7 +294,10 @@
"rawName" : "States.CANCEL", "rawName" : "States.CANCEL",
"fullIdentifier" : "States.CANCEL" "fullIdentifier" : "States.CANCEL"
} ], } ],
"event" : "Events.EVENT_CANCEL", "event" : {
"rawName" : "Events.EVENT_CANCEL",
"fullIdentifier" : "Events.EVENT_CANCEL"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -260,7 +311,10 @@
"rawName" : "States.CANCEL", "rawName" : "States.CANCEL",
"fullIdentifier" : "States.CANCEL" "fullIdentifier" : "States.CANCEL"
} ], } ],
"event" : "Events.EVENT_CANCEL", "event" : {
"rawName" : "Events.EVENT_CANCEL",
"fullIdentifier" : "Events.EVENT_CANCEL"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -274,7 +328,10 @@
"rawName" : "States.CANCEL", "rawName" : "States.CANCEL",
"fullIdentifier" : "States.CANCEL" "fullIdentifier" : "States.CANCEL"
} ], } ],
"event" : "Events.EVENT_CANCEL", "event" : {
"rawName" : "Events.EVENT_CANCEL",
"fullIdentifier" : "Events.EVENT_CANCEL"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -288,7 +345,10 @@
"rawName" : "States.CANCEL", "rawName" : "States.CANCEL",
"fullIdentifier" : "States.CANCEL" "fullIdentifier" : "States.CANCEL"
} ], } ],
"event" : "Events.EVENT_CANCEL", "event" : {
"rawName" : "Events.EVENT_CANCEL",
"fullIdentifier" : "Events.EVENT_CANCEL"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -302,7 +362,10 @@
"rawName" : "States.STATEY", "rawName" : "States.STATEY",
"fullIdentifier" : "States.STATEY" "fullIdentifier" : "States.STATEY"
} ], } ],
"event" : "Events.EVENTY", "event" : {
"rawName" : "Events.EVENTY",
"fullIdentifier" : "Events.EVENTY"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -764,7 +827,10 @@
"rawName" : "States.STATE_EXTRA_1_1", "rawName" : "States.STATE_EXTRA_1_1",
"fullIdentifier" : "States.STATE_EXTRA_1_1" "fullIdentifier" : "States.STATE_EXTRA_1_1"
} ], } ],
"event" : "Events.EVENT_1_1", "event" : {
"rawName" : "Events.EVENT_1_1",
"fullIdentifier" : "Events.EVENT_1_1"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -813,7 +879,10 @@
"rawName" : "States.STATE_EXTRA_1", "rawName" : "States.STATE_EXTRA_1",
"fullIdentifier" : "States.STATE_EXTRA_1" "fullIdentifier" : "States.STATE_EXTRA_1"
} ], } ],
"event" : "Events.EVENT_1_2", "event" : {
"rawName" : "Events.EVENT_1_2",
"fullIdentifier" : "Events.EVENT_1_2"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -827,7 +896,10 @@
"rawName" : "States.CANCEL", "rawName" : "States.CANCEL",
"fullIdentifier" : "States.CANCEL" "fullIdentifier" : "States.CANCEL"
} ], } ],
"event" : "Events.EVENT_CANCEL_2", "event" : {
"rawName" : "Events.EVENT_CANCEL_2",
"fullIdentifier" : "Events.EVENT_CANCEL_2"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -841,7 +913,10 @@
"rawName" : "States.CANCEL", "rawName" : "States.CANCEL",
"fullIdentifier" : "States.CANCEL" "fullIdentifier" : "States.CANCEL"
} ], } ],
"event" : "Events.EVENT_CANCEL_2", "event" : {
"rawName" : "Events.EVENT_CANCEL_2",
"fullIdentifier" : "Events.EVENT_CANCEL_2"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -855,7 +930,10 @@
"rawName" : "States.CANCEL", "rawName" : "States.CANCEL",
"fullIdentifier" : "States.CANCEL" "fullIdentifier" : "States.CANCEL"
} ], } ],
"event" : "Events.EVENT_CANCEL_2", "event" : {
"rawName" : "Events.EVENT_CANCEL_2",
"fullIdentifier" : "Events.EVENT_CANCEL_2"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null

Binary file not shown.

Before

Width:  |  Height:  |  Size: 258 KiB

After

Width:  |  Height:  |  Size: 257 KiB

View File

@@ -36,27 +36,27 @@ state States.STATE9 <<choice>>
state States.STATE8 <<choice>> state States.STATE8 <<choice>>
state States.STATE_EXTRA_1_2 <<choice>> state States.STATE_EXTRA_1_2 <<choice>>
States.STATE1 -[#1E90FF,bold]-> States.STATE2 <<external>> <<e_Events_EVENT1>> : Events.EVENT1 States.STATE1 -[#1E90FF,bold]-> States.STATE2 <<external>> : Events.EVENT1
States.STATE2 -[#1E90FF,bold]-> States.STATE3 <<external>> <<e_Events_EVENT2>> : Events.EVENT2 States.STATE2 -[#1E90FF,bold]-> States.STATE3 <<external>> : Events.EVENT2
States.STATE3 -[#1E90FF,bold]-> States.STATE4 <<external>> <<e_Events_EVENT3>> : Events.EVENT3 States.STATE3 -[#1E90FF,bold]-> States.STATE4 <<external>> : Events.EVENT3
States.STATE4 -[#1E90FF,bold]-> States.STATE5 <<external>> <<e_Events_EVENT4>> : Events.EVENT4 States.STATE4 -[#1E90FF,bold]-> States.STATE5 <<external>> : Events.EVENT4
States.STATE5 -[#1E90FF,bold]-> States.STATE6 <<external>> <<e_Events_EVENT5>> : Events.EVENT5 States.STATE5 -[#1E90FF,bold]-> States.STATE6 <<external>> : Events.EVENT5
States.STATE6 -[#1E90FF,bold]-> States.STATE7 <<external>> <<e_Events_EVENT6>> : Events.EVENT6 States.STATE6 -[#1E90FF,bold]-> States.STATE7 <<external>> : Events.EVENT6
States.STATE7 -[#1E90FF,bold]-> States.STATE8 <<external>> <<e_Events_EVENT7>> : Events.EVENT7 States.STATE7 -[#1E90FF,bold]-> States.STATE8 <<external>> : Events.EVENT7
States.STATE8 -[#1E90FF,bold]-> States.STATE9 <<external>> <<e_Events_EVENT8>> : Events.EVENT8 States.STATE8 -[#1E90FF,bold]-> States.STATE9 <<external>> : Events.EVENT8
States.STATE9 -[#1E90FF,bold]-> States.STATE10 <<external>> <<e_Events_EVENT9>> : Events.EVENT9 States.STATE9 -[#1E90FF,bold]-> States.STATE10 <<external>> : Events.EVENT9
States.STATE10 -[#1E90FF,bold]-> States.STATE11 <<external>> <<e_Events_EVENT10>> : Events.EVENT10 States.STATE10 -[#1E90FF,bold]-> States.STATE11 <<external>> : Events.EVENT10
States.STATE11 -[#1E90FF,bold]-> States.STATE12 <<external>> <<e_Events_EVENT11>> : Events.EVENT11 States.STATE11 -[#1E90FF,bold]-> States.STATE12 <<external>> : Events.EVENT11
States.STATE12 -[#1E90FF,bold]-> States.STATE13 <<external>> <<e_Events_EVENT12>> : Events.EVENT12 States.STATE12 -[#1E90FF,bold]-> States.STATE13 <<external>> : Events.EVENT12
States.STATE13 -[#1E90FF,bold]-> States.STATE14 <<external>> <<e_Events_EVENT13>> : Events.EVENT13 States.STATE13 -[#1E90FF,bold]-> States.STATE14 <<external>> : Events.EVENT13
States.STATE14 -[#1E90FF,bold]-> States.STATE15 <<external>> <<e_Events_EVENT14>> : Events.EVENT14 States.STATE14 -[#1E90FF,bold]-> States.STATE15 <<external>> : Events.EVENT14
States.STATE15 -[#1E90FF,bold]-> States.STATE16 <<external>> <<e_Events_EVENT15>> : Events.EVENT15 States.STATE15 -[#1E90FF,bold]-> States.STATE16 <<external>> : Events.EVENT15
States.STATE1 -[#1E90FF,bold]-> States.CANCEL <<external>> <<e_Events_EVENT_CANCEL>> : Events.EVENT_CANCEL States.STATE1 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL
States.STATE2 -[#1E90FF,bold]-> States.CANCEL <<external>> <<e_Events_EVENT_CANCEL>> : Events.EVENT_CANCEL States.STATE2 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL
States.STATE3 -[#1E90FF,bold]-> States.CANCEL <<external>> <<e_Events_EVENT_CANCEL>> : Events.EVENT_CANCEL States.STATE3 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL
States.STATE4 -[#1E90FF,bold]-> States.CANCEL <<external>> <<e_Events_EVENT_CANCEL>> : Events.EVENT_CANCEL States.STATE4 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL
States.STATE5 -[#1E90FF,bold]-> States.CANCEL <<external>> <<e_Events_EVENT_CANCEL>> : Events.EVENT_CANCEL States.STATE5 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL
States.STATE4 -[#1E90FF,bold]-> States.STATEY <<external>> <<e_Events_EVENTY>> : Events.EVENTY States.STATE4 -[#1E90FF,bold]-> States.STATEY <<external>> : Events.EVENTY
States.STATEY -[#FF6347,bold]-> States.STATEX <<choice_type>> : [guardVarEquals("value1")] (order=0) States.STATEY -[#FF6347,bold]-> States.STATEX <<choice_type>> : [guardVarEquals("value1")] (order=0)
States.STATEY -[#FF6347,bold]-> States.STATEZ <<choice_type>> : (order=1) States.STATEY -[#FF6347,bold]-> States.STATEZ <<choice_type>> : (order=1)
States.STATE16 -[#FF6347,bold]-> States.STATE17 <<choice_type>> : [guardVarEquals("value1")] (order=0) States.STATE16 -[#FF6347,bold]-> States.STATE17 <<choice_type>> : [guardVarEquals("value1")] (order=0)
@@ -82,13 +82,13 @@ States.STATE9 -[#FF6347,bold]-> States.STATE7 <<choice_type>> : [guardVarEquals(
States.STATE9 -[#FF6347,bold]-> States.STATE6 <<choice_type>> : (order=2) States.STATE9 -[#FF6347,bold]-> States.STATE6 <<choice_type>> : (order=2)
States.STATE8 -[#FF6347,bold]-> States.STATE9 <<choice_type>> : [guardVarEquals("forward9")] (order=0) States.STATE8 -[#FF6347,bold]-> States.STATE9 <<choice_type>> : [guardVarEquals("forward9")] (order=0)
States.STATE8 -[#FF6347,bold]-> States.STATE10 <<choice_type>> : (order=1) States.STATE8 -[#FF6347,bold]-> States.STATE10 <<choice_type>> : (order=1)
States.STATE9 -[#1E90FF,bold]-> States.STATE_EXTRA_1_1 <<external>> <<e_Events_EVENT_1_1>> : Events.EVENT_1_1 States.STATE9 -[#1E90FF,bold]-> States.STATE_EXTRA_1_1 <<external>> : Events.EVENT_1_1
States.STATE_EXTRA_1_2 -[#FF6347,bold]-> States.STATE_EXTRA_1_1 <<choice_type>> : [guardEventHeaderEquals("header1","foo")] (order=0) States.STATE_EXTRA_1_2 -[#FF6347,bold]-> States.STATE_EXTRA_1_1 <<choice_type>> : [guardEventHeaderEquals("header1","foo")] (order=0)
States.STATE_EXTRA_1_2 -[#FF6347,bold]-> States.STATE_EXTRA_1_3 <<choice_type>> : (order=1) States.STATE_EXTRA_1_2 -[#FF6347,bold]-> States.STATE_EXTRA_1_3 <<choice_type>> : (order=1)
States.STATE_EXTRA_1_3 -[#1E90FF,bold]-> States.STATE_EXTRA_1 <<external>> <<e_Events_EVENT_1_2>> : Events.EVENT_1_2 States.STATE_EXTRA_1_3 -[#1E90FF,bold]-> States.STATE_EXTRA_1 <<external>> : Events.EVENT_1_2
States.STATE_EXTRA_1_1 -[#1E90FF,bold]-> States.CANCEL <<external>> <<e_Events_EVENT_CANCEL_2>> : Events.EVENT_CANCEL_2 States.STATE_EXTRA_1_1 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL_2
States.STATE_EXTRA_1_2 -[#1E90FF,bold]-> States.CANCEL <<external>> <<e_Events_EVENT_CANCEL_2>> : 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>> <<e_Events_EVENT_CANCEL_2>> : Events.EVENT_CANCEL_2 States.STATE_EXTRA_1 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL_2
States.STATEZ --> [*] States.STATEZ --> [*]
@enduml @enduml

View File

@@ -22,7 +22,10 @@
"rawName" : "States.FORK", "rawName" : "States.FORK",
"fullIdentifier" : "States.FORK" "fullIdentifier" : "States.FORK"
} ], } ],
"event" : "Events.TO_FORK", "event" : {
"rawName" : "Events.TO_FORK",
"fullIdentifier" : "Events.TO_FORK"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -53,7 +56,10 @@
"rawName" : "States.REGION1_STATE2", "rawName" : "States.REGION1_STATE2",
"fullIdentifier" : "States.REGION1_STATE2" "fullIdentifier" : "States.REGION1_STATE2"
} ], } ],
"event" : "Events.R1_NEXT", "event" : {
"rawName" : "Events.R1_NEXT",
"fullIdentifier" : "Events.R1_NEXT"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -67,7 +73,10 @@
"rawName" : "States.REGION2_STATE2", "rawName" : "States.REGION2_STATE2",
"fullIdentifier" : "States.REGION2_STATE2" "fullIdentifier" : "States.REGION2_STATE2"
} ], } ],
"event" : "Events.R2_NEXT", "event" : {
"rawName" : "Events.R2_NEXT",
"fullIdentifier" : "Events.R2_NEXT"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -98,7 +107,10 @@
"rawName" : "States.END", "rawName" : "States.END",
"fullIdentifier" : "States.END" "fullIdentifier" : "States.END"
} ], } ],
"event" : "Events.TO_END", "event" : {
"rawName" : "Events.TO_END",
"fullIdentifier" : "Events.TO_END"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -24,14 +24,14 @@ skinparam svgLinkTarget _self
[*] --> States.START [*] --> States.START
States.START -[#1E90FF,bold]-> States.FORK <<external>> <<e_Events_TO_FORK>> : Events.TO_FORK States.START -[#1E90FF,bold]-> States.FORK <<external>> : Events.TO_FORK
States.FORK -[#20B2AA,bold]-> States.REGION1_STATE1 <<fork>> States.FORK -[#20B2AA,bold]-> States.REGION1_STATE1 <<fork>>
States.FORK -[#20B2AA,bold]-> States.REGION2_STATE1 <<fork>> States.FORK -[#20B2AA,bold]-> States.REGION2_STATE1 <<fork>>
States.REGION1_STATE1 -[#1E90FF,bold]-> States.REGION1_STATE2 <<external>> <<e_Events_R1_NEXT>> : Events.R1_NEXT States.REGION1_STATE1 -[#1E90FF,bold]-> States.REGION1_STATE2 <<external>> : Events.R1_NEXT
States.REGION2_STATE1 -[#1E90FF,bold]-> States.REGION2_STATE2 <<external>> <<e_Events_R2_NEXT>> : Events.R2_NEXT States.REGION2_STATE1 -[#1E90FF,bold]-> States.REGION2_STATE2 <<external>> : Events.R2_NEXT
States.REGION1_STATE2 -[#8A2BE2,bold]-> States.JOIN <<join>> 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>> <<e_Events_TO_END>> : Events.TO_END States.JOIN -[#1E90FF,bold]-> States.END <<external>> : Events.TO_END
States.END --> [*] States.END --> [*]
@enduml @enduml

View File

@@ -22,7 +22,10 @@
"rawName" : "States.STATE2", "rawName" : "States.STATE2",
"fullIdentifier" : "States.STATE2" "fullIdentifier" : "States.STATE2"
} ], } ],
"event" : "Events.EVENT1", "event" : {
"rawName" : "Events.EVENT1",
"fullIdentifier" : "Events.EVENT1"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -36,7 +39,10 @@
"rawName" : "States.STATE3", "rawName" : "States.STATE3",
"fullIdentifier" : "States.STATE3" "fullIdentifier" : "States.STATE3"
} ], } ],
"event" : "Events.EVENT2", "event" : {
"rawName" : "Events.EVENT2",
"fullIdentifier" : "Events.EVENT2"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -50,7 +56,10 @@
"rawName" : "States.STATE4", "rawName" : "States.STATE4",
"fullIdentifier" : "States.STATE4" "fullIdentifier" : "States.STATE4"
} ], } ],
"event" : "Events.EVENT3", "event" : {
"rawName" : "Events.EVENT3",
"fullIdentifier" : "Events.EVENT3"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -64,7 +73,10 @@
"rawName" : "States.STATE5", "rawName" : "States.STATE5",
"fullIdentifier" : "States.STATE5" "fullIdentifier" : "States.STATE5"
} ], } ],
"event" : "Events.EVENT4", "event" : {
"rawName" : "Events.EVENT4",
"fullIdentifier" : "Events.EVENT4"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -78,7 +90,10 @@
"rawName" : "States.STATE6", "rawName" : "States.STATE6",
"fullIdentifier" : "States.STATE6" "fullIdentifier" : "States.STATE6"
} ], } ],
"event" : "Events.EVENT5", "event" : {
"rawName" : "Events.EVENT5",
"fullIdentifier" : "Events.EVENT5"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -92,7 +107,10 @@
"rawName" : "States.STATE7", "rawName" : "States.STATE7",
"fullIdentifier" : "States.STATE7" "fullIdentifier" : "States.STATE7"
} ], } ],
"event" : "Events.EVENT6", "event" : {
"rawName" : "Events.EVENT6",
"fullIdentifier" : "Events.EVENT6"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -106,7 +124,10 @@
"rawName" : "States.STATE8", "rawName" : "States.STATE8",
"fullIdentifier" : "States.STATE8" "fullIdentifier" : "States.STATE8"
} ], } ],
"event" : "Events.EVENT7", "event" : {
"rawName" : "Events.EVENT7",
"fullIdentifier" : "Events.EVENT7"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -120,7 +141,10 @@
"rawName" : "States.STATE9", "rawName" : "States.STATE9",
"fullIdentifier" : "States.STATE9" "fullIdentifier" : "States.STATE9"
} ], } ],
"event" : "Events.EVENT8", "event" : {
"rawName" : "Events.EVENT8",
"fullIdentifier" : "Events.EVENT8"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -134,7 +158,10 @@
"rawName" : "States.STATE10", "rawName" : "States.STATE10",
"fullIdentifier" : "States.STATE10" "fullIdentifier" : "States.STATE10"
} ], } ],
"event" : "Events.EVENT9", "event" : {
"rawName" : "Events.EVENT9",
"fullIdentifier" : "Events.EVENT9"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -148,7 +175,10 @@
"rawName" : "States.CANCEL", "rawName" : "States.CANCEL",
"fullIdentifier" : "States.CANCEL" "fullIdentifier" : "States.CANCEL"
} ], } ],
"event" : "Events.EVENT_CANCEL", "event" : {
"rawName" : "Events.EVENT_CANCEL",
"fullIdentifier" : "Events.EVENT_CANCEL"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -162,7 +192,10 @@
"rawName" : "States.CANCEL", "rawName" : "States.CANCEL",
"fullIdentifier" : "States.CANCEL" "fullIdentifier" : "States.CANCEL"
} ], } ],
"event" : "Events.EVENT_CANCEL", "event" : {
"rawName" : "Events.EVENT_CANCEL",
"fullIdentifier" : "Events.EVENT_CANCEL"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -176,7 +209,10 @@
"rawName" : "States.CANCEL", "rawName" : "States.CANCEL",
"fullIdentifier" : "States.CANCEL" "fullIdentifier" : "States.CANCEL"
} ], } ],
"event" : "Events.EVENT_CANCEL", "event" : {
"rawName" : "Events.EVENT_CANCEL",
"fullIdentifier" : "Events.EVENT_CANCEL"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -190,7 +226,10 @@
"rawName" : "States.CANCEL", "rawName" : "States.CANCEL",
"fullIdentifier" : "States.CANCEL" "fullIdentifier" : "States.CANCEL"
} ], } ],
"event" : "Events.EVENT_CANCEL", "event" : {
"rawName" : "Events.EVENT_CANCEL",
"fullIdentifier" : "Events.EVENT_CANCEL"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -204,7 +243,10 @@
"rawName" : "States.CANCEL", "rawName" : "States.CANCEL",
"fullIdentifier" : "States.CANCEL" "fullIdentifier" : "States.CANCEL"
} ], } ],
"event" : "Events.EVENT_CANCEL", "event" : {
"rawName" : "Events.EVENT_CANCEL",
"fullIdentifier" : "Events.EVENT_CANCEL"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -218,7 +260,10 @@
"rawName" : "States.STATEY", "rawName" : "States.STATEY",
"fullIdentifier" : "States.STATEY" "fullIdentifier" : "States.STATEY"
} ], } ],
"event" : "Events.EVENTY", "event" : {
"rawName" : "Events.EVENTY",
"fullIdentifier" : "Events.EVENTY"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -358,7 +403,10 @@
"rawName" : "States.STATE_EXTRA_2", "rawName" : "States.STATE_EXTRA_2",
"fullIdentifier" : "States.STATE_EXTRA_2" "fullIdentifier" : "States.STATE_EXTRA_2"
} ], } ],
"event" : "Events.EVENT_1_2", "event" : {
"rawName" : "Events.EVENT_1_2",
"fullIdentifier" : "Events.EVENT_1_2"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -372,7 +420,10 @@
"rawName" : "States.STATE_EXTRA_3", "rawName" : "States.STATE_EXTRA_3",
"fullIdentifier" : "States.STATE_EXTRA_3" "fullIdentifier" : "States.STATE_EXTRA_3"
} ], } ],
"event" : "Events.EVENT_1_2", "event" : {
"rawName" : "Events.EVENT_1_2",
"fullIdentifier" : "Events.EVENT_1_2"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -386,7 +437,10 @@
"rawName" : "States.CANCEL", "rawName" : "States.CANCEL",
"fullIdentifier" : "States.CANCEL" "fullIdentifier" : "States.CANCEL"
} ], } ],
"event" : "Events.EVENT_CANCEL", "event" : {
"rawName" : "Events.EVENT_CANCEL",
"fullIdentifier" : "Events.EVENT_CANCEL"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

View File

@@ -27,21 +27,21 @@ state States.STATEY <<choice>>
state States.STATE9 <<choice>> state States.STATE9 <<choice>>
state States.STATE8 <<choice>> state States.STATE8 <<choice>>
States.STATE1 -[#1E90FF,bold]-> States.STATE2 <<external>> <<e_Events_EVENT1>> : Events.EVENT1 States.STATE1 -[#1E90FF,bold]-> States.STATE2 <<external>> : Events.EVENT1
States.STATE2 -[#1E90FF,bold]-> States.STATE3 <<external>> <<e_Events_EVENT2>> : Events.EVENT2 States.STATE2 -[#1E90FF,bold]-> States.STATE3 <<external>> : Events.EVENT2
States.STATE3 -[#1E90FF,bold]-> States.STATE4 <<external>> <<e_Events_EVENT3>> : Events.EVENT3 States.STATE3 -[#1E90FF,bold]-> States.STATE4 <<external>> : Events.EVENT3
States.STATE4 -[#1E90FF,bold]-> States.STATE5 <<external>> <<e_Events_EVENT4>> : Events.EVENT4 States.STATE4 -[#1E90FF,bold]-> States.STATE5 <<external>> : Events.EVENT4
States.STATE5 -[#1E90FF,bold]-> States.STATE6 <<external>> <<e_Events_EVENT5>> : Events.EVENT5 States.STATE5 -[#1E90FF,bold]-> States.STATE6 <<external>> : Events.EVENT5
States.STATE6 -[#1E90FF,bold]-> States.STATE7 <<external>> <<e_Events_EVENT6>> : Events.EVENT6 States.STATE6 -[#1E90FF,bold]-> States.STATE7 <<external>> : Events.EVENT6
States.STATE7 -[#1E90FF,bold]-> States.STATE8 <<external>> <<e_Events_EVENT7>> : Events.EVENT7 States.STATE7 -[#1E90FF,bold]-> States.STATE8 <<external>> : Events.EVENT7
States.STATE8 -[#1E90FF,bold]-> States.STATE9 <<external>> <<e_Events_EVENT8>> : Events.EVENT8 States.STATE8 -[#1E90FF,bold]-> States.STATE9 <<external>> : Events.EVENT8
States.STATE9 -[#1E90FF,bold]-> States.STATE10 <<external>> <<e_Events_EVENT9>> : Events.EVENT9 States.STATE9 -[#1E90FF,bold]-> States.STATE10 <<external>> : Events.EVENT9
States.STATE1 -[#1E90FF,bold]-> States.CANCEL <<external>> <<e_Events_EVENT_CANCEL>> : Events.EVENT_CANCEL States.STATE1 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL
States.STATE2 -[#1E90FF,bold]-> States.CANCEL <<external>> <<e_Events_EVENT_CANCEL>> : Events.EVENT_CANCEL States.STATE2 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL
States.STATE3 -[#1E90FF,bold]-> States.CANCEL <<external>> <<e_Events_EVENT_CANCEL>> : Events.EVENT_CANCEL States.STATE3 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL
States.STATE4 -[#1E90FF,bold]-> States.CANCEL <<external>> <<e_Events_EVENT_CANCEL>> : Events.EVENT_CANCEL States.STATE4 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL
States.STATE5 -[#1E90FF,bold]-> States.CANCEL <<external>> <<e_Events_EVENT_CANCEL>> : Events.EVENT_CANCEL States.STATE5 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL
States.STATE4 -[#1E90FF,bold]-> States.STATEY <<external>> <<e_Events_EVENTY>> : Events.EVENTY States.STATE4 -[#1E90FF,bold]-> States.STATEY <<external>> : Events.EVENTY
States.STATEY -[#FF6347,bold]-> States.STATEX <<choice_type>> : [guardVarEquals("value1")] (order=0) States.STATEY -[#FF6347,bold]-> States.STATEX <<choice_type>> : [guardVarEquals("value1")] (order=0)
States.STATEY -[#FF6347,bold]-> States.STATEZ <<choice_type>> : (order=1) States.STATEY -[#FF6347,bold]-> States.STATEZ <<choice_type>> : (order=1)
States.STATE9 -[#FF6347,bold]-> States.STATE8 <<choice_type>> : [guardVarEquals("stepBack")] (order=0) States.STATE9 -[#FF6347,bold]-> States.STATE8 <<choice_type>> : [guardVarEquals("stepBack")] (order=0)
@@ -49,9 +49,9 @@ States.STATE9 -[#FF6347,bold]-> States.STATE7 <<choice_type>> : [guardVarEquals(
States.STATE9 -[#FF6347,bold]-> States.STATE6 <<choice_type>> : (order=2) States.STATE9 -[#FF6347,bold]-> States.STATE6 <<choice_type>> : (order=2)
States.STATE8 -[#FF6347,bold]-> States.STATE9 <<choice_type>> : [guardVarEquals("forward9")] (order=0) States.STATE8 -[#FF6347,bold]-> States.STATE9 <<choice_type>> : [guardVarEquals("forward9")] (order=0)
States.STATE8 -[#FF6347,bold]-> States.STATE10 <<choice_type>> : (order=1) States.STATE8 -[#FF6347,bold]-> States.STATE10 <<choice_type>> : (order=1)
States.STATE3 -[#1E90FF,bold]-> States.STATE_EXTRA_2 <<external>> <<e_Events_EVENT_1_2>> : Events.EVENT_1_2 States.STATE3 -[#1E90FF,bold]-> States.STATE_EXTRA_2 <<external>> : Events.EVENT_1_2
States.STATE_EXTRA_2 -[#1E90FF,bold]-> States.STATE_EXTRA_3 <<external>> <<e_Events_EVENT_1_2>> : 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>> <<e_Events_EVENT_CANCEL>> : Events.EVENT_CANCEL States.STATE_EXTRA_3 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL
States.STATEZ --> [*] States.STATEZ --> [*]
@enduml @enduml

View File

@@ -22,7 +22,10 @@
"rawName" : "States.STATE2", "rawName" : "States.STATE2",
"fullIdentifier" : "States.STATE2" "fullIdentifier" : "States.STATE2"
} ], } ],
"event" : "Events.EVENT1", "event" : {
"rawName" : "Events.EVENT1",
"fullIdentifier" : "Events.EVENT1"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -36,7 +39,10 @@
"rawName" : "States.STATE3", "rawName" : "States.STATE3",
"fullIdentifier" : "States.STATE3" "fullIdentifier" : "States.STATE3"
} ], } ],
"event" : "Events.EVENT2", "event" : {
"rawName" : "Events.EVENT2",
"fullIdentifier" : "Events.EVENT2"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -50,7 +56,10 @@
"rawName" : "States.STATE4", "rawName" : "States.STATE4",
"fullIdentifier" : "States.STATE4" "fullIdentifier" : "States.STATE4"
} ], } ],
"event" : "Events.EVENT3", "event" : {
"rawName" : "Events.EVENT3",
"fullIdentifier" : "Events.EVENT3"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -64,7 +73,10 @@
"rawName" : "States.STATE5", "rawName" : "States.STATE5",
"fullIdentifier" : "States.STATE5" "fullIdentifier" : "States.STATE5"
} ], } ],
"event" : "Events.EVENT4", "event" : {
"rawName" : "Events.EVENT4",
"fullIdentifier" : "Events.EVENT4"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -78,7 +90,10 @@
"rawName" : "States.STATE6", "rawName" : "States.STATE6",
"fullIdentifier" : "States.STATE6" "fullIdentifier" : "States.STATE6"
} ], } ],
"event" : "Events.EVENT5", "event" : {
"rawName" : "Events.EVENT5",
"fullIdentifier" : "Events.EVENT5"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -92,7 +107,10 @@
"rawName" : "States.STATE7", "rawName" : "States.STATE7",
"fullIdentifier" : "States.STATE7" "fullIdentifier" : "States.STATE7"
} ], } ],
"event" : "Events.EVENT6", "event" : {
"rawName" : "Events.EVENT6",
"fullIdentifier" : "Events.EVENT6"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -106,7 +124,10 @@
"rawName" : "States.STATE8", "rawName" : "States.STATE8",
"fullIdentifier" : "States.STATE8" "fullIdentifier" : "States.STATE8"
} ], } ],
"event" : "Events.EVENT7", "event" : {
"rawName" : "Events.EVENT7",
"fullIdentifier" : "Events.EVENT7"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -120,7 +141,10 @@
"rawName" : "States.STATE9", "rawName" : "States.STATE9",
"fullIdentifier" : "States.STATE9" "fullIdentifier" : "States.STATE9"
} ], } ],
"event" : "Events.EVENT8", "event" : {
"rawName" : "Events.EVENT8",
"fullIdentifier" : "Events.EVENT8"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -134,7 +158,10 @@
"rawName" : "States.STATE10", "rawName" : "States.STATE10",
"fullIdentifier" : "States.STATE10" "fullIdentifier" : "States.STATE10"
} ], } ],
"event" : "Events.EVENT9", "event" : {
"rawName" : "Events.EVENT9",
"fullIdentifier" : "Events.EVENT9"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -148,7 +175,10 @@
"rawName" : "States.CANCEL", "rawName" : "States.CANCEL",
"fullIdentifier" : "States.CANCEL" "fullIdentifier" : "States.CANCEL"
} ], } ],
"event" : "Events.EVENT_CANCEL", "event" : {
"rawName" : "Events.EVENT_CANCEL",
"fullIdentifier" : "Events.EVENT_CANCEL"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -162,7 +192,10 @@
"rawName" : "States.CANCEL", "rawName" : "States.CANCEL",
"fullIdentifier" : "States.CANCEL" "fullIdentifier" : "States.CANCEL"
} ], } ],
"event" : "Events.EVENT_CANCEL", "event" : {
"rawName" : "Events.EVENT_CANCEL",
"fullIdentifier" : "Events.EVENT_CANCEL"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -176,7 +209,10 @@
"rawName" : "States.CANCEL", "rawName" : "States.CANCEL",
"fullIdentifier" : "States.CANCEL" "fullIdentifier" : "States.CANCEL"
} ], } ],
"event" : "Events.EVENT_CANCEL", "event" : {
"rawName" : "Events.EVENT_CANCEL",
"fullIdentifier" : "Events.EVENT_CANCEL"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -190,7 +226,10 @@
"rawName" : "States.CANCEL", "rawName" : "States.CANCEL",
"fullIdentifier" : "States.CANCEL" "fullIdentifier" : "States.CANCEL"
} ], } ],
"event" : "Events.EVENT_CANCEL", "event" : {
"rawName" : "Events.EVENT_CANCEL",
"fullIdentifier" : "Events.EVENT_CANCEL"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -204,7 +243,10 @@
"rawName" : "States.CANCEL", "rawName" : "States.CANCEL",
"fullIdentifier" : "States.CANCEL" "fullIdentifier" : "States.CANCEL"
} ], } ],
"event" : "Events.EVENT_CANCEL", "event" : {
"rawName" : "Events.EVENT_CANCEL",
"fullIdentifier" : "Events.EVENT_CANCEL"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -218,7 +260,10 @@
"rawName" : "States.STATEY", "rawName" : "States.STATEY",
"fullIdentifier" : "States.STATEY" "fullIdentifier" : "States.STATEY"
} ], } ],
"event" : "Events.EVENTY", "event" : {
"rawName" : "Events.EVENTY",
"fullIdentifier" : "Events.EVENTY"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -358,7 +403,10 @@
"rawName" : "States.STATE_EXTRA_1", "rawName" : "States.STATE_EXTRA_1",
"fullIdentifier" : "States.STATE_EXTRA_1" "fullIdentifier" : "States.STATE_EXTRA_1"
} ], } ],
"event" : "Events.EVENT_1_2", "event" : {
"rawName" : "Events.EVENT_1_2",
"fullIdentifier" : "Events.EVENT_1_2"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -372,7 +420,10 @@
"rawName" : "States.STATE_EXTRA_2", "rawName" : "States.STATE_EXTRA_2",
"fullIdentifier" : "States.STATE_EXTRA_2" "fullIdentifier" : "States.STATE_EXTRA_2"
} ], } ],
"event" : "Events.EVENT_1_3", "event" : {
"rawName" : "Events.EVENT_1_3",
"fullIdentifier" : "Events.EVENT_1_3"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -386,7 +437,10 @@
"rawName" : "States.CANCEL", "rawName" : "States.CANCEL",
"fullIdentifier" : "States.CANCEL" "fullIdentifier" : "States.CANCEL"
} ], } ],
"event" : "Events.EVENT_CANCEL", "event" : {
"rawName" : "Events.EVENT_CANCEL",
"fullIdentifier" : "Events.EVENT_CANCEL"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -400,7 +454,10 @@
"rawName" : "States.CANCEL", "rawName" : "States.CANCEL",
"fullIdentifier" : "States.CANCEL" "fullIdentifier" : "States.CANCEL"
} ], } ],
"event" : "Events.EVENT_CANCEL", "event" : {
"rawName" : "Events.EVENT_CANCEL",
"fullIdentifier" : "Events.EVENT_CANCEL"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

View File

@@ -27,21 +27,21 @@ state States.STATEY <<choice>>
state States.STATE9 <<choice>> state States.STATE9 <<choice>>
state States.STATE8 <<choice>> state States.STATE8 <<choice>>
States.STATE1 -[#1E90FF,bold]-> States.STATE2 <<external>> <<e_Events_EVENT1>> : Events.EVENT1 States.STATE1 -[#1E90FF,bold]-> States.STATE2 <<external>> : Events.EVENT1
States.STATE2 -[#1E90FF,bold]-> States.STATE3 <<external>> <<e_Events_EVENT2>> : Events.EVENT2 States.STATE2 -[#1E90FF,bold]-> States.STATE3 <<external>> : Events.EVENT2
States.STATE3 -[#1E90FF,bold]-> States.STATE4 <<external>> <<e_Events_EVENT3>> : Events.EVENT3 States.STATE3 -[#1E90FF,bold]-> States.STATE4 <<external>> : Events.EVENT3
States.STATE4 -[#1E90FF,bold]-> States.STATE5 <<external>> <<e_Events_EVENT4>> : Events.EVENT4 States.STATE4 -[#1E90FF,bold]-> States.STATE5 <<external>> : Events.EVENT4
States.STATE5 -[#1E90FF,bold]-> States.STATE6 <<external>> <<e_Events_EVENT5>> : Events.EVENT5 States.STATE5 -[#1E90FF,bold]-> States.STATE6 <<external>> : Events.EVENT5
States.STATE6 -[#1E90FF,bold]-> States.STATE7 <<external>> <<e_Events_EVENT6>> : Events.EVENT6 States.STATE6 -[#1E90FF,bold]-> States.STATE7 <<external>> : Events.EVENT6
States.STATE7 -[#1E90FF,bold]-> States.STATE8 <<external>> <<e_Events_EVENT7>> : Events.EVENT7 States.STATE7 -[#1E90FF,bold]-> States.STATE8 <<external>> : Events.EVENT7
States.STATE8 -[#1E90FF,bold]-> States.STATE9 <<external>> <<e_Events_EVENT8>> : Events.EVENT8 States.STATE8 -[#1E90FF,bold]-> States.STATE9 <<external>> : Events.EVENT8
States.STATE9 -[#1E90FF,bold]-> States.STATE10 <<external>> <<e_Events_EVENT9>> : Events.EVENT9 States.STATE9 -[#1E90FF,bold]-> States.STATE10 <<external>> : Events.EVENT9
States.STATE1 -[#1E90FF,bold]-> States.CANCEL <<external>> <<e_Events_EVENT_CANCEL>> : Events.EVENT_CANCEL States.STATE1 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL
States.STATE2 -[#1E90FF,bold]-> States.CANCEL <<external>> <<e_Events_EVENT_CANCEL>> : Events.EVENT_CANCEL States.STATE2 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL
States.STATE3 -[#1E90FF,bold]-> States.CANCEL <<external>> <<e_Events_EVENT_CANCEL>> : Events.EVENT_CANCEL States.STATE3 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL
States.STATE4 -[#1E90FF,bold]-> States.CANCEL <<external>> <<e_Events_EVENT_CANCEL>> : Events.EVENT_CANCEL States.STATE4 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL
States.STATE5 -[#1E90FF,bold]-> States.CANCEL <<external>> <<e_Events_EVENT_CANCEL>> : Events.EVENT_CANCEL States.STATE5 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL
States.STATE4 -[#1E90FF,bold]-> States.STATEY <<external>> <<e_Events_EVENTY>> : Events.EVENTY States.STATE4 -[#1E90FF,bold]-> States.STATEY <<external>> : Events.EVENTY
States.STATEY -[#FF6347,bold]-> States.STATEX <<choice_type>> : [guardVarEquals("value1")] (order=0) States.STATEY -[#FF6347,bold]-> States.STATEX <<choice_type>> : [guardVarEquals("value1")] (order=0)
States.STATEY -[#FF6347,bold]-> States.STATEZ <<choice_type>> : (order=1) States.STATEY -[#FF6347,bold]-> States.STATEZ <<choice_type>> : (order=1)
States.STATE9 -[#FF6347,bold]-> States.STATE8 <<choice_type>> : [guardVarEquals("stepBack")] (order=0) States.STATE9 -[#FF6347,bold]-> States.STATE8 <<choice_type>> : [guardVarEquals("stepBack")] (order=0)
@@ -49,10 +49,10 @@ States.STATE9 -[#FF6347,bold]-> States.STATE7 <<choice_type>> : [guardVarEquals(
States.STATE9 -[#FF6347,bold]-> States.STATE6 <<choice_type>> : (order=2) States.STATE9 -[#FF6347,bold]-> States.STATE6 <<choice_type>> : (order=2)
States.STATE8 -[#FF6347,bold]-> States.STATE9 <<choice_type>> : [guardVarEquals("forward9")] (order=0) States.STATE8 -[#FF6347,bold]-> States.STATE9 <<choice_type>> : [guardVarEquals("forward9")] (order=0)
States.STATE8 -[#FF6347,bold]-> States.STATE10 <<choice_type>> : (order=1) States.STATE8 -[#FF6347,bold]-> States.STATE10 <<choice_type>> : (order=1)
States.STATE3 -[#1E90FF,bold]-> States.STATE_EXTRA_1 <<external>> <<e_Events_EVENT_1_2>> : Events.EVENT_1_2 States.STATE3 -[#1E90FF,bold]-> States.STATE_EXTRA_1 <<external>> : Events.EVENT_1_2
States.STATE_EXTRA_1 -[#1E90FF,bold]-> States.STATE_EXTRA_2 <<external>> <<e_Events_EVENT_1_3>> : Events.EVENT_1_3 States.STATE_EXTRA_1 -[#1E90FF,bold]-> States.STATE_EXTRA_2 <<external>> : Events.EVENT_1_3
States.STATE_EXTRA_1 -[#1E90FF,bold]-> States.CANCEL <<external>> <<e_Events_EVENT_CANCEL>> : Events.EVENT_CANCEL States.STATE_EXTRA_1 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL
States.STATE_EXTRA_2 -[#1E90FF,bold]-> States.CANCEL <<external>> <<e_Events_EVENT_CANCEL>> : Events.EVENT_CANCEL States.STATE_EXTRA_2 -[#1E90FF,bold]-> States.CANCEL <<external>> : Events.EVENT_CANCEL
States.STATEZ --> [*] States.STATEZ --> [*]
@enduml @enduml

View File

@@ -7,7 +7,9 @@
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritance/service/ProcessingServiceImpl.java", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritance/service/ProcessingServiceImpl.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 15 "sourceState" : null,
"lineNumber" : 15,
"polymorphicEvents" : null
} ], } ],
"entryPoints" : [ { "entryPoints" : [ {
"type" : "REST", "type" : "REST",
@@ -53,8 +55,16 @@
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritance/service/ProcessingServiceImpl.java", "sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritance/service/ProcessingServiceImpl.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 15 "sourceState" : null,
} "lineNumber" : 15,
"polymorphicEvents" : null
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "START",
"targetState" : "WORKING",
"event" : "INHERITED_SUBMIT"
} ]
} ], } ],
"properties" : { "properties" : {
"default" : { } "default" : { }
@@ -73,7 +83,10 @@
"rawName" : "\"WORKING\"", "rawName" : "\"WORKING\"",
"fullIdentifier" : "WORKING" "fullIdentifier" : "WORKING"
} ], } ],
"event" : "INHERITED_SUBMIT", "event" : {
"rawName" : "\"INHERITED_SUBMIT\"",
"fullIdentifier" : "INHERITED_SUBMIT"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -24,7 +24,7 @@ skinparam svgLinkTarget _self
[*] --> START [*] --> START
START -[#1E90FF,bold]-> WORKING <<external>> <<e_INHERITED_SUBMIT>> : INHERITED_SUBMIT START -[#1E90FF,bold]-> WORKING <<external>> : INHERITED_SUBMIT
WORKING --> [*] WORKING --> [*]
@enduml @enduml

View File

@@ -22,7 +22,10 @@
"rawName" : "OrderStates.PAID", "rawName" : "OrderStates.PAID",
"fullIdentifier" : "OrderStates.PAID" "fullIdentifier" : "OrderStates.PAID"
} ], } ],
"event" : "OrderEvents.PAY", "event" : {
"rawName" : "OrderEvents.PAY",
"fullIdentifier" : "OrderEvents.PAY"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -36,7 +39,10 @@
"rawName" : "OrderStates.FULFILLED", "rawName" : "OrderStates.FULFILLED",
"fullIdentifier" : "OrderStates.FULFILLED" "fullIdentifier" : "OrderStates.FULFILLED"
} ], } ],
"event" : "OrderEvents.FULFILL", "event" : {
"rawName" : "OrderEvents.FULFILL",
"fullIdentifier" : "OrderEvents.FULFILL"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -50,7 +56,10 @@
"rawName" : "OrderStates.CANCELED", "rawName" : "OrderStates.CANCELED",
"fullIdentifier" : "OrderStates.CANCELED" "fullIdentifier" : "OrderStates.CANCELED"
} ], } ],
"event" : "OrderEvents.CANCEL", "event" : {
"rawName" : "OrderEvents.CANCEL",
"fullIdentifier" : "OrderEvents.CANCEL"
},
"guard" : { "guard" : {
"expression" : "new Guard<OrderStates,OrderEvents>(){\n @Override public boolean evaluate( StateContext<OrderStates,OrderEvents> context){\n return false;\n }\n}\n", "expression" : "new Guard<OrderStates,OrderEvents>(){\n @Override public boolean evaluate( StateContext<OrderStates,OrderEvents> context){\n return false;\n }\n}\n",
"isLambda" : true, "isLambda" : true,
@@ -71,7 +80,10 @@
"rawName" : "OrderStates.CANCELED", "rawName" : "OrderStates.CANCELED",
"fullIdentifier" : "OrderStates.CANCELED" "fullIdentifier" : "OrderStates.CANCELED"
} ], } ],
"event" : "OrderEvents.CANCEL", "event" : {
"rawName" : "OrderEvents.CANCEL",
"fullIdentifier" : "OrderEvents.CANCEL"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -82,7 +94,10 @@
"fullIdentifier" : "OrderStates.SUBMITTED" "fullIdentifier" : "OrderStates.SUBMITTED"
} ], } ],
"targetStates" : [ ], "targetStates" : [ ],
"event" : "OrderEvents.ABCD", "event" : {
"rawName" : "OrderEvents.ABCD",
"fullIdentifier" : "OrderEvents.ABCD"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -96,7 +111,10 @@
"rawName" : "OrderStates.PAID1", "rawName" : "OrderStates.PAID1",
"fullIdentifier" : "OrderStates.PAID1" "fullIdentifier" : "OrderStates.PAID1"
} ], } ],
"event" : "OrderEvents.ABCD", "event" : {
"rawName" : "OrderEvents.ABCD",
"fullIdentifier" : "OrderEvents.ABCD"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -212,7 +230,10 @@
"fullIdentifier" : "OrderStates.PAID2" "fullIdentifier" : "OrderStates.PAID2"
} ], } ],
"targetStates" : [ ], "targetStates" : [ ],
"event" : "OrderEvents.ABCD", "event" : {
"rawName" : "OrderEvents.ABCD",
"fullIdentifier" : "OrderEvents.ABCD"
},
"guard" : null, "guard" : null,
"actions" : [ { "actions" : [ {
"expression" : "new Action<OrderStates,OrderEvents>(){\n @Override public void execute( StateContext<OrderStates,OrderEvents> context){\n }\n}\n", "expression" : "new Action<OrderStates,OrderEvents>(){\n @Override public void execute( StateContext<OrderStates,OrderEvents> context){\n }\n}\n",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -25,19 +25,19 @@ skinparam svgLinkTarget _self
state OrderStates.PAID1 <<choice>> state OrderStates.PAID1 <<choice>>
OrderStates.SUBMITTED -[#1E90FF,bold]-> OrderStates.PAID <<external>> <<e_OrderEvents_PAY>> : OrderEvents.PAY OrderStates.SUBMITTED -[#1E90FF,bold]-> OrderStates.PAID <<external>> : OrderEvents.PAY
OrderStates.PAID -[#1E90FF,bold]-> OrderStates.FULFILLED <<external>> <<e_OrderEvents_FULFILL>> : OrderEvents.FULFILL OrderStates.PAID -[#1E90FF,bold]-> OrderStates.FULFILLED <<external>> : OrderEvents.FULFILL
OrderStates.SUBMITTED -[#1E90FF,bold]-> OrderStates.CANCELED <<external>> <<e_OrderEvents_CANCEL>> : OrderEvents.CANCEL [λ] OrderStates.SUBMITTED -[#1E90FF,bold]-> OrderStates.CANCELED <<external>> : OrderEvents.CANCEL [λ]
OrderStates.PAID -[#1E90FF,bold]-> OrderStates.CANCELED <<external>> <<e_OrderEvents_CANCEL>> : OrderEvents.CANCEL OrderStates.PAID -[#1E90FF,bold]-> OrderStates.CANCELED <<external>> : OrderEvents.CANCEL
OrderStates.SUBMITTED -[#1E90FF,bold]-> OrderStates.SUBMITTED <<external>> <<e_OrderEvents_ABCD>> : OrderEvents.ABCD OrderStates.SUBMITTED -[#1E90FF,bold]-> OrderStates.SUBMITTED <<external>> : OrderEvents.ABCD
OrderStates.SUBMITTED -[#1E90FF,bold]-> OrderStates.PAID1 <<external>> <<e_OrderEvents_ABCD>> : OrderEvents.ABCD OrderStates.SUBMITTED -[#1E90FF,bold]-> OrderStates.PAID1 <<external>> : OrderEvents.ABCD
OrderStates.PAID1 -[#FF6347,bold]-> OrderStates.PAID2 <<choice_type>> : [λ] (order=0) OrderStates.PAID1 -[#FF6347,bold]-> OrderStates.PAID2 <<choice_type>> : [λ] (order=0)
OrderStates.PAID1 -[#FF6347,bold]-> OrderStates.PAID3 <<choice_type>> : [guard1] (order=1) OrderStates.PAID1 -[#FF6347,bold]-> OrderStates.PAID3 <<choice_type>> : [guard1] (order=1)
OrderStates.PAID1 -[#FF6347,bold]-> OrderStates.HAPPEN <<choice_type>> : [λ] (order=2) OrderStates.PAID1 -[#FF6347,bold]-> OrderStates.HAPPEN <<choice_type>> : [λ] (order=2)
OrderStates.PAID1 -[#FF6347,bold]-> OrderStates.CANCELED <<choice_type>> : (order=3) OrderStates.PAID1 -[#FF6347,bold]-> OrderStates.CANCELED <<choice_type>> : (order=3)
OrderStates.PAID2 -[#1E90FF,bold]-> OrderStates.CANCELED <<external>> 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>> <<e_OrderEvents_ABCD>> : OrderEvents.ABCD / λ, action2 OrderStates.PAID2 -[#1E90FF,bold]-> OrderStates.PAID2 <<external>> : OrderEvents.ABCD / λ, action2
OrderStates.CANCELED --> [*] OrderStates.CANCELED --> [*]
OrderStates.FULFILLED --> [*] OrderStates.FULFILLED --> [*]

View File

@@ -7,7 +7,9 @@
"sourceFile" : "core-module/src/main/java/click/kamil/maven/core/MavenOrderStateMachine.java", "sourceFile" : "core-module/src/main/java/click/kamil/maven/core/MavenOrderStateMachine.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 40 "sourceState" : null,
"lineNumber" : 40,
"polymorphicEvents" : null
}, { }, {
"event" : "ORDER_EVENT", "event" : "ORDER_EVENT",
"className" : "click.kamil.maven.core.MavenOrderStateMachine.OrderService", "className" : "click.kamil.maven.core.MavenOrderStateMachine.OrderService",
@@ -15,7 +17,9 @@
"sourceFile" : "core-module/src/main/java/click/kamil/maven/core/MavenOrderStateMachine.java", "sourceFile" : "core-module/src/main/java/click/kamil/maven/core/MavenOrderStateMachine.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 52 "sourceState" : null,
"lineNumber" : 52,
"polymorphicEvents" : null
} ], } ],
"entryPoints" : [ { "entryPoints" : [ {
"type" : "REST", "type" : "REST",
@@ -64,7 +68,7 @@
"name" : "JMS: order.queue", "name" : "JMS: order.queue",
"className" : "click.kamil.maven.api.JmsOrderListener", "className" : "click.kamil.maven.api.JmsOrderListener",
"methodName" : "onMessage", "methodName" : "onMessage",
"sourceFile" : null, "sourceFile" : "api-module/src/main/java/click/kamil/maven/api/JmsOrderListener.java",
"metadata" : { "metadata" : {
"protocol" : "JMS", "protocol" : "JMS",
"destination" : "order.queue" "destination" : "order.queue"
@@ -100,8 +104,16 @@
"sourceFile" : "core-module/src/main/java/click/kamil/maven/core/MavenOrderStateMachine.java", "sourceFile" : "core-module/src/main/java/click/kamil/maven/core/MavenOrderStateMachine.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 40 "sourceState" : null,
} "lineNumber" : 40,
"polymorphicEvents" : null
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "INIT",
"targetState" : "BUSY",
"event" : "SUBMIT"
} ]
} ], } ],
"properties" : { "properties" : {
"default" : { } "default" : { }
@@ -120,7 +132,10 @@
"rawName" : "\"BUSY\"", "rawName" : "\"BUSY\"",
"fullIdentifier" : "BUSY" "fullIdentifier" : "BUSY"
} ], } ],
"event" : "SUBMIT", "event" : {
"rawName" : "\"SUBMIT\"",
"fullIdentifier" : "SUBMIT"
},
"guard" : null, "guard" : null,
"actions" : [ { "actions" : [ {
"expression" : "loggingAction()", "expression" : "loggingAction()",
@@ -141,7 +156,10 @@
"rawName" : "\"DONE\"", "rawName" : "\"DONE\"",
"fullIdentifier" : "DONE" "fullIdentifier" : "DONE"
} ], } ],
"event" : "ORDER_EVENT", "event" : {
"rawName" : "\"ORDER_EVENT\"",
"fullIdentifier" : "ORDER_EVENT"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null

View File

@@ -24,8 +24,8 @@ skinparam svgLinkTarget _self
[*] --> INIT [*] --> INIT
INIT -[#1E90FF,bold]-> BUSY <<external>> <<e_SUBMIT>> : SUBMIT / loggingAction() INIT -[#1E90FF,bold]-> BUSY <<external>> : SUBMIT / loggingAction()
BUSY -[#1E90FF,bold]-> DONE <<external>> <<e_ORDER_EVENT>> : ORDER_EVENT BUSY -[#1E90FF,bold]-> DONE <<external>> : ORDER_EVENT
DONE --> [*] DONE --> [*]
@enduml @enduml

View File

@@ -22,7 +22,10 @@
"rawName" : "States.STATE2", "rawName" : "States.STATE2",
"fullIdentifier" : "States.STATE2" "fullIdentifier" : "States.STATE2"
} ], } ],
"event" : "Events.EVENT1", "event" : {
"rawName" : "Events.EVENT1",
"fullIdentifier" : "Events.EVENT1"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -36,7 +39,10 @@
"rawName" : "States.STATE3", "rawName" : "States.STATE3",
"fullIdentifier" : "States.STATE3" "fullIdentifier" : "States.STATE3"
} ], } ],
"event" : "Events.EVENT2", "event" : {
"rawName" : "Events.EVENT2",
"fullIdentifier" : "Events.EVENT2"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -50,7 +56,10 @@
"rawName" : "States.STATE4", "rawName" : "States.STATE4",
"fullIdentifier" : "States.STATE4" "fullIdentifier" : "States.STATE4"
} ], } ],
"event" : "Events.EVENT3", "event" : {
"rawName" : "Events.EVENT3",
"fullIdentifier" : "Events.EVENT3"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -64,7 +73,10 @@
"rawName" : "States.STATE5", "rawName" : "States.STATE5",
"fullIdentifier" : "States.STATE5" "fullIdentifier" : "States.STATE5"
} ], } ],
"event" : "Events.EVENT4", "event" : {
"rawName" : "Events.EVENT4",
"fullIdentifier" : "Events.EVENT4"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -78,7 +90,10 @@
"rawName" : "States.STATE6", "rawName" : "States.STATE6",
"fullIdentifier" : "States.STATE6" "fullIdentifier" : "States.STATE6"
} ], } ],
"event" : "Events.EVENT5", "event" : {
"rawName" : "Events.EVENT5",
"fullIdentifier" : "Events.EVENT5"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -92,7 +107,10 @@
"rawName" : "States.STATE7", "rawName" : "States.STATE7",
"fullIdentifier" : "States.STATE7" "fullIdentifier" : "States.STATE7"
} ], } ],
"event" : "Events.EVENT6", "event" : {
"rawName" : "Events.EVENT6",
"fullIdentifier" : "Events.EVENT6"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -106,7 +124,10 @@
"rawName" : "States.STATE8", "rawName" : "States.STATE8",
"fullIdentifier" : "States.STATE8" "fullIdentifier" : "States.STATE8"
} ], } ],
"event" : "Events.EVENT7", "event" : {
"rawName" : "Events.EVENT7",
"fullIdentifier" : "Events.EVENT7"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -120,7 +141,10 @@
"rawName" : "States.STATE9", "rawName" : "States.STATE9",
"fullIdentifier" : "States.STATE9" "fullIdentifier" : "States.STATE9"
} ], } ],
"event" : "Events.EVENT8", "event" : {
"rawName" : "Events.EVENT8",
"fullIdentifier" : "Events.EVENT8"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -134,7 +158,10 @@
"rawName" : "States.STATE10", "rawName" : "States.STATE10",
"fullIdentifier" : "States.STATE10" "fullIdentifier" : "States.STATE10"
} ], } ],
"event" : "Events.EVENT9", "event" : {
"rawName" : "Events.EVENT9",
"fullIdentifier" : "Events.EVENT9"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -148,7 +175,10 @@
"rawName" : "States.STATE11", "rawName" : "States.STATE11",
"fullIdentifier" : "States.STATE11" "fullIdentifier" : "States.STATE11"
} ], } ],
"event" : "Events.EVENT10", "event" : {
"rawName" : "Events.EVENT10",
"fullIdentifier" : "Events.EVENT10"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -162,7 +192,10 @@
"rawName" : "States.STATE12", "rawName" : "States.STATE12",
"fullIdentifier" : "States.STATE12" "fullIdentifier" : "States.STATE12"
} ], } ],
"event" : "Events.EVENT11", "event" : {
"rawName" : "Events.EVENT11",
"fullIdentifier" : "Events.EVENT11"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -176,7 +209,10 @@
"rawName" : "States.STATE13", "rawName" : "States.STATE13",
"fullIdentifier" : "States.STATE13" "fullIdentifier" : "States.STATE13"
} ], } ],
"event" : "Events.EVENT12", "event" : {
"rawName" : "Events.EVENT12",
"fullIdentifier" : "Events.EVENT12"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -190,7 +226,10 @@
"rawName" : "States.STATE14", "rawName" : "States.STATE14",
"fullIdentifier" : "States.STATE14" "fullIdentifier" : "States.STATE14"
} ], } ],
"event" : "Events.EVENT13", "event" : {
"rawName" : "Events.EVENT13",
"fullIdentifier" : "Events.EVENT13"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -204,7 +243,10 @@
"rawName" : "States.STATE15", "rawName" : "States.STATE15",
"fullIdentifier" : "States.STATE15" "fullIdentifier" : "States.STATE15"
} ], } ],
"event" : "Events.EVENT14", "event" : {
"rawName" : "Events.EVENT14",
"fullIdentifier" : "Events.EVENT14"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -218,7 +260,10 @@
"rawName" : "States.STATE16", "rawName" : "States.STATE16",
"fullIdentifier" : "States.STATE16" "fullIdentifier" : "States.STATE16"
} ], } ],
"event" : "Events.EVENT15", "event" : {
"rawName" : "Events.EVENT15",
"fullIdentifier" : "Events.EVENT15"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -232,7 +277,10 @@
"rawName" : "States.STATEY", "rawName" : "States.STATEY",
"fullIdentifier" : "States.STATEY" "fullIdentifier" : "States.STATEY"
} ], } ],
"event" : "Events.EVENTY", "event" : {
"rawName" : "Events.EVENTY",
"fullIdentifier" : "Events.EVENTY"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -694,7 +742,10 @@
"rawName" : "States.STATE_EXTRA_1", "rawName" : "States.STATE_EXTRA_1",
"fullIdentifier" : "States.STATE_EXTRA_1" "fullIdentifier" : "States.STATE_EXTRA_1"
} ], } ],
"event" : "Events.EVENTX", "event" : {
"rawName" : "Events.EVENTX",
"fullIdentifier" : "Events.EVENTX"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

After

Width:  |  Height:  |  Size: 168 KiB

View File

@@ -35,22 +35,22 @@ state States.STATE14 <<choice>>
state States.STATE9 <<choice>> state States.STATE9 <<choice>>
state States.STATE8 <<choice>> state States.STATE8 <<choice>>
States.STATE1 -[#1E90FF,bold]-> States.STATE2 <<external>> <<e_Events_EVENT1>> : Events.EVENT1 States.STATE1 -[#1E90FF,bold]-> States.STATE2 <<external>> : Events.EVENT1
States.STATE2 -[#1E90FF,bold]-> States.STATE3 <<external>> <<e_Events_EVENT2>> : Events.EVENT2 States.STATE2 -[#1E90FF,bold]-> States.STATE3 <<external>> : Events.EVENT2
States.STATE3 -[#1E90FF,bold]-> States.STATE4 <<external>> <<e_Events_EVENT3>> : Events.EVENT3 States.STATE3 -[#1E90FF,bold]-> States.STATE4 <<external>> : Events.EVENT3
States.STATE4 -[#1E90FF,bold]-> States.STATE5 <<external>> <<e_Events_EVENT4>> : Events.EVENT4 States.STATE4 -[#1E90FF,bold]-> States.STATE5 <<external>> : Events.EVENT4
States.STATE5 -[#1E90FF,bold]-> States.STATE6 <<external>> <<e_Events_EVENT5>> : Events.EVENT5 States.STATE5 -[#1E90FF,bold]-> States.STATE6 <<external>> : Events.EVENT5
States.STATE6 -[#1E90FF,bold]-> States.STATE7 <<external>> <<e_Events_EVENT6>> : Events.EVENT6 States.STATE6 -[#1E90FF,bold]-> States.STATE7 <<external>> : Events.EVENT6
States.STATE7 -[#1E90FF,bold]-> States.STATE8 <<external>> <<e_Events_EVENT7>> : Events.EVENT7 States.STATE7 -[#1E90FF,bold]-> States.STATE8 <<external>> : Events.EVENT7
States.STATE8 -[#1E90FF,bold]-> States.STATE9 <<external>> <<e_Events_EVENT8>> : Events.EVENT8 States.STATE8 -[#1E90FF,bold]-> States.STATE9 <<external>> : Events.EVENT8
States.STATE9 -[#1E90FF,bold]-> States.STATE10 <<external>> <<e_Events_EVENT9>> : Events.EVENT9 States.STATE9 -[#1E90FF,bold]-> States.STATE10 <<external>> : Events.EVENT9
States.STATE10 -[#1E90FF,bold]-> States.STATE11 <<external>> <<e_Events_EVENT10>> : Events.EVENT10 States.STATE10 -[#1E90FF,bold]-> States.STATE11 <<external>> : Events.EVENT10
States.STATE11 -[#1E90FF,bold]-> States.STATE12 <<external>> <<e_Events_EVENT11>> : Events.EVENT11 States.STATE11 -[#1E90FF,bold]-> States.STATE12 <<external>> : Events.EVENT11
States.STATE12 -[#1E90FF,bold]-> States.STATE13 <<external>> <<e_Events_EVENT12>> : Events.EVENT12 States.STATE12 -[#1E90FF,bold]-> States.STATE13 <<external>> : Events.EVENT12
States.STATE13 -[#1E90FF,bold]-> States.STATE14 <<external>> <<e_Events_EVENT13>> : Events.EVENT13 States.STATE13 -[#1E90FF,bold]-> States.STATE14 <<external>> : Events.EVENT13
States.STATE14 -[#1E90FF,bold]-> States.STATE15 <<external>> <<e_Events_EVENT14>> : Events.EVENT14 States.STATE14 -[#1E90FF,bold]-> States.STATE15 <<external>> : Events.EVENT14
States.STATE15 -[#1E90FF,bold]-> States.STATE16 <<external>> <<e_Events_EVENT15>> : Events.EVENT15 States.STATE15 -[#1E90FF,bold]-> States.STATE16 <<external>> : Events.EVENT15
States.STATE4 -[#1E90FF,bold]-> States.STATEY <<external>> <<e_Events_EVENTY>> : Events.EVENTY States.STATE4 -[#1E90FF,bold]-> States.STATEY <<external>> : Events.EVENTY
States.STATEY -[#FF6347,bold]-> States.STATEX <<choice_type>> : [guardVarEquals("value1")] (order=0) States.STATEY -[#FF6347,bold]-> States.STATEX <<choice_type>> : [guardVarEquals("value1")] (order=0)
States.STATEY -[#FF6347,bold]-> States.STATEZ <<choice_type>> : (order=1) States.STATEY -[#FF6347,bold]-> States.STATEZ <<choice_type>> : (order=1)
States.STATE16 -[#FF6347,bold]-> States.STATE17 <<choice_type>> : [guardVarEquals("value1")] (order=0) States.STATE16 -[#FF6347,bold]-> States.STATE17 <<choice_type>> : [guardVarEquals("value1")] (order=0)
@@ -76,7 +76,7 @@ States.STATE9 -[#FF6347,bold]-> States.STATE7 <<choice_type>> : [guardVarEquals(
States.STATE9 -[#FF6347,bold]-> States.STATE6 <<choice_type>> : (order=2) States.STATE9 -[#FF6347,bold]-> States.STATE6 <<choice_type>> : (order=2)
States.STATE8 -[#FF6347,bold]-> States.STATE9 <<choice_type>> : [guardVarEquals("forward9")] (order=0) States.STATE8 -[#FF6347,bold]-> States.STATE9 <<choice_type>> : [guardVarEquals("forward9")] (order=0)
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>> <<e_Events_EVENTX>> : Events.EVENTX States.STATE5 -[#1E90FF,bold]-> States.STATE_EXTRA_1 <<external>> : Events.EVENTX
States.STATEZ --> [*] States.STATEZ --> [*]
@enduml @enduml

View File

@@ -7,7 +7,9 @@
"sourceFile" : "core-module/src/main/java/click/kamil/multi/core/OrderController.java", "sourceFile" : "core-module/src/main/java/click/kamil/multi/core/OrderController.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 18 "sourceState" : null,
"lineNumber" : 18,
"polymorphicEvents" : null
} ], } ],
"entryPoints" : [ { "entryPoints" : [ {
"type" : "REST", "type" : "REST",
@@ -65,8 +67,16 @@
"sourceFile" : "core-module/src/main/java/click/kamil/multi/core/OrderController.java", "sourceFile" : "core-module/src/main/java/click/kamil/multi/core/OrderController.java",
"sourceModule" : null, "sourceModule" : null,
"stateMachineId" : null, "stateMachineId" : null,
"lineNumber" : 18 "sourceState" : null,
} "lineNumber" : 18,
"polymorphicEvents" : null
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "NEW",
"targetState" : "PROCESSING",
"event" : "SUBMIT"
} ]
} ], } ],
"properties" : { "properties" : {
"default" : { } "default" : { }
@@ -85,7 +95,10 @@
"rawName" : "\"PROCESSING\"", "rawName" : "\"PROCESSING\"",
"fullIdentifier" : "PROCESSING" "fullIdentifier" : "PROCESSING"
} ], } ],
"event" : "SUBMIT", "event" : {
"rawName" : "\"SUBMIT\"",
"fullIdentifier" : "SUBMIT"
},
"guard" : null, "guard" : null,
"actions" : [ { "actions" : [ {
"expression" : "processAction()", "expression" : "processAction()",
@@ -106,7 +119,10 @@
"rawName" : "\"COMPLETED\"", "rawName" : "\"COMPLETED\"",
"fullIdentifier" : "COMPLETED" "fullIdentifier" : "COMPLETED"
} ], } ],
"event" : "FINISH", "event" : {
"rawName" : "\"FINISH\"",
"fullIdentifier" : "FINISH"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null

View File

@@ -24,8 +24,8 @@ skinparam svgLinkTarget _self
[*] --> NEW [*] --> NEW
NEW -[#1E90FF,bold]-> PROCESSING <<external>> <<e_SUBMIT>> : SUBMIT / processAction() NEW -[#1E90FF,bold]-> PROCESSING <<external>> : SUBMIT / processAction()
PROCESSING -[#1E90FF,bold]-> COMPLETED <<external>> <<e_FINISH>> : FINISH PROCESSING -[#1E90FF,bold]-> COMPLETED <<external>> : FINISH
COMPLETED --> [*] COMPLETED --> [*]
@enduml @enduml

View File

@@ -22,7 +22,10 @@
"rawName" : "OrderStates.PAID", "rawName" : "OrderStates.PAID",
"fullIdentifier" : "OrderStates.PAID" "fullIdentifier" : "OrderStates.PAID"
} ], } ],
"event" : "OrderEvents.PAY", "event" : {
"rawName" : "OrderEvents.PAY",
"fullIdentifier" : "OrderEvents.PAY"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -36,7 +39,10 @@
"rawName" : "OrderStates.FULFILLED", "rawName" : "OrderStates.FULFILLED",
"fullIdentifier" : "OrderStates.FULFILLED" "fullIdentifier" : "OrderStates.FULFILLED"
} ], } ],
"event" : "OrderEvents.FULFILL", "event" : {
"rawName" : "OrderEvents.FULFILL",
"fullIdentifier" : "OrderEvents.FULFILL"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -50,7 +56,10 @@
"rawName" : "OrderStates.CANCELED", "rawName" : "OrderStates.CANCELED",
"fullIdentifier" : "OrderStates.CANCELED" "fullIdentifier" : "OrderStates.CANCELED"
} ], } ],
"event" : "OrderEvents.CANCEL", "event" : {
"rawName" : "OrderEvents.CANCEL",
"fullIdentifier" : "OrderEvents.CANCEL"
},
"guard" : { "guard" : {
"expression" : "new Guard<OrderStates,OrderEvents>(){\n @Override public boolean evaluate( StateContext<OrderStates,OrderEvents> context){\n return false;\n }\n}\n", "expression" : "new Guard<OrderStates,OrderEvents>(){\n @Override public boolean evaluate( StateContext<OrderStates,OrderEvents> context){\n return false;\n }\n}\n",
"isLambda" : true, "isLambda" : true,
@@ -71,7 +80,10 @@
"rawName" : "OrderStates.CANCELED", "rawName" : "OrderStates.CANCELED",
"fullIdentifier" : "OrderStates.CANCELED" "fullIdentifier" : "OrderStates.CANCELED"
} ], } ],
"event" : "OrderEvents.CANCEL", "event" : {
"rawName" : "OrderEvents.CANCEL",
"fullIdentifier" : "OrderEvents.CANCEL"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -82,7 +94,10 @@
"fullIdentifier" : "OrderStates.SUBMITTED" "fullIdentifier" : "OrderStates.SUBMITTED"
} ], } ],
"targetStates" : [ ], "targetStates" : [ ],
"event" : "OrderEvents.ABCD", "event" : {
"rawName" : "OrderEvents.ABCD",
"fullIdentifier" : "OrderEvents.ABCD"
},
"guard" : null, "guard" : null,
"actions" : [ ], "actions" : [ ],
"order" : null "order" : null
@@ -233,7 +248,10 @@
"fullIdentifier" : "OrderStates.PAID1" "fullIdentifier" : "OrderStates.PAID1"
} ], } ],
"targetStates" : [ ], "targetStates" : [ ],
"event" : "OrderEvents.ABCD", "event" : {
"rawName" : "OrderEvents.ABCD",
"fullIdentifier" : "OrderEvents.ABCD"
},
"guard" : null, "guard" : null,
"actions" : [ { "actions" : [ {
"expression" : "new Action<OrderStates,OrderEvents>(){\n @Override public void execute( StateContext<OrderStates,OrderEvents> context){\n ;\n }\n}\n", "expression" : "new Action<OrderStates,OrderEvents>(){\n @Override public void execute( StateContext<OrderStates,OrderEvents> context){\n ;\n }\n}\n",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

View File

@@ -26,11 +26,11 @@ skinparam svgLinkTarget _self
state OrderStates.SUBMITTED <<choice>> state OrderStates.SUBMITTED <<choice>>
state OrderStates.PAID <<choice>> state OrderStates.PAID <<choice>>
OrderStates.SUBMITTED -[#1E90FF,bold]-> OrderStates.PAID <<external>> <<e_OrderEvents_PAY>> : OrderEvents.PAY OrderStates.SUBMITTED -[#1E90FF,bold]-> OrderStates.PAID <<external>> : OrderEvents.PAY
OrderStates.PAID -[#1E90FF,bold]-> OrderStates.FULFILLED <<external>> <<e_OrderEvents_FULFILL>> : OrderEvents.FULFILL OrderStates.PAID -[#1E90FF,bold]-> OrderStates.FULFILLED <<external>> : OrderEvents.FULFILL
OrderStates.SUBMITTED -[#1E90FF,bold]-> OrderStates.CANCELED <<external>> <<e_OrderEvents_CANCEL>> : OrderEvents.CANCEL [λ] OrderStates.SUBMITTED -[#1E90FF,bold]-> OrderStates.CANCELED <<external>> : OrderEvents.CANCEL [λ]
OrderStates.PAID -[#1E90FF,bold]-> OrderStates.CANCELED <<external>> <<e_OrderEvents_CANCEL>> : OrderEvents.CANCEL OrderStates.PAID -[#1E90FF,bold]-> OrderStates.CANCELED <<external>> : OrderEvents.CANCEL
OrderStates.SUBMITTED -[#1E90FF,bold]-> OrderStates.SUBMITTED <<external>> <<e_OrderEvents_ABCD>> : OrderEvents.ABCD OrderStates.SUBMITTED -[#1E90FF,bold]-> OrderStates.SUBMITTED <<external>> : OrderEvents.ABCD
OrderStates.SUBMITTED -[#FF6347,bold]-> OrderStates.PAID2 <<choice_type>> : [λ] (order=0) OrderStates.SUBMITTED -[#FF6347,bold]-> OrderStates.PAID2 <<choice_type>> : [λ] (order=0)
OrderStates.SUBMITTED -[#FF6347,bold]-> OrderStates.PAID3 <<choice_type>> : (order=1) OrderStates.SUBMITTED -[#FF6347,bold]-> OrderStates.PAID3 <<choice_type>> : (order=1)
OrderStates.PAID -[#FF6347,bold]-> OrderStates.PAID1 <<choice_type>> : [λ] (order=0) OrderStates.PAID -[#FF6347,bold]-> OrderStates.PAID1 <<choice_type>> : [λ] (order=0)
@@ -39,7 +39,7 @@ OrderStates.PAID -[#FF6347,bold]-> OrderStates.HAPPEN <<choice_type>> : [λ] (or
OrderStates.PAID -[#FF6347,bold]-> OrderStates.PAID3 <<choice_type>> : (order=3) OrderStates.PAID -[#FF6347,bold]-> OrderStates.PAID3 <<choice_type>> : (order=3)
OrderStates.PAID2 -[#1E90FF,bold]-> OrderStates.CANCELED <<external>> 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>> <<e_OrderEvents_ABCD>> : OrderEvents.ABCD / λ, action2 OrderStates.PAID1 -[#1E90FF,bold]-> OrderStates.PAID1 <<external>> : OrderEvents.ABCD / λ, action2
OrderStates.CANCELED --> [*] OrderStates.CANCELED --> [*]
OrderStates.FULFILLED --> [*] OrderStates.FULFILLED --> [*]

View File

@@ -0,0 +1,15 @@
digraph statemachine {
rankdir=LR;
node [shape=rounded, style=filled, fillcolor=white, fontname="Arial"];
edge [fontname="Arial", fontsize=10];
_start [shape=circle, label="", fillcolor=black, width=0.1];
_start -> NEW;
COMPLETED [fillcolor=lightgray];
CANCELLED [fillcolor=lightgray];
NEW -> PROCESSING [label="OrderEvent.PROCESS", style="solid", color="black"];
PROCESSING -> COMPLETED [label="OrderEvent.COMPLETE", style="solid", color="black"];
NEW -> CANCELLED [label="OrderEvent.CANCEL", style="solid", color="black"];
PROCESSING -> CANCELLED [label="OrderEvent.CANCEL", style="solid", color="black"];
}

View File

@@ -0,0 +1,439 @@
{
"metadata" : {
"triggers" : [ {
"event" : "event",
"className" : "click.kamil.service.StateMachineServiceImpl",
"methodName" : "sendMessage",
"sourceFile" : "service/src/main/java/click/kamil/service/StateMachineServiceImpl.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 25,
"polymorphicEvents" : null
}, {
"event" : "eventProvider",
"className" : "click.kamil.service.StateMachineServiceImpl",
"methodName" : "sendMessageWithProvider",
"sourceFile" : "service/src/main/java/click/kamil/service/StateMachineServiceImpl.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 50,
"polymorphicEvents" : null
}, {
"event" : "customMessage",
"className" : "click.kamil.service.StateMachineServiceImpl",
"methodName" : "sendCustomMessage",
"sourceFile" : "service/src/main/java/click/kamil/service/StateMachineServiceImpl.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 78,
"polymorphicEvents" : null
} ],
"entryPoints" : [ {
"type" : "REST",
"name" : "POST /api/orders/process",
"className" : "click.kamil.web.OrderController",
"methodName" : "processOrder",
"sourceFile" : "web/src/main/java/click/kamil/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/process",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/orders/complete",
"className" : "click.kamil.web.OrderController",
"methodName" : "completeOrder",
"sourceFile" : "web/src/main/java/click/kamil/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/complete",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/orders/cancel",
"className" : "click.kamil.web.OrderController",
"methodName" : "cancelOrder",
"sourceFile" : "web/src/main/java/click/kamil/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/cancel",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "REST",
"name" : "POST /api/orders/functional-complete",
"className" : "click.kamil.web.OrderController",
"methodName" : "functionalCompleteOrder",
"sourceFile" : "web/src/main/java/click/kamil/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/functional-complete",
"verb" : "POST"
},
"parameters" : [ ]
}, {
"type" : "RABBIT",
"name" : "RABBIT: order.custom.transition.queue",
"className" : "click.kamil.web.RabbitOrderListener",
"methodName" : "handleCustomTransition",
"sourceFile" : "web/src/main/java/click/kamil/web/RabbitOrderListener.java",
"metadata" : {
"protocol" : "RABBIT",
"destination" : "order.custom.transition.queue"
},
"parameters" : [ {
"name" : "payload",
"type" : "String",
"annotations" : [ ]
} ]
}, {
"type" : "JMS",
"name" : "JMS: order.process.queue",
"className" : "click.kamil.web.JmsOrderListener",
"methodName" : "receiveProcessCommand",
"sourceFile" : "web/src/main/java/click/kamil/web/JmsOrderListener.java",
"metadata" : {
"protocol" : "JMS",
"destination" : "order.process.queue"
},
"parameters" : [ {
"name" : "message",
"type" : "String",
"annotations" : [ ]
} ]
}, {
"type" : "JMS",
"name" : "JMS: order.cancel.queue",
"className" : "click.kamil.web.JmsOrderListener",
"methodName" : "receiveCancelCommand",
"sourceFile" : "web/src/main/java/click/kamil/web/JmsOrderListener.java",
"metadata" : {
"protocol" : "JMS",
"destination" : "order.cancel.queue"
},
"parameters" : [ {
"name" : "message",
"type" : "String",
"annotations" : [ ]
} ]
} ],
"callChains" : [ {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/orders/process",
"className" : "click.kamil.web.OrderController",
"methodName" : "processOrder",
"sourceFile" : "web/src/main/java/click/kamil/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/process",
"verb" : "POST"
},
"parameters" : [ ]
},
"methodChain" : [ "click.kamil.web.OrderController.processOrder", "click.kamil.service.StateMachineServiceImpl.sendMessageWithOutbox", "click.kamil.service.StateMachineServiceImpl.sendMessage" ],
"triggerPoint" : {
"event" : "OrderEvent.PROCESS",
"className" : "click.kamil.service.StateMachineServiceImpl",
"methodName" : "sendMessage",
"sourceFile" : "service/src/main/java/click/kamil/service/StateMachineServiceImpl.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 25,
"polymorphicEvents" : [ ]
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "OrderState.NEW",
"targetState" : "OrderState.PROCESSING",
"event" : "OrderEvent.PROCESS"
} ]
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/orders/complete",
"className" : "click.kamil.web.OrderController",
"methodName" : "completeOrder",
"sourceFile" : "web/src/main/java/click/kamil/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/complete",
"verb" : "POST"
},
"parameters" : [ ]
},
"methodChain" : [ "click.kamil.web.OrderController.completeOrder", "click.kamil.service.StateMachineServiceImpl.sendMessage" ],
"triggerPoint" : {
"event" : "OrderEvent.COMPLETE",
"className" : "click.kamil.service.StateMachineServiceImpl",
"methodName" : "sendMessage",
"sourceFile" : "service/src/main/java/click/kamil/service/StateMachineServiceImpl.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 25,
"polymorphicEvents" : [ ]
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "OrderState.PROCESSING",
"targetState" : "OrderState.COMPLETED",
"event" : "OrderEvent.COMPLETE"
} ]
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/orders/cancel",
"className" : "click.kamil.web.OrderController",
"methodName" : "cancelOrder",
"sourceFile" : "web/src/main/java/click/kamil/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/cancel",
"verb" : "POST"
},
"parameters" : [ ]
},
"methodChain" : [ "click.kamil.web.OrderController.cancelOrder", "click.kamil.service.StateMachineServiceImpl.sendMessage" ],
"triggerPoint" : {
"event" : "OrderEvent.CANCEL",
"className" : "click.kamil.service.StateMachineServiceImpl",
"methodName" : "sendMessage",
"sourceFile" : "service/src/main/java/click/kamil/service/StateMachineServiceImpl.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 25,
"polymorphicEvents" : [ ]
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "OrderState.NEW",
"targetState" : "OrderState.CANCELLED",
"event" : "OrderEvent.CANCEL"
}, {
"sourceState" : "OrderState.PROCESSING",
"targetState" : "OrderState.CANCELLED",
"event" : "OrderEvent.CANCEL"
} ]
}, {
"entryPoint" : {
"type" : "REST",
"name" : "POST /api/orders/functional-complete",
"className" : "click.kamil.web.OrderController",
"methodName" : "functionalCompleteOrder",
"sourceFile" : "web/src/main/java/click/kamil/web/OrderController.java",
"metadata" : {
"path" : "/api/orders/functional-complete",
"verb" : "POST"
},
"parameters" : [ ]
},
"methodChain" : [ "click.kamil.web.OrderController.functionalCompleteOrder", "click.kamil.web.OrderController.triggerTransitionFunction", "click.kamil.service.StateMachineServiceImpl.sendMessage" ],
"triggerPoint" : {
"event" : "OrderEvent.COMPLETE",
"className" : "click.kamil.service.StateMachineServiceImpl",
"methodName" : "sendMessage",
"sourceFile" : "service/src/main/java/click/kamil/service/StateMachineServiceImpl.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 25,
"polymorphicEvents" : [ ]
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "OrderState.PROCESSING",
"targetState" : "OrderState.COMPLETED",
"event" : "OrderEvent.COMPLETE"
} ]
}, {
"entryPoint" : {
"type" : "RABBIT",
"name" : "RABBIT: order.custom.transition.queue",
"className" : "click.kamil.web.RabbitOrderListener",
"methodName" : "handleCustomTransition",
"sourceFile" : "web/src/main/java/click/kamil/web/RabbitOrderListener.java",
"metadata" : {
"protocol" : "RABBIT",
"destination" : "order.custom.transition.queue"
},
"parameters" : [ {
"name" : "payload",
"type" : "String",
"annotations" : [ ]
} ]
},
"methodChain" : [ "click.kamil.web.RabbitOrderListener.handleCustomTransition", "click.kamil.service.StateMachineServiceImpl.sendCustomMessage" ],
"triggerPoint" : {
"event" : "OrderEvent.PROCESS",
"className" : "click.kamil.service.StateMachineServiceImpl",
"methodName" : "sendCustomMessage",
"sourceFile" : "service/src/main/java/click/kamil/service/StateMachineServiceImpl.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 78,
"polymorphicEvents" : [ ]
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "OrderState.NEW",
"targetState" : "OrderState.PROCESSING",
"event" : "OrderEvent.PROCESS"
} ]
}, {
"entryPoint" : {
"type" : "JMS",
"name" : "JMS: order.process.queue",
"className" : "click.kamil.web.JmsOrderListener",
"methodName" : "receiveProcessCommand",
"sourceFile" : "web/src/main/java/click/kamil/web/JmsOrderListener.java",
"metadata" : {
"protocol" : "JMS",
"destination" : "order.process.queue"
},
"parameters" : [ {
"name" : "message",
"type" : "String",
"annotations" : [ ]
} ]
},
"methodChain" : [ "click.kamil.web.JmsOrderListener.receiveProcessCommand", "click.kamil.service.StateMachineServiceImpl.sendMessageWithOutbox", "click.kamil.service.StateMachineServiceImpl.sendMessage" ],
"triggerPoint" : {
"event" : "OrderEvent.PROCESS",
"className" : "click.kamil.service.StateMachineServiceImpl",
"methodName" : "sendMessage",
"sourceFile" : "service/src/main/java/click/kamil/service/StateMachineServiceImpl.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 25,
"polymorphicEvents" : [ ]
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "OrderState.NEW",
"targetState" : "OrderState.PROCESSING",
"event" : "OrderEvent.PROCESS"
} ]
}, {
"entryPoint" : {
"type" : "JMS",
"name" : "JMS: order.cancel.queue",
"className" : "click.kamil.web.JmsOrderListener",
"methodName" : "receiveCancelCommand",
"sourceFile" : "web/src/main/java/click/kamil/web/JmsOrderListener.java",
"metadata" : {
"protocol" : "JMS",
"destination" : "order.cancel.queue"
},
"parameters" : [ {
"name" : "message",
"type" : "String",
"annotations" : [ ]
} ]
},
"methodChain" : [ "click.kamil.web.JmsOrderListener.receiveCancelCommand", "click.kamil.service.StateMachineServiceImpl.sendMessageWithProvider" ],
"triggerPoint" : {
"event" : "OrderEvent.CANCEL",
"className" : "click.kamil.service.StateMachineServiceImpl",
"methodName" : "sendMessageWithProvider",
"sourceFile" : "service/src/main/java/click/kamil/service/StateMachineServiceImpl.java",
"sourceModule" : null,
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 50,
"polymorphicEvents" : [ ]
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "OrderState.NEW",
"targetState" : "OrderState.CANCELLED",
"event" : "OrderEvent.CANCEL"
}, {
"sourceState" : "OrderState.PROCESSING",
"targetState" : "OrderState.CANCELLED",
"event" : "OrderEvent.CANCEL"
} ]
} ],
"properties" : {
"default" : { }
}
},
"name" : "click.kamil.domain.StateMachineConfig",
"renderChoicesAsDiamonds" : true,
"startStates" : [ "OrderState.NEW" ],
"transitions" : [ {
"type" : "EXTERNAL",
"sourceStates" : [ {
"rawName" : "OrderState.NEW",
"fullIdentifier" : "OrderState.NEW"
} ],
"targetStates" : [ {
"rawName" : "OrderState.PROCESSING",
"fullIdentifier" : "OrderState.PROCESSING"
} ],
"event" : {
"rawName" : "OrderEvent.PROCESS",
"fullIdentifier" : "OrderEvent.PROCESS"
},
"guard" : null,
"actions" : [ ],
"order" : null
}, {
"type" : "EXTERNAL",
"sourceStates" : [ {
"rawName" : "OrderState.PROCESSING",
"fullIdentifier" : "OrderState.PROCESSING"
} ],
"targetStates" : [ {
"rawName" : "OrderState.COMPLETED",
"fullIdentifier" : "OrderState.COMPLETED"
} ],
"event" : {
"rawName" : "OrderEvent.COMPLETE",
"fullIdentifier" : "OrderEvent.COMPLETE"
},
"guard" : null,
"actions" : [ ],
"order" : null
}, {
"type" : "EXTERNAL",
"sourceStates" : [ {
"rawName" : "OrderState.NEW",
"fullIdentifier" : "OrderState.NEW"
} ],
"targetStates" : [ {
"rawName" : "OrderState.CANCELLED",
"fullIdentifier" : "OrderState.CANCELLED"
} ],
"event" : {
"rawName" : "OrderEvent.CANCEL",
"fullIdentifier" : "OrderEvent.CANCEL"
},
"guard" : null,
"actions" : [ ],
"order" : null
}, {
"type" : "EXTERNAL",
"sourceStates" : [ {
"rawName" : "OrderState.PROCESSING",
"fullIdentifier" : "OrderState.PROCESSING"
} ],
"targetStates" : [ {
"rawName" : "OrderState.CANCELLED",
"fullIdentifier" : "OrderState.CANCELLED"
} ],
"event" : {
"rawName" : "OrderEvent.CANCEL",
"fullIdentifier" : "OrderEvent.CANCEL"
},
"guard" : null,
"actions" : [ ],
"order" : null
} ],
"endStates" : [ "OrderState.COMPLETED", "OrderState.CANCELLED" ]
}

Some files were not shown because too many files have changed in this diff Show More