diff --git a/README.md b/README.md index fb2f643..ad21225 100644 --- a/README.md +++ b/README.md @@ -198,7 +198,8 @@ fun main() = runBlocking { * [Undo transition sample](./samples/src/commonMain/kotlin/ru/nsk/samples/UndoTransitionSample.kt) * [PlantUML nested states export sample](./samples/src/commonMain/kotlin/ru/nsk/samples/PlantUmlExportSample.kt) * [Mermaid nested states export sample](./samples/src/commonMain/kotlin/ru/nsk/samples/MermaidExportSample.kt) -* [PlantUML with MetaInfo export sample](./samples/src/commonMain/kotlin/ru/nsk/samples/PlantUmlExportWithMetaInfoSample.kt) +* [PlantUML with UmlMetaInfo export sample](./samples/src/commonMain/kotlin/ru/nsk/samples/PlantUmlExportWithUmlMetaInfoSample.kt) +* [PlantUML with ExportMetaInfo unsafe export sample](https://github.com/KStateMachine/kstatemachine/tree/master/samples/src/commonMain/kotlin/ru/nsk/samples/PlantUmlUnsafeExportWithExportMetaInfoSample.kt) * [Inherit transitions by grouping states sample](./samples/src/commonMain/kotlin/ru/nsk/samples/InheritTransitionsSample.kt) * [Minimal sealed classes sample](./samples/src/commonMain/kotlin/ru/nsk/samples/MinimalSealedClassesSample.kt) * [Usage without Kotlin Coroutines sample](./samples/src/commonMain/kotlin/ru/nsk/samples/StdLibMinimalSealedClassesSample.kt) diff --git a/docs/pages/export.md b/docs/pages/export.md index 1bb29e2..4e66423 100644 --- a/docs/pages/export.md +++ b/docs/pages/export.md @@ -74,7 +74,7 @@ state("State1") { } ``` -See [PlantUML with MetaInfo export sample](https://github.com/KStateMachine/kstatemachine/tree/master/samples/src/commonMain/kotlin/ru/nsk/samples/PlantUmlExportWithMetaInfoSample.kt) +See [PlantUML with UmlMetaInfo export sample](https://github.com/KStateMachine/kstatemachine/tree/master/samples/src/commonMain/kotlin/ru/nsk/samples/PlantUmlExportWithUmlMetaInfoSample.kt) ## Export with `unsafeCallConditionalLambdas` flag @@ -125,20 +125,20 @@ transitionConditionally { } } metaInfo = buildExportMetaInfo { - resolutionHints = setOf( - // the library does not need to call "direction" lambda, this hint provides the result (state1) directly - StateResolutionHint("when 1", state1), - // calls "direction" lambda during export with specified Event and optional argument (lambda will return state2) - EventAndArgumentResolutionHint("when 2", ValueEvent(2)), - // you can specify set of states that represents parallel target states - StateResolutionHint("when 3", setOf(state1, state2)), - // describes stay() behaviour without calling "direction" lambda - StateResolutionHint("when 4", this@createStateMachine), - // resolves to stay() by calling "direction" lambda - EventAndArgumentResolutionHint("when 4", ValueEvent(4)), - // useless, does not affect export output as it resolves to noTransition() - EventAndArgumentResolutionHint("else", ValueEvent(5)), - ) + // the library does not need to call "direction" lambda, this hint provides the result (state1) directly + addStateResolutionHint("when 1", state1) + // calls "direction" lambda during export with specified Event and optional argument (lambda will return state2) + addEventAndArgumentResolutionHint("when 2", ValueEvent(2)) + // you can specify set of states that represents parallel target states + addStateResolutionHint("when 3", setOf(state1, state2)) + // describes stay() behaviour without calling "direction" lambda + addStateResolutionHint("when 4", this@createStateMachine) + // resolves to stay() by calling "direction" lambda + addEventAndArgumentResolutionHint("when 4", ValueEvent(4)) + // useless, does not affect export output as it resolves to noTransition() + addEventAndArgumentResolutionHint("else", ValueEvent(5)) } } -``` \ No newline at end of file +``` + +See [PlantUML with ExportMetaInfo unsafe export sample](https://github.com/KStateMachine/kstatemachine/tree/master/samples/src/commonMain/kotlin/ru/nsk/samples/PlantUmlUnsafeExportWithExportMetaInfoSample.kt) \ No newline at end of file diff --git a/kstatemachine/api/kstatemachine.api b/kstatemachine/api/kstatemachine.api index 13d45be..a5ee4c9 100644 --- a/kstatemachine/api/kstatemachine.api +++ b/kstatemachine/api/kstatemachine.api @@ -100,22 +100,18 @@ public abstract interface class ru/nsk/kstatemachine/metainfo/CompositeMetaInfoB public abstract fun setMetaInfoSet (Ljava/util/Set;)V } -public final class ru/nsk/kstatemachine/metainfo/EventAndArgumentResolutionHint : ru/nsk/kstatemachine/metainfo/ResolutionHint { - public fun (Ljava/lang/String;Lru/nsk/kstatemachine/event/Event;Ljava/lang/Object;)V - public synthetic fun (Ljava/lang/String;Lru/nsk/kstatemachine/event/Event;Ljava/lang/Object;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun getArgument ()Ljava/lang/Object; - public final fun getDescription ()Ljava/lang/String; - public final fun getEvent ()Lru/nsk/kstatemachine/event/Event; - public final fun getEventAndArgument ()Lru/nsk/kstatemachine/transition/EventAndArgument; -} - public abstract interface class ru/nsk/kstatemachine/metainfo/ExportMetaInfo : ru/nsk/kstatemachine/metainfo/MetaInfo { public abstract fun getResolutionHints ()Ljava/util/Set; } public abstract interface class ru/nsk/kstatemachine/metainfo/ExportMetaInfoBuilder : ru/nsk/kstatemachine/metainfo/ExportMetaInfo { - public abstract fun getResolutionHints ()Ljava/util/Set; - public abstract fun setResolutionHints (Ljava/util/Set;)V + public abstract fun addEventAndArgumentResolutionHint (Ljava/lang/String;Lru/nsk/kstatemachine/event/Event;Ljava/lang/Object;)V + public abstract fun addStateResolutionHint (Ljava/lang/String;Ljava/util/Set;)V + public abstract fun addStateResolutionHint (Ljava/lang/String;Lru/nsk/kstatemachine/state/IState;)V +} + +public final class ru/nsk/kstatemachine/metainfo/ExportMetaInfoBuilder$DefaultImpls { + public static synthetic fun addEventAndArgumentResolutionHint$default (Lru/nsk/kstatemachine/metainfo/ExportMetaInfoBuilder;Ljava/lang/String;Lru/nsk/kstatemachine/event/Event;Ljava/lang/Object;ILjava/lang/Object;)V } public final class ru/nsk/kstatemachine/metainfo/ExportMetaInfoKt { @@ -137,13 +133,6 @@ public final class ru/nsk/kstatemachine/metainfo/MetaInfoKt { public abstract interface class ru/nsk/kstatemachine/metainfo/ResolutionHint { } -public final class ru/nsk/kstatemachine/metainfo/StateResolutionHint : ru/nsk/kstatemachine/metainfo/ResolutionHint { - public fun (Ljava/lang/String;Ljava/util/Set;)V - public fun (Ljava/lang/String;Lru/nsk/kstatemachine/state/IState;)V - public final fun getDescription ()Ljava/lang/String; - public final fun getTargetStates ()Ljava/util/Set; -} - public abstract interface class ru/nsk/kstatemachine/metainfo/UmlMetaInfo : ru/nsk/kstatemachine/metainfo/MetaInfo { public abstract fun getUmlLabel ()Ljava/lang/String; public abstract fun getUmlNotes ()Ljava/util/List; diff --git a/kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/metainfo/ExportMetaInfo.kt b/kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/metainfo/ExportMetaInfo.kt index 105c5d2..1f0979b 100644 --- a/kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/metainfo/ExportMetaInfo.kt +++ b/kstatemachine/src/commonMain/kotlin/ru/nsk/kstatemachine/metainfo/ExportMetaInfo.kt @@ -24,16 +24,11 @@ sealed interface ResolutionHint * branches. * User is responsible to provide correct hints. */ -class StateResolutionHint( +internal class StateResolutionHint( val description: String, /** Allows to specify parallel target states. Must be non-empty */ val targetStates: Set, ) : ResolutionHint { - constructor( - description: String, - targetState: IState, - ) : this(description, setOf(targetState)) - init { require(targetStates.isNotEmpty()) { "targetStates must be non-empty, use single state or multiple states for parallel transitions" @@ -49,10 +44,10 @@ class StateResolutionHint( * lambda branches. * User is responsible to provide correct hints. */ -class EventAndArgumentResolutionHint( +internal class EventAndArgumentResolutionHint( val description: String, val event: Event, - val argument: Any? = null + val argument: Any? ) : ResolutionHint { val eventAndArgument = EventAndArgument(event, argument) } @@ -79,12 +74,31 @@ interface ExportMetaInfo : MetaInfo { * use [buildExportMetaInfo] instead. */ interface ExportMetaInfoBuilder : ExportMetaInfo { - override var resolutionHints: Set + /** See [StateResolutionHint] */ + fun addStateResolutionHint(description: String, targetState: IState) + + /** See [StateResolutionHint] */ + fun addStateResolutionHint(description: String, targetStates: Set) + + /** See [EventAndArgumentResolutionHint] */ + fun addEventAndArgumentResolutionHint(description: String, event: Event, argument: Any? = null) } private data class ExportMetaInfoBuilderImpl( - override var resolutionHints: Set = emptySet(), -) : ExportMetaInfoBuilder + override val resolutionHints: MutableSet = mutableSetOf(), +) : ExportMetaInfoBuilder { + override fun addEventAndArgumentResolutionHint(description: String, event: Event, argument: Any?) { + resolutionHints += EventAndArgumentResolutionHint(description, event, argument) + } + + override fun addStateResolutionHint(description: String, targetState: IState) { + resolutionHints += StateResolutionHint(description, setOf(targetState)) + } + + override fun addStateResolutionHint(description: String, targetStates: Set) { + resolutionHints += StateResolutionHint(description, targetStates) + } +} fun buildExportMetaInfo(builder: ExportMetaInfoBuilder.() -> Unit): ExportMetaInfo = ExportMetaInfoBuilderImpl().apply(builder).copy() \ No newline at end of file diff --git a/samples/src/commonMain/kotlin/ru/nsk/samples/MermaidExportSample.kt b/samples/src/commonMain/kotlin/ru/nsk/samples/MermaidExportSample.kt index 9bf147b..840e4a2 100644 --- a/samples/src/commonMain/kotlin/ru/nsk/samples/MermaidExportSample.kt +++ b/samples/src/commonMain/kotlin/ru/nsk/samples/MermaidExportSample.kt @@ -9,6 +9,7 @@ package ru.nsk.samples import kotlinx.coroutines.runBlocking import ru.nsk.kstatemachine.event.Event +import ru.nsk.kstatemachine.metainfo.UmlMetaInfo import ru.nsk.kstatemachine.state.* import ru.nsk.kstatemachine.statemachine.createStateMachine import ru.nsk.kstatemachine.visitors.export.exportToMermaid @@ -18,6 +19,9 @@ private object MermaidExportSample { object SwitchEvent : Event } +/** + * The sample shows basic Mermaid export + */ fun main() = runBlocking { val machine = createStateMachine(this, name = "Nested states") { val state1 = initialState("State1") diff --git a/samples/src/commonMain/kotlin/ru/nsk/samples/PlantUmlExportSample.kt b/samples/src/commonMain/kotlin/ru/nsk/samples/PlantUmlExportSample.kt index 6091265..5d35506 100644 --- a/samples/src/commonMain/kotlin/ru/nsk/samples/PlantUmlExportSample.kt +++ b/samples/src/commonMain/kotlin/ru/nsk/samples/PlantUmlExportSample.kt @@ -18,6 +18,9 @@ private object PlantUmlExportSample { object SwitchEvent : Event } +/** + * The sample shows basic PlantUML export + */ fun main() = runBlocking { val machine = createStateMachine(this, "Nested states") { val state1 = initialState("State1") diff --git a/samples/src/commonMain/kotlin/ru/nsk/samples/PlantUmlExportWithMetaInfoSample.kt b/samples/src/commonMain/kotlin/ru/nsk/samples/PlantUmlExportWithUmlMetaInfoSample.kt similarity index 72% rename from samples/src/commonMain/kotlin/ru/nsk/samples/PlantUmlExportWithMetaInfoSample.kt rename to samples/src/commonMain/kotlin/ru/nsk/samples/PlantUmlExportWithUmlMetaInfoSample.kt index eb79f5b..5ee204c 100644 --- a/samples/src/commonMain/kotlin/ru/nsk/samples/PlantUmlExportWithMetaInfoSample.kt +++ b/samples/src/commonMain/kotlin/ru/nsk/samples/PlantUmlExportWithUmlMetaInfoSample.kt @@ -9,43 +9,41 @@ package ru.nsk.samples import kotlinx.coroutines.runBlocking import ru.nsk.kstatemachine.event.Event -import ru.nsk.kstatemachine.metainfo.MetaInfo +import ru.nsk.kstatemachine.metainfo.UmlMetaInfo import ru.nsk.kstatemachine.metainfo.buildUmlMetaInfo -import ru.nsk.kstatemachine.state.State import ru.nsk.kstatemachine.state.finalState import ru.nsk.kstatemachine.state.initialState -import ru.nsk.kstatemachine.state.transitionOn +import ru.nsk.kstatemachine.state.transition import ru.nsk.kstatemachine.statemachine.createStateMachine import ru.nsk.kstatemachine.visitors.export.exportToPlantUml -import ru.nsk.samples.PlantUmlExportWithMetaInfoSample.SwitchEvent +import ru.nsk.samples.PlantUmlExportWithUmlMetaInfoSample.SwitchEvent -private object PlantUmlExportWithMetaInfoSample { +private object PlantUmlExportWithUmlMetaInfoSample { object SwitchEvent : Event } /** - * The sample shows hot to use [MetaInfo] to beautify export output + * The sample shows how to use [UmlMetaInfo] to beautify export output */ fun main() = runBlocking { - lateinit var state2: State val machine = createStateMachine(this) { metaInfo = buildUmlMetaInfo { umlLabel = "Nested states sm" } - initialState("State1") { - metaInfo = buildUmlMetaInfo { umlLabel = "State 1 Label" } - transitionOn { - metaInfo = buildUmlMetaInfo { umlLabel = "Transition to State 2" } - targetState = { state2 } - } - } - - state2 = finalState("State2") { + val state2 = finalState("State2") { metaInfo = buildUmlMetaInfo { umlLabel = "FinalState 2 Label" umlStateDescriptions = listOf("Description 1", "Description 2") umlNotes = listOf("Note 1", "Note 2") } } + + initialState("State1") { + metaInfo = buildUmlMetaInfo { umlLabel = "State 1 Label" } + transition { + metaInfo = buildUmlMetaInfo { umlLabel = "Transition to State 2" } + targetState = state2 + } + } } println(machine.exportToPlantUml(showEventLabels = true)) diff --git a/samples/src/commonMain/kotlin/ru/nsk/samples/PlantUmlUnsafeExportWithExportMetaInfoSample.kt b/samples/src/commonMain/kotlin/ru/nsk/samples/PlantUmlUnsafeExportWithExportMetaInfoSample.kt new file mode 100644 index 0000000..6dd27b0 --- /dev/null +++ b/samples/src/commonMain/kotlin/ru/nsk/samples/PlantUmlUnsafeExportWithExportMetaInfoSample.kt @@ -0,0 +1,62 @@ +/* + * Author: Mikhail Fedotov + * Github: https://github.com/KStateMachine + * Copyright (c) 2024. + * All rights reserved. + */ + +package ru.nsk.samples + +import kotlinx.coroutines.runBlocking +import ru.nsk.kstatemachine.event.Event +import ru.nsk.kstatemachine.metainfo.ExportMetaInfo +import ru.nsk.kstatemachine.metainfo.buildExportMetaInfo +import ru.nsk.kstatemachine.state.State +import ru.nsk.kstatemachine.state.finalState +import ru.nsk.kstatemachine.state.initialState +import ru.nsk.kstatemachine.state.transitionOn +import ru.nsk.kstatemachine.statemachine.createStateMachine +import ru.nsk.kstatemachine.visitors.export.exportToPlantUml +import ru.nsk.samples.PlantUmlUnsafeExportWithExportMetaInfoSample.FirstEvent +import ru.nsk.samples.PlantUmlUnsafeExportWithExportMetaInfoSample.SecondEvent + +private object PlantUmlUnsafeExportWithExportMetaInfoSample { + class FirstEvent(val data: Int) : Event + class SecondEvent(val data: Int) : Event +} + +/** + * The sample shows how to use [ExportMetaInfo] to get complete export output even with conditional lambdas. + * You have EventAndArgumentResolutionHint and EventAndArgumentResolutionHint alternative, you can choose one of them, + * or use them together mixing in a single metaInfo if necessary. + */ +fun main() = runBlocking { + lateinit var state2: State + lateinit var state3: State + val machine = createStateMachine(this) { + state2 = finalState("State2") + state3 = finalState("State3") + + initialState("State1") { + transitionOn { + metaInfo = buildExportMetaInfo { + // using EventAndArgumentResolutionHint calls targetState lambda with specified events + addEventAndArgumentResolutionHint("data == 42", FirstEvent(42)) + addEventAndArgumentResolutionHint("else", FirstEvent(42)) + } + targetState = { if (event.data == 42) state2 else state3 } + } + + transitionOn { + metaInfo = buildExportMetaInfo { + // using StateResolutionHint does not require targetState lambda call + addStateResolutionHint("data == 123", state2) + addStateResolutionHint("else", state3) + } + targetState = { if (event.data == 123) state2 else state3 } + } + } + } + + println(machine.exportToPlantUml(showEventLabels = true, unsafeCallConditionalLambdas = true)) +} \ No newline at end of file diff --git a/tests/src/commonTest/kotlin/ru/nsk/kstatemachine/metainfo/ExportMetaInfoTest.kt b/tests/src/commonTest/kotlin/ru/nsk/kstatemachine/metainfo/ExportMetaInfoTest.kt index 4e20b04..4b25962 100644 --- a/tests/src/commonTest/kotlin/ru/nsk/kstatemachine/metainfo/ExportMetaInfoTest.kt +++ b/tests/src/commonTest/kotlin/ru/nsk/kstatemachine/metainfo/ExportMetaInfoTest.kt @@ -114,10 +114,8 @@ private suspend fun createTestMachine(coroutineStarterType: CoroutineStarterType transitionOn { targetState = { if (event.value == 42) state11 else state12 } metaInfo = buildExportMetaInfo { - resolutionHints = setOf( - EventAndArgumentResolutionHint("if (event.value == 42)", ValueEvent1(42)), - EventAndArgumentResolutionHint("else", ValueEvent1(0)), - ) + addEventAndArgumentResolutionHint("if (event.value == 42)", ValueEvent1(42)) + addEventAndArgumentResolutionHint("else", ValueEvent1(0)) } } } @@ -134,21 +132,17 @@ private suspend fun createTestMachine(coroutineStarterType: CoroutineStarterType } } metaInfo = buildExportMetaInfo { - resolutionHints = setOf( - StateResolutionHint("when 1", state1), - EventAndArgumentResolutionHint("when 2", ValueEvent2(2)), - StateResolutionHint("when 3", setOf(state1, state2)), - StateResolutionHint("when 4", this@createTestStateMachine), - EventAndArgumentResolutionHint("else", ValueEvent2(5)), - ) + addStateResolutionHint("when 1", state1) + addEventAndArgumentResolutionHint("when 2", ValueEvent2(2)) + addStateResolutionHint("when 3", setOf(state1, state2)) + addStateResolutionHint("when 4", this@createTestStateMachine) + addEventAndArgumentResolutionHint("else", ValueEvent2(5)) } } val choiceState = choiceState("choiceState") { if (true) state1 else state2 } choiceState.metaInfo = buildExportMetaInfo { - resolutionHints = setOf( - StateResolutionHint("if (true)", state1), - StateResolutionHint(" ", state2), - ) + addStateResolutionHint("if (true)", state1) + addStateResolutionHint(" ", state2) } } }