A lightweight finite state machine featuring transition remarks and dry-run capability, inspired by Tinder's StateMachine.
Define and do state transition on an FSM for H2O, you can get full codes here.
sealed class State {
object Solid : State()
object Liquid : State()
object Vapour : State()
override fun toString(): String {
return this.javaClass.simpleName
}
}
sealed class Event {
object Melt : Event()
object Freeze : Event()
object Vaporize : Event()
object Condense : Event()
object Sublimate : Event()
object Deposit : Event()
override fun toString(): String {
return this.javaClass.simpleName
}
}
sealed class SideEffect {
// absorb heat
object Endothermic : SideEffect()
// release heat
object Exothermic : SideEffect()
override fun toString(): String {
return this.javaClass.simpleName
}
}
StateMachine.create {
initialState(State.Liquid)
state<State.Liquid> {
on<Event.Freeze> {
transitionTo(state = State.Solid, sideEffect = SideEffect.Exothermic)
}
on<Event.Vaporize> {
transitionTo(state = State.Vapour, sideEffect = SideEffect.Endothermic)
}
}
state<State.Solid> {
on<Event.Melt> {
transitionTo(state = State.Liquid, sideEffect = SideEffect.Endothermic)
}
on<Event.Sublimate> {
transitionTo(state = State.Vapour, sideEffect = SideEffect.Endothermic)
}
}
state<State.Vapour> {
on<Event.Condense> {
transitionTo(state = State.Liquid, sideEffect = SideEffect.Exothermic)
}
on<Event.Deposit> {
transitionTo(state = State.Solid, sideEffect = SideEffect.Exothermic)
}
}
onTransition {
val validTransition =
it as? StateMachine.Transition.Valid ?: throw kotlin.Exception("Invalid transition")
println(
"transition" +
" from state ${validTransition.fromState}" +
" to state ${validTransition.toState}" +
" on ${validTransition.event}" +
" with side effect ${validTransition.sideEffect}"
)
when (validTransition.sideEffect) {
SideEffect.Exothermic -> {
println("Release heat 🔥🔥🔥 " + (validTransition.eventRemark ?: ""))
}
SideEffect.Endothermic -> {
println("Absorb heat ❄️❄️❄️ " + (validTransition.eventRemark ?: ""))
}
else -> {
println("unexpected side effect ${validTransition.sideEffect}")
}
}
}
}
class StateMachineTest {
@Test
fun test() {
val fsm = h2oFsm()
// dry run transition valid: false, current state: Liquid
val dryRunTransition1 = fsm.dryRunTransition(event = Event.Condense, eventRemark = "it shouldn't happen!")
println("dry run transition valid: ${dryRunTransition1 is StateMachine.Transition.Valid}, current state: ${fsm.state}")
println()
// dry run transition valid: true, current state: Liquid
val dryRunTransition2 = fsm.dryRunTransition(event = Event.Freeze)
println("dry run transition valid: ${dryRunTransition2 is StateMachine.Transition.Valid}, current state: ${fsm.state}")
println()
// transition exception: Invalid transition
// transition valid: false, current state: Liquid
val transition1Valid = try {
val transition = fsm.transition(event = Event.Condense, eventRemark = "it shouldn't happen!")
transition is StateMachine.Transition.Valid
} catch (e: Exception) {
println("transition exception: ${e.localizedMessage}")
false
}
println("transition valid: ${transition1Valid}, current state: ${fsm.state}")
println()
// transition from state Liquid to state Solid on Freeze with side effect Exothermic
// Release heat 🔥🔥🔥 for that it's -2 degree Celsius outside!
// transition valid: true, current state: Solid
val transition2Valid = try {
val transition = fsm.transition(
event = Event.Freeze,
eventRemark = "for that it's -2 degree Celsius outside!"
)
transition is StateMachine.Transition.Valid
} catch (e: Exception) {
println("transition exception: ${e.localizedMessage}")
false
}
println("transition valid: ${transition2Valid}, current state: ${fsm.state}")
println()
// transition from state Solid to state Vapour on Sublimate with side effect Endothermic
// Absorb heat ❄️❄️❄️ for we hava a frozen wet cloth dries in cold winter air.
// transition valid: true, current state: Vapour
val transition3Valid = try {
val transition = fsm.transition(
event = Event.Sublimate,
eventRemark = "for we hava a frozen wet cloth dries in cold winter air."
)
transition is StateMachine.Transition.Valid
} catch (e: Exception) {
println("transition exception: ${e.localizedMessage}")
false
}
println("transition valid: ${transition3Valid}, current state: ${fsm.state}")
println()
}
}