updated for functions
This commit is contained in:
@@ -1,10 +1,15 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'application'
|
||||
}
|
||||
|
||||
group = 'click.kamil.springstatemachineexporter'
|
||||
version = '0.0.1-SNAPSHOT'
|
||||
|
||||
application {
|
||||
mainClass = 'click.kamil.springstatemachineexporter.Main'
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(21)
|
||||
|
||||
@@ -68,21 +68,15 @@ public class Main {
|
||||
StateMachineAggregator aggregator = new StateMachineAggregator(context);
|
||||
List<Transition> transitions = aggregator.aggregateTransitions(td);
|
||||
|
||||
Set<String> startStatesTransitions = TransitionStateUtils.findStartStates(transitions);
|
||||
Set<String> endStatesTransitions = TransitionStateUtils.findEndStates(transitions);
|
||||
|
||||
Set<String> initialStatesAst = new HashSet<>();
|
||||
Set<String> endStatesAst = new HashSet<>();
|
||||
aggregator.aggregateStates(td, initialStatesAst, endStatesAst);
|
||||
aggregator.aggregateStates(td);
|
||||
Set<String> initialStatesAst = aggregator.getInitialStates();
|
||||
Set<String> endStatesAst = aggregator.getEndStates();
|
||||
|
||||
System.out.println("Start States Ast: " + initialStatesAst);
|
||||
System.out.println("End States Ast: " + endStatesAst);
|
||||
|
||||
Set<String> startStates = new HashSet<>(startStatesTransitions);
|
||||
startStates.addAll(initialStatesAst);
|
||||
|
||||
Set<String> endStates = new HashSet<>(endStatesTransitions);
|
||||
endStates.addAll(endStatesAst);
|
||||
Set<String> startStates = TransitionStateUtils.findStartStates(transitions, initialStatesAst);
|
||||
Set<String> endStates = TransitionStateUtils.findEndStates(transitions, endStatesAst);
|
||||
|
||||
generateOutputs(outputDir, className, transitions, startStates, endStates, outputs);
|
||||
}
|
||||
|
||||
@@ -56,6 +56,10 @@ public class AstTransitionParser {
|
||||
}
|
||||
|
||||
public static List<Transition> parseTransitionsFromExpression(MethodInvocation rootCall, Map<String, Expression> argsMap) {
|
||||
return parseTransitionsFromExpression(rootCall, argsMap, false);
|
||||
}
|
||||
|
||||
public static List<Transition> parseTransitionsFromExpression(MethodInvocation rootCall, Map<String, Expression> argsMap, boolean isContinuation) {
|
||||
List<Transition> transitions = new ArrayList<>();
|
||||
|
||||
// Step 1: Unroll method chain
|
||||
@@ -82,14 +86,32 @@ public class AstTransitionParser {
|
||||
if (!currentSegment.isEmpty()) segments.add(currentSegment);
|
||||
|
||||
// Step 3: Parse each segment
|
||||
for (List<MethodInvocation> segment : segments) {
|
||||
for (int i = 0; i < segments.size(); i++) {
|
||||
List<MethodInvocation> segment = segments.get(i);
|
||||
boolean isChoice = segment.stream().anyMatch(mi -> "withChoice".equals(mi.getName().getIdentifier()));
|
||||
boolean isJunction = segment.stream().anyMatch(mi -> "withJunction".equals(mi.getName().getIdentifier()));
|
||||
|
||||
// If it's a continuation and we are at the first segment, we should be careful
|
||||
boolean isFirstSegmentOfContinuation = isContinuation && i == 0;
|
||||
|
||||
if (isChoice || isJunction) {
|
||||
transitions.addAll(parseChoiceOrJunctionTransitions(segment, isChoice ? "withChoice" : "withJunction", argsMap));
|
||||
List<Transition> choiceTransitions = parseChoiceOrJunctionTransitions(segment, isChoice ? "withChoice" : "withJunction", argsMap);
|
||||
if (isFirstSegmentOfContinuation) {
|
||||
// For choice continuations, the first transition often has an empty source (inherited from receiver)
|
||||
// We keep them as standard choice parsing handles it, but we ensure we don't introduce new start states
|
||||
}
|
||||
transitions.addAll(choiceTransitions);
|
||||
} else {
|
||||
transitions.add(parseStandardTransition(segment, argsMap));
|
||||
Transition t = parseStandardTransition(segment, argsMap);
|
||||
// If it's a continuation's first segment and it has no source, it's just refining the receiver
|
||||
if (isFirstSegmentOfContinuation && t.getSourceStates().isEmpty()) {
|
||||
// Don't add it as a new transition if it doesn't have source/target
|
||||
if (!t.getTargetStates().isEmpty()) {
|
||||
transitions.add(t);
|
||||
}
|
||||
} else if (!t.getSourceStates().isEmpty() || !t.getTargetStates().isEmpty() || t.getType() != null) {
|
||||
transitions.add(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package click.kamil.springstatemachineexporter.ast.app;
|
||||
|
||||
import click.kamil.springstatemachineexporter.ast.app.domain.Transition;
|
||||
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||
import lombok.Getter;
|
||||
import org.eclipse.jdt.core.dom.*;
|
||||
|
||||
import java.util.*;
|
||||
@@ -19,7 +20,7 @@ public class StateMachineAggregator {
|
||||
return allTransitions;
|
||||
}
|
||||
|
||||
private void aggregateTransitionsRecursive(TypeDeclaration currentTd, TypeDeclaration searchStartClass, List<Transition> allTransitions, Set<String> visitedClasses, Set<MethodDeclaration> visitedMethods, Map<String, Object> argsMap) {
|
||||
private void aggregateTransitionsRecursive(TypeDeclaration currentTd, TypeDeclaration searchStartClass, List<Transition> allTransitions, Set<String> visitedClasses, Set<MethodVisit> visitedMethods, Map<String, Object> argsMap) {
|
||||
if (currentTd == null || visitedClasses.contains(currentTd.getName().getFullyQualifiedName())) return;
|
||||
visitedClasses.add(currentTd.getName().getFullyQualifiedName());
|
||||
|
||||
@@ -28,48 +29,62 @@ public class StateMachineAggregator {
|
||||
allTransitions.addAll(parseTransitionsWithMethodCalls(configureMethod, searchStartClass, visitedMethods, argsMap));
|
||||
}
|
||||
|
||||
// Hierarchy - Superclass
|
||||
Type superclassType = currentTd.getSuperclassType();
|
||||
if (superclassType != null) {
|
||||
String superclassName = getSimpleName(superclassType);
|
||||
TypeDeclaration superclassTd = context.getTypeDeclaration(superclassName);
|
||||
TypeDeclaration superclassTd = context.getTypeDeclaration(getSimpleName(superclassType));
|
||||
if (superclassTd != null) {
|
||||
aggregateTransitionsRecursive(superclassTd, searchStartClass, allTransitions, visitedClasses, visitedMethods, argsMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Transition> parseTransitionsWithMethodCalls(MethodDeclaration method, TypeDeclaration searchStartClass, Set<MethodDeclaration> visitedMethods, Map<String, Object> argsMap) {
|
||||
List<Transition> transitions = new ArrayList<>();
|
||||
if (visitedMethods.contains(method)) return transitions;
|
||||
visitedMethods.add(method);
|
||||
|
||||
Block body = method.getBody();
|
||||
if (body == null) return transitions;
|
||||
|
||||
for (Object stmt : body.statements()) {
|
||||
if (stmt instanceof ExpressionStatement es) {
|
||||
transitions.addAll(parseTransitionsInExpression(es.getExpression(), searchStartClass, visitedMethods, argsMap));
|
||||
} else if (stmt instanceof VariableDeclarationStatement vds) {
|
||||
for (Object fragmentObj : vds.fragments()) {
|
||||
if (fragmentObj instanceof VariableDeclarationFragment fragment) {
|
||||
transitions.addAll(parseTransitionsInExpression(fragment.getInitializer(), searchStartClass, visitedMethods, argsMap));
|
||||
}
|
||||
// Hierarchy - Interfaces
|
||||
for (Object interfaceTypeObj : currentTd.superInterfaceTypes()) {
|
||||
if (interfaceTypeObj instanceof Type interfaceType) {
|
||||
TypeDeclaration interfaceTd = context.getTypeDeclaration(getSimpleName(interfaceType));
|
||||
if (interfaceTd != null) {
|
||||
aggregateTransitionsRecursive(interfaceTd, searchStartClass, allTransitions, visitedClasses, visitedMethods, argsMap);
|
||||
}
|
||||
} else if (stmt instanceof EnhancedForStatement efs) {
|
||||
transitions.addAll(parseEnhancedForLoop(efs, searchStartClass, visitedMethods, argsMap, true, null, null));
|
||||
}
|
||||
}
|
||||
return transitions;
|
||||
}
|
||||
|
||||
private List<Transition> parseTransitionsInExpression(Expression expr, TypeDeclaration searchStartClass, Set<MethodDeclaration> visitedMethods, Map<String, Object> argsMap) {
|
||||
private record MethodVisit(MethodDeclaration method, Map<String, Object> args) {}
|
||||
|
||||
private List<Transition> parseTransitionsWithMethodCalls(MethodDeclaration method, TypeDeclaration searchStartClass, Set<MethodVisit> visitedMethods, Map<String, Object> argsMap) {
|
||||
MethodVisit visit = new MethodVisit(method, new HashMap<>(argsMap));
|
||||
if (visitedMethods.contains(visit)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
visitedMethods.add(visit);
|
||||
|
||||
Block body = method.getBody();
|
||||
if (body == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
return parseTransitionsInBlock(body, searchStartClass, visitedMethods, argsMap);
|
||||
}
|
||||
|
||||
private List<Transition> parseTransitionsInExpression(Expression expr, TypeDeclaration searchStartClass, Set<MethodVisit> visitedMethods, Map<String, Object> argsMap) {
|
||||
List<Transition> transitions = new ArrayList<>();
|
||||
if (expr instanceof MethodInvocation mi) {
|
||||
if (isTransitionChain(mi)) {
|
||||
if (isTransitionChainEntry(mi)) {
|
||||
transitions.addAll(AstTransitionParser.parseTransitionsFromExpression(mi, convertToExpressionMap(argsMap)));
|
||||
} else if (isTransitionChainContinuation(mi, argsMap)) {
|
||||
transitions.addAll(AstTransitionParser.parseTransitionsFromExpression(mi, convertToExpressionMap(argsMap), true));
|
||||
} else if (isForEach(mi)) {
|
||||
transitions.addAll(parseForEach(mi, searchStartClass, visitedMethods, argsMap, true, null, null));
|
||||
} else {
|
||||
// Check if it's a call on a lambda parameter or a variable that resolved to a lambda
|
||||
Expression receiver = mi.getExpression();
|
||||
if (receiver instanceof SimpleName sn) {
|
||||
Object resolved = argsMap.get(sn.getIdentifier());
|
||||
if (resolved instanceof LambdaExpression lambda) {
|
||||
transitions.addAll(parseLambdaInvocation(lambda, mi.arguments(), searchStartClass, visitedMethods, argsMap, true, null, null));
|
||||
}
|
||||
}
|
||||
|
||||
MethodDeclaration calledMethod = findMethodInHierarchy(mi.getName().getIdentifier(), searchStartClass);
|
||||
if (calledMethod != null) {
|
||||
Map<String, Object> nextArgsMap = buildArgsMap(calledMethod, mi, argsMap);
|
||||
@@ -84,7 +99,29 @@ public class StateMachineAggregator {
|
||||
return mi.getName().getIdentifier().equals("forEach");
|
||||
}
|
||||
|
||||
private List<Transition> parseForEach(MethodInvocation forEachMi, TypeDeclaration searchStartClass, Set<MethodDeclaration> visitedMethods, Map<String, Object> argsMap, boolean isTransitions, Set<String> initialStates, Set<String> endStates) {
|
||||
private List<Transition> parseLambdaInvocation(LambdaExpression lambda, List<?> arguments, TypeDeclaration searchStartClass, Set<MethodVisit> visitedMethods, Map<String, Object> argsMap, boolean isTransitions, Set<String> initialStates, Set<String> endStates) {
|
||||
Map<String, Object> lambdaArgsMap = new HashMap<>(argsMap);
|
||||
List<?> params = lambda.parameters();
|
||||
for (int i = 0; i < params.size() && i < arguments.size(); i++) {
|
||||
Object p = params.get(i);
|
||||
String paramName = "";
|
||||
if (p instanceof VariableDeclaration vp) paramName = vp.getName().getIdentifier();
|
||||
if (!paramName.isEmpty()) {
|
||||
lambdaArgsMap.put(paramName, resolveExpression((Expression) arguments.get(i), argsMap));
|
||||
}
|
||||
}
|
||||
|
||||
if (isTransitions) {
|
||||
if (lambda.getBody() instanceof Block block) return parseTransitionsInBlock(block, searchStartClass, visitedMethods, lambdaArgsMap);
|
||||
else if (lambda.getBody() instanceof Expression lambdaExpr) return parseTransitionsInExpression(lambdaExpr, searchStartClass, visitedMethods, lambdaArgsMap);
|
||||
} else {
|
||||
if (lambda.getBody() instanceof Block block) parseStatesInBlock(block, searchStartClass, initialStates, endStates, visitedMethods, lambdaArgsMap);
|
||||
else if (lambda.getBody() instanceof Expression lambdaExpr) parseStatesInExpression(lambdaExpr, searchStartClass, initialStates, endStates, visitedMethods, lambdaArgsMap);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private List<Transition> parseForEach(MethodInvocation forEachMi, TypeDeclaration searchStartClass, Set<MethodVisit> visitedMethods, Map<String, Object> argsMap, boolean isTransitions, Set<String> initialStates, Set<String> endStates) {
|
||||
List<Transition> transitions = new ArrayList<>();
|
||||
Expression receiver = forEachMi.getExpression();
|
||||
List<Expression> elements = unrollCollection(receiver, argsMap);
|
||||
@@ -109,7 +146,7 @@ public class StateMachineAggregator {
|
||||
return transitions;
|
||||
}
|
||||
|
||||
private List<Transition> parseEnhancedForLoop(EnhancedForStatement efs, TypeDeclaration searchStartClass, Set<MethodDeclaration> visitedMethods, Map<String, Object> argsMap, boolean isTransitions, Set<String> initialStates, Set<String> endStates) {
|
||||
private List<Transition> parseEnhancedForLoop(EnhancedForStatement efs, TypeDeclaration searchStartClass, Set<MethodVisit> visitedMethods, Map<String, Object> argsMap, boolean isTransitions, Set<String> initialStates, Set<String> endStates) {
|
||||
List<Transition> transitions = new ArrayList<>();
|
||||
List<Expression> elements = unrollCollection(efs.getExpression(), argsMap);
|
||||
String paramName = efs.getParameter().getName().getIdentifier();
|
||||
@@ -135,6 +172,13 @@ public class StateMachineAggregator {
|
||||
Expression receiver = mi.getExpression();
|
||||
if ((name.equals("of") && receiver != null && (receiver.toString().equals("Stream") || receiver.toString().equals("List")))
|
||||
|| (name.equals("asList") && receiver != null && receiver.toString().equals("Arrays"))) {
|
||||
if (mi.arguments().size() == 1) {
|
||||
Expression arg = (Expression) mi.arguments().get(0);
|
||||
if (arg instanceof SimpleName sn) {
|
||||
Object resolved = argsMap.get(sn.getIdentifier());
|
||||
if (resolved instanceof List list) return (List<Expression>) list;
|
||||
}
|
||||
}
|
||||
return (List<Expression>) mi.arguments();
|
||||
}
|
||||
}
|
||||
@@ -155,27 +199,62 @@ public class StateMachineAggregator {
|
||||
return "";
|
||||
}
|
||||
|
||||
private List<Transition> parseTransitionsInBlock(Block block, TypeDeclaration searchStartClass, Set<MethodDeclaration> visitedMethods, Map<String, Object> argsMap) {
|
||||
private List<Transition> parseTransitionsInBlock(Block block, TypeDeclaration searchStartClass, Set<MethodVisit> visitedMethods, Map<String, Object> argsMap) {
|
||||
List<Transition> transitions = new ArrayList<>();
|
||||
for (Object stmt : block.statements()) {
|
||||
if (stmt instanceof ExpressionStatement es) transitions.addAll(parseTransitionsInExpression(es.getExpression(), searchStartClass, visitedMethods, argsMap));
|
||||
else if (stmt instanceof VariableDeclarationStatement vds) {
|
||||
for (Object fragmentObj : vds.fragments()) {
|
||||
if (fragmentObj instanceof VariableDeclarationFragment fragment) {
|
||||
Expression initializer = fragment.getInitializer();
|
||||
if (initializer != null) {
|
||||
Object resolved = resolve(initializer, argsMap);
|
||||
if (resolved != null) argsMap.put(fragment.getName().getIdentifier(), resolved);
|
||||
transitions.addAll(parseTransitionsInExpression(initializer, searchStartClass, visitedMethods, argsMap));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (stmt instanceof EnhancedForStatement efs) transitions.addAll(parseEnhancedForLoop(efs, searchStartClass, visitedMethods, argsMap, true, null, null));
|
||||
else if (stmt instanceof TryStatement ts) transitions.addAll(parseTransitionsInBlock(ts.getBody(), searchStartClass, visitedMethods, argsMap));
|
||||
}
|
||||
return transitions;
|
||||
}
|
||||
|
||||
private void parseStatesInBlock(Block block, TypeDeclaration searchStartClass, Set<String> initialStates, Set<String> endStates, Set<MethodDeclaration> visitedMethods, Map<String, Object> argsMap) {
|
||||
private void parseStatesInBlock(Block block, TypeDeclaration searchStartClass, Set<String> initialStates, Set<String> endStates, Set<MethodVisit> visitedMethods, Map<String, Object> argsMap) {
|
||||
for (Object stmt : block.statements()) {
|
||||
if (stmt instanceof ExpressionStatement es) parseStatesInExpression(es.getExpression(), searchStartClass, initialStates, endStates, visitedMethods, argsMap);
|
||||
else if (stmt instanceof VariableDeclarationStatement vds) {
|
||||
for (Object fragmentObj : vds.fragments()) {
|
||||
if (fragmentObj instanceof VariableDeclarationFragment fragment) {
|
||||
Expression initializer = fragment.getInitializer();
|
||||
if (initializer != null) {
|
||||
Object resolved = resolve(initializer, argsMap);
|
||||
if (resolved != null) argsMap.put(fragment.getName().getIdentifier(), resolved);
|
||||
parseStatesInExpression(initializer, searchStartClass, initialStates, endStates, visitedMethods, argsMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (stmt instanceof EnhancedForStatement efs) parseEnhancedForLoop(efs, searchStartClass, visitedMethods, argsMap, false, initialStates, endStates);
|
||||
else if (stmt instanceof TryStatement ts) parseStatesInBlock(ts.getBody(), searchStartClass, initialStates, endStates, visitedMethods, argsMap);
|
||||
}
|
||||
}
|
||||
|
||||
private void parseStatesInExpression(Expression expr, TypeDeclaration searchStartClass, Set<String> initialStates, Set<String> endStates, Set<MethodDeclaration> visitedMethods, Map<String, Object> argsMap) {
|
||||
private void parseStatesInExpression(Expression expr, TypeDeclaration searchStartClass, Set<String> initialStates, Set<String> endStates, Set<MethodVisit> visitedMethods, Map<String, Object> argsMap) {
|
||||
if (expr instanceof MethodInvocation mi) {
|
||||
if (isWithStatesChain(mi)) parseStateChain(mi, initialStates, endStates, argsMap);
|
||||
if (isWithStatesChain(mi, argsMap)) parseStateChain(mi, initialStates, endStates, argsMap);
|
||||
else if (isForEach(mi)) parseForEach(mi, searchStartClass, visitedMethods, argsMap, false, initialStates, endStates);
|
||||
else {
|
||||
// Check if it's a call on a lambda parameter or a variable that resolved to a lambda
|
||||
Expression receiver = mi.getExpression();
|
||||
if (receiver instanceof SimpleName sn) {
|
||||
Object resolved = argsMap.get(sn.getIdentifier());
|
||||
if (resolved instanceof LambdaExpression lambda) {
|
||||
parseLambdaInvocation(lambda, mi.arguments(), searchStartClass, visitedMethods, argsMap, false, initialStates, endStates);
|
||||
}
|
||||
}
|
||||
|
||||
MethodDeclaration calledMethod = findMethodInHierarchy(mi.getName().getIdentifier(), searchStartClass);
|
||||
if (calledMethod != null) {
|
||||
Map<String, Object> nextArgsMap = buildArgsMap(calledMethod, mi, argsMap);
|
||||
@@ -186,7 +265,7 @@ public class StateMachineAggregator {
|
||||
}
|
||||
|
||||
private Map<String, Object> buildArgsMap(MethodDeclaration method, MethodInvocation call, Map<String, Object> currentArgsMap) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
Map<String, Object> map = new HashMap<>(currentArgsMap);
|
||||
List<?> params = method.parameters();
|
||||
List<?> args = call.arguments();
|
||||
|
||||
@@ -196,24 +275,41 @@ public class StateMachineAggregator {
|
||||
if (param.isVarargs() && i < params.size()) {
|
||||
List<Expression> varargList = new ArrayList<>();
|
||||
for (int j = i; j < args.size(); j++) {
|
||||
varargList.add(resolveExpression((Expression) args.get(j), currentArgsMap));
|
||||
Object resolved = resolve( (Expression) args.get(j), currentArgsMap);
|
||||
if (resolved instanceof List<?> list) {
|
||||
for (Object item : list) {
|
||||
if (item instanceof Expression e) varargList.add(e);
|
||||
}
|
||||
} else if (resolved instanceof Expression e) {
|
||||
varargList.add(e);
|
||||
}
|
||||
}
|
||||
map.put(paramName, varargList);
|
||||
} else if (i < args.size()) {
|
||||
map.put(paramName, resolveExpression((Expression) args.get(i), currentArgsMap));
|
||||
map.put(paramName, resolve((Expression) args.get(i), currentArgsMap));
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private Expression resolveExpression(Expression expr, Map<String, Object> argsMap) {
|
||||
private Object resolve(Expression expr, Map<String, Object> argsMap) {
|
||||
if (expr instanceof NullLiteral) return null;
|
||||
if (expr instanceof SimpleName sn) {
|
||||
Object resolved = argsMap.get(sn.getIdentifier());
|
||||
if (resolved instanceof Expression e) return e;
|
||||
if (resolved instanceof Expression nextExpr && nextExpr != expr) {
|
||||
return resolve(nextExpr, argsMap);
|
||||
}
|
||||
if (resolved != null) return resolved;
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
private Expression resolveExpression(Expression expr, Map<String, Object> argsMap) {
|
||||
Object resolved = resolve(expr, argsMap);
|
||||
if (resolved instanceof Expression e) return e;
|
||||
return expr;
|
||||
}
|
||||
|
||||
private Map<String, Expression> convertToExpressionMap(Map<String, Object> argsMap) {
|
||||
Map<String, Expression> map = new HashMap<>();
|
||||
for (Map.Entry<String, Object> entry : argsMap.entrySet()) {
|
||||
@@ -222,10 +318,23 @@ public class StateMachineAggregator {
|
||||
return map;
|
||||
}
|
||||
|
||||
private boolean isTransitionChain(MethodInvocation mi) {
|
||||
private boolean isTransitionChainEntry(MethodInvocation mi) {
|
||||
String name = mi.getName().getIdentifier();
|
||||
if (Set.of("withExternal", "withInternal", "withLocal", "withFork", "withJoin", "withChoice", "withJunction").contains(name)) return true;
|
||||
return mi.getExpression() instanceof MethodInvocation next && isTransitionChain(next);
|
||||
Expression receiver = mi.getExpression();
|
||||
if (receiver instanceof MethodInvocation next) return isTransitionChainEntry(next);
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isTransitionChainContinuation(MethodInvocation mi, Map<String, Object> argsMap) {
|
||||
String name = mi.getName().getIdentifier();
|
||||
if (Set.of("and", "source", "target", "event", "first", "then", "last").contains(name)) {
|
||||
Expression receiver = mi.getExpression();
|
||||
if (receiver instanceof MethodInvocation next) return isTransitionChainContinuation(next, argsMap) || isTransitionChainEntry(next);
|
||||
Object resolved = resolve(receiver, argsMap);
|
||||
return resolved instanceof MethodInvocation || resolved == null; // null means it's a parameter we track
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private MethodDeclaration findMethodInHierarchy(String methodName, TypeDeclaration td) {
|
||||
@@ -251,43 +360,55 @@ public class StateMachineAggregator {
|
||||
return ((SingleVariableDeclaration) method.parameters().get(0)).getType().toString().startsWith("StateMachineTransitionConfigurer");
|
||||
}
|
||||
|
||||
public void aggregateStates(TypeDeclaration startClass, Set<String> initialStates, Set<String> endStates) {
|
||||
@Getter
|
||||
private final Set<String> initialStates = new HashSet<>();
|
||||
@Getter
|
||||
private final Set<String> endStates = new HashSet<>();
|
||||
|
||||
public void aggregateStates(TypeDeclaration startClass) {
|
||||
aggregateStatesRecursive(startClass, startClass, initialStates, endStates, new HashSet<>(), new HashSet<>(), new HashMap<>());
|
||||
}
|
||||
|
||||
private void aggregateStatesRecursive(TypeDeclaration currentTd, TypeDeclaration searchStartClass, Set<String> initialStates, Set<String> endStates, Set<String> visitedClasses, Set<MethodDeclaration> visitedMethods, Map<String, Object> argsMap) {
|
||||
private void aggregateStatesRecursive(TypeDeclaration currentTd, TypeDeclaration searchStartClass, Set<String> initialStates, Set<String> endStates, Set<String> visitedClasses, Set<MethodVisit> visitedMethods, Map<String, Object> argsMap) {
|
||||
if (currentTd == null || visitedClasses.contains(currentTd.getName().getFullyQualifiedName())) return;
|
||||
visitedClasses.add(currentTd.getName().getFullyQualifiedName());
|
||||
|
||||
MethodDeclaration configureMethod = findConfigureStatesMethod(currentTd);
|
||||
if (configureMethod != null) parseStatesWithMethodCalls(configureMethod, searchStartClass, initialStates, endStates, visitedMethods, argsMap);
|
||||
|
||||
// Hierarchy - Superclass
|
||||
Type superclassType = currentTd.getSuperclassType();
|
||||
if (superclassType != null) {
|
||||
TypeDeclaration superclassTd = context.getTypeDeclaration(getSimpleName(superclassType));
|
||||
if (superclassTd != null) aggregateStatesRecursive(superclassTd, searchStartClass, initialStates, endStates, visitedClasses, visitedMethods, argsMap);
|
||||
}
|
||||
}
|
||||
|
||||
private void parseStatesWithMethodCalls(MethodDeclaration method, TypeDeclaration searchStartClass, Set<String> initialStates, Set<String> endStates, Set<MethodDeclaration> visitedMethods, Map<String, Object> argsMap) {
|
||||
if (visitedMethods.contains(method)) return;
|
||||
visitedMethods.add(method);
|
||||
Block body = method.getBody();
|
||||
if (body == null) return;
|
||||
|
||||
for (Object stmt : body.statements()) {
|
||||
if (stmt instanceof ExpressionStatement es) parseStatesInExpression(es.getExpression(), searchStartClass, initialStates, endStates, visitedMethods, argsMap);
|
||||
else if (stmt instanceof VariableDeclarationStatement vds) {
|
||||
for (Object fragmentObj : vds.fragments()) {
|
||||
if (fragmentObj instanceof VariableDeclarationFragment fragment) parseStatesInExpression(fragment.getInitializer(), searchStartClass, initialStates, endStates, visitedMethods, argsMap);
|
||||
}
|
||||
} else if (stmt instanceof EnhancedForStatement efs) parseEnhancedForLoop(efs, searchStartClass, visitedMethods, argsMap, false, initialStates, endStates);
|
||||
// Hierarchy - Interfaces
|
||||
for (Object interfaceTypeObj : currentTd.superInterfaceTypes()) {
|
||||
if (interfaceTypeObj instanceof Type interfaceType) {
|
||||
TypeDeclaration interfaceTd = context.getTypeDeclaration(getSimpleName(interfaceType));
|
||||
if (interfaceTd != null) aggregateStatesRecursive(interfaceTd, searchStartClass, initialStates, endStates, visitedClasses, visitedMethods, argsMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isWithStatesChain(MethodInvocation mi) {
|
||||
private void parseStatesWithMethodCalls(MethodDeclaration method, TypeDeclaration searchStartClass, Set<String> initialStates, Set<String> endStates, Set<MethodVisit> visitedMethods, Map<String, Object> argsMap) {
|
||||
MethodVisit visit = new MethodVisit(method, new HashMap<>(argsMap));
|
||||
if (visitedMethods.contains(visit)) return;
|
||||
visitedMethods.add(visit);
|
||||
Block body = method.getBody();
|
||||
if (body == null) return;
|
||||
|
||||
parseStatesInBlock(body, searchStartClass, initialStates, endStates, visitedMethods, argsMap);
|
||||
}
|
||||
|
||||
private boolean isWithStatesChain(MethodInvocation mi, Map<String, Object> argsMap) {
|
||||
if (mi.getName().getIdentifier().equals("withStates")) return true;
|
||||
return mi.getExpression() instanceof MethodInvocation next && isWithStatesChain(next);
|
||||
Expression receiver = mi.getExpression();
|
||||
if (receiver instanceof MethodInvocation next) return isWithStatesChain(next, argsMap);
|
||||
Object resolved = resolve(receiver, argsMap);
|
||||
if (resolved instanceof MethodInvocation next) return isWithStatesChain(next, argsMap);
|
||||
return false;
|
||||
}
|
||||
|
||||
private void parseStateChain(MethodInvocation mi, Set<String> initialStates, Set<String> endStates, Map<String, Object> argsMap) {
|
||||
|
||||
@@ -9,6 +9,13 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class TransitionStateUtils {
|
||||
public static Set<String> findStartStates(Collection<Transition> transitions, Set<String> initialStates) {
|
||||
if (initialStates != null && !initialStates.isEmpty()) {
|
||||
return initialStates;
|
||||
}
|
||||
return findStartStates(transitions);
|
||||
}
|
||||
|
||||
public static Set<String> findStartStates(Collection<Transition> transitions) {
|
||||
Set<String> allTargetStates = transitions.stream()
|
||||
.flatMap(t -> normalizeStates(t.getTargetStates()))
|
||||
@@ -19,6 +26,13 @@ public class TransitionStateUtils {
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public static Set<String> findEndStates(Collection<Transition> transitions, Set<String> endStates) {
|
||||
if (endStates != null && !endStates.isEmpty()) {
|
||||
return endStates;
|
||||
}
|
||||
return findEndStates(transitions);
|
||||
}
|
||||
|
||||
public static Set<String> findEndStates(Collection<Transition> transitions) {
|
||||
Set<String> allSourceStates = transitions.stream()
|
||||
.flatMap(t -> normalizeStates(t.getSourceStates()))
|
||||
|
||||
@@ -87,17 +87,27 @@ public class CodebaseContext {
|
||||
}
|
||||
|
||||
public boolean extendsStateMachineConfigurerAdapter(TypeDeclaration td) {
|
||||
Set<String> knownAdapters = Set.of("EnumStateMachineConfigurerAdapter", "StateMachineConfigurerAdapter");
|
||||
Set<String> knownAdapters = Set.of("EnumStateMachineConfigurerAdapter", "StateMachineConfigurerAdapter", "StateMachineConfigurer");
|
||||
|
||||
// Check superclass
|
||||
Type superclass = td.getSuperclassType();
|
||||
if (superclass == null) return false;
|
||||
|
||||
String superclassName = getSimpleName(superclass);
|
||||
if (knownAdapters.contains(superclassName)) return true;
|
||||
|
||||
TypeDeclaration superTd = getTypeDeclaration(superclassName);
|
||||
if (superTd != null) {
|
||||
return extendsStateMachineConfigurerAdapter(superTd);
|
||||
if (superclass != null) {
|
||||
String superclassName = getSimpleName(superclass);
|
||||
if (knownAdapters.contains(superclassName)) return true;
|
||||
TypeDeclaration superTd = getTypeDeclaration(superclassName);
|
||||
if (superTd != null && extendsStateMachineConfigurerAdapter(superTd)) return true;
|
||||
}
|
||||
|
||||
// Check interfaces
|
||||
for (Object interfaceTypeObj : td.superInterfaceTypes()) {
|
||||
if (interfaceTypeObj instanceof Type interfaceType) {
|
||||
String interfaceName = getSimpleName(interfaceType);
|
||||
if (knownAdapters.contains(interfaceName)) return true;
|
||||
TypeDeclaration interfaceTd = getTypeDeclaration(interfaceName);
|
||||
if (interfaceTd != null && extendsStateMachineConfigurerAdapter(interfaceTd)) return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ public class PlantUml implements StateMachineExporter {
|
||||
|
||||
@Override
|
||||
public String getFileExtension() {
|
||||
return ".plantuml.dot";
|
||||
return ".puml";
|
||||
}
|
||||
|
||||
private String getGuardText(Transition t, boolean useLambdaGuards) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.junit.jupiter.api.io.TempDir;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -173,12 +173,10 @@ class StateMachineAggregatorTest {
|
||||
context.scan(tempDir);
|
||||
|
||||
TypeDeclaration childTd = context.getTypeDeclaration("ChildConfig");
|
||||
Set<String> initialStates = new HashSet<>();
|
||||
Set<String> endStates = new HashSet<>();
|
||||
aggregator.aggregateStates(childTd, initialStates, endStates);
|
||||
aggregator.aggregateStates(childTd);
|
||||
|
||||
assertThat(initialStates).containsExactly("BASE_START");
|
||||
assertThat(endStates).containsExactly("CHILD_END");
|
||||
assertThat(aggregator.getInitialStates()).containsExactly("BASE_START");
|
||||
assertThat(aggregator.getEndStates()).containsExactly("CHILD_END");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -199,11 +197,9 @@ class StateMachineAggregatorTest {
|
||||
context.scan(tempDir);
|
||||
|
||||
TypeDeclaration td = context.getTypeDeclaration("StreamStateConfig");
|
||||
Set<String> initialStates = new HashSet<>();
|
||||
Set<String> endStates = new HashSet<>();
|
||||
aggregator.aggregateStates(td, initialStates, endStates);
|
||||
aggregator.aggregateStates(td);
|
||||
|
||||
assertThat(initialStates).containsExactlyInAnyOrder("S1", "S2");
|
||||
assertThat(aggregator.getInitialStates()).containsExactlyInAnyOrder("S1", "S2");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
@startuml
|
||||
skinparam state {
|
||||
BackgroundColor<<choice>> LightYellow
|
||||
}
|
||||
|
||||
[*] --> STATE1
|
||||
|
||||
state STATE17 <<choice>>
|
||||
state STATE16 <<choice>>
|
||||
state STATE14 <<choice>>
|
||||
state STATE12 <<choice>>
|
||||
state STATE11 <<choice>>
|
||||
state STATE8 <<choice>>
|
||||
state STATE20 <<choice>>
|
||||
state STATE9 <<choice>>
|
||||
state STATE19 <<choice>>
|
||||
state STATE18 <<choice>>
|
||||
|
||||
STATE1 -[#1E90FF,bold]-> STATE2 : EVENT1
|
||||
STATE2 -[#1E90FF,bold]-> STATE3 : EVENT2
|
||||
STATE3 -[#1E90FF,bold]-> STATE4 : EVENT3
|
||||
STATE4 -[#1E90FF,bold]-> STATE5 : EVENT4
|
||||
STATE5 -[#1E90FF,bold]-> STATE6 : EVENT5
|
||||
STATE6 -[#1E90FF,bold]-> STATE7 : EVENT6
|
||||
STATE7 -[#1E90FF,bold]-> STATE8 : EVENT7
|
||||
STATE8 -[#1E90FF,bold]-> STATE9 : EVENT8
|
||||
STATE9 -[#1E90FF,bold]-> STATE10 : EVENT9
|
||||
STATE10 -[#1E90FF,bold]-> STATE11 : EVENT10
|
||||
STATE11 -[#1E90FF,bold]-> STATE12 : EVENT11
|
||||
STATE12 -[#1E90FF,bold]-> STATE13 : EVENT12
|
||||
STATE13 -[#1E90FF,bold]-> STATE14 : EVENT13
|
||||
STATE14 -[#1E90FF,bold]-> STATE15 : EVENT14
|
||||
STATE15 -[#1E90FF,bold]-> STATE16 : EVENT15
|
||||
STATE16 -[#4682B4,bold]-> STATE17 : [guardVarEquals("value1")] (order=0)
|
||||
STATE16 -[#4682B4,bold]-> STATE18 : [guardVarEquals("value2")] (order=1)
|
||||
STATE16 -[#4682B4,bold]-> STATE19 : (order=2)
|
||||
STATE17 -[#FF6347,bold]-> STATE20 : [guardEventHeaderEquals("header1","foo")] (order=0)
|
||||
STATE17 -[#FF6347,bold]-> STATE16 : (order=1)
|
||||
STATE18 -[#FFD700,bold]-> STATE19 : [guardVarEquals("value3")] (order=0)
|
||||
STATE18 -[#FFD700,bold]-> STATE20 : (order=1)
|
||||
STATE19 -[#32CD32,bold]-> STATE1 : [guardVarEquals("reset")] (order=0)
|
||||
STATE19 -[#32CD32,bold]-> STATE20 : (order=1)
|
||||
STATE20 -[#FF6347,bold]-> STATE5 : [guardEventHeaderEquals("header2","bar")] (order=0)
|
||||
STATE20 -[#FF6347,bold]-> STATE1 : (order=1)
|
||||
STATE11 -[#6A5ACD,bold]-> STATE13 : [guardVarEquals("goTo13")] (order=0)
|
||||
STATE11 -[#6A5ACD,bold]-> STATE14 : [guardVarEquals("goTo14")] (order=1)
|
||||
STATE11 -[#6A5ACD,bold]-> STATE15 : (order=2)
|
||||
STATE12 -[#FFD700,bold]-> STATE10 : [guardVarEquals("goBack10")] (order=0)
|
||||
STATE12 -[#FFD700,bold]-> STATE11 : (order=1)
|
||||
STATE14 -[#32CD32,bold]-> STATE12 : [guardVarEquals("loop12")] (order=0)
|
||||
STATE14 -[#32CD32,bold]-> STATE16 : (order=1)
|
||||
STATE9 -[#4682B4,bold]-> STATE8 : [guardVarEquals("stepBack")] (order=0)
|
||||
STATE9 -[#4682B4,bold]-> STATE7 : [guardVarEquals("stepBackMore")] (order=1)
|
||||
STATE9 -[#4682B4,bold]-> STATE6 : (order=2)
|
||||
STATE8 -[#FF69B4,bold]-> STATE9 : [guardVarEquals("forward9")] (order=0)
|
||||
STATE8 -[#FF69B4,bold]-> STATE10 : (order=1)
|
||||
|
||||
@enduml
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
@startuml
|
||||
skinparam state {
|
||||
BackgroundColor<<choice>> LightYellow
|
||||
}
|
||||
|
||||
[*] --> START
|
||||
|
||||
|
||||
START -[#1E90FF,bold]-> FORK : TO_FORK
|
||||
FORK -[#20B2AA,bold]-> REGION1_STATE1
|
||||
FORK -[#20B2AA,bold]-> REGION2_STATE1
|
||||
REGION1_STATE1 -[#1E90FF,bold]-> REGION1_STATE2 : R1_NEXT
|
||||
REGION2_STATE1 -[#1E90FF,bold]-> REGION2_STATE2 : R2_NEXT
|
||||
REGION1_STATE2 -[#8A2BE2,bold]-> JOIN
|
||||
REGION2_STATE2 -[#8A2BE2,bold]-> JOIN
|
||||
JOIN -[#1E90FF,bold]-> END : TO_END
|
||||
|
||||
END --> [*]
|
||||
@enduml
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
@startuml
|
||||
skinparam state {
|
||||
BackgroundColor<<choice>> LightYellow
|
||||
}
|
||||
|
||||
[*] --> SUBMITTED
|
||||
|
||||
state PAID1 <<choice>>
|
||||
|
||||
SUBMITTED -[#1E90FF,bold]-> PAID : PAY
|
||||
PAID -[#1E90FF,bold]-> FULFILLED : FULFILL
|
||||
SUBMITTED -[#1E90FF,bold]-> CANCELED : CANCEL [λ]
|
||||
PAID -[#1E90FF,bold]-> CANCELED : CANCEL
|
||||
SUBMITTED -[#1E90FF,bold]-> SUBMITTED : ABCD
|
||||
SUBMITTED -[#1E90FF,bold]-> PAID1 : ABCD
|
||||
PAID1 -[#FF6347,bold]-> PAID2 : [λ] (order=0)
|
||||
PAID1 -[#FF6347,bold]-> PAID3 : [guard1] (order=1)
|
||||
PAID1 -[#FF6347,bold]-> HAPPEN : [λ] (order=2)
|
||||
PAID1 -[#FF6347,bold]-> CANCELED : (order=3)
|
||||
PAID2 -[#1E90FF,bold]-> CANCELED
|
||||
PAID3 -[#1E90FF,bold]-> CANCELED
|
||||
PAID2 -[#1E90FF,bold]-> PAID2 : ABCD / λ, action2
|
||||
|
||||
CANCELED --> [*]
|
||||
FULFILLED --> [*]
|
||||
@enduml
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
@startuml
|
||||
skinparam state {
|
||||
BackgroundColor<<choice>> LightYellow
|
||||
}
|
||||
|
||||
[*] --> STATE1
|
||||
|
||||
state STATE17 <<choice>>
|
||||
state STATE16 <<choice>>
|
||||
state STATE14 <<choice>>
|
||||
state STATE12 <<choice>>
|
||||
state STATE11 <<choice>>
|
||||
state STATE8 <<choice>>
|
||||
state STATEY <<choice>>
|
||||
state STATE20 <<choice>>
|
||||
state STATE9 <<choice>>
|
||||
state STATE19 <<choice>>
|
||||
state STATE18 <<choice>>
|
||||
|
||||
STATE1 -[#1E90FF,bold]-> STATE2 : EVENT1
|
||||
STATE2 -[#1E90FF,bold]-> STATE3 : EVENT2
|
||||
STATE3 -[#1E90FF,bold]-> STATE4 : EVENT3
|
||||
STATE4 -[#1E90FF,bold]-> STATE5 : EVENT4
|
||||
STATE5 -[#1E90FF,bold]-> STATE6 : EVENT5
|
||||
STATE6 -[#1E90FF,bold]-> STATE7 : EVENT6
|
||||
STATE7 -[#1E90FF,bold]-> STATE8 : EVENT7
|
||||
STATE8 -[#1E90FF,bold]-> STATE9 : EVENT8
|
||||
STATE9 -[#1E90FF,bold]-> STATE10 : EVENT9
|
||||
STATE10 -[#1E90FF,bold]-> STATE11 : EVENT10
|
||||
STATE11 -[#1E90FF,bold]-> STATE12 : EVENT11
|
||||
STATE12 -[#1E90FF,bold]-> STATE13 : EVENT12
|
||||
STATE13 -[#1E90FF,bold]-> STATE14 : EVENT13
|
||||
STATE14 -[#1E90FF,bold]-> STATE15 : EVENT14
|
||||
STATE15 -[#1E90FF,bold]-> STATE16 : EVENT15
|
||||
STATE4 -[#1E90FF,bold]-> STATEY : EVENTY
|
||||
STATEY -[#FF6347,bold]-> STATEX : [guardVarEquals("value1")] (order=0)
|
||||
STATEY -[#FF6347,bold]-> STATEZ : (order=1)
|
||||
STATE16 -[#4682B4,bold]-> STATE17 : [guardVarEquals("value1")] (order=0)
|
||||
STATE16 -[#4682B4,bold]-> STATE18 : [guardVarEquals("value2")] (order=1)
|
||||
STATE16 -[#4682B4,bold]-> STATE19 : (order=2)
|
||||
STATE17 -[#FF6347,bold]-> STATE20 : [guardEventHeaderEquals("header1","foo")] (order=0)
|
||||
STATE17 -[#FF6347,bold]-> STATE16 : (order=1)
|
||||
STATE18 -[#6A5ACD,bold]-> STATE19 : [guardVarEquals("value3")] (order=0)
|
||||
STATE18 -[#6A5ACD,bold]-> STATE20 : (order=1)
|
||||
STATE19 -[#FFD700,bold]-> STATE1 : [guardVarEquals("reset")] (order=0)
|
||||
STATE19 -[#FFD700,bold]-> STATE20 : (order=1)
|
||||
STATE20 -[#4682B4,bold]-> STATE5 : [guardEventHeaderEquals("header2","bar")] (order=0)
|
||||
STATE20 -[#4682B4,bold]-> STATE1 : (order=1)
|
||||
STATE11 -[#6A5ACD,bold]-> STATE13 : [guardVarEquals("goTo13")] (order=0)
|
||||
STATE11 -[#6A5ACD,bold]-> STATE14 : [guardVarEquals("goTo14")] (order=1)
|
||||
STATE11 -[#6A5ACD,bold]-> STATE15 : (order=2)
|
||||
STATE12 -[#FFD700,bold]-> STATE10 : [guardVarEquals("goBack10")] (order=0)
|
||||
STATE12 -[#FFD700,bold]-> STATE11 : (order=1)
|
||||
STATE14 -[#32CD32,bold]-> STATE12 : [guardVarEquals("loop12")] (order=0)
|
||||
STATE14 -[#32CD32,bold]-> STATE16 : (order=1)
|
||||
STATE9 -[#32CD32,bold]-> STATE8 : [guardVarEquals("stepBack")] (order=0)
|
||||
STATE9 -[#32CD32,bold]-> STATE7 : [guardVarEquals("stepBackMore")] (order=1)
|
||||
STATE9 -[#32CD32,bold]-> STATE6 : (order=2)
|
||||
STATE8 -[#FF69B4,bold]-> STATE9 : [guardVarEquals("forward9")] (order=0)
|
||||
STATE8 -[#FF69B4,bold]-> STATE10 : (order=1)
|
||||
STATE5 -[#1E90FF,bold]-> STATE_EXTRA_1 : EVENTX
|
||||
|
||||
STATEZ --> [*]
|
||||
@enduml
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
@startuml
|
||||
skinparam state {
|
||||
BackgroundColor<<choice>> LightYellow
|
||||
}
|
||||
|
||||
[*] --> SUBMITTED
|
||||
|
||||
state PAID <<choice>>
|
||||
state SUBMITTED <<choice>>
|
||||
|
||||
SUBMITTED -[#1E90FF,bold]-> PAID : PAY
|
||||
PAID -[#1E90FF,bold]-> FULFILLED : FULFILL
|
||||
SUBMITTED -[#1E90FF,bold]-> CANCELED : CANCEL [λ]
|
||||
PAID -[#1E90FF,bold]-> CANCELED : CANCEL
|
||||
SUBMITTED -[#1E90FF,bold]-> SUBMITTED : ABCD
|
||||
SUBMITTED -[#4682B4,bold]-> PAID2 : [λ] (order=0)
|
||||
SUBMITTED -[#4682B4,bold]-> PAID3 : (order=1)
|
||||
PAID -[#FF6347,bold]-> PAID1 : [λ] (order=0)
|
||||
PAID -[#FF6347,bold]-> PAID2 : [guard1] (order=1)
|
||||
PAID -[#FF6347,bold]-> HAPPEN : [λ] (order=2)
|
||||
PAID -[#FF6347,bold]-> PAID3 : (order=3)
|
||||
PAID2 -[#1E90FF,bold]-> CANCELED
|
||||
PAID3 -[#1E90FF,bold]-> CANCELED
|
||||
PAID1 -[#1E90FF,bold]-> PAID1 : ABCD / λ, action2
|
||||
|
||||
CANCELED --> [*]
|
||||
FULFILLED --> [*]
|
||||
@enduml
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
@startuml
|
||||
skinparam state {
|
||||
BackgroundColor<<choice>> LightYellow
|
||||
}
|
||||
|
||||
[*] --> STATE1
|
||||
|
||||
state STATE17 <<choice>>
|
||||
state STATE16 <<choice>>
|
||||
state STATE14 <<choice>>
|
||||
state STATE12 <<choice>>
|
||||
state STATE11 <<choice>>
|
||||
state STATE8 <<choice>>
|
||||
state STATEY <<choice>>
|
||||
state STATE20 <<choice>>
|
||||
state STATE9 <<choice>>
|
||||
state STATE19 <<choice>>
|
||||
state STATE18 <<choice>>
|
||||
|
||||
STATE1 -[#1E90FF,bold]-> STATE2 : EVENT1
|
||||
STATE2 -[#1E90FF,bold]-> STATE3 : EVENT2
|
||||
STATE3 -[#1E90FF,bold]-> STATE4 : EVENT3
|
||||
STATE4 -[#1E90FF,bold]-> STATE5 : EVENT4
|
||||
STATE5 -[#1E90FF,bold]-> STATE6 : EVENT5
|
||||
STATE6 -[#1E90FF,bold]-> STATE7 : EVENT6
|
||||
STATE7 -[#1E90FF,bold]-> STATE8 : EVENT7
|
||||
STATE8 -[#1E90FF,bold]-> STATE9 : EVENT8
|
||||
STATE9 -[#1E90FF,bold]-> STATE10 : EVENT9
|
||||
STATE10 -[#1E90FF,bold]-> STATE11 : EVENT10
|
||||
STATE11 -[#1E90FF,bold]-> STATE12 : EVENT11
|
||||
STATE12 -[#1E90FF,bold]-> STATE13 : EVENT12
|
||||
STATE13 -[#1E90FF,bold]-> STATE14 : EVENT13
|
||||
STATE14 -[#1E90FF,bold]-> STATE15 : EVENT14
|
||||
STATE15 -[#1E90FF,bold]-> STATE16 : EVENT15
|
||||
STATE1 -[#1E90FF,bold]-> CANCEL : EVENT_CANCEL
|
||||
STATE2 -[#1E90FF,bold]-> CANCEL : EVENT_CANCEL
|
||||
STATE3 -[#1E90FF,bold]-> CANCEL : EVENT_CANCEL
|
||||
STATE4 -[#1E90FF,bold]-> CANCEL : EVENT_CANCEL
|
||||
STATE5 -[#1E90FF,bold]-> CANCEL : EVENT_CANCEL
|
||||
STATE4 -[#1E90FF,bold]-> STATEY : EVENTY
|
||||
STATEY -[#FF6347,bold]-> STATEX : [guardVarEquals("value1")] (order=0)
|
||||
STATEY -[#FF6347,bold]-> STATEZ : (order=1)
|
||||
STATE16 -[#4682B4,bold]-> STATE17 : [guardVarEquals("value1")] (order=0)
|
||||
STATE16 -[#4682B4,bold]-> STATE18 : [guardVarEquals("value2")] (order=1)
|
||||
STATE16 -[#4682B4,bold]-> STATE19 : (order=2)
|
||||
STATE17 -[#FF6347,bold]-> STATE20 : [guardEventHeaderEquals("header1","foo")] (order=0)
|
||||
STATE17 -[#FF6347,bold]-> STATE16 : (order=1)
|
||||
STATE18 -[#6A5ACD,bold]-> STATE19 : [guardVarEquals("value3")] (order=0)
|
||||
STATE18 -[#6A5ACD,bold]-> STATE20 : (order=1)
|
||||
STATE19 -[#FFD700,bold]-> STATE1 : [guardVarEquals("reset")] (order=0)
|
||||
STATE19 -[#FFD700,bold]-> STATE20 : (order=1)
|
||||
STATE20 -[#4682B4,bold]-> STATE5 : [guardEventHeaderEquals("header2","bar")] (order=0)
|
||||
STATE20 -[#4682B4,bold]-> STATE1 : (order=1)
|
||||
STATE11 -[#6A5ACD,bold]-> STATE13 : [guardVarEquals("goTo13")] (order=0)
|
||||
STATE11 -[#6A5ACD,bold]-> STATE14 : [guardVarEquals("goTo14")] (order=1)
|
||||
STATE11 -[#6A5ACD,bold]-> STATE15 : (order=2)
|
||||
STATE12 -[#FFD700,bold]-> STATE10 : [guardVarEquals("goBack10")] (order=0)
|
||||
STATE12 -[#FFD700,bold]-> STATE11 : (order=1)
|
||||
STATE14 -[#32CD32,bold]-> STATE12 : [guardVarEquals("loop12")] (order=0)
|
||||
STATE14 -[#32CD32,bold]-> STATE16 : (order=1)
|
||||
STATE9 -[#32CD32,bold]-> STATE8 : [guardVarEquals("stepBack")] (order=0)
|
||||
STATE9 -[#32CD32,bold]-> STATE7 : [guardVarEquals("stepBackMore")] (order=1)
|
||||
STATE9 -[#32CD32,bold]-> STATE6 : (order=2)
|
||||
STATE8 -[#FF69B4,bold]-> STATE9 : [guardVarEquals("forward9")] (order=0)
|
||||
STATE8 -[#FF69B4,bold]-> STATE10 : (order=1)
|
||||
STATE5 -[#1E90FF,bold]-> STATE_EXTRA_1 : EVENTX
|
||||
STATE_EXTRA_1 -[#1E90FF,bold]-> CANCEL : EVENT_CANCEL
|
||||
STATE_EXTRA_1 -[#1E90FF,bold]-> CANCEL : EVENT_CANCEL_2
|
||||
|
||||
STATEZ --> [*]
|
||||
@enduml
|
||||
|
||||
Reference in New Issue
Block a user