Skip to content

Commit

Permalink
Add unsafe export sample
Browse files Browse the repository at this point in the history
  • Loading branch information
nsk90 committed Dec 16, 2024
1 parent 9f1e4f5 commit aaa74ad
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 77 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
32 changes: 16 additions & 16 deletions docs/pages/export.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -125,20 +125,20 @@ transitionConditionally<ValueEvent> {
}
}
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))
}
}
```
```

See [PlantUML with ExportMetaInfo unsafe export sample](https://github.com/KStateMachine/kstatemachine/tree/master/samples/src/commonMain/kotlin/ru/nsk/samples/PlantUmlUnsafeExportWithExportMetaInfoSample.kt)
25 changes: 7 additions & 18 deletions kstatemachine/api/kstatemachine.api
Original file line number Diff line number Diff line change
Expand Up @@ -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 <init> (Ljava/lang/String;Lru/nsk/kstatemachine/event/Event;Ljava/lang/Object;)V
public synthetic fun <init> (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 {
Expand All @@ -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 <init> (Ljava/lang/String;Ljava/util/Set;)V
public fun <init> (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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<IState>,
) : 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"
Expand All @@ -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)
}
Expand All @@ -79,12 +74,31 @@ interface ExportMetaInfo : MetaInfo {
* use [buildExportMetaInfo] instead.
*/
interface ExportMetaInfoBuilder : ExportMetaInfo {
override var resolutionHints: Set<ResolutionHint>
/** See [StateResolutionHint] */
fun addStateResolutionHint(description: String, targetState: IState)

/** See [StateResolutionHint] */
fun addStateResolutionHint(description: String, targetStates: Set<IState>)

/** See [EventAndArgumentResolutionHint] */
fun addEventAndArgumentResolutionHint(description: String, event: Event, argument: Any? = null)
}

private data class ExportMetaInfoBuilderImpl(
override var resolutionHints: Set<ResolutionHint> = emptySet(),
) : ExportMetaInfoBuilder
override val resolutionHints: MutableSet<ResolutionHint> = mutableSetOf<ResolutionHint>(),
) : 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<IState>) {
resolutionHints += StateResolutionHint(description, targetStates)
}
}

fun buildExportMetaInfo(builder: ExportMetaInfoBuilder.() -> Unit): ExportMetaInfo =
ExportMetaInfoBuilderImpl().apply(builder).copy()
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<SwitchEvent> {
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<SwitchEvent> {
metaInfo = buildUmlMetaInfo { umlLabel = "Transition to State 2" }
targetState = state2
}
}
}

println(machine.exportToPlantUml(showEventLabels = true))
Expand Down
Original file line number Diff line number Diff line change
@@ -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<FirstEvent> {
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<SecondEvent> {
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))
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,8 @@ private suspend fun createTestMachine(coroutineStarterType: CoroutineStarterType
transitionOn<ValueEvent1> {
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))
}
}
}
Expand All @@ -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)
}
}
}
Expand Down

0 comments on commit aaa74ad

Please sign in to comment.