User Guide Finite State Machine

Features

The FSM library aims to provide the following features:

  • Easy to use flat state machine for simple use cases. This type of FSM is taught in bachelor engineering courses.

  • Hierarchical state machine structure to ease complex state configuration. The hierarchical type of FSM is part of the UML notation.

  • Usage of events, transitions, guards and actions. Transition actions, state entry actions, and state exit actions are supported.

  • Builder pattern for easy creation of a complete state machine declaration using a fluent approach,

  • Recipes for usual use cases,

  • State machine event listeners,

  • Diagrams generators for Graphviz, plantUML and Mermaid diagrams are living documentation.

State machines are powerful because their behavior is always guaranteed to be consistent, making it relatively easy to debug. This is because operational rules are written in stone when the machine is started. The idea is that your application may exist in a finite number of states and certain predefined events can take your application from one state to the next. Such triggers can be based on either events or timers.

It is much easier to define high-level logic outside your application and then rely on the state machine to manage state. You can interact with the state machine by sending an event, listening for changes or simply request a current state.

Scenarios

Applications are a good candidate to use a finite state machine if:

  • Application or part of its structure can be represented as states.

  • You want to split complex logic into smaller manageable tasks.

  • Application is already suffering concurrency issues with i.e. some operations are happening asynchronously.

You are already trying to implement a finite state machine if you:

  • Use of boolean flags or enums to model situations and states.

  • Have variables which only have meaning for some part of your application lifecycle.

  • Loop through if/else structure and check if a particular flag or enum is set and then make further exceptions what to do when a certain combination of your flags and enums exists or does not exist together.

Finite state machines provide a clear and well-documented approach for complex logic with states.

Get Started

FSM finite state machine module supports both a fluent API and a more declarative manner to define a state machine, and also enables a user to define the action methods in a straightforward manner. Lambda constructs support a compact and legible notation.

Actions and guards declarations use the lambda feature of Java 8. Guard is a boolean function, and an action is a method without return value. Once a finite state machine is declared, you can execute multiple instances of the declaration in your application.

  • StateMachine interface takes free generic type parameters.

    • E stands for the type of implemented event. The event values must be a Java enumeration type. The enumeration type defines all the different event values

    • S stands for the type of implemented state. The state values must be a Java enumeration type. The enumeration type defines the possible states of the finite state machine

    • C stands for the class owning the state machine and defining the context in which the machine is executed.

  • States represent the system state available for some time.

    • A simple state is as a state without children and without history.

    • Composite state is a hierarchical state. It may contain nested simple or composite states. The composite children states may themselves have nested children. This may proceed to any depth. When a hierarchical state is active, one and only one of its child states is active

    • The initial state is the state selected when entering a composite state no having active history states

    • The final state is a state with only afferent transitions and no efferent transitions

    • State with history allows a composite state to remember its configuration. A transition taking the history composite state as its target will return the child state stored in the history as the last active state last time the target composite state was exited

  • Transitions represent a possible change from a start state to a destination state.

    • Normal transition moving from a source state to a destination state

    • Internal transition means after the transition completes, no state is excited or entered. Therefore, no entry or exit actions are executed

  • Guards are implemented as an extension of a standard Java BiPredicate functional interface. The parameters are the event triggering the guard and the context.

    • boolean expression lambda expression deciding if the transition shall be fired or not.

  • Actions are implemented as an extension of a standard Java BiFunction functional interface. The parameters are the event triggering the guard and the context.

    • Entry actions are executed upon activation of the state when the fired transition enters the state

    • Exit actions are executed upon deactivation of the state when the fired transition leaves the state

    • Transition actions are executed when a transition is fired

The design requires that the set of states and the set of events are declared as enumeration types. Enumeration types allow static checking at compile time. They diminish runtime errors.

Modern development environments allow fast round trip changes, therefore, static type declarations should not slow your development speed.

Below the UML diagram of the public interface of the finite state machine component.

fsm-userGuideFsm-Interface

Fluent Examples

How to create the state machine builder

In order to create a state machine, you need to create a state machine builder first. For example:

DefinitionBuilder<Owner, States, Events> builder = new DefinitionBuilder<>(States.Root);

After a state machine builder was created, we can use the fluent API to define states, transitions and actions of the state machine declaration. The internal objects are implicitly built during creation.

How to create states

Below how to create a state Off as substrate of the root state.

builder.root().add(States.Off)

Below two variants how to create an initial state with an entry and an exit action. The isInitial marker specifies that the state Off is an initial state.

builder.root().add(States.Off).isInitial().onEntry(Fsm::logOffEntry).onExit(Fsm::logOffExit);

builder.addToRoot(States.Off).isInitial().onEntry(Fsm::logOffEntry).onExit(Fsm::logOffExit);

Below how to create a nested initial state with an entry and an exit action. The state DAB is a substate of state On. The isInitial marker specifies that the state DAB is an initial state.

builder.in(States.On).add(States.DAB).isInitial().onEntry(Fsm::logDabEntry).onExit(Fsm::logDabExit);

How to create transitions

Below how to create a transition between two states with an event and an action – without a guard -. The transition starts on state Maintenance when the event TogglePower is received and finishes in state Off_. The action logTransitionFromMaintenanceToOff__ is executed when the transition is traversed.

builder.in(States.Maintenance).on(Events.TogglePower).to(States.Off).execute(Fsm::logTransitionFromMaintenanceToOff);

