This appendix provides generic information about state machines.
Assuming we have states STATE1, STATE2 and events EVENT1, EVENT2, logic of state machine can be defined as shown in below quick example.
public enum States { STATE1, STATE2 } public enum Events { EVENT1, EVENT2 }
@Configuration @EnableStateMachine public class Config1 extends EnumStateMachineConfigurerAdapter<States, Events> { @Override public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception { states .withStates() .initial(States.STATE1) .states(EnumSet.allOf(States.class)); } @Override public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception { transitions .withExternal() .source(States.STATE1).target(States.STATE2) .event(Events.EVENT1) .and() .withExternal() .source(States.STATE2).target(States.STATE1) .event(Events.EVENT2); } }
@WithStateMachine public class MyBean { @OnTransition(target = "STATE1") void toState1() { } @OnTransition(target = "STATE2") void toState2() { } }
public class MyApp { @Autowired StateMachine<States, Events> stateMachine; void doSignals() { stateMachine.sendEvent(Events.EVENT1); stateMachine.sendEvent(Events.EVENT2); } }
This appendix provides generic crash course to a state machine concepts.
A state is a model which a state machine can be in. It is always easier to describe state as a real world example rather than trying to abstract concepts with a generic documentation. For example let’s take a simple example of a keyboard most of us are using every single day. If you have a full keyboard which has normal keys on a left side and the numeric keypad on a right side you may have noticed that the numeric keypad may be in a two different states depending whether numlock is activated or not. If it is not active then typing will result navigation using arrows, etc. If numpad is active then typing will result numbers to be used. Essentially numpad part of a keyboard can be in two different states.
To relate state concept to programming it means that instead of using flags, nested if/else/break clauses or other impractical logic you simply rely on state, state variables or other interaction with a state machine.
PseudoState is a special type of state which usually introduces more higher level logic into a state machine by either giving a state a special meaning like initial state. State machine can then internally react to these states by doing various actions available in UML state machine concepts.
Initial pseudostate state is always needed for every single state machine whether you have a simple one level state machine or more complex state machine composed with submachines or regions. Initial state simple defines where state machine should go when it starts and without it state machine is ill-formed.
Terminate pseudostate which is also called as end state will indicate that a particular state machine has reached its final state. Effectively this mean that a state machine will no longer process any events and will not transit to any other state. However in a case of submachines are regions, state machine is able to restart from its terminal state.
Choice pseudostate is used to choose a dynamic conditional branch of a transition from this state. Dynamic condition is evaluated by guards so that at least one and at most one branch is selected. Usually a simple if/elseif/else structure is used to make sure that at least one branch is selected. Otherwise state machine might end up in a deadlock and configuration would be ill-formed.
Junction pseudostate is functionally similar than choice as both are implemented with if/elseif/else structure. Only real difference is that junction allows multiple incoming transitions while choice only allows one. Thus difference is purely academic but have some differences i.e. when state machine is designed using real UI modeling framework.
History pseudostate can be used to remember a last active state configuration. After state machine has been exited, history state can be used to restore previous knows configuration. There are two types of history states available, SHALLOW only remember active state of a state machine itself while DEEP also remembers nested states.
History state could be implemented externally by listening state machine events but this would soon make logic very difficult to work with, especially if state machine contains complex nested structures. Letting state machine itself to handle recording of history states makes things much simpler. What is left for user to do is simply do a transition into a history state and state machine will hand the needed logic to go back to its last known recorded state.
In cases where a Transition terminates on a history state when the state has not been entered before (i.e., no prior history) or it had reached its End State, there is an option to force a transition to a specific substate, using the default history mechanism. This is a Transition that originates in the history state and terminates on a specific Vertex (the default history state) of the Region containing the history state. This Transition is only taken if execution leads to the history state and the state had never been active before. Otherwise, the normal history entry into the Region is executed. If no default history transition is defined, then standard default entry of the region is performed.
Fork pseudostate can be used to do an explicit entry into one or more regions.
Target state can be a parent state hosting regions, which simply means that regions are activated by entering its initial states. It’s also possible to add targets directly to any state in a region which allows more controlled entry into a state.
Join pseudostate is used to merge several transitions together originating from different regions. It is generally used to wait and block for participating regions to get into its join target states.
Source state can be a parent state hosting regions, which means that join states will be a terminate states of a participating regions. It’s also possible to define source states to be any state in a regions which allows controlled exit from a regions.
An Entry Point pseudostate represents an entry point for a state machine or a composite state that provides encapsulation of the insides of the state or state machine. In each region of the state machine or composite state owning the Entry Point , there is at most a single transition from the entry point to a Vertex within that Region.
An Exit Point pseudostate is an exit point of a state machine or composite state that provides encapsulation of the insides of the state or state machine. Transitions terminating on an Exit Point within any region of the composite state or a state machine referenced by a submachine state implies exiting of this composite state or submachine state (with execution of its associated exit behavior).
Guard conditions are expressions which evaluates either to TRUE or FALSE based on extended state variables and event parameters. Guards are used with actions and transitions to dynamically choose if particular action or transition should be executed. Aspects of guards, event parameters and extended state variables are simply to make state machine design much more simple.
Event is the most used trigger behaviour to drive a state machine. There are other ways to trigger behaviour to happen in state machine like a timer but events are the ones which really allows user to interact with a state machine. Events are also called as signals to possibly alter a state machine state.
A transition is a relationship between a source state and a target state. A switch from a state to another is a state transition caused by a trigger.
Internal transition is used when action needs to be executed without causing a state transition. With internal transition source and target state is always a same and it is identical with self-transition in the absence of state entry and exit actions.
Most of the cases external and local transition are functionally equivalent except in cases where transition is happening between super and sub states. Local transition doesn’t cause exit and entry to source state if target state is a substate of a source state. Other way around, local transition doesn’t cause exit and entry to target state if target is a superstate of a source state.
Above image shows a different between local and external transitions with a very simplistic super and sub states.
Actions are the ones which really glues state machine state changes with a user’s own code. State machine can execute action on various changes and steps in a state machine like entering or exiting a state, or doing a state transition.
Actions usually have access to a state context which gives running code a choice to interact with a state machine in a various ways. State context i.e. is exposing a whole state machine so user can access extended state variables, event headers if transition is based on an event, or actual transition where it is possible to see more detailed where this state change is coming from and where it is going.
Concept of a hierarchical state machine is used to simplify state design when particular states can only exist together.
Hierarchical states are really an innovation in UML state machine over a traditional state machines like Mealy or Moore machines. Hierarchical states allows to define some level of abstraction is a sense how java developer would define a class structure with abstract classes. For example having a nested state machine user is able to define transition on a multiple level of states possibly with a different conditions. State machine will always try to see if current state is able to handle an event together with a transition guard conditions. If these conditions are not evaluated to true, state machine will simply see what a super state can handle.
Regions which are also called as orthogonal regions are usually viewed as exclusive OR operation applied to a states. Concept of a region in terms of a state machine is usually a little difficult to understand but things gets a little simpler with a simple example.
Some of us have a full size keyboard with main keys on a left side and numeric keys on a right side. You’ve probably noticed that both sides really have their own state which you see if you press a numlock key which only alters behaviour of numbad itself. If you don’t have a full size keyboard you can buy a simple external usb numbad having only numbad part of a keys. If left and right side can freely exist without the other they must have a totally different states which means they are operating on different state machines.
It would be a little inconvenient to handle two different state machines as totally separate entities because in a sense they are still working together in a sense. This is why orthogonal regions can combine together a multiple simultaneous states within a single state in a state machine.