From 2ecc8e08de6c9de77b8c7d3f1cff6843f51a4aca Mon Sep 17 00:00:00 2001 From: Kamil Patryk Kozakowski Date: Sun, 7 Jun 2026 09:58:39 +0200 Subject: [PATCH] one more example --- settings.gradle | 1 + .../springstatemachineexporter/Main.java | 14 +- .../RegressionTest.java | 26 ++- .../.gitignore | 45 ++++ .../build.gradle | 42 ++++ .../AbstractStateMachineConfiguration.java | 27 +++ ...bstractThreeStateMachineConfiguration.java | 203 ++++++++++++++++++ .../Events.java | 8 + .../States.java | 9 + .../ThreeStateMachineConfiguration.java | 40 ++++ .../app/StateMachineApplication.java | 11 + .../src/main/resources/application.properties | 1 + .../kamil/examples/statemachine/MyTest.java | 11 + 13 files changed, 423 insertions(+), 15 deletions(-) create mode 100644 state_machines/inheritance_extra_functions2_state_machine/.gitignore create mode 100644 state_machines/inheritance_extra_functions2_state_machine/build.gradle create mode 100644 state_machines/inheritance_extra_functions2_state_machine/src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate2/AbstractStateMachineConfiguration.java create mode 100644 state_machines/inheritance_extra_functions2_state_machine/src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate2/AbstractThreeStateMachineConfiguration.java create mode 100644 state_machines/inheritance_extra_functions2_state_machine/src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate2/Events.java create mode 100644 state_machines/inheritance_extra_functions2_state_machine/src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate2/States.java create mode 100644 state_machines/inheritance_extra_functions2_state_machine/src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate2/ThreeStateMachineConfiguration.java create mode 100644 state_machines/inheritance_extra_functions2_state_machine/src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate2/app/StateMachineApplication.java create mode 100644 state_machines/inheritance_extra_functions2_state_machine/src/main/resources/application.properties create mode 100644 state_machines/inheritance_extra_functions2_state_machine/src/test/java/click/kamil/examples/statemachine/MyTest.java diff --git a/settings.gradle b/settings.gradle index cb5dc22..5920b6e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -9,4 +9,5 @@ include ':state_machines:enumstate_state_machine' include ':state_machines:forkjoin_state_machine' include ':state_machines:inheritance_state_machine' include ':state_machines:inheritance_extra_functions_state_machine' +include ':state_machines:inheritance_extra_functions2_state_machine' include ':state_machines:simple_state_machine' diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/Main.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/Main.java index 1d22e55..b5d40f4 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/Main.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/Main.java @@ -45,11 +45,8 @@ public class Main { new Scxml() ); - List entryPoints = context.findEntryPointClasses(TARGET_ANNOTATIONS); - for (TypeDeclaration td : entryPoints) { - processEntryPoint(td, outputs, outputDir, context); - } - + exportAll(context, outputs, outputDir); + // Still handle methods returning StateMachine for now (might be harder to refactor to CodebaseContext) AstFileFinder finder = new AstFileFinder(); List javaFiles = FileUtils.findFilesWithExtension(Set.of(inputDir), EXTENSION); @@ -61,6 +58,13 @@ public class Main { } } + public static void exportAll(CodebaseContext context, List outputs, Path outputDir) throws IOException { + List entryPoints = context.findEntryPointClasses(TARGET_ANNOTATIONS); + for (TypeDeclaration td : entryPoints) { + processEntryPoint(td, outputs, outputDir, context); + } + } + static void processEntryPoint(TypeDeclaration td, List outputs, Path outputDir, CodebaseContext context) throws IOException { String className = td.getName().getFullyQualifiedName(); System.out.println("Processing state machine config: " + className); diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/RegressionTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/RegressionTest.java index 367a6b0..1056818 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/RegressionTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/RegressionTest.java @@ -1,5 +1,6 @@ package click.kamil.springstatemachineexporter; +import click.kamil.springstatemachineexporter.ast.common.CodebaseContext; import click.kamil.springstatemachineexporter.ast.out.Dot; import click.kamil.springstatemachineexporter.ast.out.PlantUml; import click.kamil.springstatemachineexporter.ast.out.Scxml; @@ -15,9 +16,9 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; -class RegressionTest { +public class RegressionTest { - static List provideTestScenarios() { + private static List provideTestScenarios() { return List.of( new TestScenario( "Complex State Machine", @@ -54,23 +55,28 @@ class RegressionTest { Path.of("../state_machines/inheritance_extra_functions_state_machine"), Path.of("src/test/resources/golden/TwoStateMachineExtraFunctionsConfiguration"), "TwoStateMachineConfiguration" + ), + new TestScenario( + "Three State Machine (Extra Functions 2)", + Path.of("../state_machines/inheritance_extra_functions2_state_machine"), + Path.of("src/test/resources/golden/ThreeStateMachineConfiguration"), + "ThreeStateMachineConfiguration" ) ); } + @ParameterizedTest(name = "{0}") @MethodSource("provideTestScenarios") void testOutputMatchesGolden(TestScenario scenario, @TempDir Path tempDir) throws Exception { - Main.runExporter(scenario.inputPath(), tempDir); + CodebaseContext context = new CodebaseContext(); + context.scan(scenario.inputPath()); - List exporters = List.of( - new PlantUml(), - new Dot(), - new Scxml() - ); + List outputs = List.of(new PlantUml(), new Dot(), new Scxml()); + + Main.exportAll(context, outputs, tempDir); String baseName = scenario.baseName(); - - for (StateMachineExporter exporter : exporters) { + for (StateMachineExporter exporter : outputs) { String fileName = baseName + exporter.getFileExtension(); Path actualFile = tempDir.resolve(baseName).resolve(fileName); diff --git a/state_machines/inheritance_extra_functions2_state_machine/.gitignore b/state_machines/inheritance_extra_functions2_state_machine/.gitignore new file mode 100644 index 0000000..c022c2a --- /dev/null +++ b/state_machines/inheritance_extra_functions2_state_machine/.gitignore @@ -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 \ No newline at end of file diff --git a/state_machines/inheritance_extra_functions2_state_machine/build.gradle b/state_machines/inheritance_extra_functions2_state_machine/build.gradle new file mode 100644 index 0000000..0f14971 --- /dev/null +++ b/state_machines/inheritance_extra_functions2_state_machine/build.gradle @@ -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() +} diff --git a/state_machines/inheritance_extra_functions2_state_machine/src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate2/AbstractStateMachineConfiguration.java b/state_machines/inheritance_extra_functions2_state_machine/src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate2/AbstractStateMachineConfiguration.java new file mode 100644 index 0000000..800aa78 --- /dev/null +++ b/state_machines/inheritance_extra_functions2_state_machine/src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate2/AbstractStateMachineConfiguration.java @@ -0,0 +1,27 @@ +package click.kamil.examples.statemachine.inheritanceextrafunctionsstate2; + +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 extends StateMachineConfigurerAdapter { + protected void addTransitionForMultipleEvents( + StateMachineTransitionConfigurer transitions, + S source, + S target, + Consumer> transitionCustomizer, + E... events) { + Stream.of(events).forEach(event -> { + try { + ExternalTransitionConfigurer transitionConfigurer = + transitions.withExternal().event(event).source(source).target(target); + transitionCustomizer.accept(transitionConfigurer); + } catch (Exception e) { + throw new IllegalStateException(e); + } + }); + } +} diff --git a/state_machines/inheritance_extra_functions2_state_machine/src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate2/AbstractThreeStateMachineConfiguration.java b/state_machines/inheritance_extra_functions2_state_machine/src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate2/AbstractThreeStateMachineConfiguration.java new file mode 100644 index 0000000..adaa874 --- /dev/null +++ b/state_machines/inheritance_extra_functions2_state_machine/src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate2/AbstractThreeStateMachineConfiguration.java @@ -0,0 +1,203 @@ +package click.kamil.examples.statemachine.inheritanceextrafunctionsstate2; + +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 AbstractThreeStateMachineConfiguration extends AbstractStateMachineConfiguration { + + protected abstract void addAdditionalStates(StateConfigurer states); + + protected abstract void addAdditionalCancellationTransitions(StateMachineTransitionConfigurer transitions); + + protected abstract void addAdditionalTransitions(ExternalTransitionConfigurer transitions) throws Exception; + + protected void addAllCancellationTransitionsForSource(StateMachineTransitionConfigurer transitions, States source) { + addCancellationTransitionForCancelEvents(transitions, source, Events.EVENT_CANCEL_2); + } + + private void addCancellationTransitionForCancelEvents( + StateMachineTransitionConfigurer 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 config) throws Exception { + config + .withConfiguration() + .autoStartup(true); + } + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + StateConfigurer 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 transitions) throws Exception { + + // External transitions + ExternalTransitionConfigurer 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); + } + + protected Guard guardVarEquals(String expected) { + return context -> { + Object var = context.getExtendedState().getVariables().get("var"); + return expected.equals(var); + }; + } + + protected Guard guardEventHeaderEquals(String headerName, String expected) { + return context -> { + Object header = context.getMessageHeader(headerName); + return expected.equals(header); + }; + } + + private Action logEntryAction(String msg) { + return context -> System.out.println(msg); + } + + private Action logExitAction(String msg) { + return context -> System.out.println(msg); + } +} diff --git a/state_machines/inheritance_extra_functions2_state_machine/src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate2/Events.java b/state_machines/inheritance_extra_functions2_state_machine/src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate2/Events.java new file mode 100644 index 0000000..ffc77e7 --- /dev/null +++ b/state_machines/inheritance_extra_functions2_state_machine/src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate2/Events.java @@ -0,0 +1,8 @@ +package click.kamil.examples.statemachine.inheritanceextrafunctionsstate2; + +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 +} diff --git a/state_machines/inheritance_extra_functions2_state_machine/src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate2/States.java b/state_machines/inheritance_extra_functions2_state_machine/src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate2/States.java new file mode 100644 index 0000000..9594126 --- /dev/null +++ b/state_machines/inheritance_extra_functions2_state_machine/src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate2/States.java @@ -0,0 +1,9 @@ +package click.kamil.examples.statemachine.inheritanceextrafunctionsstate2; + +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, STATE_EXTRA_2, STATE_EXTRA_3, CANCEL +} diff --git a/state_machines/inheritance_extra_functions2_state_machine/src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate2/ThreeStateMachineConfiguration.java b/state_machines/inheritance_extra_functions2_state_machine/src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate2/ThreeStateMachineConfiguration.java new file mode 100644 index 0000000..fa92409 --- /dev/null +++ b/state_machines/inheritance_extra_functions2_state_machine/src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate2/ThreeStateMachineConfiguration.java @@ -0,0 +1,40 @@ +package click.kamil.examples.statemachine.inheritanceextrafunctionsstate2; + +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.config.configurers.ExternalTransitionConfigurer; +import org.springframework.statemachine.config.configurers.StateConfigurer; + +class ThreeStateMachineConfiguration extends AbstractThreeStateMachineConfiguration { + @Override + protected void addAdditionalStates(StateConfigurer states) { + states.state(States.STATE_EXTRA_1); + states.state(States.STATE_EXTRA_2); + states.state(States.STATE_EXTRA_3); + } + + @Override + protected void addAdditionalCancellationTransitions(StateMachineTransitionConfigurer transitions) { + addAllCancellationTransitionsForSource(transitions, States.STATE_EXTRA_1); + addAllCancellationTransitionsForSource(transitions, States.STATE_EXTRA_2); + } + + @Override + protected void addAdditionalTransitions(ExternalTransitionConfigurer transitions) throws Exception { + transitions + .and() + .withExternal().event(Events.EVENTX) + .source(States.STATE2) + .target(States.STATE_EXTRA_1) + .and() + .withChoice() + .source(States.STATE2) + .first(States.STATE1, guardEventHeaderEquals("header1", "foo")) + .last(States.STATE_EXTRA_1) + .and() + .withExternal() + .source(States.STATE_EXTRA_1) + .event(Events.EVENTY) + .target(States.STATE_EXTRA_3); + } + +} diff --git a/state_machines/inheritance_extra_functions2_state_machine/src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate2/app/StateMachineApplication.java b/state_machines/inheritance_extra_functions2_state_machine/src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate2/app/StateMachineApplication.java new file mode 100644 index 0000000..3184703 --- /dev/null +++ b/state_machines/inheritance_extra_functions2_state_machine/src/main/java/click/kamil/examples/statemachine/inheritanceextrafunctionsstate2/app/StateMachineApplication.java @@ -0,0 +1,11 @@ +package click.kamil.examples.statemachine.inheritanceextrafunctionsstate2.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); + } +} diff --git a/state_machines/inheritance_extra_functions2_state_machine/src/main/resources/application.properties b/state_machines/inheritance_extra_functions2_state_machine/src/main/resources/application.properties new file mode 100644 index 0000000..1cd01ff --- /dev/null +++ b/state_machines/inheritance_extra_functions2_state_machine/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.application.name=statemachinedemo diff --git a/state_machines/inheritance_extra_functions2_state_machine/src/test/java/click/kamil/examples/statemachine/MyTest.java b/state_machines/inheritance_extra_functions2_state_machine/src/test/java/click/kamil/examples/statemachine/MyTest.java new file mode 100644 index 0000000..95af6f1 --- /dev/null +++ b/state_machines/inheritance_extra_functions2_state_machine/src/test/java/click/kamil/examples/statemachine/MyTest.java @@ -0,0 +1,11 @@ +package click.kamil.examples.statemachine; + +import org.junit.jupiter.api.Test; + +class MyTest { + + @Test + void contextLoads() { + } + +}