├── .editorconfig
├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── .idea
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── copyright
│ ├── open_jumpco.xml
│ └── profiles_settings.xml
├── kotlinc.xml
├── misc.xml
└── vcs.xml
├── LICENSE
├── README.adoc
├── build-linux.sh
├── build.cmd
├── build.gradle
├── generated
├── packet-reader-detail-simple.plantuml
├── packet-reader-detail.adoc
├── packet-reader-detail.plantuml
├── packet-reader.plantuml
├── paying-turnstile-detail-simple.plantuml
├── paying-turnstile-detail.adoc
├── paying-turnstile-detail.plantuml
├── paying-turnstile.plantuml
├── secure-turnstile-detail-simple.plantuml
├── secure-turnstile-detail.adoc
├── secure-turnstile-detail.plantuml
├── secure-turnstile.plantuml
├── timeout-turnstile-detail-simple.plantuml
├── timeout-turnstile-detail.adoc
├── timeout-turnstile-detail.plantuml
├── turnstile-detail-simple.plantuml
├── turnstile-detail.adoc
├── turnstile-detail.plantuml
└── turnstile.plantuml
├── gradle.properties
├── gradle
├── docs.gradle
├── platform.gradle
├── publish.gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── kfsm-fsm.png
├── packet_reader.png
├── paying_turnstile.png
├── publish-linux.sh
├── publish.cmd
├── publish.sh
├── qodana.cmd
├── secure_turnstile.png
├── settings.gradle
├── src
├── commonMain
│ └── kotlin
│ │ └── io
│ │ └── jumpco
│ │ └── open
│ │ └── kfsm
│ │ ├── CommonTypes.kt
│ │ ├── DefaultSyncTransition.kt
│ │ ├── DslStateMachineHandler.kt
│ │ ├── DslStateMapDefaultEventHandler.kt
│ │ ├── DslStateMapEventHandler.kt
│ │ ├── DslStateMapHandler.kt
│ │ ├── SimpleSyncTransition.kt
│ │ ├── StateMachineBuilder.kt
│ │ ├── StateMachineDefinition.kt
│ │ ├── StateMachineInstance.kt
│ │ ├── StateMapBuilder.kt
│ │ ├── StateMapDefinition.kt
│ │ ├── StateMapInstance.kt
│ │ ├── SyncGuardedTransition.kt
│ │ ├── SyncTransition.kt
│ │ ├── SyncTransitionRules.kt
│ │ ├── async
│ │ ├── AsyncDslStateMachineHandler.kt
│ │ ├── AsyncDslStateMapDefaultEventHandler.kt
│ │ ├── AsyncDslStateMapEventHandler.kt
│ │ ├── AsyncDslStateMapHandler.kt
│ │ ├── AsyncGuardedTransition.kt
│ │ ├── AsyncStateMachineBuilder.kt
│ │ ├── AsyncStateMachineDefinition.kt
│ │ ├── AsyncStateMachineInstance.kt
│ │ ├── AsyncStateMapBuilder.kt
│ │ ├── AsyncStateMapDefinition.kt
│ │ ├── AsyncStateMapInstance.kt
│ │ ├── AsyncTimer.kt
│ │ ├── AsyncTimerDefinition.kt
│ │ ├── AsyncTransition.kt
│ │ ├── AsyncTransitionRules.kt
│ │ ├── DefaultAsyncTransition.kt
│ │ ├── SimpleAsyncTransition.kt
│ │ └── asyncStateMachines.kt
│ │ └── stateMachine.kt
├── commonTest
│ └── kotlin
│ │ └── io
│ │ └── jumpco
│ │ └── open
│ │ └── kfsm
│ │ └── example
│ │ ├── CarFSMTest.kt
│ │ ├── DetailMockedTests.kt
│ │ ├── DetailTests.kt
│ │ ├── ImmutableLockFSM.kt
│ │ ├── KeyboardBuffer.kt
│ │ ├── LockFsmTests.kt
│ │ ├── LockTypes.kt
│ │ ├── PacketReaderTests.kt
│ │ ├── PayingTurnstileFsmTests.kt
│ │ ├── PayingTurnstileTypes.kt
│ │ ├── SecureTurnstile.kt
│ │ ├── SecureTurnstileTests.kt
│ │ ├── TimeoutSecureTurnstile.kt
│ │ ├── TurnstileFsmTests.kt
│ │ └── TurnstileTypes.kt
├── docs
│ ├── asciidoc
│ │ ├── docinfo-footer.html
│ │ ├── docinfo.html
│ │ ├── documentation.adoc
│ │ ├── index.adoc
│ │ ├── kfsm.adoc
│ │ ├── lock-fsm.png
│ │ ├── lock_fsm.png
│ │ ├── packet-reader-fsm-guard.png
│ │ ├── packet-reader-fsm.png
│ │ ├── packet_reader_detail.png
│ │ ├── paying-turnstile-fsm.png
│ │ ├── paying_turnstile_detail.png
│ │ ├── prism.css
│ │ ├── prism.js
│ │ ├── sample-fsm.png
│ │ ├── secure-turnstile-fsm.png
│ │ ├── secure_turnstile_detail.png
│ │ ├── statemachine-classes.png
│ │ ├── statemachine-model.png
│ │ ├── statemachine-packaged.png
│ │ ├── statemachine-sequence.png
│ │ ├── timeout_turnstile_detail.png
│ │ ├── turnstile-fsm.png
│ │ ├── turnstile-scxml.xml
│ │ ├── turnstile-sequence.png
│ │ ├── turnstile_detail.png
│ │ ├── turnstile_fsm.png
│ │ ├── turnstile_scxml.png
│ │ └── useless-fsm.png
│ └── plantuml
│ │ ├── kfsm.plantuml
│ │ ├── lock-fsm.plantuml
│ │ ├── packet-reader-fsm-guard.plantuml
│ │ ├── packet-reader-fsm.plantuml
│ │ ├── paying-turnstile-fsm.plantuml
│ │ ├── sample-fsm.plantuml
│ │ ├── secure-turnstile-fsm.plantuml
│ │ ├── statemachine-classes.plantuml
│ │ ├── statemachine-model.plantuml
│ │ ├── statemachine-packaged.plantuml
│ │ ├── statemachine-sequence.plantuml
│ │ ├── turnstile-fsm.plantuml
│ │ ├── turnstile-sequence.plantuml
│ │ └── useless-fsm.plantuml
├── jvmTest
│ └── kotlin
│ │ └── io
│ │ └── jumpco
│ │ └── open
│ │ └── kfsm
│ │ └── example
│ │ ├── TimeoutSecureTurnstileTests.kt
│ │ └── VisualizeTurnstileTest.kt
└── nativeTest
│ └── kotlin
│ └── io
│ └── jumpco
│ └── open
│ └── kfsm
│ └── example
│ └── TimeoutSecureTurnstileTests.kt
└── turnstile.png
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 |
5 | # We recommend you to keep these unchanged
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | # Change these settings to your own preference
12 | indent_style = space
13 | indent_size = 4
14 |
15 | [*.{kt,java,kts}]
16 | indent_style = space
17 | indent_size = 4
18 |
19 | [*.{ts,tsx,js,jsx,json,css,scss,yml}]
20 | indent_size = 2
21 |
22 | [*.md]
23 | trim_trailing_whitespace = false
24 |
25 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: 'Build'
2 |
3 | on:
4 | push:
5 | workflow_dispatch:
6 |
7 | jobs:
8 | build:
9 | strategy:
10 | matrix:
11 | # os: [ 'ubuntu', 'macos', 'windows' ]
12 | os: [ 'ubuntu', 'windows', 'macos' ]
13 | runs-on: '${{ matrix.os }}-latest'
14 | steps:
15 | - uses: actions/checkout@v4
16 | - uses: browser-actions/setup-chrome@v1
17 | - id: parameters
18 | shell: bash
19 | run: |
20 | OS="${{matrix.os}}"
21 | case $OS in
22 | "ubuntu")
23 | echo "arch=x86" >> $GITHUB_OUTPUT
24 | echo "profile=jvm,js,linux,wasm-js" >> $GITHUB_OUTPUT
25 | echo "dokka=true" >> $GITHUB_OUTPUT
26 | ;;
27 | "windows")
28 | echo "arch=x86" >> $GITHUB_OUTPUT
29 | echo "profile=mingw" >> $GITHUB_OUTPUT
30 | echo "dokka=false" >> $GITHUB_OUTPUT
31 | ;;
32 | "macos")
33 | echo "arch=arm64" >> $GITHUB_OUTPUT
34 | echo "profile=macos" >> $GITHUB_OUTPUT
35 | echo "dokka=false" >> $GITHUB_OUTPUT
36 | ;;
37 | *)
38 | echo "Unable to determine arch from $OS"
39 | exit 1
40 | ;;
41 | esac
42 | - name: Set up JDK 11 for ${{ steps.parameters.outputs.arch }}
43 | uses: actions/setup-java@v3
44 | with:
45 | java-version: '17'
46 | distribution: 'zulu'
47 | cache: gradle
48 | - name: 'Build'
49 | shell: bash
50 | run: |
51 | chmod a+x ./gradlew
52 | echo "::info ::Build profile=${{ steps.parameters.outputs.profile }} on ${{ steps.parameters.outputs.arch }}"
53 | ./gradlew build -PbuildProfile=${{ steps.parameters.outputs.profile }} -Pdokka=${{ steps.parameters.outputs.dokka }}
54 | - name: 'Test Report - ${{ matrix.os }}'
55 | if: ${{ success() || failure() }}
56 | uses: dorny/test-reporter@v1
57 | with:
58 | name: 'Test Report - ${{ matrix.os }}'
59 | path: '**/test-results/**/*.xml'
60 | reporter: java-junit
61 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | ### Gradle template
3 | .gradle
4 | build/
5 |
6 | # Ignore Gradle GUI config
7 | gradle-app.setting
8 |
9 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
10 | !gradle-wrapper.jar
11 |
12 | # Cache of project
13 | .gradletasknamecache
14 |
15 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
16 | # gradle/wrapper/gradle-wrapper.properties
17 |
18 | ### JetBrains template
19 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
20 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
21 |
22 | # User-specific stuff
23 | .idea/**/workspace.xml
24 | .idea/**/tasks.xml
25 | .idea/**/usage.statistics.xml
26 | .idea/**/dictionaries
27 | .idea/**/shelf
28 |
29 | # Generated files
30 | .idea/**/contentModel.xml
31 |
32 | # Sensitive or high-churn files
33 | .idea/**/dataSources/
34 | .idea/**/dataSources.ids
35 | .idea/**/dataSources.local.xml
36 | .idea/**/sqlDataSources.xml
37 | .idea/**/dynamic.xml
38 | .idea/**/uiDesigner.xml
39 | .idea/**/dbnavigator.xml
40 |
41 | # Gradle
42 | .idea/**/gradle.xml
43 | .idea/**/libraries
44 |
45 | # Gradle and Maven with auto-import
46 | # When using Gradle or Maven with auto-import, you should exclude module files,
47 | # since they will be recreated, and may cause churn. Uncomment if using
48 | # auto-import.
49 | # .idea/modules.xml
50 | # .idea/*.iml
51 | # .idea/modules
52 | # *.iml
53 | # *.ipr
54 |
55 | # CMake
56 | cmake-build-*/
57 |
58 | # Mongo Explorer plugin
59 | .idea/**/mongoSettings.xml
60 |
61 | # File-based project format
62 | *.iws
63 |
64 | # IntelliJ
65 | out/
66 |
67 | # mpeltonen/sbt-idea plugin
68 | .idea_modules/
69 |
70 | # JIRA plugin
71 | atlassian-ide-plugin.xml
72 |
73 | # Cursive Clojure plugin
74 | .idea/replstate.xml
75 |
76 | # Crashlytics plugin (for Android Studio and IntelliJ)
77 | com_crashlytics_export_strings.xml
78 | crashlytics.properties
79 | crashlytics-build.properties
80 | fabric.properties
81 |
82 | # Editor-based Rest Client
83 | .idea/httpRequests
84 |
85 | # Android studio 3.1+ serialized cache file
86 | .idea/caches/build_file_checksums.ser
87 |
88 | ### Kotlin template
89 | # Compiled class file
90 | *.class
91 |
92 | # Log file
93 | *.log
94 |
95 | # BlueJ files
96 | *.ctxt
97 |
98 | # Mobile Tools for Java (J2ME)
99 | .mtj.tmp/
100 |
101 | # Package Files #
102 | *.jar
103 | *.war
104 | *.nar
105 | *.ear
106 | *.zip
107 | *.tar.gz
108 | *.rar
109 |
110 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
111 | hs_err_pid*
112 |
113 | .idea/
114 | codemr/
115 | kotlin-js-store/
116 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/copyright/open_jumpco.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019-2021 Open JumpCO
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/build-linux.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | ./gradlew -i --continue build publishToMavenLocal -x dokkaJavadoc -x dokkaGfm -x dokkaJekyll
3 |
--------------------------------------------------------------------------------
/build.cmd:
--------------------------------------------------------------------------------
1 | @echo off
2 | gradlew -S -i clean build -x dokkaJavadoc -x dokkaGfm -x dokkaJekyll
3 |
--------------------------------------------------------------------------------
/generated/packet-reader-detail-simple.plantuml:
--------------------------------------------------------------------------------
1 | @startuml
2 | skinparam monochrome true
3 | skinparam StateFontName Helvetica
4 | skinparam defaultFontName Monospaced
5 | skinparam defaultFontStyle Bold
6 | skinparam state {
7 | FontStyle Bold
8 | }
9 | state PacketReaderFSM {
10 | [*] --> RCVPCKT : CTRL
11 | RCVPCKT --> RCVDATA : CTRL
12 | RCVPCKT --> RCVCHK : BYTE
13 | RCVDATA --> RCVDATA : BYTE
14 | RCVDATA --> RCVPCKT : CTRL
15 | RCVDATA --> RCVESC : ESC
16 | RCVESC --> RCVDATA : ESC
17 | RCVESC --> RCVDATA : CTRL
18 | RCVCHK --> RCVCHK : BYTE
19 | RCVCHK --> RCVCHKESC : ESC
20 | RCVCHK --> CHKSUM : CTRL
21 | CHKSUM --> [*] : <>
22 | CHKSUM --> [*] : <>
23 | RCVCHKESC --> RCVCHK : ESC
24 | RCVCHKESC --> RCVCHK : CTRL
25 | }
26 | @enduml
27 |
--------------------------------------------------------------------------------
/generated/packet-reader-detail.adoc:
--------------------------------------------------------------------------------
1 | == PacketReaderFSM State Chart
2 |
3 | === PacketReaderFSM State Map
4 |
5 | |===
6 | | Start | Event[Guard] | Target | Action
7 |
8 | | START
9 | | CTRL `[{byte->byte == CharacterConstants.SOH}]`
10 | | RCVPCKT
11 | a|
12 |
13 | | RCVPCKT
14 | | CTRL `[{byte->byte == CharacterConstants.STX}]`
15 | | RCVDATA
16 | a| [source,kotlin]
17 | ----
18 | {
19 | addField()
20 | }
21 | ----
22 |
23 | | RCVPCKT
24 | | BYTE
25 | | RCVCHK
26 | a| [source,kotlin]
27 | ----
28 | {byte->
29 | requireNotNull(byte)
30 | addChecksum(byte)
31 | }
32 | ----
33 |
34 | | RCVDATA
35 | | BYTE
36 | | RCVDATA
37 | a| [source,kotlin]
38 | ----
39 | {byte->
40 | requireNotNull(byte)
41 | addByte(byte)
42 | }
43 | ----
44 |
45 | | RCVDATA
46 | | CTRL `[{byte->byte == CharacterConstants.ETX}]`
47 | | RCVPCKT
48 | a| [source,kotlin]
49 | ----
50 | {
51 | endField()
52 | }
53 | ----
54 |
55 | | RCVDATA
56 | | ESC
57 | | RCVESC
58 | a|
59 |
60 | | RCVESC
61 | | ESC
62 | | RCVDATA
63 | a| [source,kotlin]
64 | ----
65 | {
66 | addByte(CharacterConstants.ESC)
67 | }
68 | ----
69 |
70 | | RCVESC
71 | | CTRL
72 | | RCVDATA
73 | a| [source,kotlin]
74 | ----
75 | {byte->
76 | requireNotNull(byte)
77 | addByte(byte)
78 | }
79 | ----
80 |
81 | | RCVCHK
82 | | BYTE
83 | | RCVCHK
84 | a| [source,kotlin]
85 | ----
86 | {byte->
87 | requireNotNull(byte)
88 | addChecksum(byte)
89 | }
90 | ----
91 |
92 | | RCVCHK
93 | | ESC
94 | | RCVCHKESC
95 | a|
96 |
97 | | RCVCHK
98 | | CTRL `[{byte->byte == CharacterConstants.EOT}]`
99 | | CHKSUM
100 | a| [source,kotlin]
101 | ----
102 | {
103 | checksum()
104 | }
105 | ----
106 |
107 | | CHKSUM
108 | | \<> `[{!checksumValid}]`
109 | | [*]
110 | a| [source,kotlin]
111 | ----
112 | {
113 | sendNACK()
114 | }
115 | ----
116 |
117 | | CHKSUM
118 | | \<> `[{checksumValid}]`
119 | | [*]
120 | a| [source,kotlin]
121 | ----
122 | {
123 | sendACK()
124 | }
125 | ----
126 |
127 | | RCVCHKESC
128 | | ESC
129 | | RCVCHK
130 | a| [source,kotlin]
131 | ----
132 | {
133 | addChecksum(CharacterConstants.ESC)
134 | }
135 | ----
136 |
137 | | RCVCHKESC
138 | | CTRL
139 | | RCVCHK
140 | a| [source,kotlin]
141 | ----
142 | {byte->
143 | requireNotNull(byte)
144 | addChecksum(byte)
145 | }
146 | ----
147 | |===
148 |
149 |
--------------------------------------------------------------------------------
/generated/packet-reader-detail.plantuml:
--------------------------------------------------------------------------------
1 | @startuml
2 | skinparam monochrome true
3 | skinparam StateFontName Helvetica
4 | skinparam defaultFontName Monospaced
5 | skinparam defaultFontStyle Bold
6 | skinparam state {
7 | FontStyle Bold
8 | }
9 | state PacketReaderFSM {
10 | [*] --> RCVPCKT : CTRL [byte->byte == CharacterConstants.SOH]
11 | RCVPCKT --> RCVDATA : CTRL [byte->byte == CharacterConstants.STX] -> {\l addField()\l}
12 | RCVPCKT --> RCVCHK : BYTE -> {byte->\l requireNotNull(byte)\l addChecksum(byte)\l}
13 | RCVDATA --> RCVDATA : BYTE -> {byte->\l requireNotNull(byte)\l addByte(byte)\l}
14 | RCVDATA --> RCVPCKT : CTRL [byte->byte == CharacterConstants.ETX] -> {\l endField()\l}
15 | RCVDATA --> RCVESC : ESC
16 | RCVESC --> RCVDATA : ESC -> {\l addByte(CharacterConstants.ESC)\l}
17 | RCVESC --> RCVDATA : CTRL -> {byte->\l requireNotNull(byte)\l addByte(byte)\l}
18 | RCVCHK --> RCVCHK : BYTE -> {byte->\l requireNotNull(byte)\l addChecksum(byte)\l}
19 | RCVCHK --> RCVCHKESC : ESC
20 | RCVCHK --> CHKSUM : CTRL [byte->byte == CharacterConstants.EOT] -> {\l checksum()\l}
21 | CHKSUM --> [*] : <> [!checksumValid] -> {\l sendNACK()\l}
22 | CHKSUM --> [*] : <> [checksumValid] -> {\l sendACK()\l}
23 | RCVCHKESC --> RCVCHK : ESC -> {\l addChecksum(CharacterConstants.ESC)\l}
24 | RCVCHKESC --> RCVCHK : CTRL -> {byte->\l requireNotNull(byte)\l addChecksum(byte)\l}
25 | }
26 | @enduml
27 |
--------------------------------------------------------------------------------
/generated/packet-reader.plantuml:
--------------------------------------------------------------------------------
1 | @startuml
2 | skinparam monochrome true
3 | skinparam StateFontName Helvetica
4 | skinparam defaultFontName Monospaced
5 | skinparam defaultFontStyle Bold
6 | skinparam state {
7 | FontStyle Bold
8 | }
9 | state PacketReaderFSM {
10 | [*] --> RCVPCKT : CTRL [byte->byte == CharacterConstants.SOH]
11 | RCVPCKT --> RCVDATA : CTRL [byte->byte == CharacterConstants.STX] -> {\l addField()\l}
12 | RCVPCKT --> RCVCHK : BYTE -> {byte->\l requireNotNull(byte)\l addChecksum(byte)\l}
13 | RCVDATA --> RCVDATA : BYTE -> {byte->\l requireNotNull(byte)\l addByte(byte)\l}
14 | RCVDATA --> RCVPCKT : CTRL [byte->byte == CharacterConstants.ETX] -> {\l endField()\l}
15 | RCVDATA --> RCVESC : ESC
16 | RCVESC --> RCVDATA : ESC -> {\l addByte(CharacterConstants.ESC)\l}
17 | RCVESC --> RCVDATA : CTRL -> {byte->\l requireNotNull(byte)\l addByte(byte)\l}
18 | RCVCHK --> RCVCHK : BYTE -> {byte->\l requireNotNull(byte)\l addChecksum(byte)\l}
19 | RCVCHK --> RCVCHKESC : ESC
20 | RCVCHK --> CHKSUM : CTRL [byte->byte == CharacterConstants.EOT] -> {\l checksum()\l}
21 | CHKSUM --> [*] : <> [!checksumValid] -> {\l sendNACK()\l}
22 | CHKSUM --> [*] : <> [checksumValid] -> {\l sendACK()\l}
23 | RCVCHKESC --> RCVCHK : ESC -> {\l addChecksum(CharacterConstants.ESC)\l}
24 | RCVCHKESC --> RCVCHK : CTRL -> {byte->\l requireNotNull(byte)\l addChecksum(byte)\l}
25 | }
26 | @enduml
27 |
--------------------------------------------------------------------------------
/generated/paying-turnstile-detail-simple.plantuml:
--------------------------------------------------------------------------------
1 | @startuml
2 | skinparam monochrome true
3 | skinparam StateFontName Helvetica
4 | skinparam defaultFontName Monospaced
5 | skinparam defaultFontStyle Bold
6 | skinparam state {
7 | FontStyle Bold
8 | }
9 | state coins {
10 | COINS --> UNLOCKED : <>
11 | COINS --> UNLOCKED : <>
12 | COINS --> COINS : COIN
13 | }
14 | state PayingTurnstileFSM {
15 | [*] --> UNLOCKED
16 | LOCKED --> COINS : COIN
17 | LOCKED --> COINS : COIN
18 | UNLOCKED --> UNLOCKED : COIN
19 | UNLOCKED --> LOCKED : PASS
20 | }
21 | @enduml
22 |
--------------------------------------------------------------------------------
/generated/paying-turnstile-detail.adoc:
--------------------------------------------------------------------------------
1 | == PayingTurnstileFSM State Chart
2 |
3 | === PayingTurnstileFSM State Map
4 |
5 | |===
6 | | Start | Event[Guard] | Target | Action
7 |
8 | | <>
9 | |
10 | | UNLOCKED
11 | a|
12 |
13 | | LOCKED
14 | | COIN
15 | | COINS
16 | a| [source,kotlin]
17 | ----
18 | {value->
19 | requireNotNull(value){"argument required for COIN"}
20 | coin(value)
21 | unlock()
22 | reset()
23 | }
24 | ----
25 |
26 | | LOCKED
27 | | COIN `[{value->requireNotNull(value){"argument required for COIN"};value+coins < requiredCoins;}]`
28 | | COINS
29 | a| [source,kotlin]
30 | ----
31 | {value->
32 | requireNotNull(value){"argument required for COIN"}
33 | println("PUSH TRANSITION")
34 | coin(value)
35 | println("Coins=$coins, Please add ${requiredCoins-coins}")
36 | }
37 | ----
38 |
39 | | UNLOCKED
40 | | COIN
41 | | UNLOCKED
42 | a| [source,kotlin]
43 | ----
44 | {value->
45 | requireNotNull(value){"argument required for COIN"}
46 | returnCoin(coin(value))
47 | }
48 | ----
49 |
50 | | UNLOCKED
51 | | PASS
52 | | LOCKED
53 | a| [source,kotlin]
54 | ----
55 | {
56 | lock()
57 | }
58 | ----
59 | |===
60 |
61 | === State Map coins
62 |
63 | |===
64 | | Start | Event[Guard] | Target | Action
65 |
66 | | COINS
67 | | \<> `[{coins > requiredCoins}]`
68 | | UNLOCKED
69 | a| [source,kotlin]
70 | ----
71 | {
72 | println("automaticPop:returnCoin")
73 | returnCoin(coins-requiredCoins)
74 | unlock()
75 | reset()
76 | }
77 | ----
78 |
79 | | COINS
80 | | \<> `[{coins == requiredCoins}]`
81 | | UNLOCKED
82 | a| [source,kotlin]
83 | ----
84 | {
85 | println("automaticPop")
86 | unlock()
87 | reset()
88 | }
89 | ----
90 |
91 | | COINS
92 | | COIN
93 | | COINS
94 | a| [source,kotlin]
95 | ----
96 | {value->
97 | requireNotNull(value){"argument required for COIN"}
98 | coin(value)
99 | println("Coins=$coins")
100 | if(coins < requiredCoins){
101 | println("Please add ${requiredCoins-coins}")
102 | }
103 | }
104 | ----
105 | |===
106 |
107 |
--------------------------------------------------------------------------------
/generated/paying-turnstile-detail.plantuml:
--------------------------------------------------------------------------------
1 | @startuml
2 | skinparam monochrome true
3 | skinparam StateFontName Helvetica
4 | skinparam defaultFontName Monospaced
5 | skinparam defaultFontStyle Bold
6 | skinparam state {
7 | FontStyle Bold
8 | }
9 | state coins {
10 | COINS --> UNLOCKED : <> [coins > requiredCoins] -> {\l println("automaticPop:returnCoin")\l returnCoin(coins-requiredCoins)\l unlock()\l reset()\l}
11 | COINS --> UNLOCKED : <> [coins == requiredCoins] -> {\l println("automaticPop")\l unlock()\l reset()\l}
12 | COINS --> COINS : COIN -> {value->\l requireNotNull(value){"argument required for COIN"}\l coin(value)\l println("Coins=$coins")\l if(coins < requiredCoins){\l println("Please add ${requiredCoins-coins}")\l }\l}
13 | }
14 | state PayingTurnstileFSM {
15 | [*] --> UNLOCKED
16 | LOCKED --> COINS : COIN -> {value->\l requireNotNull(value){"argument required for COIN"}\l coin(value)\l unlock()\l reset()\l}
17 | LOCKED --> COINS : COIN [value->\l requireNotNull(value){"argument required for COIN"};\l value+coins < requiredCoins;\l] -> {value->\l requireNotNull(value){"argument required for COIN"}\l println("PUSH TRANSITION")\l coin(value)\l println("Coins=$coins, Please add ${requiredCoins-coins}")\l}
18 | UNLOCKED --> UNLOCKED : COIN -> {value->\l requireNotNull(value){"argument required for COIN"}\l returnCoin(coin(value))\l}
19 | UNLOCKED --> LOCKED : PASS -> {\l lock()\l}
20 | }
21 | note top of PayingTurnstileFSM
22 | <> {coins >= 0}
23 | end note
24 | @enduml
25 |
--------------------------------------------------------------------------------
/generated/paying-turnstile.plantuml:
--------------------------------------------------------------------------------
1 | @startuml
2 | skinparam monochrome true
3 | skinparam StateFontName Helvetica
4 | skinparam defaultFontName Monospaced
5 | skinparam defaultFontStyle Bold
6 | skinparam state {
7 | FontStyle Bold
8 | }
9 | state coins {
10 | COINS --> UNLOCKED : <> [coins > requiredCoins] -> {\l println("automaticPop:returnCoin")\l returnCoin(coins-requiredCoins)\l unlock()\l reset()\l}
11 | COINS --> UNLOCKED : <> [coins == requiredCoins] -> {\l println("automaticPop")\l unlock()\l reset()\l}
12 | COINS --> COINS : COIN -> {value->\l requireNotNull(value){"argument required for COIN"}\l coin(value)\l println("Coins=$coins")\l if(coins < requiredCoins){\l println("Please add ${requiredCoins-coins}")\l }\l}
13 | }
14 | state PayingTurnstileFSM {
15 | [*] --> UNLOCKED
16 | LOCKED --> COINS : COIN -> {value->\l requireNotNull(value){"argument required for COIN"}\l coin(value)\l unlock()\l reset()\l}
17 | LOCKED --> COINS : COIN [value->\l requireNotNull(value){"argument required for COIN"};\l value+coins < requiredCoins;\l] -> {value->\l requireNotNull(value){"argument required for COIN"}\l println("PUSH TRANSITION")\l coin(value)\l println("Coins=$coins, Please add ${requiredCoins-coins}")\l}
18 | UNLOCKED --> UNLOCKED : COIN -> {value->\l requireNotNull(value){"argument required for COIN"}\l returnCoin(coin(value))\l}
19 | UNLOCKED --> LOCKED : PASS -> {\l lock()\l}
20 | }
21 | note top of PayingTurnstileFSM
22 | <> {coins >= 0}
23 | end note
24 | @enduml
25 |
--------------------------------------------------------------------------------
/generated/secure-turnstile-detail-simple.plantuml:
--------------------------------------------------------------------------------
1 | @startuml
2 | skinparam monochrome true
3 | skinparam StateFontName Helvetica
4 | skinparam defaultFontName Monospaced
5 | skinparam defaultFontStyle Bold
6 | skinparam state {
7 | FontStyle Bold
8 | }
9 | state SecureTurnstileFSM {
10 | [*] --> LOCKED
11 | LOCKED --> LOCKED : CARD
12 | LOCKED --> LOCKED : CARD
13 | LOCKED --> UNLOCKED : CARD
14 | LOCKED --> LOCKED : CARD
15 | UNLOCKED --> LOCKED : CARD
16 | UNLOCKED --> LOCKED : PASS
17 | }
18 | @enduml
19 |
--------------------------------------------------------------------------------
/generated/secure-turnstile-detail.adoc:
--------------------------------------------------------------------------------
1 | == SecureTurnstileFSM State Chart
2 |
3 | === SecureTurnstileFSM State Map
4 |
5 | |===
6 | | Start | Event[Guard] | Target | Action
7 |
8 | | <>
9 | |
10 | | LOCKED
11 | a|
12 |
13 | | LOCKED
14 | | CARD `[{cardId->requireNotNull(cardId);isOverrideCard(cardId)&&overrideActive;}]`
15 | | LOCKED
16 | a| [source,kotlin]
17 | ----
18 | {
19 | cancelOverride()
20 | }
21 | ----
22 |
23 | | LOCKED
24 | | CARD `[{cardId->requireNotNull(cardId);isOverrideCard(cardId);}]`
25 | | LOCKED
26 | a| [source,kotlin]
27 | ----
28 | {
29 | activateOverride()
30 | }
31 | ----
32 |
33 | | LOCKED
34 | | CARD `[{cardId->requireNotNull(cardId);overrideActive\|\|isValidCard(cardId);}]`
35 | | UNLOCKED
36 | a| [source,kotlin]
37 | ----
38 | {
39 | unlock()
40 | }
41 | ----
42 |
43 | | LOCKED
44 | | CARD `[{cardId->requireNotNull(cardId){"cardId is required"};!isValidCard(cardId);}]`
45 | | LOCKED
46 | a| [source,kotlin]
47 | ----
48 | {cardId->
49 | requireNotNull(cardId)
50 | invalidCard(cardId)
51 | }
52 | ----
53 |
54 | | UNLOCKED
55 | | CARD `[{cardId->requireNotNull(cardId);isOverrideCard(cardId);}]`
56 | | LOCKED
57 | a| [source,kotlin]
58 | ----
59 | {
60 | lock()
61 | }
62 | ----
63 |
64 | | UNLOCKED
65 | | PASS
66 | | LOCKED
67 | a| [source,kotlin]
68 | ----
69 | {
70 | lock()
71 | }
72 | ----
73 | |===
74 |
75 |
--------------------------------------------------------------------------------
/generated/secure-turnstile-detail.plantuml:
--------------------------------------------------------------------------------
1 | @startuml
2 | skinparam monochrome true
3 | skinparam StateFontName Helvetica
4 | skinparam defaultFontName Monospaced
5 | skinparam defaultFontStyle Bold
6 | skinparam state {
7 | FontStyle Bold
8 | }
9 | state SecureTurnstileFSM {
10 | [*] --> LOCKED
11 | LOCKED --> LOCKED : CARD [cardId->\l requireNotNull(cardId);\l isOverrideCard(cardId)&&overrideActive;\l] -> {\l cancelOverride()\l}
12 | LOCKED --> LOCKED : CARD [cardId->\l requireNotNull(cardId);\l isOverrideCard(cardId);\l] -> {\l activateOverride()\l}
13 | LOCKED --> UNLOCKED : CARD [cardId->\l requireNotNull(cardId);\l overrideActive||isValidCard(cardId);\l] -> {\l unlock()\l}
14 | LOCKED --> LOCKED : CARD [cardId->\l requireNotNull(cardId){"cardId is required"};\l !isValidCard(cardId);\l] -> {cardId->\l requireNotNull(cardId)\l invalidCard(cardId)\l}
15 | UNLOCKED --> LOCKED : CARD [cardId->\l requireNotNull(cardId);\l isOverrideCard(cardId);\l] -> {\l lock()\l}
16 | UNLOCKED --> LOCKED : PASS -> {\l lock()\l}
17 | }
18 | @enduml
19 |
--------------------------------------------------------------------------------
/generated/secure-turnstile.plantuml:
--------------------------------------------------------------------------------
1 | @startuml
2 | skinparam monochrome true
3 | skinparam StateFontName Helvetica
4 | skinparam defaultFontName Monospaced
5 | skinparam defaultFontStyle Bold
6 | skinparam state {
7 | FontStyle Bold
8 | }
9 | state SecureTurnstileFSM {
10 | [*] --> LOCKED
11 | LOCKED --> LOCKED : CARD [cardId->\l requireNotNull(cardId);\l isOverrideCard(cardId)&&overrideActive;\l] -> {\l cancelOverride()\l}
12 | LOCKED --> LOCKED : CARD [cardId->\l requireNotNull(cardId);\l isOverrideCard(cardId);\l] -> {\l activateOverride()\l}
13 | LOCKED --> UNLOCKED : CARD [cardId->\l requireNotNull(cardId);\l overrideActive||isValidCard(cardId);\l] -> {\l unlock()\l}
14 | LOCKED --> LOCKED : CARD [cardId->\l requireNotNull(cardId){"cardId is required"};\l !isValidCard(cardId);\l] -> {cardId->\l requireNotNull(cardId)\l invalidCard(cardId)\l}
15 | UNLOCKED --> LOCKED : CARD [cardId->\l requireNotNull(cardId);\l isOverrideCard(cardId);\l] -> {\l lock()\l}
16 | UNLOCKED --> LOCKED : PASS -> {\l lock()\l}
17 | }
18 | @enduml
19 |
--------------------------------------------------------------------------------
/generated/timeout-turnstile-detail-simple.plantuml:
--------------------------------------------------------------------------------
1 | @startuml
2 | skinparam monochrome true
3 | skinparam StateFontName Helvetica
4 | skinparam defaultFontName Monospaced
5 | skinparam defaultFontStyle Bold
6 | skinparam state {
7 | FontStyle Bold
8 | }
9 | state TimerSecureTurnstileFSM {
10 | [*] --> LOCKED
11 | LOCKED --> LOCKED : CARD
12 | LOCKED --> LOCKED : CARD
13 | LOCKED --> UNLOCKED : CARD
14 | LOCKED --> LOCKED : CARD
15 | UNLOCKED --> LOCKED : <>
16 | UNLOCKED --> LOCKED : CARD
17 | UNLOCKED --> LOCKED : PASS
18 | }
19 | @enduml
20 |
--------------------------------------------------------------------------------
/generated/timeout-turnstile-detail.adoc:
--------------------------------------------------------------------------------
1 | == TimerSecureTurnstileFSM State Chart
2 |
3 | === TimerSecureTurnstileFSM State Map
4 |
5 | |===
6 | | Start | Event[Guard] | Target | Action
7 |
8 | | <>
9 | |
10 | | LOCKED
11 | a|
12 |
13 | | LOCKED
14 | | CARD `[{cardId->requireNotNull(cardId);isOverrideCard(cardId)&&overrideActive;}]`
15 | | LOCKED
16 | a| [source,kotlin]
17 | ----
18 | {
19 | cancelOverride()
20 | }
21 | ----
22 |
23 | | LOCKED
24 | | CARD `[{cardId->requireNotNull(cardId);isOverrideCard(cardId);}]`
25 | | LOCKED
26 | a| [source,kotlin]
27 | ----
28 | {
29 | activateOverride()
30 | }
31 | ----
32 |
33 | | LOCKED
34 | | CARD `[{cardId->requireNotNull(cardId);overrideActive\|\|isValidCard(cardId);}]`
35 | | UNLOCKED
36 | a| [source,kotlin]
37 | ----
38 | {
39 | unlock()
40 | }
41 | ----
42 |
43 | | LOCKED
44 | | CARD `[{cardId->requireNotNull(cardId){"cardId is required"};!isValidCard(cardId);}]`
45 | | LOCKED
46 | a| [source,kotlin]
47 | ----
48 | {cardId->
49 | requireNotNull(cardId)
50 | invalidCard(cardId)
51 | }
52 | ----
53 |
54 | | UNLOCKED
55 | | \<>
56 | | LOCKED
57 | a| [source,kotlin]
58 | ----
59 | {
60 | println("Timeout. Locking")
61 | lock()
62 | }
63 | ----
64 |
65 | | UNLOCKED
66 | | CARD `[{cardId->requireNotNull(cardId);isOverrideCard(cardId);}]`
67 | | LOCKED
68 | a| [source,kotlin]
69 | ----
70 | {
71 | lock()
72 | }
73 | ----
74 |
75 | | UNLOCKED
76 | | PASS
77 | | LOCKED
78 | a| [source,kotlin]
79 | ----
80 | {
81 | lock()
82 | }
83 | ----
84 | |===
85 |
86 |
--------------------------------------------------------------------------------
/generated/timeout-turnstile-detail.plantuml:
--------------------------------------------------------------------------------
1 | @startuml
2 | skinparam monochrome true
3 | skinparam StateFontName Helvetica
4 | skinparam defaultFontName Monospaced
5 | skinparam defaultFontStyle Bold
6 | skinparam state {
7 | FontStyle Bold
8 | }
9 | state TimerSecureTurnstileFSM {
10 | [*] --> LOCKED
11 | LOCKED --> LOCKED : CARD [cardId->\l requireNotNull(cardId);\l isOverrideCard(cardId)&&overrideActive;\l] -> {\l cancelOverride()\l}
12 | LOCKED --> LOCKED : CARD [cardId->\l requireNotNull(cardId);\l isOverrideCard(cardId);\l] -> {\l activateOverride()\l}
13 | LOCKED --> UNLOCKED : CARD [cardId->\l requireNotNull(cardId);\l overrideActive||isValidCard(cardId);\l] -> {\l unlock()\l}
14 | LOCKED --> LOCKED : CARD [cardId->\l requireNotNull(cardId){"cardId is required"};\l !isValidCard(cardId);\l] -> {cardId->\l requireNotNull(cardId)\l invalidCard(cardId)\l}
15 | UNLOCKED --> LOCKED : <> -> {\l println("Timeout. Locking")\l lock()\l}
16 | UNLOCKED --> LOCKED : CARD [cardId->\l requireNotNull(cardId);\l isOverrideCard(cardId);\l] -> {\l lock()\l}
17 | UNLOCKED --> LOCKED : PASS -> {\l lock()\l}
18 | }
19 | @enduml
20 |
--------------------------------------------------------------------------------
/generated/turnstile-detail-simple.plantuml:
--------------------------------------------------------------------------------
1 | @startuml
2 | skinparam monochrome true
3 | skinparam StateFontName Helvetica
4 | skinparam defaultFontName Monospaced
5 | skinparam defaultFontStyle Bold
6 | skinparam state {
7 | FontStyle Bold
8 | }
9 | state TurnstileFSM {
10 | [*] --> LOCKED
11 | LOCKED --> UNLOCKED : COIN
12 | UNLOCKED --> UNLOCKED : COIN
13 | UNLOCKED --> LOCKED : PASS
14 | }
15 | @enduml
16 |
--------------------------------------------------------------------------------
/generated/turnstile-detail.adoc:
--------------------------------------------------------------------------------
1 | == TurnstileFSM State Chart
2 |
3 | === TurnstileFSM State Map
4 |
5 | |===
6 | | Start | Event[Guard] | Target | Action
7 |
8 | | <>
9 | |
10 | | LOCKED
11 | a|
12 |
13 | | LOCKED
14 | | COIN
15 | | UNLOCKED
16 | a| [source,kotlin]
17 | ----
18 | {
19 | unlock()
20 | }
21 | ----
22 |
23 | | UNLOCKED
24 | | COIN
25 | | UNLOCKED
26 | a| [source,kotlin]
27 | ----
28 | {
29 | returnCoin()
30 | }
31 | ----
32 |
33 | | UNLOCKED
34 | | PASS
35 | | LOCKED
36 | a| [source,kotlin]
37 | ----
38 | {
39 | lock()
40 | }
41 | ----
42 | |===
43 |
44 |
--------------------------------------------------------------------------------
/generated/turnstile-detail.plantuml:
--------------------------------------------------------------------------------
1 | @startuml
2 | skinparam monochrome true
3 | skinparam StateFontName Helvetica
4 | skinparam defaultFontName Monospaced
5 | skinparam defaultFontStyle Bold
6 | skinparam state {
7 | FontStyle Bold
8 | }
9 | state TurnstileFSM {
10 | [*] --> LOCKED
11 | LOCKED --> UNLOCKED : COIN -> {\l unlock()\l}
12 | UNLOCKED --> UNLOCKED : COIN -> {\l returnCoin()\l}
13 | UNLOCKED --> LOCKED : PASS -> {\l lock()\l}
14 | }
15 | @enduml
16 |
--------------------------------------------------------------------------------
/generated/turnstile.plantuml:
--------------------------------------------------------------------------------
1 | @startuml
2 | skinparam monochrome true
3 | skinparam StateFontName Helvetica
4 | skinparam defaultFontName Monospaced
5 | skinparam defaultFontStyle Bold
6 | skinparam state {
7 | FontStyle Bold
8 | }
9 | state TurnstileFSM {
10 | [*] --> LOCKED
11 | LOCKED --> UNLOCKED : COIN -> {\l unlock()\l}
12 | UNLOCKED --> UNLOCKED : COIN -> {\l returnCoin()\l}
13 | UNLOCKED --> LOCKED : PASS -> {\l lock()\l}
14 | }
15 | @enduml
16 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # org.gradle.caching=true
2 | org.gradle.jvmargs=-Xmx3g
3 | # kotlin.incremental.js=false
4 | kotlin.code.style=official
5 | kotlin.js.compiler=ir
6 | buildProfile=jvm,js,default,wasm-js
7 | # defaultProfile=jvm,default
8 | vcs=https://github.com/open-jumpco/kfsm.git
9 |
10 | signing.keyId=9B78DD13
11 | skipSign=true
12 |
--------------------------------------------------------------------------------
/gradle/docs.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019-2021 Open JumpCO
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
7 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial
10 | portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
15 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16 | DEALINGS IN THE SOFTWARE.
17 | */
18 |
19 | apply plugin: 'org.jetbrains.dokka'
20 | apply plugin: 'org.asciidoctor.jvm.convert'
21 |
22 |
23 | asciidoctor {
24 | inputs.files(fileTree('src/commonTest/kotlin/*'))
25 | baseDirFollowsSourceDir()
26 | logDocuments = true
27 | asciidoctorj {
28 | fatalWarnings missingIncludes()
29 | }
30 |
31 | sources {
32 | include 'index.adoc'
33 | }
34 |
35 | resources {
36 | from(sourceDir) {
37 | include '**/*.png'
38 | include '**/*.xml'
39 | include '**/*.js'
40 | include '**/*.css'
41 | }
42 | }
43 |
44 | attributes toc: 'left',
45 | 'source-highlighter': 'prism',
46 | idprefix: '',
47 | idseparator: '-',
48 | docinfo1: ''
49 |
50 | requires ['asciidoctor-prism-extension']
51 |
52 | }
53 |
54 | task docs(type: Zip, dependsOn: [asciidoctor, dokkaHtml]) {
55 | archiveBaseName = project.name
56 | archiveClassifier = 'doc'
57 | duplicatesStrategy = DuplicatesStrategy.EXCLUDE
58 |
59 | from(file('build/docs/asciidoc'))
60 |
61 | from(file('build/dokka/html')) {
62 | into('javadoc')
63 | }
64 | }
65 |
66 | publishing {
67 | publications {
68 | documentation(MavenPublication) {
69 | artifact docs
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/gradle/platform.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019-2024 Open JumpCO
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
7 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial
10 | portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
15 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16 | DEALINGS IN THE SOFTWARE.
17 | */
18 | import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
19 |
20 | apply plugin: 'org.jetbrains.kotlin.multiplatform'
21 | def targetList = ['mingw', 'linux', 'macos', 'js', 'jvm', 'wasm-js']
22 |
23 | project.ext.useTarget = [:]
24 | def useTarget = project.ext.useTarget
25 |
26 | def arch = DefaultNativePlatform.currentArchitecture
27 | def os = DefaultNativePlatform.currentOperatingSystem
28 |
29 | def profile = ext.find('profile') ?: buildProfile ?: ''
30 | def buildTargetList = profile.tokenize(',')
31 | if (buildTargetList.contains('default')) {
32 | if (os.isWindows()) {
33 | useTarget['mingw'] = true
34 | logger.lifecycle "Detected ${os} using mingw"
35 | }
36 | if (os.isLinux()) {
37 | useTarget['linux'] = true
38 | logger.lifecycle "Detected ${org.gradle.internal.os.OperatingSystem.current()} using linux"
39 | }
40 | if (os.isMacOsX()) {
41 | useTarget['macos'] = true
42 | logger.lifecycle "Detected ${org.gradle.internal.os.OperatingSystem.current()} using macos"
43 | }
44 | }
45 |
46 | targetList.forEach { target ->
47 | if (!useTarget[target]) {
48 | useTarget[target] = buildTargetList.contains('all') || buildTargetList.contains(target)
49 | }
50 | if (buildTargetList.contains("-$target")) {
51 | useTarget[target] = false
52 | }
53 | }
54 |
55 | static def configureNative(srcSetMain, srcSetTest) {
56 | srcSetMain.kotlin.srcDirs = ['src/nativeMain/kotlin']
57 | srcSetTest.kotlin.srcDirs = ['src/nativeTest/kotlin']
58 | }
59 |
60 |
61 | logger.lifecycle(":platforms from: ${os.displayName} on ${arch.displayName}:${buildTargetList}=${useTarget}")
62 | kotlin {
63 |
64 | if (useTarget['jvm']) {
65 | jvm() {
66 | mavenPublication {
67 | artifactId = "${project.name}-jvm"
68 | }
69 | }
70 | }
71 | if (useTarget['js']) {
72 | js(IR) {
73 | mavenPublication {
74 | artifactId = "${project.name}-js"
75 | }
76 | nodejs()
77 | browser {
78 | testTask {
79 | useKarma {
80 | // useFirefox()
81 | useChromeHeadless()
82 | }
83 | }
84 | }
85 | compilations.main {
86 | kotlinOptions {
87 | metaInfo = true
88 | sourceMap = true
89 | verbose = true
90 | moduleKind = "umd"
91 | }
92 | }
93 | }
94 | }
95 | if (useTarget['mingw']) {
96 | mingwX64 {
97 | mavenPublication {
98 | artifactId = "${project.name}-mingw64"
99 | }
100 | }
101 | }
102 | if (useTarget['linux']) {
103 | if (arch.arm64) {
104 | linuxArm64() {
105 | mavenPublication {
106 | artifactId = "${project.name}-linuxarm64"
107 | }
108 | }
109 | } else if (arch.amd64) {
110 | linuxX64() {
111 | mavenPublication {
112 | artifactId = "${project.name}-linuxx64"
113 | }
114 | }
115 | } else {
116 | logger.error("Cannot use linux on:${arch.name}:${arch.displayName}")
117 | }
118 | }
119 | if (useTarget['macos']) {
120 | iosArm64() {
121 | mavenPublication {
122 | artifactId = "${project.name}-iosarm64"
123 | }
124 | }
125 | macosArm64() {
126 | mavenPublication {
127 | artifactId = "${project.name}-macosarm64"
128 | }
129 | }
130 | watchosArm64() {
131 | mavenPublication {
132 | artifactId = "${project.name}-watcharm64"
133 | }
134 | }
135 | tvosArm64() {
136 | mavenPublication {
137 | artifactId = "${project.name}-tvosarm64"
138 | }
139 | }
140 | }
141 | if (useTarget['wasm-js']) {
142 | wasmJs {
143 | mavenPublication {
144 | artifactId = "${project.name}-wasm-js"
145 | }
146 | browser {
147 | testTask {
148 | useKarma {
149 | // useFirefox()
150 | useChromeHeadless()
151 | }
152 | }
153 | }
154 | }
155 | }
156 | sourceSets {
157 | commonMain {
158 | dependencies {
159 | implementation kotlin('stdlib')
160 | }
161 | }
162 | commonTest {
163 | dependencies {
164 | implementation kotlin('test')
165 | implementation kotlin('test-annotations-common')
166 | }
167 | }
168 | if (useTarget['linux']) {
169 | if (arch.arm64) {
170 | configureNative(linuxArm64Main, linuxArm64Test)
171 | } else if (arch.amd64) {
172 | configureNative(linuxX64Main, linuxX64Test)
173 | }
174 | }
175 | if (useTarget['mingw']) {
176 | configureNative(mingwX64Main, mingwX64Test)
177 | }
178 | if (useTarget['macos']) {
179 | configureNative(macosArm64Main, macosArm64Test)
180 | configureNative(macosX64Main, macosX64Test)
181 | }
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/gradle/publish.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019-2021 Open JumpCO
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
7 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial
10 | portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
15 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16 | DEALINGS IN THE SOFTWARE.
17 | */
18 |
19 | apply plugin: 'signing'
20 |
21 | def useTarget = project.ext.useTarget
22 |
23 | task javadocJar(type: Jar) {
24 | archivesBaseName = 'kfsm'
25 | archiveClassifier = 'javadoc'
26 | from dokkaHtml.outputDirectory
27 | }
28 |
29 |
30 | gradle.afterProject {
31 | tasks.forEach { task ->
32 | if (task.name.contains('dokka')) {
33 | javadocJar.dependsOn(task)
34 | }
35 | if (task.name.contains('Publication')) {
36 | publish.dependsOn(task)
37 | }
38 | }
39 | }
40 |
41 |
42 | artifacts {
43 | archives javadocJar
44 | }
45 |
46 | def pomConfig = {
47 | licenses {
48 | license {
49 | name 'Apache License, Version 2.0'
50 | url 'http://www.apache.org/licenses/LICENSE-2.0'
51 | distribution 'repo'
52 | }
53 | }
54 | developers {
55 | developer {
56 | id 'corneil_jumpco'
57 | name 'Corneil @ JumpCO'
58 | organization 'Open JumpCO'
59 | organizationUrl 'https://open.jumpco.io'
60 | }
61 | }
62 |
63 | scm {
64 | url project.vcs
65 | }
66 | }
67 |
68 | def configureMavenCentralMetadata = { pom ->
69 | def root = asNode()
70 | root.appendNode('name', project.name)
71 | root.appendNode('description', project.description)
72 | root.appendNode('url', project.vcs)
73 | root.children().last() + pomConfig
74 | }
75 |
76 | publishing {
77 | repositories {
78 | maven {
79 | logger.lifecycle("maven:publish:version=$version")
80 | def snapshotsRepoUrl = 'https://oss.sonatype.org/content/repositories/snapshots'
81 | def releasesRepoUrl = 'https://oss.sonatype.org/service/local/staging/deploy/maven2'
82 | url = project.version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
83 | credentials {
84 | username rootProject.findProperty('ossJiraUser') ?: System.getenv('OSS_JIRA_USER')
85 | password rootProject.findProperty('ossJiraPwd') ?: System.getenv('OSS_JIRA_PWD')
86 | }
87 | }
88 | }
89 | def platforms = ['js', 'jvm', 'linux', 'mingw', 'macos', 'wasm-js']
90 | publications.all { publication ->
91 | pom.withXml(configureMavenCentralMetadata)
92 | logger.info "publication:$publication.name"
93 | if (platforms.contains(publication.name)) {
94 | publication.artifact javadocJar
95 | }
96 | }
97 | }
98 |
99 | def skipSign = hasProperty('skipSign') ?: 'false'
100 | if (!skipSign.asBoolean()) {
101 | signing {
102 | sign publishing.publications.kotlinMultiplatform
103 | // sign publishing.publications.metadata
104 | sign publishing.publications.documentation
105 | publishing.publications.forEach {
106 | if (it.name != publishing.publications.kotlinMultiplatform.name && it.name != publishing.publications.documentation.name) {
107 | logger.info("sign:$it.name")
108 | sign it
109 | }
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-jumpco/kfsm/25c0f744ad21bfcedb51b8a4946b3d724489aec9/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/kfsm-fsm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-jumpco/kfsm/25c0f744ad21bfcedb51b8a4946b3d724489aec9/kfsm-fsm.png
--------------------------------------------------------------------------------
/packet_reader.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-jumpco/kfsm/25c0f744ad21bfcedb51b8a4946b3d724489aec9/packet_reader.png
--------------------------------------------------------------------------------
/paying_turnstile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-jumpco/kfsm/25c0f744ad21bfcedb51b8a4946b3d724489aec9/paying_turnstile.png
--------------------------------------------------------------------------------
/publish-linux.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | ./gradlew -i --continue publishLinuxPublicationToMavenRepository -x dokkaJavadoc -x dokkaGfm -x dokkaJekyll
3 |
--------------------------------------------------------------------------------
/publish.cmd:
--------------------------------------------------------------------------------
1 | @echo off
2 | gradlew -S -i build publishAllPublicationsToMavenRepository publishDocumentationPublicationToMavenRepository -x dokkaJavadoc -x dokkaGfm -x dokkaJekyll
3 |
--------------------------------------------------------------------------------
/publish.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | ./gradlew -S -i build publishAllPublicationsToMavenRepository publishDocumentationPublicationToMavenRepository -x dokkaJavadoc -x dokkaGfm -x dokkaJekyll
3 |
--------------------------------------------------------------------------------
/qodana.cmd:
--------------------------------------------------------------------------------
1 | docker run --rm -it -p 8080:8080 -v src/:/data/project/ -v build/qodana/:/data/results/ jetbrains/qodana-jvm --show-report
2 |
--------------------------------------------------------------------------------
/secure_turnstile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-jumpco/kfsm/25c0f744ad21bfcedb51b8a4946b3d724489aec9/secure_turnstile.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | mavenLocal()
4 | maven { url = 'https://maven.pkg.jetbrains.space/kotlin/p/wasm/experimental' }
5 | gradlePluginPortal()
6 | mavenCentral()
7 | }
8 | }
9 |
10 | //plugins {
11 | // id "com.gradle.enterprise" version "3.16.2"
12 | //}
13 | //
14 | //gradleEnterprise {
15 | //}
16 |
17 | rootProject.name = 'kfsm'
18 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/jumpco/open/kfsm/DefaultSyncTransition.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019-2021 Open JumpCO
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
7 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial
10 | portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
15 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16 | DEALINGS IN THE SOFTWARE.
17 | */
18 |
19 | package io.jumpco.open.kfsm
20 |
21 | /**
22 | * @author Corneil du Plessis
23 | * @soundtrack Wolfgang Amadeus Mozart
24 | * @suppress
25 | * Represents a DefaultTransition
26 | * @param event The event identifies the transition
27 | * @param targetState when optional represents an internal transition
28 | * @param action optional lambda will be invoked when transition occurs.
29 | */
30 | class DefaultSyncTransition(
31 | internal val event: E,
32 | targetState: S?,
33 | targetMap: String?,
34 | automatic: Boolean,
35 | type: TransitionType,
36 | action: SyncStateAction?
37 | ) : SyncTransition(targetState, targetMap, automatic, type, action)
38 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/jumpco/open/kfsm/DslStateMachineHandler.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019-2021 Open JumpCO
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
7 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial
10 | portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
15 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16 | DEALINGS IN THE SOFTWARE.
17 | */
18 |
19 | package io.jumpco.open.kfsm
20 |
21 | /**
22 | * This handler will be active inside the top level of the stateMachine definition.
23 | * @author Corneil du Plessis
24 | * @soundtrack Wolfgang Amadeus Mozart
25 | */
26 | class DslStateMachineHandler(private val fsm: StateMachineBuilder) {
27 | /**
28 | * Defines an expression that will determine the initial state of the state machine based on the values of the context.
29 | * @param deriveInitialState A lambda expression receiving context:C and returning state S.
30 | */
31 | fun initialState(deriveInitialState: StateQuery): DslStateMachineHandler {
32 | fsm.initialState(deriveInitialState)
33 | return this
34 | }
35 |
36 | var defaultInitialState: S?
37 | get() = fsm.defaultInitialState
38 | set(state) {
39 | fsm.defaultInitialState = state
40 | }
41 |
42 | /**
43 | * Provides for a list of pairs with state and map name that will be pushed and the last entry will be popped and become the current map.
44 | * This is required when using state machine with named maps.
45 | *
46 |
47 | ```
48 | initialStates {
49 | mutableListOf>().apply {
50 | if (locked) {
51 | this.add(PayingTurnstileStates.LOCKED to "default")
52 | } else {
53 | this.add(PayingTurnstileStates.UNLOCKED to "default")
54 | }
55 | if (coins > 0) {
56 | this.add(PayingTurnstileStates.COINS to "coins")
57 | }
58 | }
59 | }
60 | ```
61 |
62 | */
63 | fun initialStates(deriveInitialMap: StateMapQuery): DslStateMachineHandler {
64 | fsm.initialStates(deriveInitialMap)
65 | return this
66 | }
67 |
68 | fun invariant(message: String, condition: Condition): DslStateMachineHandler {
69 | fsm.invariant(message, condition)
70 | return this
71 | }
72 |
73 | /**
74 | * Defines an action that will be invoked after a transition to a new state.
75 | * Any exceptions thrown by the action will be ignored.
76 | */
77 | fun onStateChange(action: StateChangeAction) {
78 | fsm.afterStateChange(action)
79 | }
80 |
81 | /**
82 | * Defines a section for a specific state.
83 | * @param currentState The give state
84 | * @param handler A lambda with definitions for the given state
85 | */
86 | fun whenState(currentState: S, handler: DslStateMapEventHandler.() -> Unit):
87 | DslStateMapEventHandler =
88 | DslStateMapEventHandler(currentState, fsm.defaultStateMap).apply(handler)
89 |
90 | /**
91 | * Defines a section for default behaviour for the state machine.
92 | * @param handler A lambda with definition for the default behaviour of the state machine.
93 | */
94 | fun default(handler: DslStateMapDefaultEventHandler.() -> Unit):
95 | DslStateMapDefaultEventHandler =
96 | DslStateMapDefaultEventHandler(fsm.defaultStateMap).apply(handler)
97 |
98 | /**
99 | * Returns the completed fsm.
100 | */
101 | fun build() = fsm.complete()
102 |
103 | /**
104 | * creates a named statemap
105 | */
106 | fun stateMap(
107 | /**
108 | * The name of the state map. ,
115 | /**
116 | * The lambda to configure the statemap
117 | */
118 | handler: DslStateMapHandler.() -> Unit
119 | ): DslStateMapHandler {
120 | return DslStateMapHandler(fsm.stateMap(name.trim(), validStates)).apply(handler)
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/jumpco/open/kfsm/DslStateMapDefaultEventHandler.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019-2021 Open JumpCO
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
7 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial
10 | portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
15 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16 | DEALINGS IN THE SOFTWARE.
17 | */
18 |
19 | package io.jumpco.open.kfsm
20 |
21 | /**
22 | * This handler will be active inside the default section of the statemachine.
23 | * @author Corneil du Plessis
24 | * @soundtrack Wolfgang Amadeus Mozart
25 | */
26 | class DslStateMapDefaultEventHandler(private val fsm: StateMapBuilder) {
27 | /**
28 | * Define a default action that will be applied when no other transitions are matched.
29 | * @param action Will be invoked when no transitions match
30 | */
31 | fun action(action: DefaultStateAction) {
32 | fsm.defaultAction(action)
33 | }
34 |
35 | /**
36 | * Defines an action to perform before a change in the currentState of the FSM
37 | * @param action This action will be performed when entering a new state.
38 | */
39 | fun onEntry(action: DefaultEntryExitAction) {
40 | fsm.defaultEntry(action)
41 | }
42 |
43 | /**
44 | * Defines an action to be performed after the currentState was changed.
45 | * @param action The action will be performed when leaving any state.
46 | */
47 | fun onExit(action: DefaultEntryExitAction) {
48 | fsm.defaultExit(action)
49 | }
50 |
51 | /**
52 | * Defines a default transition when an on is received to a specific state.
53 | * @param event Pair representing an on and targetState for transition. Can be written as EVENT to STATE
54 | * @param action The action will be performed before transition is completed
55 | */
56 | fun onEvent(
57 | event: EventState,
58 | action: SyncStateAction?
59 | ): DslStateMapDefaultEventHandler {
60 | fsm.default(event, action)
61 | return this
62 | }
63 |
64 | /**
65 | * Defines a default internal transition for a specific event with no change in state.
66 | * @param event The event that triggers this transition
67 | * @param action The action will be invoked for this transition
68 | */
69 | fun onEvent(event: E, action: SyncStateAction?): DslStateMapDefaultEventHandler {
70 | fsm.default(event, action)
71 | return this
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/jumpco/open/kfsm/DslStateMapHandler.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019-2021 Open JumpCO
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
7 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial
10 | portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
15 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16 | DEALINGS IN THE SOFTWARE.
17 | */
18 |
19 | package io.jumpco.open.kfsm
20 |
21 | /**
22 | * This handler will be active inside the top level of the stateMachine definition.
23 | * @author Corneil du Plessis
24 | * @soundtrack Wolfgang Amadeus Mozart
25 | */
26 | class DslStateMapHandler(private val fsm: StateMapBuilder) {
27 |
28 | /**
29 | * Defines a section for a specific state.
30 | * @param currentState The give state
31 | * @param handler A lambda with definitions for the given state
32 | */
33 | fun whenState(currentState: S, handler: DslStateMapEventHandler.() -> Unit):
34 | DslStateMapEventHandler =
35 | DslStateMapEventHandler(currentState, fsm).apply(handler)
36 |
37 | /**
38 | * Defines a section for default behaviour for the state machine.
39 | * @param handler A lambda with definition for the default behaviour of the state machine.
40 | */
41 | fun default(handler: DslStateMapDefaultEventHandler.() -> Unit):
42 | DslStateMapDefaultEventHandler =
43 | DslStateMapDefaultEventHandler(fsm).apply(handler)
44 | }
45 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/jumpco/open/kfsm/SimpleSyncTransition.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019-2021 Open JumpCO
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
7 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial
10 | portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
15 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16 | DEALINGS IN THE SOFTWARE.
17 | */
18 |
19 | package io.jumpco.open.kfsm
20 |
21 | /**
22 | * @author Corneil du Plessis
23 | * @soundtrack Wolfgang Amadeus Mozart
24 | * @suppress
25 | * Represents a transition from a given state and event.
26 | * @param startState The given state
27 | * @param event The given event
28 | * @param targetState when optional represents an internal transition
29 | * @param action An optional lambda that will be invoked.
30 | */
31 | open class SimpleSyncTransition(
32 | internal val startState: S,
33 | internal val event: E?,
34 | targetState: S?,
35 | targetMap: String?,
36 | automatic: Boolean,
37 | type: TransitionType,
38 | action: SyncStateAction?
39 | ) : SyncTransition(targetState, targetMap, automatic, type, action)
40 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/jumpco/open/kfsm/StateMachineDefinition.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019-2024 Open JumpCO
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
7 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial
10 | portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
15 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16 | DEALINGS IN THE SOFTWARE.
17 | */
18 |
19 | package io.jumpco.open.kfsm
20 |
21 | /**
22 | * This class represents an immutable definition of a state machine.
23 | * @author Corneil du Plessis
24 | * @soundtrack Wolfgang Amadeus Mozart
25 | */
26 | class StateMachineDefinition(
27 | val defaultInitialState: S?,
28 | private val deriveInitialState: StateQuery?,
29 | private val deriveInitialMap: StateMapQuery?,
30 | /**
31 | * The top level state map will be created when the state machine is created.
32 | */
33 | val defaultStateMap: StateMapDefinition,
34 | /**
35 | * The named state maps can be accessed via a push transition.
36 | */
37 | val namedStateMaps: Map>
38 | ) {
39 | private fun createMap(
40 | mapName: String,
41 | context: C,
42 | parentFsm: StateMachineInstance,
43 | initial: S
44 | ) {
45 | if (mapName == "default") {
46 | parentFsm.pushMap(StateMapInstance(context, initial, null, parentFsm, defaultStateMap))
47 | } else {
48 | val stateMap = namedStateMaps[mapName] ?: error("Invalid map $mapName")
49 | parentFsm.pushMap(StateMapInstance(context, initial, mapName, parentFsm, stateMap))
50 | }
51 | }
52 |
53 | /**
54 | * This function will create a state machine instance provided with content and optional initialState.
55 | * @param context The context will be provided to actions
56 | * @param initialState If this is not provided the function defined in `initial` will be invoked to derive the initialState.
57 | * @see StateMachineBuilder.initialState
58 | */
59 | internal fun create(
60 | context: C,
61 | parentFsm: StateMachineInstance,
62 | initialState: S? = null,
63 | intitialExternalState: ExternalState? = null
64 | ): StateMapInstance {
65 | return when {
66 | intitialExternalState != null -> {
67 | intitialExternalState.forEach { (initial, mapName) ->
68 | createMap(mapName, context, parentFsm, initial)
69 | }
70 | parentFsm.mapStack.pop()
71 | }
72 |
73 | deriveInitialMap != null -> {
74 | deriveInitialMap.invoke(context).forEach { (initial, mapName) ->
75 | createMap(mapName, context, parentFsm, initial)
76 | }
77 | parentFsm.mapStack.pop()
78 | }
79 |
80 | else -> {
81 | val initial = initialState ?: deriveInitialState?.invoke(context) ?: defaultInitialState
82 | ?: error("Definition requires deriveInitialState or deriveInitialMap")
83 | StateMapInstance(context, initial, null, parentFsm, defaultStateMap)
84 | }
85 | }
86 | }
87 |
88 | internal fun createStateMap(
89 | name: String,
90 | context: C,
91 | parentFsm: StateMachineInstance,
92 | initialState: S
93 | ): StateMapInstance =
94 | StateMapInstance(
95 | context,
96 | initialState,
97 | name,
98 | parentFsm,
99 | namedStateMaps[name] ?: error("Named map $name not found")
100 | )
101 |
102 | /**
103 | * This function will create a state machine instance and set it to the state to a previously externalised state.
104 | * @param context The instance will operate on the provided context
105 | * @param initialExternalState The previously externalised state
106 | */
107 | fun create(context: C, initialExternalState: ExternalState): StateMachineInstance =
108 | StateMachineInstance(context, this, null, initialExternalState)
109 |
110 | /**
111 | * This function will create a state machine instance and set it to the initial state.
112 | * @param context The instance will operate on the provided context
113 | * @param initial The initial state
114 | *
115 | */
116 | fun create(context: C, initial: S? = null): StateMachineInstance =
117 | StateMachineInstance(context, this, initial)
118 |
119 | /**
120 | * This function will provide a list of possible events given a specific state.
121 | * The actual events may fail because of guard conditions or named state maps and the default state map behaviour being different.
122 | * @param state The given state
123 | * @param includeDefault consider the default state and event handlers
124 | */
125 | fun possibleEvents(state: S, includeDefault: Boolean): Set {
126 | val result = mutableSetOf()
127 | result.addAll(defaultStateMap.allowed(state, includeDefault))
128 | namedStateMaps.values.forEach {
129 | if (it.validStates.contains(state)) {
130 | result.addAll(it.allowed(state, includeDefault))
131 | }
132 | }
133 | return result.toSet()
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/jumpco/open/kfsm/StateMapDefinition.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019-2021 Open JumpCO
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
7 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial
10 | portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
15 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16 | DEALINGS IN THE SOFTWARE.
17 | */
18 |
19 | package io.jumpco.open.kfsm
20 |
21 | /**
22 | * Contains the definition of a state map. A state machine has at least one top-level state map.
23 | * @author Corneil du Plessis
24 | * @soundtrack Wolfgang Amadeus Mozart
25 | */
26 | class StateMapDefinition(
27 | /**
28 | * The name of the statemap. The top-level state map name is `null`
29 | */
30 | val name: String?,
31 | /**
32 | * A set of the valid states for this map.
33 | */
34 | val validStates: Set,
35 | /**
36 | * Invariant conditions are checked before and after every transition an will throw an InvariantException if false
37 | */
38 | val invariants: Set>>,
39 | /**
40 | * transitionRule contains a map of TransitionRules that is keyed by a Pair of state,event
41 | * This will be the most common transition rule.
42 | */
43 | val transitionRules: Map, SyncTransitionRules>,
44 | /**
45 | * The default transitions will be used if no transition of found matching a given event
46 | */
47 | val defaultTransitions: Map>,
48 | /**
49 | * This is a map of actions keyed by the state. A specific action will be invoked when a state is entered.
50 | */
51 | val entryActions: Map>,
52 | /**
53 | * This is a map of actions keyed by the state. A specific action will be invoked when a state is exited.
54 | */
55 | val exitActions: Map>,
56 | /**
57 | * This is a map of default actions for event on specific startState.
58 | */
59 | val defaultActions: Map>,
60 | /**
61 | * This a map of TransitionRules by state for automatic transitions.
62 | */
63 | val automaticTransitions: Map>,
64 | /**
65 | * This is the action that will be invoked of no other has been matched
66 | */
67 | val globalDefault: DefaultStateAction?,
68 | /**
69 | * This is the default action that will be invoked when entering any state when no other action has been matched.
70 | */
71 | val defaultEntryAction: DefaultEntryExitAction?,
72 | /**
73 | * This is the default action that will be invoked when exiting any state when no other action has been matched.
74 | */
75 | val defaultExitAction: DefaultEntryExitAction?,
76 | /**
77 | * This action will be invoked after a change in the state of the statemachine.
78 | * This machine will catch and ignore any exceptions thrown by the handler.
79 | */
80 | val afterStateChangeAction: StateChangeAction?
81 | ) {
82 | /**
83 | * This function will provide the set of allowed events given a specific state. It isn't a guarantee that a
84 | * subsequent transition will be successful since a guard may prevent a transition. Default state handlers are not considered.
85 | * @param given The specific state to consider
86 | * @param includeDefault When `true` will include default transitions in the list of allowed events.
87 | */
88 | fun allowed(given: S, includeDefault: Boolean = false): Set {
89 | val result = transitionRules.entries.filter {
90 | it.key.first == given
91 | }.map {
92 | it.key.second
93 | }.toSet()
94 | if (includeDefault && defaultTransitions.isNotEmpty()) {
95 | return result + defaultTransitions.keys
96 | }
97 | return result
98 | }
99 |
100 | /**
101 | * This function will provide an indicator if an event is allow for a given state.
102 | * When no state transition is declared this function will return false unless `includeDefault` is true and
103 | * there is a default transition of handler for the event.
104 | */
105 | fun eventAllowed(event: E, given: S, includeDefault: Boolean): Boolean =
106 | (includeDefault && hasDefaultStateHandler(given)) || allowed(given, includeDefault).contains(event)
107 |
108 | /**
109 | * This function will provide an indicator if a default action has been defined for a given state.
110 | */
111 | private fun hasDefaultStateHandler(given: S) = defaultActions.contains(given)
112 | }
113 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/jumpco/open/kfsm/SyncGuardedTransition.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019-2021 Open JumpCO
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
7 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial
10 | portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
15 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16 | DEALINGS IN THE SOFTWARE.
17 | */
18 |
19 | package io.jumpco.open.kfsm
20 |
21 | /**
22 | * @suppress
23 | * Represents a guarded transition. The transition will be considered if the guard expression is true
24 | * @author Corneil du Plessis
25 | * @soundtrack Wolfgang Amadeus Mozart
26 | * @param startState The given state
27 | * @param event The given event
28 | * @param targetState when optional represents an internal transition
29 | * @param guard Expression lambda returning a Boolean
30 | * @param action An optional lambda that will be invoked.
31 | */
32 | open class SyncGuardedTransition(
33 | startState: S,
34 | event: E?,
35 | targetState: S?,
36 | targetMap: String?,
37 | automatic: Boolean,
38 | type: TransitionType,
39 | val guard: StateGuard,
40 | action: SyncStateAction?
41 | ) : SimpleSyncTransition(startState, event, targetState, targetMap, automatic, type, action) {
42 | /**
43 | * This function will invoke the guard expression using the provided context to determine if transition can be considered.
44 | * @param context The provided context
45 | * @return result of guard lambda
46 | */
47 | fun guardMet(context: C, arg: A?): Boolean = guard.invoke(context, arg)
48 | }
49 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/jumpco/open/kfsm/SyncTransition.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019-2021 Open JumpCO
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
7 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial
10 | portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
15 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16 | DEALINGS IN THE SOFTWARE.
17 | */
18 |
19 | package io.jumpco.open.kfsm
20 |
21 | /**
22 | * @suppress
23 | * The base for all transitions.
24 | * @author Corneil du Plessis
25 | * @soundtrack Wolfgang Amadeus Mozart
26 | * @param targetState when optional represents an internal transition
27 | * @param action optional lambda will be invoked when transition occurs.
28 | */
29 | open class SyncTransition(
30 | val targetState: S? = null,
31 | val targetMap: String? = null,
32 | val automatic: Boolean = false,
33 | val type: TransitionType = TransitionType.NORMAL,
34 | val action: SyncStateAction? = null
35 | ) {
36 | init {
37 | if (type == TransitionType.PUSH) {
38 | require(targetState != null) { "targetState is required for push transition" }
39 | }
40 | }
41 |
42 | /**
43 | * Executed exit, optional and entry actions specific in the transition.
44 | */
45 | open fun execute(context: C, instance: StateMapInstance, arg: A?): R? {
46 |
47 | if (isExternal()) {
48 | instance.executeExit(context, targetState!!, arg)
49 | }
50 | val result = action?.invoke(context, arg)
51 | if (isExternal()) {
52 | instance.executeEntry(context, targetState!!, arg)
53 | }
54 | return result
55 | }
56 |
57 | /**
58 | * Executed exit, optional and entry actions specific in the transition.
59 | */
60 | open fun execute(
61 | context: C,
62 | sourceMap: StateMapInstance,
63 | targetMap: StateMapInstance?,
64 | arg: A?
65 | ): R? {
66 |
67 | if (isExternal()) {
68 | sourceMap.executeExit(context, targetState!!, arg)
69 | }
70 | val result = action?.invoke(context, arg)
71 | if (targetMap != null && isExternal()) {
72 | targetMap.executeEntry(context, targetState!!, arg)
73 | }
74 | return result
75 | }
76 |
77 | /**
78 | * This function provides an indicator if a Transition is internal or external.
79 | * When there is no targetState defined a Transition is considered internal and will not trigger entry or exit actions.
80 | */
81 | fun isExternal(): Boolean = targetState != null
82 | }
83 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/jumpco/open/kfsm/SyncTransitionRules.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019-2021 Open JumpCO
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
7 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial
10 | portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
15 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16 | DEALINGS IN THE SOFTWARE.
17 | */
18 |
19 | package io.jumpco.open.kfsm
20 |
21 | /**
22 | * @suppress
23 | * Represents a collection of rule with 1 transition and a list of guarded transitions.
24 | * @author Corneil du Plessis
25 | * @soundtrack Wolfgang Amadeus Mozart
26 | * @param guardedTransitions The list of guarded transitions
27 | * @param transition The transition to use if there are no guarded transitions or no guarded transitions match.
28 | */
29 | class SyncTransitionRules(
30 | val guardedTransitions: MutableList> = mutableListOf(),
31 | transition: SimpleSyncTransition? = null
32 | ) {
33 | var transition: SimpleSyncTransition? = transition
34 | internal set
35 |
36 | /**
37 | * Add a guarded transition to the end of the list
38 | */
39 | fun addGuarded(guardedTransition: SyncGuardedTransition) {
40 | guardedTransitions.add(guardedTransition)
41 | }
42 |
43 | /**
44 | * Find the first entry in the list of guarded transitions that match/
45 | * @param context The given context.
46 | */
47 | fun findGuard(context: C, arg: A? = null): SyncGuardedTransition? =
48 | guardedTransitions.firstOrNull { it.guardMet(context, arg) }
49 | }
50 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/jumpco/open/kfsm/async/AsyncDslStateMachineHandler.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019-2021 Open JumpCO
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
7 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial
10 | portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
15 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16 | DEALINGS IN THE SOFTWARE.
17 | */
18 |
19 | package io.jumpco.open.kfsm.async
20 |
21 | import io.jumpco.open.kfsm.AsyncStateChangeAction
22 | import io.jumpco.open.kfsm.StateMapQuery
23 | import io.jumpco.open.kfsm.StateQuery
24 |
25 | class AsyncDslStateMachineHandler(private val fsm: AsyncStateMachineBuilder) {
26 | /**
27 | * Defines an expression that will determine the initial state of the state machine based on the values of the context.
28 | * @param deriveInitialState A lambda expression receiving context:C and returning state S.
29 | */
30 | fun initialState(deriveInitialState: StateQuery): AsyncDslStateMachineHandler {
31 | fsm.initialState(deriveInitialState)
32 | return this
33 | }
34 |
35 | var defaultInitialState: S?
36 | get() = fsm.defaultInitialState
37 | set(state) {
38 | fsm.defaultInitialState = state
39 | }
40 |
41 | /**
42 | * Provides for a list of pairs with state and map name that will be pushed and the last entry will be popped and become the current map.
43 | * This is required when using state machine with named maps.
44 | *
45 | ```
46 | initialStates {
47 | mutableListOf>().apply {
48 | if (locked) {
49 | this.add(PayingTurnstileStates.LOCKED to "default")
50 | } else {
51 | this.add(PayingTurnstileStates.UNLOCKED to "default")
52 | }
53 | if (coins > 0) {
54 | this.add(PayingTurnstileStates.COINS to "coins")
55 | }
56 | }
57 | }
58 | ```
59 | */
60 | fun initialStates(deriveInitialMap: StateMapQuery): AsyncDslStateMachineHandler {
61 | fsm.initialStates(deriveInitialMap)
62 | return this
63 | }
64 |
65 | /**
66 | * Defines an action that will be invoked after a transition to a new state.
67 | * Any exceptions thrown by the action will be ignored.
68 | */
69 | fun onStateChange(action: AsyncStateChangeAction) {
70 | fsm.afterStateChange(action)
71 | }
72 |
73 | /**
74 | * Defines a section for a specific state.
75 | * @param currentState The give state
76 | * @param handler A lambda with definitions for the given state
77 | */
78 | fun whenState(currentState: S, handler: AsyncDslStateMapEventHandler.() -> Unit):
79 | AsyncDslStateMapEventHandler =
80 | AsyncDslStateMapEventHandler(currentState, fsm.defaultStateMap).apply(handler)
81 |
82 | /**
83 | * Defines a section for default behaviour for the state machine.
84 | * @param handler A lambda with definition for the default behaviour of the state machine.
85 | */
86 | fun default(handler: AsyncDslStateMapDefaultEventHandler.() -> Unit):
87 | AsyncDslStateMapDefaultEventHandler =
88 | AsyncDslStateMapDefaultEventHandler(fsm.defaultStateMap).apply(handler)
89 |
90 | /**
91 | * Returns the completed fsm.
92 | */
93 | fun build() = fsm.complete()
94 |
95 | /**
96 | * creates a named statemap
97 | */
98 | fun stateMap(
99 | /**
100 | * The name of the state map. ,
107 | /**
108 | * The lambda to configure the statemap
109 | */
110 | handler: AsyncDslStateMapHandler.() -> Unit
111 | ): AsyncDslStateMapHandler {
112 | return AsyncDslStateMapHandler(
113 | fsm.stateMap(
114 | name.trim(),
115 | validStates
116 | )
117 | ).apply(handler)
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/jumpco/open/kfsm/async/AsyncDslStateMapDefaultEventHandler.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019-2021 Open JumpCO
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
7 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial
10 | portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
15 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16 | DEALINGS IN THE SOFTWARE.
17 | */
18 |
19 | package io.jumpco.open.kfsm.async
20 |
21 | import io.jumpco.open.kfsm.AsyncStateAction
22 | import io.jumpco.open.kfsm.DefaultAsyncStateAction
23 | import io.jumpco.open.kfsm.DefaultEntryExitAction
24 | import io.jumpco.open.kfsm.EventState
25 |
26 | class AsyncDslStateMapDefaultEventHandler(private val fsm: AsyncStateMapBuilder) {
27 | /**
28 | * Define a default action that will be applied when no other transitions are matched.
29 | * @param action Will be invoked when no transitions matches
30 | */
31 | fun action(action: DefaultAsyncStateAction) {
32 | fsm.defaultAction(action)
33 | }
34 |
35 | /**
36 | * Defines an action to perform before a change in the currentState of the FSM
37 | * @param action This action will be performed when entering a new state.
38 | */
39 | fun onEntry(action: DefaultEntryExitAction) {
40 | fsm.defaultEntry(action)
41 | }
42 |
43 | /**
44 | * Defines an action to be performed after the currentState was changed.
45 | * @param action The action will be performed when leaving any state.
46 | */
47 | fun onExit(action: DefaultEntryExitAction) {
48 | fsm.defaultExit(action)
49 | }
50 |
51 | /**
52 | * Defines a default transition when an on is received to a specific state.
53 | * @param event Pair representing an on and targetState for transition. Can be written as EVENT to STATE
54 | * @param action The action will be performed before transition is completed
55 | */
56 | fun onEvent(
57 | event: EventState,
58 | action: AsyncStateAction?
59 | ): AsyncDslStateMapDefaultEventHandler {
60 | fsm.default(event, action)
61 | return this
62 | }
63 |
64 | /**
65 | * Defines a default internal transition for a specific event with no change in state.
66 | * @param event The event that triggers this transition
67 | * @param action The action will be invoked for this transition
68 | */
69 | fun onEvent(event: E, action: AsyncStateAction?): AsyncDslStateMapDefaultEventHandler {
70 | fsm.default(event, action)
71 | return this
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/jumpco/open/kfsm/async/AsyncDslStateMapHandler.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019-2021 Open JumpCO
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
7 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial
10 | portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
15 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16 | DEALINGS IN THE SOFTWARE.
17 | */
18 |
19 | package io.jumpco.open.kfsm.async
20 |
21 | /**
22 | * This handler will be active inside the top level of the stateMachine definition.
23 | */
24 | class AsyncDslStateMapHandler(private val fsm: AsyncStateMapBuilder) {
25 |
26 | /**
27 | * Defines a section for a specific state.
28 | * @param currentState The give state
29 | * @param handler A lambda with definitions for the given state
30 | */
31 | fun whenState(currentState: S, handler: AsyncDslStateMapEventHandler.() -> Unit):
32 | AsyncDslStateMapEventHandler =
33 | AsyncDslStateMapEventHandler(currentState, fsm).apply(handler)
34 |
35 | /**
36 | * Defines a section for default behaviour for the state machine.
37 | * @param handler A lambda with definition for the default behaviour of the state machine.
38 | */
39 | fun default(handler: AsyncDslStateMapDefaultEventHandler.() -> Unit):
40 | AsyncDslStateMapDefaultEventHandler =
41 | AsyncDslStateMapDefaultEventHandler(fsm).apply(handler)
42 | }
43 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/jumpco/open/kfsm/async/AsyncGuardedTransition.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019-2021 Open JumpCO
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
7 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial
10 | portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
15 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16 | DEALINGS IN THE SOFTWARE.
17 | */
18 |
19 | package io.jumpco.open.kfsm.async
20 |
21 | import io.jumpco.open.kfsm.AsyncStateAction
22 | import io.jumpco.open.kfsm.StateGuard
23 | import io.jumpco.open.kfsm.TransitionType
24 |
25 | open class AsyncGuardedTransition(
26 | startState: S,
27 | event: E?,
28 | targetState: S?,
29 | targetMap: String?,
30 | automatic: Boolean,
31 | type: TransitionType,
32 | val guard: StateGuard,
33 | action: AsyncStateAction?
34 | ) : SimpleAsyncTransition(startState, event, targetState, targetMap, automatic, type, action) {
35 | /**
36 | * This function will invoke the guard expression using the provided context to determine if transition can be considered.
37 | * @param context The provided context
38 | * @return result of guard lambda
39 | */
40 | fun guardMet(context: C, arg: A?): Boolean = guard.invoke(context, arg)
41 | }
42 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/jumpco/open/kfsm/async/AsyncStateMapDefinition.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019-2021 Open JumpCO
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
7 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial
10 | portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
15 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16 | DEALINGS IN THE SOFTWARE.
17 | */
18 |
19 | package io.jumpco.open.kfsm.async
20 |
21 | import io.jumpco.open.kfsm.AsyncStateChangeAction
22 | import io.jumpco.open.kfsm.Condition
23 | import io.jumpco.open.kfsm.DefaultAsyncStateAction
24 | import io.jumpco.open.kfsm.DefaultEntryExitAction
25 |
26 | class AsyncStateMapDefinition(
27 | /**
28 | * The name of the statemap. The top-level state map name is `null`
29 | */
30 | val name: String?,
31 | /**
32 | * A set of the valid states for this map.
33 | */
34 | val validStates: Set,
35 | /**
36 | * Invariant conditions are checked before and after every transition and will throw an InvariantException if false
37 | */
38 | val invariants: Set>>,
39 | /**
40 | * transitionRule contains a map of TransitionRules that is keyed by a Pair of state,event
41 | * This will be the most common transition rule.
42 | */
43 | val transitionRules: Map, AsyncTransitionRules>,
44 | /**
45 | * The default transitions will be used if no transition of found matching a given event
46 | */
47 | val defaultTransitions: Map>,
48 | /**
49 | * This is a map of actions keyed by the state. A specific action will be invoked when a state is entered.
50 | */
51 | val entryActions: Map>,
52 | /**
53 | * This is a map of actions keyed by the state. A specific action will be invoked when a state is exited.
54 | */
55 | val exitActions: Map>,
56 | /**
57 | * This is a map of default actions for event on specific startState.
58 | */
59 | val defaultActions: Map>,
60 | /**
61 | * This a map of TransitionRules by state for automatic transitions.
62 | */
63 | val automaticTransitions: Map>,
64 | /**
65 | * The timer definitions will be activated on entry to state and deactivated on state exit
66 | */
67 | val timerDefinitions: Map>,
68 | /**
69 | * This is the action that will be invoked of no other has been matched
70 | */
71 | val globalDefault: DefaultAsyncStateAction?,
72 | /**
73 | * This is the default action that will be invoked when entering any state when no other action has been matched.
74 | */
75 | val defaultEntryAction: DefaultEntryExitAction?,
76 | /**
77 | * This is the default action that will be invoked when exiting any state when no other action has been matched.
78 | */
79 | val defaultExitAction: DefaultEntryExitAction?,
80 | /**
81 | * This action will be invoked after a change in the state of the statemachine.
82 | * This machine will catch and ignore any exceptions thrown by the handler.
83 | */
84 | val afterStateChangeAction: AsyncStateChangeAction?
85 | ) {
86 | /**
87 | * This function will provide the set of allowed events given a specific state. It isn't a guarantee that a
88 | * subsequent transition will be successful since a guard may prevent a transition. Default state handlers are not considered.
89 | * @param given The specific state to consider
90 | * @param includeDefault When `true` will include default transitions in the list of allowed events.
91 | */
92 | fun allowed(given: S, includeDefault: Boolean = false): Set {
93 | val result = transitionRules.entries.filter {
94 | it.key.first == given
95 | }.map {
96 | it.key.second
97 | }.toSet()
98 | if (includeDefault && defaultTransitions.isNotEmpty()) {
99 | return result + defaultTransitions.keys
100 | }
101 | return result
102 | }
103 |
104 | /**
105 | * This function will provide an indicator if an event is allowed for a given state.
106 | * When no state transition is declared this function will return false unless `includeDefault` is true and
107 | * there is a default transition of handler for the event.
108 | */
109 | fun eventAllowed(event: E, given: S, includeDefault: Boolean): Boolean =
110 | (includeDefault &&
111 | hasDefaultStateHandler(given)) ||
112 | allowed(given, includeDefault).contains(event)
113 |
114 | /**
115 | * This function will provide an indicator if a default action has been defined for a given state.
116 | */
117 | private fun hasDefaultStateHandler(given: S) = defaultActions.contains(given)
118 | }
119 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/jumpco/open/kfsm/async/AsyncTimer.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019-2024 Open JumpCO
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
7 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial
10 | portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
15 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16 | DEALINGS IN THE SOFTWARE.
17 | */
18 |
19 | package io.jumpco.open.kfsm.async
20 |
21 | import kotlinx.coroutines.CoroutineScope
22 | import kotlinx.coroutines.Job
23 | import kotlinx.coroutines.async
24 | import kotlinx.coroutines.delay
25 | import kotlinx.atomicfu.*
26 | class AsyncTimer constructor(
27 | private val parentFsm: AsyncStateMapInstance,
28 | val context: C,
29 | val arg: A?,
30 | val definition: AsyncTimerDefinition,
31 | coroutineScope: CoroutineScope
32 | ) {
33 | private val active = atomic(false)
34 | private val timer: Job
35 |
36 | init {
37 | active.value = true
38 | timer = coroutineScope.async {
39 | delay(context.(definition.timeout)())
40 | trigger()
41 | }
42 | }
43 |
44 | fun cancel() {
45 | active.value = false
46 | }
47 |
48 | suspend fun trigger() {
49 | if (active.value) {
50 | val defaultTransition = definition.rule.transition
51 | definition.rule.findGuard(context, arg)?.apply {
52 | parentFsm.execute(this, arg)
53 | } ?: run {
54 | if (defaultTransition != null) {
55 | parentFsm.execute(defaultTransition, arg)
56 | }
57 | }
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/jumpco/open/kfsm/async/AsyncTimerDefinition.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019-2021 Open JumpCO
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
7 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial
10 | portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
15 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16 | DEALINGS IN THE SOFTWARE.
17 | */
18 |
19 | package io.jumpco.open.kfsm.async
20 |
21 | class AsyncTimerDefinition(
22 | val timeout: C.() -> Long,
23 | val rule: AsyncTransitionRules
24 | )
25 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/jumpco/open/kfsm/async/AsyncTransition.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019-2021 Open JumpCO
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
7 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial
10 | portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
15 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16 | DEALINGS IN THE SOFTWARE.
17 | */
18 |
19 | package io.jumpco.open.kfsm.async
20 |
21 | import io.jumpco.open.kfsm.AsyncStateAction
22 | import io.jumpco.open.kfsm.TransitionType
23 |
24 | open class AsyncTransition(
25 | val targetState: S? = null,
26 | val targetMap: String? = null,
27 | val automatic: Boolean = false,
28 | val type: TransitionType = TransitionType.NORMAL,
29 | val action: AsyncStateAction? = null
30 | ) {
31 | init {
32 | if (type == TransitionType.PUSH) {
33 | require(targetState != null) { "targetState is required for push transition" }
34 | }
35 | }
36 |
37 | /**
38 | * Executed exit, optional and entry actions specific in the transition.
39 | */
40 | open suspend fun execute(context: C, instance: AsyncStateMapInstance, arg: A?): R? {
41 |
42 | if (isExternal()) {
43 | instance.executeExit(context, targetState!!, arg)
44 | }
45 | val result = action?.invoke(context, arg)
46 | if (isExternal()) {
47 | instance.executeEntry(context, targetState!!, arg)
48 | }
49 | return result
50 | }
51 |
52 | /**
53 | * Executed exit, optional and entry actions specific in the transition.
54 | */
55 | open suspend fun execute(
56 | context: C,
57 | sourceMap: AsyncStateMapInstance,
58 | targetMap: AsyncStateMapInstance?,
59 | arg: A?
60 | ): R? {
61 | if (isExternal()) {
62 | sourceMap.executeExit(context, targetState!!, arg)
63 | }
64 | val result = action?.invoke(context, arg)
65 | if (targetMap != null && isExternal()) {
66 | targetMap.executeEntry(context, targetState!!, arg)
67 | }
68 | return result
69 | }
70 |
71 | /**
72 | * This function provides an indicator if a Transition is internal or external.
73 | * When there is no targetState defined a Transition is considered internal and will not trigger entry or exit actions.
74 | */
75 | fun isExternal(): Boolean = targetState != null
76 | }
77 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/jumpco/open/kfsm/async/AsyncTransitionRules.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019-2021 Open JumpCO
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
7 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial
10 | portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
15 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16 | DEALINGS IN THE SOFTWARE.
17 | */
18 |
19 | package io.jumpco.open.kfsm.async
20 |
21 | class AsyncTransitionRules(
22 | val guardedTransitions: MutableList> = mutableListOf(),
23 | transition: SimpleAsyncTransition? = null
24 | ) {
25 | var transition: SimpleAsyncTransition? = transition
26 | internal set
27 |
28 | /**
29 | * Add a guarded transition to the end of the list
30 | */
31 | fun addGuarded(guardedTransition: AsyncGuardedTransition) {
32 | guardedTransitions.add(guardedTransition)
33 | }
34 |
35 | /**
36 | * Find the first entry in the list of guarded transitions that match/
37 | * @param context The given context.
38 | */
39 | fun findGuard(context: C, arg: A? = null): AsyncGuardedTransition? =
40 | guardedTransitions.firstOrNull { it.guardMet(context, arg) }
41 | }
42 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/jumpco/open/kfsm/async/DefaultAsyncTransition.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019-2021 Open JumpCO
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
7 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial
10 | portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
15 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16 | DEALINGS IN THE SOFTWARE.
17 | */
18 |
19 | package io.jumpco.open.kfsm.async
20 |
21 | import io.jumpco.open.kfsm.AsyncStateAction
22 | import io.jumpco.open.kfsm.TransitionType
23 |
24 | class DefaultAsyncTransition(
25 | internal val event: E,
26 | targetState: S?,
27 | targetMap: String?,
28 | automatic: Boolean,
29 | type: TransitionType,
30 | action: AsyncStateAction?
31 | ) : AsyncTransition(targetState, targetMap, automatic, type, action)
32 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/jumpco/open/kfsm/async/SimpleAsyncTransition.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019-2021 Open JumpCO
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
7 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial
10 | portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
15 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16 | DEALINGS IN THE SOFTWARE.
17 | */
18 |
19 | package io.jumpco.open.kfsm.async
20 |
21 | import io.jumpco.open.kfsm.AsyncStateAction
22 | import io.jumpco.open.kfsm.TransitionType
23 |
24 | open class SimpleAsyncTransition(
25 | internal val startState: S,
26 | internal val event: E?,
27 | targetState: S?,
28 | targetMap: String?,
29 | automatic: Boolean,
30 | type: TransitionType,
31 | action: AsyncStateAction?
32 | ) : AsyncTransition(targetState, targetMap, automatic, type, action)
33 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/jumpco/open/kfsm/async/asyncStateMachines.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019-2024 Open JumpCO
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
7 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial
10 | portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
15 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16 | DEALINGS IN THE SOFTWARE.
17 | */
18 |
19 | package io.jumpco.open.kfsm.async
20 |
21 | import kotlin.reflect.KClass
22 |
23 | /**
24 | * These function are used to create state machines where the actions are suspend functions.
25 | */
26 | inline fun asyncStateMachine(
27 | validStates: Set,
28 | validEvents: Set,
29 | contextClass: KClass,
30 | argumentClass: KClass,
31 | returnClass: KClass,
32 | handler: AsyncDslStateMachineHandler.() -> Unit
33 | ) = AsyncStateMachineBuilder(validStates, validEvents).stateMachine(handler)
34 |
35 | /**
36 | * Defines the start of a state machine DSL declaration with `Any` as the return type
37 | * @param validStates A set of the possible states supported by the top-level state map
38 | * @param validEvents The class of the possible events*
39 | * @param contextClass The class of the context
40 | * @param argumentClass The class of the argument to events/actions
41 | * @param handler The state machine handler
42 | * @sample io.jumpco.open.kfsm.example.TurnstileFSM.Companion.definition
43 | */
44 | inline fun asyncStateMachine(
45 | validStates: Set,
46 | validEvents: Set,
47 | contextClass: KClass,
48 | argumentClass: KClass,
49 | handler: AsyncDslStateMachineHandler.() -> Unit
50 | ) = asyncStateMachine(validStates, validEvents, contextClass, argumentClass, Any::class, handler)
51 |
52 | /**
53 | * Defines the start of a state machine DSL declaration with `Any` as the type of arguments and returns types for events/actions
54 | * @param validStates A set of the possible states supported by the top-level state map
55 | * @param validEvents The class of the possible events
56 | * @param contextClass The class of the context
57 | * @sample io.jumpco.open.kfsm.example.TurnstileFSM.Companion.definition
58 | */
59 | inline fun asyncStateMachine(
60 | validStates: Set,
61 | validEvents: Set,
62 | contextClass: KClass,
63 | handler: AsyncDslStateMachineHandler.() -> Unit
64 | ) = asyncStateMachine(validStates, validEvents, contextClass, Any::class, Any::class, handler)
65 |
66 | inline fun asyncFunctionalStateMachine(
67 | validStates: Set,
68 | validEvents: Set,
69 | contextClass: KClass,
70 | handler: AsyncDslStateMachineHandler.() -> Unit
71 | ) = AsyncStateMachineBuilder(validStates, validEvents).stateMachine(handler)
72 |
73 | /**
74 | * An extension function that evaluates the expression and invokes the provided `block` if true or the `otherwise` block is false.
75 | */
76 | inline fun T.ifApply(expression: Boolean, block: T.() -> Unit, otherwise: T.() -> Unit): T {
77 | return if (expression) {
78 | this.apply(block)
79 | } else {
80 | this.apply(otherwise)
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/jumpco/open/kfsm/stateMachine.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019-2024 Open JumpCO
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
7 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial
10 | portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
15 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16 | DEALINGS IN THE SOFTWARE.
17 | */
18 |
19 | package io.jumpco.open.kfsm
20 |
21 | import kotlin.reflect.KClass
22 |
23 | /**
24 | * Defines the start of a state machine DSL declaration
25 | * @author Corneil du Plessis
26 | * @soundtrack Wolfgang Amadeus Mozart
27 | * @param validStates A set of the possible states supported by the top-level state map
28 | * @param validEvents The class of the possible events
29 | * @param contextClass The class of the context
30 | * @param argumentClass The class of the argument to events/actions
31 | * @param returnClass The class of the return type of events/actions
32 | * @param handler Statemachine handler
33 | * @sample io.jumpco.open.kfsm.example.TurnstileFSM.Companion.definition
34 | */
35 | inline fun stateMachine(
36 | validStates: Set,
37 | validEvents: Set,
38 | contextClass: KClass,
39 | argumentClass: KClass,
40 | returnClass: KClass,
41 | handler: DslStateMachineHandler.() -> Unit
42 | ) = StateMachineBuilder(validStates, validEvents).stateMachine(handler)
43 |
44 | /**
45 | * Defines the start of a state machine DSL declaration with `Any` as the return type
46 | * @param validStates A set of the possible states supported by the top-level state map
47 | * @param eventClass The class of the possible events
48 | * @param contextClass The class of the context
49 | * @param argumentClass The class of the argument to events/actions
50 | * @sample io.jumpco.open.kfsm.example.TurnstileFSM.Companion.definition
51 | */
52 | inline fun stateMachine(
53 | validStates: Set,
54 | validEvents: Set,
55 | contextClass: KClass,
56 | argumentClass: KClass,
57 | handler: DslStateMachineHandler.() -> Unit
58 | ) = stateMachine(
59 | validStates,
60 | validEvents,
61 | contextClass,
62 | argumentClass,
63 | Any::class,
64 | handler
65 | )
66 |
67 | /**
68 | * Defines the start of a state machine DSL declaration with `Any` as the type of arguments and returns types for events/actions
69 | * @param validStates A set of the possible states supported by the top-level state map
70 | * @param validEvents The class of the possible events
71 | * @param contextClass The class of the context
72 | * @param handler The DSL handler
73 | * @sample io.jumpco.open.kfsm.example.TurnstileFSM.definition
74 | */
75 | inline fun stateMachine(
76 | validStates: Set,
77 | validEvents: Set,
78 | contextClass: KClass,
79 | handler: DslStateMachineHandler.() -> Unit
80 | ) = stateMachine(
81 | validStates,
82 | validEvents,
83 | contextClass,
84 | Any::class,
85 | Any::class,
86 | handler
87 | )
88 |
89 | inline fun functionalStateMachine(
90 | validStates: Set,
91 | validEvents: Set,
92 | contextClass: KClass,
93 | handler: DslStateMachineHandler.() -> Unit
94 | ) = StateMachineBuilder(validStates, validEvents).stateMachine(handler)
95 |
--------------------------------------------------------------------------------
/src/commonTest/kotlin/io/jumpco/open/kfsm/example/CarFSMTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019-2021 Open JumpCO
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
7 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial
10 | portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
15 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16 | DEALINGS IN THE SOFTWARE.
17 | */
18 |
19 | package io.jumpco.open.kfsm.example
20 |
21 | import io.jumpco.open.kfsm.functionalStateMachine
22 | import kotlin.test.Test
23 |
24 | /*
25 | * Copyright (c) 2020-2021. Open JumpCO
26 | * Licensed under the Apache License, Version 2.0 (the "License");
27 | * you may not use this file except in compliance with the License.
28 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
29 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
30 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
31 | * See the License for the specific language governing permissions and limitations under the License.
32 | */
33 |
34 | /**
35 | * This example was inspired by http://stephen-walsh.com/?p=264
36 | */
37 | sealed class CarState(val identifier: String) {
38 | override fun toString(): String {
39 | return identifier
40 | }
41 | }
42 |
43 | object Driving : CarState("Driving")
44 | object Parked : CarState("Parked")
45 | object Crashed : CarState("Crashed")
46 | object BeingRepaired : CarState("BeingRepaired")
47 |
48 | sealed class CarEntity(val carType: String, val carModel: String, val yearOfManufacture: String) {
49 | override fun toString(): String {
50 | return "CarEntity(carType='$carType', carModel='$carModel', yearOfManufacture='$yearOfManufacture')"
51 | }
52 | }
53 |
54 | object Uninitialised : CarEntity("", "", "")
55 | object VWGolf : CarEntity("VW", "GOLF", "2016")
56 |
57 | data class Car(val carEntity: CarEntity, val state: CarState? = null)
58 |
59 | class CarFSM(val car: Car) {
60 | val fsm = definition.create(car)
61 | fun sendEvent(event: CarState): Car {
62 | val result = fsm.sendEvent(event, car) ?: car
63 | return result.copy(state = fsm.currentState)
64 | }
65 |
66 | companion object {
67 | private val definition = functionalStateMachine(
68 | setOf(Driving, Parked, Crashed, BeingRepaired),
69 | setOf(Driving, Parked, Crashed, BeingRepaired),
70 | Car::class
71 | ) {
72 | initialState { state ?: Parked }
73 | onStateChange { oldState, newState ->
74 | println("onStateChange:$oldState -> $newState")
75 | }
76 | default {
77 | onEntry { fromState, toState, _ ->
78 | println("onEntry:$fromState -> $toState")
79 | }
80 | action { state, event, _ ->
81 | println("We cannot go from $state, to state $event (data = $this)")
82 | this
83 | }
84 | }
85 | whenState(Parked) {
86 | onEvent(Driving to Driving) {
87 | this
88 | }
89 | onEvent(BeingRepaired to BeingRepaired) {
90 | this
91 | }
92 | }
93 | whenState(Driving) {
94 | onEvent(Parked to Parked) {
95 | this
96 | }
97 | onEvent(Crashed to Crashed) {
98 | println("We just crashed!!!")
99 | this
100 | }
101 | }
102 | whenState(Crashed) {
103 | onEvent(BeingRepaired to BeingRepaired) {
104 | this
105 | }
106 | }
107 | }.build()
108 | }
109 | }
110 |
111 | operator fun Car.plus(event: CarState): Car {
112 | val fsm = CarFSM(this)
113 | return fsm.sendEvent(event)
114 | }
115 |
116 | class CarFSMTest {
117 | @Test
118 | fun testCarFSM() {
119 | var vwGolf = Car(VWGolf)
120 | vwGolf += Driving
121 | vwGolf += Parked
122 | vwGolf += Crashed
123 | vwGolf += Driving
124 | vwGolf += Crashed
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/commonTest/kotlin/io/jumpco/open/kfsm/example/ImmutableLockFSM.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019-2024 Open JumpCO
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
7 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial
10 | portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
15 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16 | DEALINGS IN THE SOFTWARE.
17 | */
18 |
19 | package io.jumpco.open.kfsm.example
20 |
21 | import io.jumpco.open.kfsm.functionalStateMachine
22 | import kotlin.test.Test
23 | import kotlin.test.assertEquals
24 |
25 | /**
26 | * @suppress
27 | */
28 | // tag::context[]
29 | data class ImmutableLock(val locked: Int = 1) {
30 |
31 | fun lock(): ImmutableLock {
32 | require(locked == 0)
33 | println("Lock")
34 | return copy(locked = locked + 1)
35 | }
36 |
37 | fun doubleLock(): ImmutableLock {
38 | require(locked == 1)
39 | println("DoubleLock")
40 | return copy(locked = locked + 1)
41 | }
42 |
43 | fun unlock(): ImmutableLock {
44 | require(locked == 1)
45 | println("Unlock")
46 | return copy(locked = locked - 1)
47 | }
48 |
49 | fun doubleUnlock(): ImmutableLock {
50 | require(locked == 2)
51 | println("DoubleUnlock")
52 | return copy(locked = locked - 1)
53 | }
54 |
55 | override fun toString(): String {
56 | return "Lock(locked=$locked)"
57 | }
58 | }
59 | // end::context[]
60 |
61 | // tag::definition[]
62 | class ImmutableLockFSM {
63 |
64 | companion object {
65 | fun handleEvent(context: ImmutableLock, event: LockEvents): ImmutableLock {
66 | val fsm = definition.create(context)
67 | return fsm.sendEvent(event, context) ?: error("Expected context not null")
68 | }
69 |
70 | private val definition = functionalStateMachine(
71 | LockStates.entries.toSet(),
72 | LockEvents.entries.toSet(),
73 | ImmutableLock::class
74 | ) {
75 | defaultInitialState = LockStates.LOCKED
76 | initialState {
77 | when (locked) {
78 | 0 -> LockStates.UNLOCKED
79 | 1 -> LockStates.LOCKED
80 | 2 -> LockStates.DOUBLE_LOCKED
81 | else -> error("Invalid state locked=$locked")
82 | }
83 | }
84 | invariant("invalid locked value") { locked in 0..2 }
85 | onStateChange { oldState, newState ->
86 | println("onStateChange:$oldState -> $newState")
87 | }
88 | default {
89 | action { state, event, _ ->
90 | println("Default action for state($state) -> on($event) for $this")
91 | this
92 | }
93 | onEntry { startState, targetState, _ ->
94 | println("entering:$startState -> $targetState for $this")
95 | }
96 | onExit { startState, targetState, _ ->
97 | println("exiting:$startState -> $targetState for $this")
98 | }
99 | }
100 | whenState(LockStates.LOCKED) {
101 | onEvent(LockEvents.LOCK to LockStates.DOUBLE_LOCKED) {
102 | doubleLock()
103 | }
104 | onEvent(LockEvents.UNLOCK to LockStates.UNLOCKED) {
105 | unlock()
106 | }
107 | }
108 | whenState(LockStates.DOUBLE_LOCKED) {
109 | onEvent(LockEvents.UNLOCK to LockStates.LOCKED) {
110 | doubleUnlock()
111 | }
112 | }
113 | whenState(LockStates.UNLOCKED) {
114 | onEvent(LockEvents.LOCK to LockStates.LOCKED) {
115 | lock()
116 | }
117 | }
118 | }.build()
119 | }
120 | }
121 |
122 | operator fun ImmutableLock.plus(event: LockEvents): ImmutableLock {
123 | return ImmutableLockFSM.handleEvent(this, event)
124 | }
125 | // end::definition[]
126 |
127 | class TestFunctionalLock {
128 | @Test
129 | fun testState() {
130 | // tag::test[]
131 | val lock = ImmutableLock(0)
132 | val locked = lock + LockEvents.LOCK
133 | assertEquals(locked.locked, 1)
134 | val doubleLocked = locked + LockEvents.LOCK
135 | assertEquals(doubleLocked.locked, 2)
136 | // end::test[]
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/src/commonTest/kotlin/io/jumpco/open/kfsm/example/KeyboardBuffer.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019-2024 Open JumpCO
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
7 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial
10 | portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
15 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16 | DEALINGS IN THE SOFTWARE.
17 | */
18 |
19 | package io.jumpco.open.kfsm.example
20 |
21 | import io.jumpco.open.kfsm.stateMachine
22 | import kotlin.test.Test
23 | import kotlin.test.assertEquals
24 |
25 | /*
26 | * Copyright (c) 2020-2021. Open JumpCO
27 | * Licensed under the Apache License, Version 2.0 (the "License");
28 | * you may not use this file except in compliance with the License.
29 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
30 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
31 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
32 | * See the License for the specific language governing permissions and limitations under the License.
33 | */
34 |
35 | enum class KeyboardBufferStates {
36 | DEFAULT,
37 | CAPS_LOCKED
38 | }
39 |
40 | enum class KeyboardEvent {
41 | CAPS_LOCK,
42 | ANY_KEY
43 | }
44 |
45 | class KeyboardBuffer {
46 | private val buffer: MutableList = mutableListOf()
47 | fun add(ch: Char) {
48 | buffer.add(ch)
49 | }
50 |
51 | fun addUpperCase(ch: Char) {
52 | buffer.add(ch.uppercaseChar())
53 | }
54 |
55 | fun read(): Char = buffer.removeAt(0)
56 | }
57 |
58 | class KeyboardBufferFSM(context: KeyboardBuffer) {
59 | companion object {
60 | val definition = stateMachine(
61 | KeyboardBufferStates.entries.toSet(),
62 | KeyboardEvent.entries.toSet(),
63 | KeyboardBuffer::class,
64 | Char::class
65 | ) {
66 | initialState { KeyboardBufferStates.DEFAULT }
67 | onStateChange { oldState, newState ->
68 | println("onStateChange:$oldState -> $newState")
69 | }
70 | whenState(KeyboardBufferStates.DEFAULT) {
71 | onEvent(KeyboardEvent.ANY_KEY) { input ->
72 | requireNotNull(input) { "input is required" }
73 | add(input)
74 | }
75 | onEvent(KeyboardEvent.CAPS_LOCK to KeyboardBufferStates.CAPS_LOCKED) {}
76 | }
77 | whenState(KeyboardBufferStates.CAPS_LOCKED) {
78 | onEvent(KeyboardEvent.ANY_KEY) { input ->
79 | requireNotNull(input) { "input is required" }
80 | addUpperCase(input)
81 | }
82 | onEvent(KeyboardEvent.CAPS_LOCK to KeyboardBufferStates.DEFAULT) {}
83 | }
84 | }.build()
85 | }
86 |
87 | private val fsm = definition.create(context)
88 |
89 | fun capsLock() = fsm.sendEvent(KeyboardEvent.CAPS_LOCK)
90 | fun anyKey(ch: Char) = fsm.sendEvent(KeyboardEvent.ANY_KEY, ch)
91 | }
92 |
93 | class KeyboardBufferTest {
94 | @Test
95 | fun testFSM() {
96 | val buffer = KeyboardBuffer()
97 | val fsm = KeyboardBufferFSM(buffer)
98 | fsm.anyKey('A')
99 | assertEquals('A', buffer.read())
100 | fsm.anyKey('a')
101 | assertEquals('a', buffer.read())
102 | fsm.capsLock()
103 | fsm.anyKey('B')
104 | assertEquals('B', buffer.read())
105 | fsm.anyKey('b')
106 | assertEquals('B', buffer.read())
107 | fsm.capsLock()
108 | fsm.anyKey('a')
109 | assertEquals('a', buffer.read())
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/commonTest/kotlin/io/jumpco/open/kfsm/example/LockFsmTests.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2020-2024. Open JumpCO
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
6 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
7 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
8 | * See the License for the specific language governing permissions and limitations under the License.
9 | */
10 | package io.jumpco.open.kfsm.example
11 |
12 | import io.jumpco.open.kfsm.AnyStateMachineBuilder
13 | import io.jumpco.open.kfsm.AnyStateMachineInstance
14 | import io.jumpco.open.kfsm.example.LockEvents.LOCK
15 | import io.jumpco.open.kfsm.example.LockEvents.UNLOCK
16 | import io.jumpco.open.kfsm.example.LockStates.DOUBLE_LOCKED
17 | import io.jumpco.open.kfsm.example.LockStates.LOCKED
18 | import io.jumpco.open.kfsm.example.LockStates.UNLOCKED
19 | import io.jumpco.open.kfsm.stateMachine
20 | import kotlin.test.Test
21 | import kotlin.test.assertEquals
22 | import kotlin.test.assertTrue
23 | import kotlin.test.fail
24 |
25 | /**
26 | * @suppress
27 | */
28 | class LockFsmTests {
29 |
30 | private fun verifyLockFSM(fsm: AnyStateMachineInstance, lock: Lock) {
31 | // then
32 | assertTrue { fsm.currentState == LOCKED }
33 | assertTrue { lock.locked == 1 }
34 | // when
35 | fsm.sendEvent(UNLOCK)
36 | // then
37 | assertTrue { fsm.currentState == UNLOCKED }
38 | assertTrue { lock.locked == 0 }
39 | try {
40 | // when
41 | fsm.sendEvent(UNLOCK)
42 | fail("Expected an exception")
43 | } catch (x: Throwable) {
44 | println("Expected:$x")
45 | // then
46 | assertEquals("Already unlocked", x.message)
47 | }
48 | // when
49 | fsm.sendEvent(LOCK)
50 | // then
51 | assertTrue { fsm.currentState == LOCKED }
52 | assertTrue { lock.locked == 1 }
53 | // when
54 | fsm.sendEvent(LOCK)
55 | // then
56 | assertTrue { fsm.currentState == DOUBLE_LOCKED }
57 | assertTrue { lock.locked == 2 }
58 | try {
59 | // when
60 | fsm.sendEvent(LOCK)
61 | fail("Expected an exception")
62 | } catch (x: Throwable) {
63 | println("Expected:$x")
64 | // then
65 | assertEquals("Already double locked", x.message)
66 | }
67 | assertTrue { lock.locked == 2 }
68 | }
69 |
70 | @Test
71 | fun testPlainCreationOfFsm() {
72 | // given
73 | val builder = AnyStateMachineBuilder(
74 | LockStates.entries.toSet(),
75 | LockEvents.entries.toSet()
76 | )
77 | builder.initialState {
78 | when (locked) {
79 | 0 -> UNLOCKED
80 | 1 -> LOCKED
81 | 2 -> DOUBLE_LOCKED
82 | else -> error("Invalid state locked=$locked")
83 | }
84 | }
85 | builder.transition(LOCKED, UNLOCK, UNLOCKED) {
86 | unlock()
87 | }
88 | builder.transition(LOCKED, LOCK, DOUBLE_LOCKED) {
89 | doubleLock()
90 | }
91 | builder.transition(DOUBLE_LOCKED, UNLOCK, LOCKED) {
92 | doubleUnlock()
93 | }
94 | builder.transition(DOUBLE_LOCKED, LOCK) {
95 | error("Already double locked")
96 | }
97 | builder.transition(UNLOCKED, LOCK, LOCKED) {
98 | lock()
99 | }
100 | builder.transition(UNLOCKED, UNLOCK) {
101 | error("Already unlocked")
102 | }
103 | val definition = builder.complete()
104 | // when
105 | val lock = Lock()
106 | val fsm = definition.create(lock)
107 | // then
108 | verifyLockFSM(fsm, lock)
109 | }
110 |
111 | @Test
112 | fun testDslCreationOfFsm() {
113 | // given
114 | val definition = stateMachine(
115 | LockStates.entries.toSet(),
116 | LockEvents.entries.toSet(),
117 | Lock::class
118 | ) {
119 | initialState {
120 | when (locked) {
121 | 0 -> UNLOCKED
122 | 1 -> LOCKED
123 | 2 -> DOUBLE_LOCKED
124 | else -> error("Invalid state locked=$locked")
125 | }
126 | }
127 |
128 | whenState(LOCKED) {
129 | onEvent(LOCK to DOUBLE_LOCKED) {
130 | doubleLock()
131 | }
132 | onEvent(UNLOCK to UNLOCKED) {
133 | unlock()
134 | }
135 | }
136 | whenState(DOUBLE_LOCKED) {
137 | onEvent(UNLOCK to LOCKED) {
138 | doubleUnlock()
139 | }
140 | onEvent(LOCK) {
141 | error("Already double locked")
142 | }
143 | }
144 | whenState(UNLOCKED) {
145 | onEvent(LOCK to LOCKED) {
146 | lock()
147 | }
148 | onEvent(UNLOCK) {
149 | error("Already unlocked")
150 | }
151 | }
152 | }.build()
153 | // when
154 | val lock = Lock()
155 | val fsm = definition.create(lock)
156 | // then
157 | verifyLockFSM(fsm, lock)
158 | }
159 |
160 | @Test
161 | fun simpleLockTest() {
162 | val lock = Lock(0)
163 | val fsm = LockFSM(lock)
164 | println("--lock1")
165 | fsm.lock()
166 | println("--lock2")
167 | fsm.lock()
168 | println("--lock3")
169 | fsm.lock()
170 | println("--unlock1")
171 | fsm.unlock()
172 | println("--unlock2")
173 | fsm.unlock()
174 | println("--unlock3")
175 | fsm.unlock()
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/src/commonTest/kotlin/io/jumpco/open/kfsm/example/LockTypes.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019-2024 Open JumpCO
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
7 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial
10 | portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
15 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16 | DEALINGS IN THE SOFTWARE.
17 | */
18 |
19 | package io.jumpco.open.kfsm.example
20 |
21 | import io.jumpco.open.kfsm.stateMachine
22 |
23 | /**
24 | * @suppress
25 | */
26 | class Lock(initial: Int = 1) {
27 | var locked: Int = initial
28 | private set
29 |
30 | fun lock() {
31 | require(locked == 0)
32 | println("Lock")
33 | locked += 1
34 | }
35 |
36 | fun doubleLock() {
37 | require(locked == 1)
38 | println("DoubleLock")
39 | locked += 1
40 | }
41 |
42 | fun unlock() {
43 | require(locked == 1)
44 | println("Unlock")
45 | locked -= 1
46 | }
47 |
48 | fun doubleUnlock() {
49 | require(locked == 2)
50 | println("DoubleUnlock")
51 | locked -= 1
52 | }
53 |
54 | override fun toString(): String {
55 | return "Lock(locked=$locked)"
56 | }
57 | }
58 |
59 | enum class LockStates {
60 | LOCKED,
61 | DOUBLE_LOCKED,
62 | UNLOCKED
63 | }
64 |
65 | enum class LockEvents {
66 | LOCK,
67 | UNLOCK
68 | }
69 |
70 | class LockFSM(context: Lock) {
71 | private val fsm = definition.create(context)
72 |
73 | fun allowedEvents() = fsm.allowed()
74 | fun unlock() = fsm.sendEvent(LockEvents.UNLOCK)
75 | fun lock() = fsm.sendEvent(LockEvents.LOCK)
76 |
77 | companion object {
78 | private val definition = stateMachine(
79 | LockStates.entries.toSet(),
80 | LockEvents.entries.toSet(),
81 | Lock::class
82 | ) {
83 | defaultInitialState = LockStates.LOCKED
84 | initialState {
85 | when (locked) {
86 | 0 -> LockStates.UNLOCKED
87 | 1 -> LockStates.LOCKED
88 | 2 -> LockStates.DOUBLE_LOCKED
89 | else -> error("Invalid state locked=$locked")
90 | }
91 | }
92 | invariant("invalid locked value") { locked in 0..2 }
93 | onStateChange { oldState, newState ->
94 | println("onStateChange:$oldState -> $newState")
95 | }
96 | default {
97 | action { state, event, _ ->
98 | println("Default action for state($state) -> on($event) for $this")
99 | }
100 | onEntry { startState, targetState, _ ->
101 | println("entering:$startState -> $targetState for $this")
102 | }
103 | onExit { startState, targetState, _ ->
104 | println("exiting:$startState -> $targetState for $this")
105 | }
106 | }
107 | whenState(LockStates.LOCKED) {
108 | onEvent(LockEvents.LOCK to LockStates.DOUBLE_LOCKED) {
109 | doubleLock()
110 | }
111 | onEvent(LockEvents.UNLOCK to LockStates.UNLOCKED) {
112 | unlock()
113 | }
114 | }
115 | whenState(LockStates.DOUBLE_LOCKED) {
116 | onEvent(LockEvents.UNLOCK to LockStates.LOCKED) {
117 | doubleUnlock()
118 | }
119 | }
120 | whenState(LockStates.UNLOCKED) {
121 | onEvent(LockEvents.LOCK to LockStates.LOCKED) {
122 | lock()
123 | }
124 | }
125 | }.build()
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/commonTest/kotlin/io/jumpco/open/kfsm/example/PayingTurnstileFsmTests.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2024. Open JumpCO
3 | *
4 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
5 | *
6 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
7 | * You should have received a copy of the GNU General Public License along with this program. If not, see .
8 | */
9 | package io.jumpco.open.kfsm.example
10 |
11 | import kotlin.test.Test
12 | import kotlin.test.assertTrue
13 |
14 | /**
15 | * @suppress
16 | */
17 | class PayingTurnstileFsmTests {
18 | @Test
19 | fun fsmComponentTest() {
20 | // tag::test[]
21 | val fsm = PayingTurnstileFSM(50)
22 | assertTrue(fsm.turnstile.locked)
23 | println("External:${fsm.externalState()}")
24 | println("--coin1")
25 | fsm.coin(10)
26 | assertTrue(fsm.turnstile.locked)
27 | assertTrue(fsm.turnstile.coins == 10)
28 | println("--coin2")
29 | println("External:${fsm.externalState()}")
30 | val externalState = fsm.externalState()
31 | PayingTurnstileFSM(50, externalState).apply {
32 | coin(60)
33 | assertTrue(turnstile.coins == 0)
34 | assertTrue(!turnstile.locked)
35 | println("External:${externalState()}")
36 | println("--pass1")
37 | pass()
38 | assertTrue(turnstile.locked)
39 | println("--pass2")
40 | pass()
41 | println("--pass3")
42 | pass()
43 | println("--coin3")
44 | coin(40)
45 | assertTrue(turnstile.coins == 40)
46 | println("--coin4")
47 | coin(10)
48 | assertTrue(turnstile.coins == 0)
49 | assertTrue(!turnstile.locked)
50 | }
51 | // end::test[]
52 | }
53 |
54 | @Test
55 | fun fsmComponentTestExternalState() {
56 | val fsm = PayingTurnstileFSM(50)
57 | assertTrue(fsm.turnstile.locked)
58 | println("External:${fsm.externalState()}")
59 | println("--coin1")
60 | fsm.coin(10)
61 | assertTrue(fsm.turnstile.locked)
62 | assertTrue(fsm.turnstile.coins == 10)
63 | println("--coin2")
64 | val es1 = fsm.externalState()
65 | println("External:$es1")
66 | val es2 = PayingTurnstileFSM(50, es1).apply {
67 | this.coin(60)
68 | assertTrue(turnstile.coins == 0)
69 | assertTrue(!turnstile.locked)
70 | println("External:${this.externalState()}")
71 | println("--pass1")
72 | }.externalState()
73 | val es3 = PayingTurnstileFSM(50, es2).apply {
74 | this.pass()
75 | assertTrue(turnstile.locked)
76 | println("--pass2")
77 | }.externalState()
78 | val es4 = PayingTurnstileFSM(50, es3).apply {
79 | this.pass()
80 | println("--pass3")
81 | }.externalState()
82 | val es5 = PayingTurnstileFSM(50, es4).apply {
83 | this.pass()
84 | println("--coin3")
85 | }.externalState()
86 | val es6 = PayingTurnstileFSM(50, es5).apply {
87 | this.coin(40)
88 | assertTrue(turnstile.coins == 40)
89 | println("--coin4")
90 | }.externalState()
91 | PayingTurnstileFSM(50, es6).apply {
92 | this.coin(10)
93 | assertTrue(turnstile.coins == 0)
94 | assertTrue(!turnstile.locked)
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/commonTest/kotlin/io/jumpco/open/kfsm/example/SecureTurnstile.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019-2024 Open JumpCO
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
7 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial
10 | portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
15 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16 | DEALINGS IN THE SOFTWARE.
17 | */
18 |
19 | package io.jumpco.open.kfsm.example
20 |
21 | import io.jumpco.open.kfsm.stateMachine
22 |
23 | // tag::states-events[]
24 | enum class SecureTurnstileEvents {
25 | CARD,
26 | PASS
27 | }
28 | // end::states-events[]
29 |
30 | enum class SecureTurnstileStates {
31 | LOCKED,
32 | UNLOCKED
33 | }
34 |
35 | // tag::context[]
36 | class SecureTurnstile {
37 | var locked: Boolean = true
38 | private set
39 | var overrideActive: Boolean = false
40 | private set
41 |
42 | fun activateOverride() {
43 | overrideActive = true
44 | println("override activated")
45 | }
46 |
47 | fun cancelOverride() {
48 | overrideActive = false
49 | println("override canceled")
50 | }
51 |
52 | fun lock() {
53 | println("lock")
54 | locked = true
55 | overrideActive = false
56 | }
57 |
58 | fun unlock() {
59 | println("unlock")
60 | locked = false
61 | overrideActive = false
62 | }
63 |
64 | fun buzzer() {
65 | println("BUZZER")
66 | }
67 |
68 | fun invalidCard(cardId: Int) {
69 | println("Invalid card $cardId")
70 | }
71 |
72 | fun isOverrideCard(cardId: Int): Boolean {
73 | return cardId == 42
74 | }
75 |
76 | fun isValidCard(cardId: Int): Boolean {
77 | return cardId % 2 == 1
78 | }
79 | }
80 | // end::context[]
81 |
82 | // tag::fsm[]
83 | class SecureTurnstileFSM(secureTurnstile: SecureTurnstile) {
84 | companion object {
85 | val definition = stateMachine(
86 | SecureTurnstileStates.entries.toSet(),
87 | SecureTurnstileEvents.entries.toSet(),
88 | SecureTurnstile::class,
89 | Int::class
90 | ) {
91 | defaultInitialState = SecureTurnstileStates.LOCKED
92 | initialState { if (locked) SecureTurnstileStates.LOCKED else SecureTurnstileStates.UNLOCKED }
93 | default {
94 | action { _, _, _ ->
95 | buzzer()
96 | }
97 | }
98 | onStateChange { oldState, newState ->
99 | println("onStateChange:$oldState -> $newState")
100 | }
101 | whenState(SecureTurnstileStates.LOCKED) {
102 | onEvent(
103 | SecureTurnstileEvents.CARD,
104 | guard = { cardId ->
105 | requireNotNull(cardId)
106 | isOverrideCard(cardId) && overrideActive
107 | }
108 | ) {
109 | cancelOverride()
110 | }
111 | onEvent(
112 | SecureTurnstileEvents.CARD,
113 | guard = { cardId ->
114 | requireNotNull(cardId)
115 | isOverrideCard(cardId)
116 | }
117 | ) {
118 | activateOverride()
119 | }
120 | onEvent(
121 | SecureTurnstileEvents.CARD to SecureTurnstileStates.UNLOCKED,
122 | guard = { cardId ->
123 | requireNotNull(cardId)
124 | overrideActive || isValidCard(cardId)
125 | }
126 | ) {
127 | unlock()
128 | }
129 | onEvent(
130 | SecureTurnstileEvents.CARD,
131 | guard = { cardId ->
132 | requireNotNull(cardId) { "cardId is required" }
133 | !isValidCard(cardId)
134 | }
135 | ) { cardId ->
136 | requireNotNull(cardId)
137 | invalidCard(cardId)
138 | }
139 | }
140 | whenState(SecureTurnstileStates.UNLOCKED) {
141 | onEvent(
142 | SecureTurnstileEvents.CARD to SecureTurnstileStates.LOCKED,
143 | guard = { cardId ->
144 | requireNotNull(cardId)
145 | isOverrideCard(cardId)
146 | }
147 | ) {
148 | lock()
149 | }
150 | onEvent(SecureTurnstileEvents.PASS to SecureTurnstileStates.LOCKED) {
151 | lock()
152 | }
153 | }
154 | }.build()
155 | }
156 |
157 | private val fsm = definition.create(secureTurnstile)
158 | fun card(cardId: Int) = fsm.sendEvent(SecureTurnstileEvents.CARD, cardId)
159 | fun pass() = fsm.sendEvent(SecureTurnstileEvents.PASS)
160 | fun allowEvent(): Set = fsm.allowed().map { it.name.lowercase() }.toSet()
161 | }
162 | // end::fsm[]
163 |
--------------------------------------------------------------------------------
/src/commonTest/kotlin/io/jumpco/open/kfsm/example/SecureTurnstileTests.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2024. Open JumpCO
3 | *
4 | * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
5 | *
6 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
7 | * You should have received a copy of the GNU General Public License along with this program. If not, see .
8 | */
9 |
10 | package io.jumpco.open.kfsm.example
11 |
12 | import kotlin.test.Test
13 | import kotlin.test.assertTrue
14 |
15 | class SecureTurnstileTests {
16 | @Test
17 | fun testNormalOperation() {
18 | // given
19 | val turnstile = SecureTurnstile()
20 | val fsm = SecureTurnstileFSM(turnstile)
21 | // when
22 | assertTrue { fsm.allowEvent() == setOf("card") }
23 | assertTrue { turnstile.locked }
24 | fsm.card(1)
25 | assertTrue { !turnstile.locked }
26 | assertTrue { fsm.allowEvent() == setOf("pass", "card") }
27 | fsm.pass()
28 | assertTrue { turnstile.locked }
29 | }
30 |
31 | @Test
32 | fun testInvalidCard() {
33 | // given
34 | val turnstile = SecureTurnstile()
35 | val fsm = SecureTurnstileFSM(turnstile)
36 | // when
37 | assertTrue { fsm.allowEvent() == setOf("card") }
38 | assertTrue { turnstile.locked }
39 | fsm.card(2)
40 | assertTrue { fsm.allowEvent() == setOf("card") }
41 | assertTrue { turnstile.locked }
42 | fsm.pass()
43 | assertTrue { turnstile.locked }
44 | }
45 |
46 | @Test
47 | fun testInvalidCardOverride() {
48 | // given
49 | val turnstile = SecureTurnstile()
50 | val fsm = SecureTurnstileFSM(turnstile)
51 | // when
52 | assertTrue { turnstile.locked }
53 | fsm.card(2)
54 | assertTrue { turnstile.locked }
55 | fsm.card(42) // override card
56 | fsm.card(2)
57 | assertTrue { !turnstile.locked }
58 | fsm.pass()
59 | assertTrue { turnstile.locked }
60 | }
61 |
62 | @Test
63 | fun testCancelOverrideToLock() {
64 | // given
65 | val turnstile = SecureTurnstile()
66 | val fsm = SecureTurnstileFSM(turnstile)
67 | // when
68 | assertTrue { turnstile.locked }
69 | fsm.card(2)
70 | assertTrue { turnstile.locked }
71 | fsm.card(42) // override card
72 | fsm.card(2)
73 | assertTrue { !turnstile.locked }
74 | assertTrue { fsm.allowEvent() == setOf("card", "pass") }
75 | fsm.card(42) // override card
76 | assertTrue { turnstile.locked }
77 | }
78 |
79 | @Test
80 | fun testCancelOverride() {
81 | // given
82 | val turnstile = SecureTurnstile()
83 | val fsm = SecureTurnstileFSM(turnstile)
84 | // when
85 | assertTrue { turnstile.locked }
86 | fsm.card(2)
87 | assertTrue { turnstile.locked }
88 | fsm.card(42) // override card
89 | assertTrue { turnstile.overrideActive }
90 | fsm.card(42) // override card
91 | assertTrue { !turnstile.overrideActive }
92 | fsm.card(2)
93 | assertTrue { turnstile.locked }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/commonTest/kotlin/io/jumpco/open/kfsm/example/TimeoutSecureTurnstile.kt:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019-2024 Open JumpCO
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
7 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all copies or substantial
10 | portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
15 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
16 | DEALINGS IN THE SOFTWARE.
17 | */
18 |
19 | package io.jumpco.open.kfsm.example
20 |
21 | import io.jumpco.open.kfsm.async.asyncStateMachine
22 | import kotlinx.coroutines.CoroutineScope
23 |
24 | // tag::states-events[]
25 | enum class TimeoutSecureTurnstileEvents {
26 | CARD,
27 | PASS
28 | }
29 | // end::states-events[]
30 |
31 | enum class TimeoutSecureTurnstileStates {
32 | LOCKED,
33 | UNLOCKED
34 | }
35 |
36 | // tag::context[]
37 | class TimerSecureTurnstile {
38 | var locked: Boolean = true
39 | private set
40 | var overrideActive: Boolean = false
41 | private set
42 | val timeout = 500L
43 | fun activateOverride() {
44 | overrideActive = true
45 | println("override activated")
46 | }
47 |
48 | fun cancelOverride() {
49 | overrideActive = false
50 | println("override canceled")
51 | }
52 |
53 | fun lock() {
54 | println("lock")
55 | locked = true
56 | overrideActive = false
57 | }
58 |
59 | fun unlock() {
60 | println("unlock")
61 | locked = false
62 | overrideActive = false
63 | }
64 |
65 | fun buzzer() {
66 | println("BUZZER")
67 | }
68 |
69 | fun invalidCard(cardId: Int) {
70 | println("Invalid card $cardId")
71 | }
72 |
73 | fun isOverrideCard(cardId: Int): Boolean {
74 | return cardId == 42
75 | }
76 |
77 | fun isValidCard(cardId: Int): Boolean {
78 | return cardId % 2 == 1
79 | }
80 | }
81 | // end::context[]
82 |
83 | // tag::fsm[]
84 | class TimerSecureTurnstileFSM(secureTurnstile: TimerSecureTurnstile, coroutineScope: CoroutineScope) {
85 | companion object {
86 | val definition = asyncStateMachine(
87 | TimeoutSecureTurnstileStates.entries.toSet(),
88 | TimeoutSecureTurnstileEvents.entries.toSet(),
89 | TimerSecureTurnstile::class,
90 | Int::class
91 | ) {
92 | defaultInitialState = TimeoutSecureTurnstileStates.LOCKED
93 | initialState { if (locked) TimeoutSecureTurnstileStates.LOCKED else TimeoutSecureTurnstileStates.UNLOCKED }
94 | default {
95 | action { _, _, _ ->
96 | buzzer()
97 | }
98 | }
99 | onStateChange { oldState, newState ->
100 | println("onStateChange:$oldState -> $newState")
101 | }
102 | whenState(TimeoutSecureTurnstileStates.LOCKED) {
103 | onEvent(
104 | TimeoutSecureTurnstileEvents.CARD,
105 | guard = { cardId ->
106 | requireNotNull(cardId)
107 | isOverrideCard(cardId) && overrideActive
108 | }
109 | ) {
110 | cancelOverride()
111 | }
112 | onEvent(
113 | TimeoutSecureTurnstileEvents.CARD,
114 | guard = { cardId ->
115 | requireNotNull(cardId)
116 | isOverrideCard(cardId)
117 | }
118 | ) {
119 | activateOverride()
120 | }
121 | onEvent(
122 | TimeoutSecureTurnstileEvents.CARD to TimeoutSecureTurnstileStates.UNLOCKED,
123 | guard = { cardId ->
124 | requireNotNull(cardId)
125 | overrideActive || isValidCard(cardId)
126 | }
127 | ) {
128 | unlock()
129 | }
130 | onEvent(
131 | TimeoutSecureTurnstileEvents.CARD,
132 | guard = { cardId ->
133 | requireNotNull(cardId) { "cardId is required" }
134 | !isValidCard(cardId)
135 | }
136 | ) { cardId ->
137 | requireNotNull(cardId)
138 | invalidCard(cardId)
139 | }
140 | }
141 | whenState(TimeoutSecureTurnstileStates.UNLOCKED) {
142 | timeout(TimeoutSecureTurnstileStates.LOCKED, { timeout }) {
143 | println("Timeout. Locking")
144 | lock()
145 | }
146 | onEvent(
147 | TimeoutSecureTurnstileEvents.CARD to TimeoutSecureTurnstileStates.LOCKED,
148 | guard = { cardId ->
149 | requireNotNull(cardId)
150 | isOverrideCard(cardId)
151 | }
152 | ) {
153 | lock()
154 | }
155 | onEvent(TimeoutSecureTurnstileEvents.PASS to TimeoutSecureTurnstileStates.LOCKED) {
156 | lock()
157 | }
158 | }
159 | }.build()
160 | }
161 |
162 | private val fsm = definition.create(secureTurnstile, coroutineScope)
163 |
164 | suspend fun card(cardId: Int) = fsm.sendEvent(TimeoutSecureTurnstileEvents.CARD, cardId)
165 | suspend fun pass() = fsm.sendEvent(TimeoutSecureTurnstileEvents.PASS)
166 | fun allowEvent(): Set = fsm.allowed().map { it.name.lowercase() }.toSet()
167 | }
168 | // end::fsm[]
169 |
--------------------------------------------------------------------------------
/src/docs/asciidoc/docinfo-footer.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/docs/asciidoc/docinfo.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/docs/asciidoc/index.adoc:
--------------------------------------------------------------------------------
1 | = KFSM - Kotlin Finite-state machine
2 | :toc:
3 | :toclevels: 3
4 |
5 | == Introduction
6 |
7 | Having used various incarnations of link:http://smc.sourceforge.net/[SMC] over the years we decided to try it again and discovered it didn't have specific support for Kotlin.
8 | Instead of creating a generator for Kotlin we decided to attempt a Kotlin DSL along with a simple implementation.
9 |
10 | To learn more about Finite State Machines visit:
11 |
12 | * link:https://en.wikipedia.org/wiki/Finite-state_machine[Wikipedia - Finite-state machine]
13 | * link:https://brilliant.org/wiki/finite-state-machines/[Brilliant - Finite State Machines]
14 | * link:http://smc.sourceforge.net/slides/SMC_Tutorial.pdf[SMC Tutorial]
15 |
16 | The <> provides examples of how to compose a DSL and FSM.
17 |
18 | == Links
19 |
20 | === link:javadoc/kfsm/index.html[API Docs]
21 |
22 | === link:https://github.com/open-jumpco/kfsm[Source]
23 |
24 | == Features
25 |
26 | This Finite state machine implementation has the following features:
27 |
28 | * Event driven state machine.
29 | * External and internal transitions
30 | * State entry and exit actions.
31 | * Default state actions.
32 | * Default entry and exit actions.
33 | * Determine allowed events for current or given state.
34 | * Multiple state maps with push / pop transitions
35 | * Automatic transitions
36 | * Externalisation of state.
37 | * Simple Visualization.
38 | * Detailed Visualization.
39 | * Visualization Gradle Plugin
40 | * Coroutines
41 | * Timeout Transitions
42 | * StateChange notification action
43 |
44 | == Tutorial
45 |
46 | include::kfsm.adoc[]
47 |
48 | include::documentation.adoc[]
49 |
--------------------------------------------------------------------------------
/src/docs/asciidoc/lock-fsm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-jumpco/kfsm/25c0f744ad21bfcedb51b8a4946b3d724489aec9/src/docs/asciidoc/lock-fsm.png
--------------------------------------------------------------------------------
/src/docs/asciidoc/lock_fsm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-jumpco/kfsm/25c0f744ad21bfcedb51b8a4946b3d724489aec9/src/docs/asciidoc/lock_fsm.png
--------------------------------------------------------------------------------
/src/docs/asciidoc/packet-reader-fsm-guard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-jumpco/kfsm/25c0f744ad21bfcedb51b8a4946b3d724489aec9/src/docs/asciidoc/packet-reader-fsm-guard.png
--------------------------------------------------------------------------------
/src/docs/asciidoc/packet-reader-fsm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-jumpco/kfsm/25c0f744ad21bfcedb51b8a4946b3d724489aec9/src/docs/asciidoc/packet-reader-fsm.png
--------------------------------------------------------------------------------
/src/docs/asciidoc/packet_reader_detail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-jumpco/kfsm/25c0f744ad21bfcedb51b8a4946b3d724489aec9/src/docs/asciidoc/packet_reader_detail.png
--------------------------------------------------------------------------------
/src/docs/asciidoc/paying-turnstile-fsm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-jumpco/kfsm/25c0f744ad21bfcedb51b8a4946b3d724489aec9/src/docs/asciidoc/paying-turnstile-fsm.png
--------------------------------------------------------------------------------
/src/docs/asciidoc/paying_turnstile_detail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-jumpco/kfsm/25c0f744ad21bfcedb51b8a4946b3d724489aec9/src/docs/asciidoc/paying_turnstile_detail.png
--------------------------------------------------------------------------------
/src/docs/asciidoc/prism.css:
--------------------------------------------------------------------------------
1 | /* PrismJS 1.17.1
2 | https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+bash+java+javastacktrace+javadoclike+json+jsonp+json5+kotlin+js-templates+js-extras+javadoc+yaml */
3 | /**
4 | * prism.js default theme for JavaScript, CSS and HTML
5 | * Based on dabblet (http://dabblet.com)
6 | * @author Lea Verou
7 | */
8 |
9 | code[class*="language-"],
10 | pre[class*="language-"] {
11 | color: black;
12 | background: none;
13 | text-shadow: 0 1px white;
14 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
15 | font-size: 1em;
16 | text-align: left;
17 | white-space: pre;
18 | word-spacing: normal;
19 | word-break: normal;
20 | word-wrap: normal;
21 | line-height: 1.5;
22 |
23 | -moz-tab-size: 4;
24 | -o-tab-size: 4;
25 | tab-size: 4;
26 |
27 | -webkit-hyphens: none;
28 | -moz-hyphens: none;
29 | -ms-hyphens: none;
30 | hyphens: none;
31 | }
32 |
33 | pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
34 | code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
35 | text-shadow: none;
36 | background: #b3d4fc;
37 | }
38 |
39 | pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
40 | code[class*="language-"]::selection, code[class*="language-"] ::selection {
41 | text-shadow: none;
42 | background: #b3d4fc;
43 | }
44 |
45 | @media print {
46 | code[class*="language-"],
47 | pre[class*="language-"] {
48 | text-shadow: none;
49 | }
50 | }
51 |
52 | /* Code blocks */
53 | pre[class*="language-"] {
54 | padding: 1em;
55 | margin: .5em 0;
56 | overflow: auto;
57 | }
58 |
59 | :not(pre) > code[class*="language-"],
60 | pre[class*="language-"] {
61 | background: #f5f2f0;
62 | }
63 |
64 | /* Inline code */
65 | :not(pre) > code[class*="language-"] {
66 | padding: .1em;
67 | border-radius: .3em;
68 | white-space: normal;
69 | }
70 |
71 | .token.comment,
72 | .token.prolog,
73 | .token.doctype,
74 | .token.cdata {
75 | color: slategray;
76 | }
77 |
78 | .token.punctuation {
79 | color: #999;
80 | }
81 |
82 | .namespace {
83 | opacity: .7;
84 | }
85 |
86 | .token.property,
87 | .token.tag,
88 | .token.boolean,
89 | .token.number,
90 | .token.constant,
91 | .token.symbol,
92 | .token.deleted {
93 | color: #905;
94 | }
95 |
96 | .token.selector,
97 | .token.attr-name,
98 | .token.string,
99 | .token.char,
100 | .token.builtin,
101 | .token.inserted {
102 | color: #690;
103 | }
104 |
105 | .token.operator,
106 | .token.entity,
107 | .token.url,
108 | .language-css .token.string,
109 | .style .token.string {
110 | color: #9a6e3a;
111 | background: hsla(0, 0%, 100%, .5);
112 | }
113 |
114 | .token.atrule,
115 | .token.attr-value,
116 | .token.keyword {
117 | color: #07a;
118 | }
119 |
120 | .token.function,
121 | .token.class-name {
122 | color: #DD4A68;
123 | }
124 |
125 | .token.regex,
126 | .token.important,
127 | .token.variable {
128 | color: #e90;
129 | }
130 |
131 | .token.important,
132 | .token.bold {
133 | font-weight: bold;
134 | }
135 |
136 | .token.italic {
137 | font-style: italic;
138 | }
139 |
140 | .token.entity {
141 | cursor: help;
142 | }
143 |
144 |
--------------------------------------------------------------------------------
/src/docs/asciidoc/sample-fsm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-jumpco/kfsm/25c0f744ad21bfcedb51b8a4946b3d724489aec9/src/docs/asciidoc/sample-fsm.png
--------------------------------------------------------------------------------
/src/docs/asciidoc/secure-turnstile-fsm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-jumpco/kfsm/25c0f744ad21bfcedb51b8a4946b3d724489aec9/src/docs/asciidoc/secure-turnstile-fsm.png
--------------------------------------------------------------------------------
/src/docs/asciidoc/secure_turnstile_detail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-jumpco/kfsm/25c0f744ad21bfcedb51b8a4946b3d724489aec9/src/docs/asciidoc/secure_turnstile_detail.png
--------------------------------------------------------------------------------
/src/docs/asciidoc/statemachine-classes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-jumpco/kfsm/25c0f744ad21bfcedb51b8a4946b3d724489aec9/src/docs/asciidoc/statemachine-classes.png
--------------------------------------------------------------------------------
/src/docs/asciidoc/statemachine-model.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-jumpco/kfsm/25c0f744ad21bfcedb51b8a4946b3d724489aec9/src/docs/asciidoc/statemachine-model.png
--------------------------------------------------------------------------------
/src/docs/asciidoc/statemachine-packaged.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-jumpco/kfsm/25c0f744ad21bfcedb51b8a4946b3d724489aec9/src/docs/asciidoc/statemachine-packaged.png
--------------------------------------------------------------------------------
/src/docs/asciidoc/statemachine-sequence.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-jumpco/kfsm/25c0f744ad21bfcedb51b8a4946b3d724489aec9/src/docs/asciidoc/statemachine-sequence.png
--------------------------------------------------------------------------------
/src/docs/asciidoc/timeout_turnstile_detail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-jumpco/kfsm/25c0f744ad21bfcedb51b8a4946b3d724489aec9/src/docs/asciidoc/timeout_turnstile_detail.png
--------------------------------------------------------------------------------
/src/docs/asciidoc/turnstile-fsm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-jumpco/kfsm/25c0f744ad21bfcedb51b8a4946b3d724489aec9/src/docs/asciidoc/turnstile-fsm.png
--------------------------------------------------------------------------------
/src/docs/asciidoc/turnstile-scxml.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/docs/asciidoc/turnstile-sequence.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-jumpco/kfsm/25c0f744ad21bfcedb51b8a4946b3d724489aec9/src/docs/asciidoc/turnstile-sequence.png
--------------------------------------------------------------------------------
/src/docs/asciidoc/turnstile_detail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-jumpco/kfsm/25c0f744ad21bfcedb51b8a4946b3d724489aec9/src/docs/asciidoc/turnstile_detail.png
--------------------------------------------------------------------------------
/src/docs/asciidoc/turnstile_fsm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-jumpco/kfsm/25c0f744ad21bfcedb51b8a4946b3d724489aec9/src/docs/asciidoc/turnstile_fsm.png
--------------------------------------------------------------------------------
/src/docs/asciidoc/turnstile_scxml.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-jumpco/kfsm/25c0f744ad21bfcedb51b8a4946b3d724489aec9/src/docs/asciidoc/turnstile_scxml.png
--------------------------------------------------------------------------------
/src/docs/asciidoc/useless-fsm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-jumpco/kfsm/25c0f744ad21bfcedb51b8a4946b3d724489aec9/src/docs/asciidoc/useless-fsm.png
--------------------------------------------------------------------------------
/src/docs/plantuml/kfsm.plantuml:
--------------------------------------------------------------------------------
1 | @startuml
2 |
3 | skinparam classFontSize 9
4 | skinparam classFontName Monospaced
5 | skinparam state {
6 | BackgroundColor #1b74bc
7 | }
8 |
9 | [*] --right-> Convoluted : Code
10 | Convoluted --right-> Organized : KFSM
11 |
12 | @enduml
13 |
--------------------------------------------------------------------------------
/src/docs/plantuml/lock-fsm.plantuml:
--------------------------------------------------------------------------------
1 | @startuml
2 |
3 | skinparam monochrome true
4 | skinparam StateFontName Helvetica
5 | skinparam defaultFontName Monospaced
6 | skinparam defaultFontStyle Bold
7 | skinparam state {
8 | FontColor Black
9 | FontStyle Bold
10 | }
11 |
12 |
13 | [*] --> LOCKED
14 | LOCKED --> UNLOCKED : UNLOCK
15 | UNLOCKED ---> LOCKED : LOCK
16 | DOUBLE_LOCKED ---> LOCKED : UNLOCK
17 | LOCKED --> DOUBLE_LOCKED : LOCK
18 |
19 | @enduml
20 |
--------------------------------------------------------------------------------
/src/docs/plantuml/packet-reader-fsm-guard.plantuml:
--------------------------------------------------------------------------------
1 | @startuml
2 |
3 | skinparam monochrome true
4 | skinparam classFontSize 9
5 | skinparam classFontName Monospaced
6 |
7 | [*] --> RCVPCKT : CTRL { byte == SOH }
8 | RCVPCKT --> RCVDATA : CTRL { byte == STX } / addField
9 | RCVDATA --> RCVDATA : BYTE / addByte(byte)
10 | RCVDATA --> RCVPCKT : CTRL { guard == ETX } / endField
11 | RCVDATA --> RCVESC : ESC
12 | RCVDATA --> [*] : CTRL / sendNACK
13 | RCVESC --> RCVDATA: ESC \n addByte(byte)
14 | RCVESC --> RCVDATA: CTRL \n addByte(byte)
15 | RCVPCKT --> RCVCHK : BYTE / addChecksum(byte)
16 | RCVCHK --> RCVCHK : BYTE / addChecksum(byte)
17 | RCVCHK --> CHKSUM : CTRL {guard == EOT } / checksum
18 | RCVCHK --> RCVCHKESC : ESC
19 | RCVCHKESC --> RCVCHK : ESC / addChecksum(byte)
20 | RCVCHKESC --> RCVCHK : CTRL / addChecksum(byte)
21 | RCVCHK --> [*] : CTRL / sendNACK
22 | CHKSUM --> [*] : <> { checksumValid }\n sendACK
23 | CHKSUM --> [*] : <> { !checksumValid }\n sendNACK
24 | @enduml
25 |
--------------------------------------------------------------------------------
/src/docs/plantuml/packet-reader-fsm.plantuml:
--------------------------------------------------------------------------------
1 | @startuml
2 | skinparam monochrome true
3 | skinparam classFontSize 9
4 | skinparam classFontName Monospaced
5 |
6 | [*] --> RCVPCKT : SOH
7 | RCVPCKT --> RCVDATA : STX / addField
8 | RCVDATA --> RCVDATA : BYTE / addByte(byte)
9 | RCVDATA --> RCVPCKT : ETX / endField
10 | RCVDATA --> RCVESC : ESC
11 | RCVDATA --> [*] : ETX|EOT|SOH|STX\n sendNACK
12 | RCVESC --> RCVDATA: ESC|ETX|EOT|SOH|STX\n addByte(byte)
13 | RCVPCKT --> RCVCHK : BYTE / addChecksum(byte)
14 | RCVCHK --> RCVCHK : BYTE / addChecksum(byte)
15 | RCVCHK --> CHKSUM : EOT / checksum
16 | RCVCHK --> RCVCHKESC : ESC
17 | RCVCHKESC --> RCVCHK : ESC|ETX|EOT|SOH|STX\n addChecksum(byte)
18 | RCVCHK --> [*] : ETX|EOT|SOH|STX\n sendNACK
19 | CHKSUM --> [*] : <> { checksumValid }\n sendACK
20 | CHKSUM --> [*] : <> { !checksumValid }\n sendNACK
21 | @enduml
22 |
--------------------------------------------------------------------------------
/src/docs/plantuml/paying-turnstile-fsm.plantuml:
--------------------------------------------------------------------------------
1 | @startuml
2 |
3 | skinparam monochrome true
4 | skinparam classFontSize 9
5 | skinparam classFontName Monospaced
6 |
7 | [*] -right-> LOCKED
8 |
9 | state LOCKED {
10 | LOCKED --> LOCKED : <> PASS\n{ alarm() }
11 | LOCKED --> COINS : COIN(value)\n{ coin(value); }
12 | }
13 |
14 | state UNLOCKED {
15 | UNLOCKED --> LOCKED : PASS\n{ lock() }
16 | UNLOCKED --up--> COINS : COIN(value)\n{ coin(value); }
17 | }
18 |
19 | state coins {
20 | state COINS {
21 | COINS --> COINS : COIN(value)\n{ coin(value); }
22 | COINS --down--> UNLOCKED : <>\nguard:{ value + coins == requiredCoins }\n{ unlock() }
23 | COINS --down--> UNLOCKED : <>\nguard:{ value + coins > requiredCoins }\n{ returnCoin(); unlock(); }
24 | }
25 | }
26 |
27 | @enduml
28 |
--------------------------------------------------------------------------------
/src/docs/plantuml/sample-fsm.plantuml:
--------------------------------------------------------------------------------
1 | @startuml
2 |
3 | skinparam monochrome true
4 | skinparam StateFontName Helvetica
5 | skinparam defaultFontName Monospaced
6 | skinparam defaultFontStyle Bold
7 | skinparam state {
8 | FontColor Black
9 | FontStyle Bold
10 | }
11 |
12 |
13 | [*] --> STATE1
14 | STATE1 ---> STATE2 : EVENT1\n{ action1() }
15 | STATE2 ---> STATE1 : EVENT2\n{ action2() }
16 | STATE1 ---> STATE1 : EVENT2\n{ action3() }
17 | STATE2 ---> STATE2 : EVENT1\n{ action4() }
18 |
19 | @enduml
20 |
--------------------------------------------------------------------------------
/src/docs/plantuml/secure-turnstile-fsm.plantuml:
--------------------------------------------------------------------------------
1 | @startuml
2 |
3 | skinparam monochrome true
4 | skinparam StateFontName Helvetica
5 | skinparam defaultFontName Monospaced
6 | skinparam defaultFontStyle Bold
7 | skinparam state {
8 | FontColor Black
9 | FontStyle Bold
10 | }
11 |
12 | [*] -right-> LOCKED
13 |
14 | LOCKED ----> UNLOCKED : CARD(id) [{ isOverrideCard(id) }] -> {\l activateOverride()\l}
15 | LOCKED ----> UNLOCKED : CARD(id) [{ isValid(id) || overrideActive }] -> {\l unlock()\l}]
16 | LOCKED ----> UNLOCKED : CARD(id) [{ !isValid(id) }] -> {\l error("Invalid card")\l}
17 |
18 | UNLOCKED ----> LOCKED : PASS\l{\l lock()\l}
19 | UNLOCKED ----> LOCKED : CARD(id)\l [{ isOverrideCard(id) }] -> {\l lock()\l}
20 |
21 | @enduml
22 |
--------------------------------------------------------------------------------
/src/docs/plantuml/statemachine-classes.plantuml:
--------------------------------------------------------------------------------
1 | @startuml
2 |
3 | skinparam monochrome true
4 |
5 | class Context {
6 | }
7 | enum States {
8 | }
9 | enum Events {
10 | }
11 |
12 | class StateMachineBuilder