13. Showcase

Showcase is a complex state machine showing all possible transition topologies up to four levels of state nesting.

statechart2

States. 

public static enum States {
    S0, S1, S11, S12, S2, S21, S211, S212
}

Events. 

public static 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));
    }
}

Lets go through what this state machine do when it’s executed and we send various event to it.

sm>sm start
Entry state S0
Entry state S1
Entry state S11
Init foo to 0
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
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: