Skip to content

Commit

Permalink
Add metaInfo notes
Browse files Browse the repository at this point in the history
  • Loading branch information
nsk90 committed Feb 16, 2024
1 parent 05dcac3 commit 571920c
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 51 deletions.
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
package ru.nsk.kstatemachine

/**
* Additional static (designed to be immutable) info for library primitives.
* Users may extend this interface to add their own [MetaInfo] implementations
* Additional static (designed to be immutable) info for library primitives like [IState] [Transition] etc.
* Users may extend this interface to add their own [MetaInfo] implementations.
*/
interface MetaInfo

/**
* Standard meta info, to control export PlantUML and Mermaid feature visualization.
* Standard [MetaInfo], to control export PlantUML and Mermaid feature visualization.
*/
interface IUmlMetaInfo : MetaInfo {
/**
* Will be mapped to "long name" for state, and a "label" for transition
* Will be mapped to "long name" for [IState], and a "label" for [Transition]
*/
val umlLabel: String?
val stateDescriptions: List<String>
val notes: List<String>
/**
* Add description lines for [IState]
* Does not have effect for [Transition]
*/
val umlStateDescriptions: List<String>

/**
* For [IState] translated to "note right of".
* For [Transition] translated to "note on link" (supports only one note).
* Mermaid does not support this, so it will not take any effect.
*/
val umlNotes: List<String>
}

/**
Expand All @@ -24,6 +34,6 @@ interface IUmlMetaInfo : MetaInfo {
*/
data class UmlMetaInfo(
override val umlLabel: String? = null,
override val stateDescriptions: List<String> = emptyList(),
override val notes: List<String> = emptyList(),
override val umlStateDescriptions: List<String> = emptyList(),
override val umlNotes: List<String> = emptyList(),
): IUmlMetaInfo
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,15 @@ internal class ExportPlantUmlVisitor(
@Suppress("UNCHECKED_CAST")
val targetStates = state.resolveTargetState(makeDirectionProducerPolicy<Event>())
.targetStates as Set<InternalState>
state.printStateNotes()
targetStates.forEach { targetState ->
crossLevelTransitions += "${state.graphName()} --> ${targetState.targetGraphName()}"
}
}
else -> {
line("state ${state.labelGraphName()}")
state.printStateDescriptions()
state.printStateNotes()
}
}
} else {
Expand Down Expand Up @@ -107,6 +109,14 @@ internal class ExportPlantUmlVisitor(
else
crossLevelTransitions += transitionString
}

if (format != MERMAID) { // Mermaid does not support this
transition.metaInfo?.umlNotes?.forEach {
line("note on link")
line("$SINGLE_INDENT$it")
line("end note")
}
}
}