Below how to create a transition between two states with an event, a guard and an action. The first statement uses a lambda expression calling a method of the owner object. The second statement uses a lambda expression with a code block.

builder.in(States.Off).on(Events.TogglePower).to(States.Maintenance).onlyIf(Fsm::isMaintenanceMode).execute(Fsm::logTransitionFromOffToMaintenance);

builder.in(States.Off).on(Events.TogglePower).to(States.On).onlyIf((o) -> !o.isMaintenanceMode()).execute(Fsm::logTransitionFromOffToOn);

Below how to create a local transition in the state. A local transition does not trigger the exit and entry action of the state.

builder.in(States.On).onLocal(Events.StoreStation).execute(Fsm::logIgnoreStoreOperation);

An example of a complete finite state machine definition

The example below declares a complete state machine. The enumeration of States defines the states. The enumeration of Events defines the events recognized and processed through the finite state machine.

enum States {
    Root, Off, Maintenance, On, FM, DAB, Play, AutoTune,                                      (1)
}

enum Events {
    TogglePower, ToggleMode, StationLost, StationFound, StoreStation                          (2)
}

DefinitionBuilder<Owner, States, Events> builder = new DefinitionBuilder<>(States.Root);      (3)

builder.addToRoot(States.Off).isInitial().onEntry(Fsm::logOffEntry).onExit(Fsm::logOffExit);
builder.addToRoot(States.Maintenance).onEntry(Fsm::logMaintenanceEntry).onExit(Fsm::logMaintenanceExit);

builder.addToRoot(States.On).hasHistory().onEntry(Fsm::logOnEntry).onExit(Fsm::logOnExit);
builder.in(States.On).add(States.DAB).isInitial().onEntry(Fsm::logDabEntry).onExit(Fsm::logDabExit);
builder.in(States.DAB).onLocal(Events.StoreStation).execute((o, e) -> o.appendToLog("DABToDAB"));
builder.in(States.On).add(States.FM).hasHistory().onEntry(Fsm::logFmEntry).onExit(Fsm::logFmExit);

builder.in(States.FM).add(States.Play).isInitial().hasHistory().onEntry(Fsm::logPlayEntry).onExit(Fsm::logPlayExit);
builder.in(States.Play).onLocal(Events.StoreStation).execute((o, e) -> o.appendToLog("PlayToPlay"));
builder.in(States.FM).add(States.AutoTune).onEntry(Fsm::logAutoTuneEntry).onExit(Fsm::logAutoTuneExit);

builder.in(States.Off).on(Events.TogglePower).to(States.Maintenance).onlyIf(Fsm::isMaintenanceMode).execute(Fsm::logTransitionFromOffToMaintenance);
builder.in(States.Maintenance).on(Events.TogglePower).to(States.Off).execute(Fsm::logTransitionFromMaintenanceToOff);
builder.in(States.Off).on(Events.TogglePower).to(States.On).onlyIf((o) -> !o.isMaintenanceMode()).execute(Fsm::logTransitionFromOffToOn);
builder.in(States.On).on(Events.TogglePower).to(States.Off).execute(Fsm::logTransitionFromOnToOff);
builder.in(States.DAB).on(Events.ToggleMode).to(States.FM).execute(Fsm::logTransitionFromDabToFm);
builder.in(States.FM).on(Events.ToggleMode).to(States.DAB).execute(Fsm::logTransitionFromFmToDab);
builder.in(States.Play).on(Events.StationLost).to(States.AutoTune).execute(Fsm::logTransitionFromPlayToAutoTune);
builder.in(States.AutoTune).on(Events.StationFound).to(States.Play).execute(Fsm::logTransitionFromAutoTuneToPlay);

builder.machine("name of the machine", owner);                                                (4)
1 Defines the set of states for the finite state machine
2 Defines the set of events processed by the finite state machine
3 Creates a builder instance
4 Returns a finite state machine instance with the given human rea-able name and the given owning object, ready to process events

The above finite state machine description will generate the following machine.

fsm-userGuideFsm-DabFsm

How to create an instance of a finite state machine and fire events

After you have defined the state machine behavior in the description, you create a new state machine instance. The initial state of the machine is inferred from the definition of the state machine.

StateMachine<Fsm, States, Events> fsm = builder.machine("name-of-fsm", ownerInstance);

You can fire events with the statement:

fsm.fire(new Event<Events>(Events.TogglePower));

Advanced User Guide

Static Validation

The static validator verifies the syntax of finite state machine declaration. The implemented checks are

  • Each value of the state identifier enumeration is used exactly once in the declaration.

  • A state has at most one initial substate.

  • A state with a different transition has an initial substate.

  • The hierarchy of initial states allows a clean identification of the first state when the machine is reset to default.

  • A final state cannot have efferent transitions.

Dynamic Validation

The static validator verifies the semantics of a finite state machine during execution. The implemented checks are

  • To be written

Log a state machine instance

To be written

Documentation of State Machines

You can document your state machine declaration by

  • Generate a diagram in the Mermaid, plantUML or Graphviz language and visualize your state machine as a hierarchical graph.

  • Generate a textual description of the state machine declaration based on a tabular structure.

  • Add description to states, actions and guards directly in the builder. These descriptions are used to enrich the hierarchical graph.

We provide the finite state machine diagrams of the builder FSM and the Washer FSM as complete examples. Please consult the unit tests for the complete source code. Use JUnit 5 to run the state machine programs.