Pieter van den Hombergh (homberghp) <p.vandenhombergh@fontys.nl> Version 1.1

StateWalker will simplify the coding of your (complex) state machines.

Statewalker implements a hierarchical state pattern variant by using a stack of states. It uses Java 8 features and works with Java 8 and up.

Statewalker is also about testable design, in particular testing stateful behaviour. Using Mockito to mock Context and Device will let you set up your tests very easily and achieve full coverage of the state behaviour implementation in the state enum.

The class diagram below shows the typical usage of statewalker, with its three main parts, ContextBase, Device, and StateBase.

class diagram

Class diagram.

Note that the design uses generics in a way that involves all three, <C,D,S> for *C*ontext, *D*evice, and *S*tate. It makes the design a bit harder to realise but usage bothy type safe and simpler.

An example of such a hierarchical state machine can be seen in the figure below Test state machine.

Stack wise, The child-most state is on top of all, in particular it’s super state (if any) below it and so on. To change a state the code typically uses one method: changeFromToState(String message, State from, State…​ to).

The source code and the separate statewalkertest project can be found on github at the url https://github.com/sebivenlo/statewalker.

Apply it to your project:

To make it work in your project, you have to do some work yourselves.

  1. Implement a Context that extends BaseContext like the code below.

  2. Extend a State interface with default method handlers for ALL events (methods) relevant to your state machine.

  3. Have a StateEnum implement this State interface. Since the interface has default methods only, a state only has to overwrite any method that is relevant to this state.

  4. You will also need a Device that can be asked from the context and can be manipulated (as in: call methods on it) in the states. One or more devices are typically the reason to have a state machine in the first place. the Device is the combination of all things that can be turned on and off and serve a common goal, such as brewing coffee at 6:00 in the morning.

What the 'device' is and what it can do is up to you. For a coffee machine it would be the boiler and the lights. For an elevator the door motor and elevator motor, lights etc.

The Context

class Context extends ContextBase<Context, Dev, State> {

  public Context( Class<?> stateClass ) {
          super( stateClass );
  }

  void e2() {
        getTopState().e2( this );
  } //etc
}

State interface

The major part of the concept is using default methods, provided since Java 8. Abstract (or non-abstract) would also work, but

  • you can only extend one class, so it would limit your design options and

  • you cannot have an enum extend a class, only implement interfaces.

statewalkertest State interface
package nl.fontys.sebivenlo.statewalkertest;

import nl.fontys.sebivenlo.statewalker.StateBase;

/**
 *
 * @author Pieter van den Hombergh {@code <pieter.van.den.hombergh@gmail.com>}
 */
public interface State extends StateBase<Context, Dev, State> {
    // <editor-fold formatter-rule="keep-off">
    default void  e1( Context ctx ) { ctx.superState( this ). e1( ctx ); }
    default void  e2( Context ctx ) { ctx.superState( this ). e2( ctx ); }
    default void  e3( Context ctx ) { ctx.superState( this ). e3( ctx ); }
    default void  e4( Context ctx ) { ctx.superState( this ). e4( ctx ); }
    default void  e5( Context ctx ) { ctx.superState( this ). e5( ctx ); }
    default void  e6( Context ctx ) { ctx.superState( this ). e6( ctx ); }
    default void  e7( Context ctx ) { ctx.superState( this ). e7( ctx ); }
    default void  e8( Context ctx ) { ctx.superState( this ). e8( ctx ); }
    default void  e9( Context ctx ) { ctx.superState( this ). e9( ctx ); }
    default void e10( Context ctx ) { ctx.superState( this ).e10( ctx ); }
    default void e11( Context ctx ) { ctx.superState( this ).e11( ctx ); }
    default void e12( Context ctx ) { ctx.superState( this ).e12( ctx ); }
    default void e13( Context ctx ) { ctx.superState( this ).e13( ctx ); }

    @Override
    default void exit( Context ctx ) {}

    @Override
    default void enter( Context ctx ) {}
    // </editor-fold>
}

As you can see all but the pseudo events reach the event (i.e. method call) down to the next state (the super-state) on the state stack.

Then have an enum implement this interface and give it at least a NULL value, which is placed on the bottom of the stack, will never be removed and is there to catch any event not handled by any of the sub states. This NULL is then the catch all implementation that does nothing. You could have NULL log received events, but not have it reaching down any.

MyState enum template
enum MyStates extends State {
    S1{
        ...
    },
    S2{
        ...
    },
    S33{

        @Override
        public void enter(Context ctx) {
            ctx.getDevice().heater(true); (1)
        }

        @Override
        public void exit(Context ctx) {
            ctx.getDevice().heater(false); (2)
        }

        @Override
        public void e10(Context ctx) {
            ctx.changeFromToState("e10", this, S31);(3)
        }
    }
    NULL{// handlers for all events with empty bodies
        void e1(Context ctx){}
        ...

            };
}

Explanation:

1 Turns the heater on on entry.
2 Turns the heater off on exit.
3 Changes state to S31, implicitly leaving this state which should automagically call exit on this state, which is S33 and thus turns off the heater.
the device class header
public class Dev implements Device<Context, Dev, State> {

For an example see the maven project statewalkertest in the repository, which implements the state bahavior in the Test state machine below:

Test state machine

State Walker test state machine.

The Java statewalker API doc is also available at API Docs. The maven generated site can be found at StateWalker Maven site.