private fun <E : Event> makeDirectionProducerPolicy(): TransitionDirectionProducerPolicy<E> {
Expand All @@ -120,6 +130,8 @@ internal class ExportPlantUmlVisitor(

private suspend fun processStateBody(state: IState) {
state.printStateDescriptions()
state.printStateNotes()

val states = state.states.toList()
// visit child states
for (s in states.indices) {
Expand Down Expand Up @@ -153,11 +165,16 @@ internal class ExportPlantUmlVisitor(
}

private fun IState.printStateDescriptions() {
val stateDescriptions = (metaInfo as? IUmlMetaInfo)?.stateDescriptions.orEmpty()
stateDescriptions.forEach { line("${graphName()} : $it") }
val descriptions = (metaInfo as? IUmlMetaInfo)?.umlStateDescriptions.orEmpty()
descriptions.forEach { line("${graphName()} : $it") }
}

private fun IState.printStateNotes() {
metaInfo?.umlNotes?.forEach { line("note right of ${graphName()} : $it") }
}

private companion object {
val MetaInfo.umlNotes get() = (this as? IUmlMetaInfo)?.umlNotes.orEmpty()
val MetaInfo.umlLabel get() = (this as? IUmlMetaInfo)?.umlLabel

fun IState.graphName(): String {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,50 +13,26 @@ private object PlantUmlExportWithMetaInfoSample {
* The sample shows hot to use [MetaInfo] to beautify export output
*/
fun main() = runBlocking {
lateinit var state2: State
val machine = createStateMachine(this) {
// label for state machine
metaInfo = UmlMetaInfo("Nested states sm")
metaInfo = UmlMetaInfo(umlLabel = "Nested states sm")

val state1 = initialState("State1") {
// label for state
metaInfo = UmlMetaInfo("State 1")
}
val state3 = finalState("State3") {
// label for state
metaInfo = UmlMetaInfo("State 3")
}

val state2 = state("State2") {
// label for state
metaInfo = UmlMetaInfo("State 2")
transition<SwitchEvent> {
// label for transition
metaInfo = UmlMetaInfo("That's all")
targetState = state3
}
transition<SwitchEvent> {
// label for transition
metaInfo = UmlMetaInfo("back to State 1")
targetState = state1
}
val finalSubState = finalState {
// label for state
metaInfo = UmlMetaInfo("Final sub state")
}
initialState("Initial subState") {
transition<SwitchEvent> { targetState = finalSubState }
initialState("State1") {
metaInfo = UmlMetaInfo("State 1 Label")
transitionOn<SwitchEvent> {
metaInfo = UmlMetaInfo("Transition to State 2")
targetState = { state2 }
}
}

state1 {
transition<SwitchEvent> {
metaInfo = UmlMetaInfo("go to ${state2.name}")
targetState = state2
}
transition<SwitchEvent> { targetState = this@state1 }
transition<SwitchEvent>()
state2 = finalState("State2") {
metaInfo = UmlMetaInfo(
umlLabel = "FinalState 2 Label",
umlStateDescriptions = listOf("Description 1", "Description 2"),
umlNotes = listOf("Note 1", "Note 2"),
)
}
}

println(machine.exportToPlantUml())
println(machine.exportToPlantUml(showEventLabels = true))
}
Original file line number Diff line number Diff line change
Expand Up @@ -191,12 +191,16 @@ hide empty description
state State_1 {
state State12
state "Choice label" as ChoiceState <<choice>>
note right of ChoiceState : Note 1
note right of ChoiceState : Note 2
[*] --> ChoiceState
}
state "Long State 3" as State3
State3 : Description 1
State3 : Description 2
note right of State3 : Note 1
note right of State3 : Note 2
state "Long State 2" as State2 {
state "Final sub state" as FinalState
state Initial_subState
Expand All @@ -209,6 +213,12 @@ state "Long State 2" as State2 {
[*] --> State_1
State_1 --> State2 : go to State2, SwitchEvent
State_1 --> State_1 : self targeted, SwitchEvent
note on link
Note 1
end note
note on link
Note 2
end note
State2 --> State3 : That's all, SwitchEvent
State2 --> State_1 : back to State 1, SwitchEvent
State3 --> [*]
Expand Down Expand Up @@ -375,7 +385,8 @@ class ExportToPlantUmlTest : StringSpec({
// label for state
metaInfo = UmlMetaInfo(
umlLabel = "Long State 3",
stateDescriptions = listOf("Description 1", "Description 2")
umlStateDescriptions = listOf("Description 1", "Description 2"),
umlNotes = listOf("Note 1", "Note 2"),
)
}

Expand Down Expand Up @@ -406,15 +417,19 @@ class ExportToPlantUmlTest : StringSpec({
metaInfo = UmlMetaInfo("go to ${state2.name}")
targetState = state2
}
transition<SwitchEvent>("self targeted") { targetState = this@state1 }
transition<SwitchEvent>("self targeted") {
targetState = this@state1
metaInfo = UmlMetaInfo(umlNotes = listOf("Note 1", "Note 2"))
}
transition<SwitchEvent>()

val state12 = state("State12")
val choiceState = initialChoiceState("ChoiceState") { state12 }
choiceState.metaInfo = UmlMetaInfo(
umlLabel = "Choice label",
// no plantUml nor Mermaid can draw this
stateDescriptions = listOf("Description 1", "Description 2")
umlStateDescriptions = listOf("Description 1", "Description 2"),
umlNotes = listOf("Note 1", "Note 2")
)
}
}
Expand Down

0 comments on commit 571920c

Please sign in to comment.