test
This commit is contained in:
@@ -8,4 +8,5 @@ include ':state_machines:dynamic_state_machine'
|
|||||||
include ':state_machines:enumstate_state_machine'
|
include ':state_machines:enumstate_state_machine'
|
||||||
include ':state_machines:forkjoin_state_machine'
|
include ':state_machines:forkjoin_state_machine'
|
||||||
include ':state_machines:inheritance_state_machine'
|
include ':state_machines:inheritance_state_machine'
|
||||||
|
include ':state_machines:inheritance_extra_functions_state_machine'
|
||||||
include ':state_machines:simple_state_machine'
|
include ':state_machines:simple_state_machine'
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
package click.kamil.springstatemachineexporter;
|
package click.kamil.springstatemachineexporter;
|
||||||
|
|
||||||
import click.kamil.springstatemachineexporter.ast.app.AstTransitionParser;
|
import click.kamil.springstatemachineexporter.ast.app.AstTransitionParser;
|
||||||
|
import click.kamil.springstatemachineexporter.ast.app.StateMachineAggregator;
|
||||||
import click.kamil.springstatemachineexporter.ast.app.StateMachineStateConfigurationMethodFinder;
|
import click.kamil.springstatemachineexporter.ast.app.StateMachineStateConfigurationMethodFinder;
|
||||||
import click.kamil.springstatemachineexporter.ast.app.StateMachineTransitionConfigurationMethodFinder;
|
import click.kamil.springstatemachineexporter.ast.app.StateMachineTransitionConfigurationMethodFinder;
|
||||||
import click.kamil.springstatemachineexporter.ast.app.TransitionStateUtils;
|
import click.kamil.springstatemachineexporter.ast.app.TransitionStateUtils;
|
||||||
import click.kamil.springstatemachineexporter.ast.app.domain.Transition;
|
import click.kamil.springstatemachineexporter.ast.app.domain.Transition;
|
||||||
|
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||||
import click.kamil.springstatemachineexporter.ast.common.FileUtils;
|
import click.kamil.springstatemachineexporter.ast.common.FileUtils;
|
||||||
import click.kamil.springstatemachineexporter.ast.in.AstFileFinder;
|
import click.kamil.springstatemachineexporter.ast.in.AstFileFinder;
|
||||||
import click.kamil.springstatemachineexporter.ast.out.Dot;
|
import click.kamil.springstatemachineexporter.ast.out.Dot;
|
||||||
import click.kamil.springstatemachineexporter.ast.out.PlantUml;
|
import click.kamil.springstatemachineexporter.ast.out.PlantUml;
|
||||||
import click.kamil.springstatemachineexporter.ast.out.Scxml;
|
import click.kamil.springstatemachineexporter.ast.out.Scxml;
|
||||||
import click.kamil.springstatemachineexporter.ast.out.StateMachineExporter;
|
import click.kamil.springstatemachineexporter.ast.out.StateMachineExporter;
|
||||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
|
||||||
import org.eclipse.jdt.core.dom.MethodDeclaration;
|
import org.eclipse.jdt.core.dom.MethodDeclaration;
|
||||||
|
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
@@ -33,39 +35,76 @@ public class Main {
|
|||||||
runExporter(Path.of(START_DIR), Path.of(OUTPUT_DIR));
|
runExporter(Path.of(START_DIR), Path.of(OUTPUT_DIR));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void processAnnotationFoundSource(String source, Path javaFile, List<StateMachineExporter> outputs, Path outputDir) throws IOException {
|
public static void runExporter(Path inputDir, Path outputDir) throws IOException {
|
||||||
MethodDeclaration configureMethod = new StateMachineTransitionConfigurationMethodFinder(source).findConfigureMethod();
|
CodebaseContext context = new CodebaseContext();
|
||||||
if (configureMethod != null) {
|
context.scan(inputDir);
|
||||||
System.out.println("Found configure method in: " + javaFile.toAbsolutePath());
|
|
||||||
System.out.println("Method body:\n" + configureMethod.getBody());
|
|
||||||
|
|
||||||
List<Transition> transitions = AstTransitionParser.parseTransitions(configureMethod);
|
List<StateMachineExporter> outputs = List.of(
|
||||||
for (Transition t : transitions) {
|
new PlantUml(),
|
||||||
System.out.println(t);
|
new Dot(),
|
||||||
|
new Scxml()
|
||||||
|
);
|
||||||
|
|
||||||
|
List<TypeDeclaration> entryPoints = context.findEntryPointClasses(TARGET_ANNOTATIONS);
|
||||||
|
for (TypeDeclaration td : entryPoints) {
|
||||||
|
processEntryPoint(td, outputs, outputDir, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Still handle methods returning StateMachine for now (might be harder to refactor to CodebaseContext)
|
||||||
|
AstFileFinder finder = new AstFileFinder();
|
||||||
|
List<Path> javaFiles = FileUtils.findFilesWithExtension(Set.of(inputDir), EXTENSION);
|
||||||
|
for (Path javaFile : javaFiles) {
|
||||||
|
String source = Files.readString(javaFile);
|
||||||
|
if (finder.hasFunctionReturningType(source, STATE_MACHINE_RETURN_TYPES)) {
|
||||||
|
processReturnTypeFoundSource(source, javaFile, outputs, outputDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void processEntryPoint(TypeDeclaration td, List<StateMachineExporter> outputs, Path outputDir, CodebaseContext context) throws IOException {
|
||||||
|
String className = td.getName().getFullyQualifiedName();
|
||||||
|
System.out.println("Processing state machine config: " + className);
|
||||||
|
|
||||||
|
StateMachineAggregator aggregator = new StateMachineAggregator(context);
|
||||||
|
List<Transition> transitions = aggregator.aggregateTransitions(td);
|
||||||
|
|
||||||
Set<String> startStatesTransitions = TransitionStateUtils.findStartStates(transitions);
|
Set<String> startStatesTransitions = TransitionStateUtils.findStartStates(transitions);
|
||||||
Set<String> endStatesTransitions = TransitionStateUtils.findEndStates(transitions);
|
Set<String> endStatesTransitions = TransitionStateUtils.findEndStates(transitions);
|
||||||
|
|
||||||
CompilationUnit cu = StateMachineStateConfigurationMethodFinder.parse(source);
|
Set<String> initialStatesAst = new HashSet<>();
|
||||||
StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor visitor = new StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor();
|
Set<String> endStatesAst = new HashSet<>();
|
||||||
cu.accept(visitor);
|
aggregator.aggregateStates(td, initialStatesAst, endStatesAst);
|
||||||
|
|
||||||
System.out.println("Start States Ast: " + visitor.getInitialStates());
|
System.out.println("Start States Ast: " + initialStatesAst);
|
||||||
System.out.println("End States Ast: " + visitor.getEndStates());
|
System.out.println("End States Ast: " + endStatesAst);
|
||||||
System.out.println("Start States: " + startStatesTransitions);
|
|
||||||
System.out.println("End States: " + endStatesTransitions);
|
|
||||||
|
|
||||||
Set<String> startStates = new HashSet<>(startStatesTransitions);
|
Set<String> startStates = new HashSet<>(startStatesTransitions);
|
||||||
startStates.addAll(visitor.getInitialStates());
|
startStates.addAll(initialStatesAst);
|
||||||
|
|
||||||
Set<String> endStates = new HashSet<>(endStatesTransitions);
|
Set<String> endStates = new HashSet<>(endStatesTransitions);
|
||||||
endStates.addAll(visitor.getEndStates());
|
endStates.addAll(endStatesAst);
|
||||||
|
|
||||||
|
generateOutputs(outputDir, className, transitions, startStates, endStates, outputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void processReturnTypeFoundSource(String source, Path javaFile, List<StateMachineExporter> outputs, Path outputDir) throws IOException {
|
||||||
|
Set<MethodDeclaration> methodsWithReturnType = new StateMachineTransitionConfigurationMethodFinder(source).findMethodsWithReturnType(STATE_MACHINE_RETURN_TYPES);
|
||||||
|
if (!methodsWithReturnType.isEmpty()) {
|
||||||
|
for (MethodDeclaration m : methodsWithReturnType) {
|
||||||
|
System.out.println("Found method returning StateMachine in: " + javaFile.toAbsolutePath());
|
||||||
|
List<Transition> transitions = AstTransitionParser.parseTransitions2(m);
|
||||||
|
|
||||||
|
Set<String> startStatesTransitions = TransitionStateUtils.findStartStates(transitions);
|
||||||
|
Set<String> endStatesTransitions = TransitionStateUtils.findEndStates(transitions);
|
||||||
|
|
||||||
|
Set<String> startStates = new HashSet<>(startStatesTransitions);
|
||||||
|
Set<String> endStates = new HashSet<>(endStatesTransitions);
|
||||||
|
|
||||||
String baseName = javaFile.getFileName().toString().replaceFirst("[.][^.]+$", "");
|
String baseName = javaFile.getFileName().toString().replaceFirst("[.][^.]+$", "");
|
||||||
generateOutputs(outputDir, baseName, transitions, startStates, endStates, outputs);
|
generateOutputs(outputDir, baseName, transitions, startStates, endStates, outputs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void generateOutputs(Path outputDir, String baseName, List<Transition> transitions,
|
private static void generateOutputs(Path outputDir, String baseName, List<Transition> transitions,
|
||||||
Set<String> startStates, Set<String> endStates,
|
Set<String> startStates, Set<String> endStates,
|
||||||
@@ -83,59 +122,4 @@ public class Main {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void runExporter(Path inputDir, Path outputDir) throws IOException {
|
|
||||||
AstFileFinder finder = new AstFileFinder();
|
|
||||||
List<Path> javaFiles = FileUtils.findFilesWithExtension(Set.of(inputDir), EXTENSION);
|
|
||||||
|
|
||||||
List<StateMachineExporter> outputs = List.of(
|
|
||||||
new PlantUml(),
|
|
||||||
new Dot(),
|
|
||||||
new Scxml()
|
|
||||||
);
|
|
||||||
|
|
||||||
for (Path javaFile : javaFiles) {
|
|
||||||
String source = Files.readString(javaFile);
|
|
||||||
if (finder.hasClassWithAnnotation(source, TARGET_ANNOTATIONS)) {
|
|
||||||
processAnnotationFoundSource(source, javaFile, outputs, outputDir);
|
|
||||||
} else if (finder.hasFunctionReturningType(source, STATE_MACHINE_RETURN_TYPES)) {
|
|
||||||
processReturnTypeFoundSource(source, javaFile, outputs, outputDir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void processReturnTypeFoundSource(String source, Path javaFile, List<StateMachineExporter> outputs, Path outputDir) throws IOException {
|
|
||||||
Set<MethodDeclaration> methodsWithReturnType = new StateMachineTransitionConfigurationMethodFinder(source).findMethodsWithReturnType(STATE_MACHINE_RETURN_TYPES);
|
|
||||||
if (!methodsWithReturnType.isEmpty()) {
|
|
||||||
for (MethodDeclaration m : methodsWithReturnType) {
|
|
||||||
System.out.println("Found method returning StateMachine in: " + javaFile.toAbsolutePath());
|
|
||||||
System.out.println("Method body:\n" + m.getBody());
|
|
||||||
List<Transition> transitions = AstTransitionParser.parseTransitions2(m);
|
|
||||||
for (Transition t : transitions) {
|
|
||||||
System.out.println(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<String> startStatesTransitions = TransitionStateUtils.findStartStates(transitions);
|
|
||||||
Set<String> endStatesTransitions = TransitionStateUtils.findEndStates(transitions);
|
|
||||||
|
|
||||||
CompilationUnit cu = StateMachineStateConfigurationMethodFinder.parse(source);
|
|
||||||
StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor visitor = new StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor();
|
|
||||||
cu.accept(visitor);
|
|
||||||
|
|
||||||
System.out.println("Start States Ast: " + visitor.getInitialStates());
|
|
||||||
System.out.println("End States Ast: " + visitor.getEndStates());
|
|
||||||
System.out.println("Start States: " + startStatesTransitions);
|
|
||||||
System.out.println("End States: " + endStatesTransitions);
|
|
||||||
|
|
||||||
Set<String> startStates = new HashSet<>(startStatesTransitions);
|
|
||||||
startStates.addAll(visitor.getInitialStates());
|
|
||||||
|
|
||||||
Set<String> endStates = new HashSet<>(endStatesTransitions);
|
|
||||||
endStates.addAll(visitor.getEndStates());
|
|
||||||
|
|
||||||
String baseName = javaFile.getFileName().toString().replaceFirst("[.][^.]+$", "");
|
|
||||||
generateOutputs(outputDir, baseName, transitions, startStates, endStates, outputs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ public class AstTransitionParser {
|
|||||||
return chain;
|
return chain;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<Transition> parseTransitionsFromExpression(MethodInvocation rootCall) {
|
public static List<Transition> parseTransitionsFromExpression(MethodInvocation rootCall) {
|
||||||
List<Transition> transitions = new ArrayList<>();
|
List<Transition> transitions = new ArrayList<>();
|
||||||
|
|
||||||
// Step 1: Unroll method chain
|
// Step 1: Unroll method chain
|
||||||
|
|||||||
@@ -0,0 +1,243 @@
|
|||||||
|
package click.kamil.springstatemachineexporter.ast.app;
|
||||||
|
|
||||||
|
import click.kamil.springstatemachineexporter.ast.app.domain.Transition;
|
||||||
|
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||||
|
import org.eclipse.jdt.core.dom.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class StateMachineAggregator {
|
||||||
|
private final CodebaseContext context;
|
||||||
|
|
||||||
|
public StateMachineAggregator(CodebaseContext context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Transition> aggregateTransitions(TypeDeclaration startClass) {
|
||||||
|
List<Transition> allTransitions = new ArrayList<>();
|
||||||
|
Set<String> visitedClasses = new HashSet<>();
|
||||||
|
aggregateTransitionsRecursive(startClass, startClass, allTransitions, visitedClasses, new HashSet<>());
|
||||||
|
return allTransitions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void aggregateTransitionsRecursive(TypeDeclaration currentTd, TypeDeclaration searchStartClass, List<Transition> allTransitions, Set<String> visitedClasses, Set<MethodDeclaration> visitedMethods) {
|
||||||
|
if (currentTd == null || visitedClasses.contains(currentTd.getName().getFullyQualifiedName())) return;
|
||||||
|
visitedClasses.add(currentTd.getName().getFullyQualifiedName());
|
||||||
|
|
||||||
|
// Process this class
|
||||||
|
MethodDeclaration configureMethod = findConfigureTransitionsMethod(currentTd);
|
||||||
|
if (configureMethod != null) {
|
||||||
|
allTransitions.addAll(parseTransitionsWithMethodCalls(configureMethod, searchStartClass, visitedMethods));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process superclass
|
||||||
|
Type superclassType = currentTd.getSuperclassType();
|
||||||
|
if (superclassType != null) {
|
||||||
|
String superclassName = getSimpleName(superclassType);
|
||||||
|
TypeDeclaration superclassTd = context.getTypeDeclaration(superclassName);
|
||||||
|
if (superclassTd != null) {
|
||||||
|
aggregateTransitionsRecursive(superclassTd, searchStartClass, allTransitions, visitedClasses, visitedMethods);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Transition> parseTransitionsWithMethodCalls(MethodDeclaration method, TypeDeclaration searchStartClass, Set<MethodDeclaration> visitedMethods) {
|
||||||
|
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()) {
|
||||||
|
Expression expr = null;
|
||||||
|
if (stmt instanceof ExpressionStatement es) {
|
||||||
|
expr = es.getExpression();
|
||||||
|
} else if (stmt instanceof VariableDeclarationStatement vds) {
|
||||||
|
for (Object fragmentObj : vds.fragments()) {
|
||||||
|
if (fragmentObj instanceof VariableDeclarationFragment fragment) {
|
||||||
|
expr = fragment.getInitializer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expr instanceof MethodInvocation mi) {
|
||||||
|
if (isTransitionChain(mi)) {
|
||||||
|
transitions.addAll(AstTransitionParser.parseTransitionsFromExpression(mi));
|
||||||
|
} else {
|
||||||
|
// Possible method call to follow
|
||||||
|
MethodDeclaration calledMethod = findMethodInHierarchy(mi.getName().getIdentifier(), searchStartClass);
|
||||||
|
if (calledMethod != null && isRelevantMethod(calledMethod)) {
|
||||||
|
transitions.addAll(parseTransitionsWithMethodCalls(calledMethod, searchStartClass, visitedMethods));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return transitions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isTransitionChain(MethodInvocation mi) {
|
||||||
|
String name = mi.getName().getIdentifier();
|
||||||
|
if (Set.of("withExternal", "withInternal", "withLocal", "withFork", "withJoin", "withChoice", "withJunction").contains(name)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Expression expr = mi.getExpression();
|
||||||
|
if (expr instanceof MethodInvocation next) {
|
||||||
|
return isTransitionChain(next);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MethodDeclaration findMethodInHierarchy(String methodName, TypeDeclaration td) {
|
||||||
|
if (td == null) return null;
|
||||||
|
for (MethodDeclaration method : td.getMethods()) {
|
||||||
|
if (method.getName().getIdentifier().equals(methodName)) {
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Type superclassType = td.getSuperclassType();
|
||||||
|
if (superclassType != null) {
|
||||||
|
TypeDeclaration superclassTd = context.getTypeDeclaration(getSimpleName(superclassType));
|
||||||
|
return findMethodInHierarchy(methodName, superclassTd);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isRelevantMethod(MethodDeclaration method) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MethodDeclaration findConfigureTransitionsMethod(TypeDeclaration td) {
|
||||||
|
for (MethodDeclaration method : td.getMethods()) {
|
||||||
|
if (isConfigureTransitionsMethod(method)) return method;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isConfigureTransitionsMethod(MethodDeclaration method) {
|
||||||
|
if (!method.getName().getIdentifier().equals("configure")) return false;
|
||||||
|
List<?> params = method.parameters();
|
||||||
|
if (params.size() != 1) return false;
|
||||||
|
SingleVariableDeclaration param = (SingleVariableDeclaration) params.get(0);
|
||||||
|
String typeName = param.getType().toString();
|
||||||
|
return typeName.startsWith("StateMachineTransitionConfigurer");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void aggregateStates(TypeDeclaration startClass, Set<String> initialStates, Set<String> endStates) {
|
||||||
|
Set<String> visitedClasses = new HashSet<>();
|
||||||
|
aggregateStatesRecursive(startClass, startClass, initialStates, endStates, visitedClasses, new HashSet<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void aggregateStatesRecursive(TypeDeclaration currentTd, TypeDeclaration searchStartClass, Set<String> initialStates, Set<String> endStates, Set<String> visitedClasses, Set<MethodDeclaration> visitedMethods) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
Type superclassType = currentTd.getSuperclassType();
|
||||||
|
if (superclassType != null) {
|
||||||
|
TypeDeclaration superclassTd = context.getTypeDeclaration(getSimpleName(superclassType));
|
||||||
|
if (superclassTd != null) {
|
||||||
|
aggregateStatesRecursive(superclassTd, searchStartClass, initialStates, endStates, visitedClasses, visitedMethods);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseStatesWithMethodCalls(MethodDeclaration method, TypeDeclaration searchStartClass, Set<String> initialStates, Set<String> endStates, Set<MethodDeclaration> visitedMethods) {
|
||||||
|
if (visitedMethods.contains(method)) return;
|
||||||
|
visitedMethods.add(method);
|
||||||
|
|
||||||
|
Block body = method.getBody();
|
||||||
|
if (body == null) return;
|
||||||
|
|
||||||
|
for (Object stmt : body.statements()) {
|
||||||
|
Expression expr = null;
|
||||||
|
if (stmt instanceof ExpressionStatement es) {
|
||||||
|
expr = es.getExpression();
|
||||||
|
} else if (stmt instanceof VariableDeclarationStatement vds) {
|
||||||
|
for (Object fragmentObj : vds.fragments()) {
|
||||||
|
if (fragmentObj instanceof VariableDeclarationFragment fragment) {
|
||||||
|
expr = fragment.getInitializer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expr instanceof MethodInvocation mi) {
|
||||||
|
if (isWithStatesChain(mi)) {
|
||||||
|
parseStateChain(mi, initialStates, endStates);
|
||||||
|
} else {
|
||||||
|
MethodDeclaration calledMethod = findMethodInHierarchy(mi.getName().getIdentifier(), searchStartClass);
|
||||||
|
if (calledMethod != null) {
|
||||||
|
parseStatesWithMethodCalls(calledMethod, searchStartClass, initialStates, endStates, visitedMethods);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isWithStatesChain(MethodInvocation mi) {
|
||||||
|
if (mi.getName().getIdentifier().equals("withStates")) return true;
|
||||||
|
Expression expr = mi.getExpression();
|
||||||
|
if (expr instanceof MethodInvocation next) return isWithStatesChain(next);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseStateChain(MethodInvocation mi, Set<String> initialStates, Set<String> endStates) {
|
||||||
|
List<MethodInvocation> chain = new ArrayList<>();
|
||||||
|
Expression current = mi;
|
||||||
|
while (current instanceof MethodInvocation m) {
|
||||||
|
chain.addFirst(m);
|
||||||
|
current = m.getExpression();
|
||||||
|
}
|
||||||
|
for (MethodInvocation call : chain) {
|
||||||
|
String methodName = call.getName().getIdentifier();
|
||||||
|
List<?> args = call.arguments();
|
||||||
|
if (args.isEmpty()) continue;
|
||||||
|
Expression arg = (Expression) args.get(0);
|
||||||
|
if ("initial".equals(methodName)) {
|
||||||
|
if (!isInsideRegionOrFork(call)) {
|
||||||
|
initialStates.add(QuotedExpression.of(arg).toStringWithoutQuotes());
|
||||||
|
}
|
||||||
|
} else if ("end".equals(methodName)) {
|
||||||
|
endStates.add(QuotedExpression.of(arg).toStringWithoutQuotes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isInsideRegionOrFork(MethodInvocation mi) {
|
||||||
|
Expression expr = mi.getExpression();
|
||||||
|
while (expr instanceof MethodInvocation parent) {
|
||||||
|
String methodName = parent.getName().getIdentifier();
|
||||||
|
if (List.of("fork", "region").contains(methodName)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
expr = parent.getExpression();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MethodDeclaration findConfigureStatesMethod(TypeDeclaration td) {
|
||||||
|
for (MethodDeclaration method : td.getMethods()) {
|
||||||
|
if (isConfigureStatesMethod(method)) return method;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isConfigureStatesMethod(MethodDeclaration method) {
|
||||||
|
if (!method.getName().getIdentifier().equals("configure")) return false;
|
||||||
|
List<?> params = method.parameters();
|
||||||
|
if (params.size() != 1) return false;
|
||||||
|
SingleVariableDeclaration param = (SingleVariableDeclaration) params.get(0);
|
||||||
|
String typeName = param.getType().toString();
|
||||||
|
return typeName.startsWith("StateMachineStateConfigurer");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getSimpleName(Type type) {
|
||||||
|
if (type.isSimpleType()) return ((SimpleType) type).getName().getFullyQualifiedName();
|
||||||
|
if (type.isParameterizedType()) return getSimpleName(((ParameterizedType) type).getType());
|
||||||
|
return type.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package click.kamil.springstatemachineexporter.ast.common;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.core.dom.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class CodebaseContext {
|
||||||
|
private final Map<String, CompilationUnit> classes = new HashMap<>();
|
||||||
|
private final Map<String, String> classSources = new HashMap<>();
|
||||||
|
private final Map<String, Path> classPaths = new HashMap<>();
|
||||||
|
|
||||||
|
public void scan(Path rootDir) throws IOException {
|
||||||
|
List<Path> javaFiles = FileUtils.findFilesWithExtension(Set.of(rootDir), ".java");
|
||||||
|
for (Path javaFile : javaFiles) {
|
||||||
|
String source = Files.readString(javaFile);
|
||||||
|
CompilationUnit cu = parse(source);
|
||||||
|
for (Object type : cu.types()) {
|
||||||
|
if (type instanceof TypeDeclaration td) {
|
||||||
|
String className = td.getName().getFullyQualifiedName();
|
||||||
|
classes.put(className, cu);
|
||||||
|
classSources.put(className, source);
|
||||||
|
classPaths.put(className, javaFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CompilationUnit parse(String source) {
|
||||||
|
ASTParser parser = ASTParser.newParser(AST.getJLSLatest());
|
||||||
|
parser.setSource(source.toCharArray());
|
||||||
|
parser.setKind(ASTParser.K_COMPILATION_UNIT);
|
||||||
|
parser.setResolveBindings(false);
|
||||||
|
return (CompilationUnit) parser.createAST(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TypeDeclaration getTypeDeclaration(String className) {
|
||||||
|
CompilationUnit cu = classes.get(className);
|
||||||
|
if (cu == null) return null;
|
||||||
|
for (Object type : cu.types()) {
|
||||||
|
if (type instanceof TypeDeclaration td && td.getName().getFullyQualifiedName().equals(className)) {
|
||||||
|
return td;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getPath(String className) {
|
||||||
|
return classPaths.get(className);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<TypeDeclaration> findEntryPointClasses(List<String> targetAnnotations) {
|
||||||
|
List<TypeDeclaration> entryPoints = new ArrayList<>();
|
||||||
|
for (CompilationUnit cu : classes.values()) {
|
||||||
|
for (Object type : cu.types()) {
|
||||||
|
if (type instanceof TypeDeclaration td) {
|
||||||
|
if (isEntryPoint(td, targetAnnotations)) {
|
||||||
|
entryPoints.add(td);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entryPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isEntryPoint(TypeDeclaration td, List<String> targetAnnotations) {
|
||||||
|
boolean isAbstract = false;
|
||||||
|
// Has annotation
|
||||||
|
for (Object modifier : td.modifiers()) {
|
||||||
|
if (modifier instanceof Annotation annotation &&
|
||||||
|
targetAnnotations.contains(annotation.getTypeName().getFullyQualifiedName())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (modifier instanceof Modifier m && m.isAbstract()) {
|
||||||
|
isAbstract = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's a leaf class (not abstract) and extends StateMachineConfigurerAdapter indirectly
|
||||||
|
if (!isAbstract && extendsStateMachineConfigurerAdapter(td)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean extendsStateMachineConfigurerAdapter(TypeDeclaration td) {
|
||||||
|
Set<String> knownAdapters = Set.of("EnumStateMachineConfigurerAdapter", "StateMachineConfigurerAdapter");
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getSimpleName(Type type) {
|
||||||
|
if (type.isSimpleType()) return ((SimpleType) type).getName().getFullyQualifiedName();
|
||||||
|
if (type.isParameterizedType()) return getSimpleName(((ParameterizedType) type).getType());
|
||||||
|
return type.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,6 +41,12 @@ class RegressionTest {
|
|||||||
Path.of("../state_machines/simple_state_machine"),
|
Path.of("../state_machines/simple_state_machine"),
|
||||||
Path.of("src/test/resources/golden/SimpleEnumStateMachineConfiguration"),
|
Path.of("src/test/resources/golden/SimpleEnumStateMachineConfiguration"),
|
||||||
"SimpleEnumStateMachineConfiguration"
|
"SimpleEnumStateMachineConfiguration"
|
||||||
|
),
|
||||||
|
new TestScenario(
|
||||||
|
"One State Machine (Inheritance)",
|
||||||
|
Path.of("../state_machines/inheritance_state_machine"),
|
||||||
|
Path.of("src/test/resources/golden/OneStateMachineConfiguration"),
|
||||||
|
"OneStateMachineConfiguration"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
45
state_machines/inheritance_extra_functions_state_machine/.gitignore
vendored
Normal file
45
state_machines/inheritance_extra_functions_state_machine/.gitignore
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
HELP.md
|
||||||
|
.gradle
|
||||||
|
build/
|
||||||
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### STS ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
bin/
|
||||||
|
!**/src/main/**/bin/
|
||||||
|
!**/src/test/**/bin/
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
out/
|
||||||
|
!**/src/main/**/out/
|
||||||
|
!**/src/test/**/out/
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### added
|
||||||
|
*.png
|
||||||
|
*.dot
|
||||||
|
*.scxml.xml
|
||||||
|
Combined.java
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
id 'org.springframework.boot' version '3.5.3'
|
||||||
|
id 'io.spring.dependency-management' version '1.1.7'
|
||||||
|
}
|
||||||
|
|
||||||
|
group = 'click.kamil'
|
||||||
|
version = '0.0.1-SNAPSHOT'
|
||||||
|
|
||||||
|
java {
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(21)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
compileOnly {
|
||||||
|
extendsFrom annotationProcessor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter'
|
||||||
|
implementation 'org.springframework.statemachine:spring-statemachine-starter:3.2.0'
|
||||||
|
|
||||||
|
compileOnly 'org.projectlombok:lombok'
|
||||||
|
annotationProcessor 'org.projectlombok:lombok'
|
||||||
|
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||||
|
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named('test') {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
package click.kamil.examples.statemachine.inheritanceextrafunctionsstate;
|
||||||
|
|
||||||
|
import org.springframework.statemachine.action.Action;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||||
|
import org.springframework.statemachine.config.configurers.ExternalTransitionConfigurer;
|
||||||
|
import org.springframework.statemachine.config.configurers.StateConfigurer;
|
||||||
|
import org.springframework.statemachine.guard.Guard;
|
||||||
|
|
||||||
|
abstract class AbstractOneStateMachineConfiguration extends AbstractStateMachineConfiguration<States, Events> {
|
||||||
|
|
||||||
|
protected abstract void addAdditionalStates(StateConfigurer<States, Events> states);
|
||||||
|
|
||||||
|
protected abstract void addAdditionalCancellationTransitions(StateMachineTransitionConfigurer<States, Events> transitions);
|
||||||
|
|
||||||
|
protected abstract void addAdditionalTransitions(ExternalTransitionConfigurer<States, Events> transitions) throws Exception;
|
||||||
|
|
||||||
|
protected void addAllCancellationTransitionsForSource(StateMachineTransitionConfigurer<States, Events> transitions, States source) {
|
||||||
|
addCancellationTransitionForCancelEvents(transitions, source, Events.EVENT_CANCEL, Events.EVENT_CANCEL_2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addCancellationTransitionForCancelEvents(
|
||||||
|
StateMachineTransitionConfigurer<States, Events> transitions,
|
||||||
|
States source,
|
||||||
|
Events... cancelEvents) {
|
||||||
|
addTransitionForMultipleEvents(
|
||||||
|
transitions,
|
||||||
|
source,
|
||||||
|
States.CANCEL,
|
||||||
|
transitionConfigurer -> transitionConfigurer.action((context) -> System.out.println(context.getEvent().toString() + " " + context.getTransition().toString())),
|
||||||
|
cancelEvents
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineConfigurationConfigurer<States, Events> config) throws Exception {
|
||||||
|
config
|
||||||
|
.withConfiguration()
|
||||||
|
.autoStartup(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception {
|
||||||
|
StateConfigurer<States, Events> end = states
|
||||||
|
.withStates()
|
||||||
|
.initial(States.STATE1)
|
||||||
|
.state(States.STATE2)
|
||||||
|
.state(States.STATE3)
|
||||||
|
.state(States.STATE4)
|
||||||
|
.state(States.STATE5)
|
||||||
|
.state(States.STATE6)
|
||||||
|
.state(States.STATE7)
|
||||||
|
.state(States.STATE8)
|
||||||
|
.state(States.STATE9)
|
||||||
|
.state(States.STATE10)
|
||||||
|
.state(States.STATE11)
|
||||||
|
.state(States.STATE12)
|
||||||
|
.state(States.STATE13)
|
||||||
|
.state(States.STATE14)
|
||||||
|
.state(States.STATE15)
|
||||||
|
.state(States.STATE16)
|
||||||
|
.state(States.STATE17)
|
||||||
|
.state(States.STATE18)
|
||||||
|
.state(States.STATE19)
|
||||||
|
.state(States.STATE20)
|
||||||
|
.state(States.STATEX)
|
||||||
|
.choice(States.STATEY)
|
||||||
|
.end(States.STATEZ);
|
||||||
|
addAdditionalStates(end);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception {
|
||||||
|
|
||||||
|
// External transitions
|
||||||
|
ExternalTransitionConfigurer<States, Events> t1 = transitions
|
||||||
|
.withExternal().source(States.STATE1).target(States.STATE2).event(Events.EVENT1)
|
||||||
|
.and()
|
||||||
|
.withExternal().source(States.STATE2).target(States.STATE3).event(Events.EVENT2)
|
||||||
|
.and()
|
||||||
|
.withExternal().source(States.STATE3).target(States.STATE4).event(Events.EVENT3)
|
||||||
|
.and()
|
||||||
|
.withExternal().source(States.STATE4).target(States.STATE5).event(Events.EVENT4)
|
||||||
|
.and()
|
||||||
|
.withExternal().source(States.STATE5).target(States.STATE6).event(Events.EVENT5)
|
||||||
|
.and()
|
||||||
|
.withExternal().source(States.STATE6).target(States.STATE7).event(Events.EVENT6)
|
||||||
|
.and()
|
||||||
|
.withExternal().source(States.STATE7).target(States.STATE8).event(Events.EVENT7)
|
||||||
|
.and()
|
||||||
|
.withExternal().source(States.STATE8).target(States.STATE9).event(Events.EVENT8)
|
||||||
|
.and()
|
||||||
|
.withExternal().source(States.STATE9).target(States.STATE10).event(Events.EVENT9)
|
||||||
|
.and()
|
||||||
|
.withExternal().source(States.STATE10).target(States.STATE11).event(Events.EVENT10)
|
||||||
|
.and()
|
||||||
|
.withExternal().source(States.STATE11).target(States.STATE12).event(Events.EVENT11)
|
||||||
|
.and()
|
||||||
|
.withExternal().source(States.STATE12).target(States.STATE13).event(Events.EVENT12)
|
||||||
|
.and()
|
||||||
|
.withExternal().source(States.STATE13).target(States.STATE14).event(Events.EVENT13)
|
||||||
|
.and()
|
||||||
|
.withExternal().source(States.STATE14).target(States.STATE15).event(Events.EVENT14)
|
||||||
|
.and()
|
||||||
|
.withExternal().source(States.STATE15).target(States.STATE16).event(Events.EVENT15);
|
||||||
|
|
||||||
|
transitions.withExternal().source(States.STATE1).target(States.CANCEL).event(Events.EVENT_CANCEL)
|
||||||
|
.and()
|
||||||
|
.withExternal().source(States.STATE2).target(States.CANCEL).event(Events.EVENT_CANCEL)
|
||||||
|
.and()
|
||||||
|
.withExternal().source(States.STATE3).target(States.CANCEL).event(Events.EVENT_CANCEL)
|
||||||
|
.and()
|
||||||
|
.withExternal().source(States.STATE4).target(States.CANCEL).event(Events.EVENT_CANCEL)
|
||||||
|
.and()
|
||||||
|
.withExternal().source(States.STATE5).target(States.CANCEL).event(Events.EVENT_CANCEL);
|
||||||
|
|
||||||
|
// EXTRA CHOICE
|
||||||
|
transitions.withExternal().source(States.STATE4).target(States.STATEY).event(Events.EVENTY);
|
||||||
|
transitions.withChoice().source(States.STATEY).first(States.STATEX, guardVarEquals("value1")).last(States.STATEZ);
|
||||||
|
|
||||||
|
// Choice transitions (at least 10)
|
||||||
|
transitions
|
||||||
|
.withChoice()
|
||||||
|
.source(States.STATE16)
|
||||||
|
.first(States.STATE17, guardVarEquals("value1"))
|
||||||
|
.then(States.STATE18, guardVarEquals("value2"))
|
||||||
|
.last(States.STATE19)
|
||||||
|
.and()
|
||||||
|
.withChoice()
|
||||||
|
.source(States.STATE17)
|
||||||
|
.first(States.STATE20, guardEventHeaderEquals("header1", "foo"))
|
||||||
|
.last(States.STATE16)
|
||||||
|
.and()
|
||||||
|
.withChoice()
|
||||||
|
.source(States.STATE18)
|
||||||
|
.first(States.STATE19, guardVarEquals("value3"))
|
||||||
|
.last(States.STATE20)
|
||||||
|
.and()
|
||||||
|
.withChoice()
|
||||||
|
.source(States.STATE19)
|
||||||
|
.first(States.STATE1, guardVarEquals("reset"))
|
||||||
|
.last(States.STATE20)
|
||||||
|
.and()
|
||||||
|
.withChoice()
|
||||||
|
.source(States.STATE20)
|
||||||
|
.first(States.STATE5, guardEventHeaderEquals("header2", "bar"))
|
||||||
|
.last(States.STATE1)
|
||||||
|
.and()
|
||||||
|
.withChoice()
|
||||||
|
.source(States.STATE11)
|
||||||
|
.first(States.STATE13, guardVarEquals("goTo13"))
|
||||||
|
.then(States.STATE14, guardVarEquals("goTo14"))
|
||||||
|
.last(States.STATE15)
|
||||||
|
.and()
|
||||||
|
.withChoice()
|
||||||
|
.source(States.STATE12)
|
||||||
|
.first(States.STATE10, guardVarEquals("goBack10"))
|
||||||
|
.last(States.STATE11)
|
||||||
|
.and()
|
||||||
|
.withChoice()
|
||||||
|
.source(States.STATE14)
|
||||||
|
.first(States.STATE12, guardVarEquals("loop12"))
|
||||||
|
.last(States.STATE16)
|
||||||
|
.and()
|
||||||
|
.withChoice()
|
||||||
|
.source(States.STATE9)
|
||||||
|
.first(States.STATE8, guardVarEquals("stepBack"))
|
||||||
|
.then(States.STATE7, guardVarEquals("stepBackMore"))
|
||||||
|
.last(States.STATE6)
|
||||||
|
.and()
|
||||||
|
.withChoice()
|
||||||
|
.source(States.STATE8)
|
||||||
|
.first(States.STATE9, guardVarEquals("forward9"))
|
||||||
|
.last(States.STATE10);
|
||||||
|
|
||||||
|
addAdditionalTransitions(t1);
|
||||||
|
|
||||||
|
addAdditionalCancellationTransitions(transitions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Guard<States, Events> guardVarEquals(String expected) {
|
||||||
|
return context -> {
|
||||||
|
Object var = context.getExtendedState().getVariables().get("var");
|
||||||
|
return expected.equals(var);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Guard<States, Events> guardEventHeaderEquals(String headerName, String expected) {
|
||||||
|
return context -> {
|
||||||
|
Object header = context.getMessageHeader(headerName);
|
||||||
|
return expected.equals(header);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Action<States, Events> logEntryAction(String msg) {
|
||||||
|
return context -> System.out.println(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Action<States, Events> logExitAction(String msg) {
|
||||||
|
return context -> System.out.println(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package click.kamil.examples.statemachine.inheritanceextrafunctionsstate;
|
||||||
|
|
||||||
|
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||||
|
import org.springframework.statemachine.config.configurers.ExternalTransitionConfigurer;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
class AbstractStateMachineConfiguration<S,E> extends StateMachineConfigurerAdapter<S,E> {
|
||||||
|
protected void addTransitionForMultipleEvents(
|
||||||
|
StateMachineTransitionConfigurer<S, E> transitions,
|
||||||
|
S source,
|
||||||
|
S target,
|
||||||
|
Consumer<ExternalTransitionConfigurer<S,E>> transitionCustomizer,
|
||||||
|
E... events) {
|
||||||
|
Stream.of(events).forEach(event -> {
|
||||||
|
try {
|
||||||
|
ExternalTransitionConfigurer<S, E> transitionConfigurer =
|
||||||
|
transitions.withExternal().event(event).source(source).target(target);
|
||||||
|
transitionCustomizer.accept(transitionConfigurer);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package click.kamil.examples.statemachine.inheritanceextrafunctionsstate;
|
||||||
|
|
||||||
|
public enum Events {
|
||||||
|
EVENT1, EVENT2, EVENT3, EVENT4, EVENT5,
|
||||||
|
EVENT6, EVENT7, EVENT8, EVENT9, EVENT10,
|
||||||
|
EVENT11, EVENT12, EVENT13, EVENT14, EVENT15,
|
||||||
|
EVENTX, EVENTY, EVENTZ, EVENT_CANCEL, EVENT_CANCEL_2
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package click.kamil.examples.statemachine.inheritanceextrafunctionsstate;
|
||||||
|
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||||
|
import org.springframework.statemachine.config.configurers.ExternalTransitionConfigurer;
|
||||||
|
import org.springframework.statemachine.config.configurers.StateConfigurer;
|
||||||
|
|
||||||
|
class OneStateMachineConfiguration extends AbstractOneStateMachineConfiguration {
|
||||||
|
@Override
|
||||||
|
protected void addAdditionalStates(StateConfigurer<States, Events> states) {
|
||||||
|
states.state(States.STATE_EXTRA_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void addAdditionalCancellationTransitions(StateMachineTransitionConfigurer<States, Events> transitions) {
|
||||||
|
addAllCancellationTransitionsForSource(transitions, States.STATE_EXTRA_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void addAdditionalTransitions(ExternalTransitionConfigurer<States, Events> transitions) throws Exception {
|
||||||
|
transitions
|
||||||
|
.and()
|
||||||
|
.withExternal().event(Events.EVENTX)
|
||||||
|
.source(States.STATE5)
|
||||||
|
.target(States.STATE_EXTRA_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package click.kamil.examples.statemachine.inheritanceextrafunctionsstate;
|
||||||
|
|
||||||
|
public enum States {
|
||||||
|
STATE1, STATE2, STATE3, STATE4, STATE5,
|
||||||
|
STATE6, STATE7, STATE8, STATE9, STATE10,
|
||||||
|
STATE11, STATE12, STATE13, STATE14, STATE15,
|
||||||
|
STATE16, STATE17, STATE18, STATE19, STATE20,
|
||||||
|
STATEX, STATEY, STATEZ, STATE_EXTRA_1, CANCEL
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package click.kamil.examples.statemachine.inheritanceextrafunctionsstate.app;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
class StateMachineApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(StateMachineApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
spring.application.name=statemachinedemo
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package click.kamil.examples.statemachine;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class MyTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void contextLoads() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -87,7 +87,7 @@ abstract class AbstractOneStateMachineConfiguration extends AbstractStateMachine
|
|||||||
.withExternal().source(States.STATE15).target(States.STATE16).event(Events.EVENT15);
|
.withExternal().source(States.STATE15).target(States.STATE16).event(Events.EVENT15);
|
||||||
|
|
||||||
// EXTRA CHOICE
|
// EXTRA CHOICE
|
||||||
|
transitions.withExternal().source(States.STATE4).target(States.STATEY).event(Events.EVENTY);
|
||||||
transitions.withChoice().source(States.STATEY).first(States.STATEX, guardVarEquals("value1")).last(States.STATEZ);
|
transitions.withChoice().source(States.STATEY).first(States.STATEX, guardVarEquals("value1")).last(States.STATEZ);
|
||||||
|
|
||||||
// Choice transitions (at least 10)
|
// Choice transitions (at least 10)
|
||||||
|
|||||||
Reference in New Issue
Block a user