Showcase is a complex state machine showing all possible transition topologies up to four levels of state nesting.
States.
public enum States { S0, S1, S11, S12, S2, S21, S211, S212 }
Events.
public enum Events {
A, B, C, D, E, F, G, H, I
}
Configuration - states.
@Override public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception { states .withStates() .initial(States.S0, fooAction()) .state(States.S0) .and() .withStates() .parent(States.S0) .initial(States.S1) .state(States.S1) .and() .withStates() .parent(States.S1) .initial(States.S11) .state(States.S11) .state(States.S12) .and() .withStates() .parent(States.S0) .state(States.S2) .and() .withStates() .parent(States.S2) .initial(States.S21) .state(States.S21) .and() .withStates() .parent(States.S21) .initial(States.S211) .state(States.S211) .state(States.S212); }
Configuration - transitions.
@Override public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception { transitions .withExternal() .source(States.S1).target(States.S1).event(Events.A) .guard(foo1Guard()) .and() .withExternal() .source(States.S1).target(States.S11).event(Events.B) .and() .withExternal() .source(States.S21).target(States.S211).event(Events.B) .and() .withExternal() .source(States.S1).target(States.S2).event(Events.C) .and() .withExternal() .source(States.S2).target(States.S1).event(Events.C) .and() .withExternal() .source(States.S1).target(States.S0).event(Events.D) .and() .withExternal() .source(States.S211).target(States.S21).event(Events.D) .and() .withExternal() .source(States.S0).target(States.S211).event(Events.E) .and() .withExternal() .source(States.S1).target(States.S211).event(Events.F) .and() .withExternal() .source(States.S2).target(States.S11).event(Events.F) .and() .withExternal() .source(States.S11).target(States.S211).event(Events.G) .and() .withExternal() .source(States.S211).target(States.S0).event(Events.G) .and() .withInternal() .source(States.S0).event(Events.H) .guard(foo0Guard()) .action(fooAction()) .and() .withInternal() .source(States.S2).event(Events.H) .guard(foo1Guard()) .action(fooAction()) .and() .withInternal() .source(States.S1).event(Events.H) .and() .withExternal() .source(States.S11).target(States.S12).event(Events.I) .and() .withExternal() .source(States.S211).target(States.S212).event(Events.I) .and() .withExternal() .source(States.S12).target(States.S212).event(Events.I); }
Configuration - actions and guard.
@Bean public FooGuard foo0Guard() { return new FooGuard(0); } @Bean public FooGuard foo1Guard() { return new FooGuard(1); } @Bean public FooAction fooAction() { return new FooAction(); }
Action.
private static class FooAction implements Action<States, Events> { @Override public void execute(StateContext<States, Events> context) { Map<Object, Object> variables = context.getExtendedState().getVariables(); Integer foo = context.getExtendedState().get("foo", Integer.class); if (foo == null) { log.info("Init foo to 0"); variables.put("foo", 0); } else if (foo == 0) { log.info("Switch foo to 1"); variables.put("foo", 1); } else if (foo == 1) { log.info("Switch foo to 0"); variables.put("foo", 0); } } }
Guard.
private static class FooGuard implements Guard<States, Events> { private final int match; public FooGuard(int match) { this.match = match; } @Override public boolean evaluate(StateContext<States, Events> context) { Object foo = context.getExtendedState().getVariables().get("foo"); return !(foo == null || !foo.equals(match)); } }
Let’s go through what this state machine do when it’s executed and we send various event to it.
sm>sm start Init foo to 0 Entry state S0 Entry state S1 Entry state S11 State machine started sm>sm event A Event A send sm>sm event C Exit state S11 Exit state S1 Entry state S2 Entry state S21 Entry state S211 Event C send sm>sm event H Switch foo to 1 Internal transition source=S0 Event H send sm>sm event C Exit state S211 Exit state S21 Exit state S2 Entry state S1 Entry state S11 Event C send sm>sm event A Exit state S11 Exit state S1 Entry state S1 Entry state S11 Event A send
What happens in above sample:
foo
is
init to 0
.
foo
to
be 1
.
foo
. Then we simply go back using event C.
Let’s take closer look of how hierarchical states and their event handling works with a below example.
sm>sm variables No variables sm>sm start Init foo to 0 Entry state S0 Entry state S1 Entry state S11 State machine started sm>sm variables foo=0 sm>sm event H Internal transition source=S1 Event H send sm>sm variables foo=0 sm>sm event C Exit state S11 Exit state S1 Entry state S2 Entry state S21 Entry state S211 Event C send sm>sm variables foo=0 sm>sm event H Switch foo to 1 Internal transition source=S0 Event H send sm>sm variables foo=1 sm>sm event H Switch foo to 0 Internal transition source=S2 Event H send sm>sm variables foo=0
What happens in above sample:
foo
flag
is always flipped around. However in state S1 event H always
match to its dummy transition without guard or action, not never
happens.