diff --git a/docs/src/reference/asciidoc/faq.adoc b/docs/src/reference/asciidoc/faq.adoc index 5f00a7ef..d0b56698 100644 --- a/docs/src/reference/asciidoc/faq.adoc +++ b/docs/src/reference/asciidoc/faq.adoc @@ -21,12 +21,6 @@ There are few choices a state machine developer can choose. state transition into a next state when state has entry and its actions has been completed. -.How do I defer an event - -{zwsp} + - -For more complete example and explanation, see cdplayer sample. - == Extented State .How I can initialise variables on state machine start diff --git a/docs/src/reference/asciidoc/images/statechart2.png b/docs/src/reference/asciidoc/images/statechart2.png index a855d1c7..aa407e93 100644 Binary files a/docs/src/reference/asciidoc/images/statechart2.png and b/docs/src/reference/asciidoc/images/statechart2.png differ diff --git a/docs/src/reference/asciidoc/sm-examples.adoc b/docs/src/reference/asciidoc/sm-examples.adoc index c5d44e72..4e2af932 100644 --- a/docs/src/reference/asciidoc/sm-examples.adoc +++ b/docs/src/reference/asciidoc/sm-examples.adoc @@ -123,6 +123,72 @@ include::samples/demo/showcase/Application.java[tags=snippetD] include::samples/demo/showcase/Application.java[tags=snippetE] ---- +Lets go throught what this state machine do when it's executed and we +send various event to it. + +[source,text] +---- +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: + +* State machine is started which takes it to its initial state _S11_ + via superstates _S1_ and _S0_. Also extended state variable `foo` is + init to `0`. +* We try to execute self transition in state _S1_ with event _A_ but + nothing happens because transition is guarded by variable `foo` to + be `1`. +* We send event _C_ which takes us to other state machine where + initial state _S211_ and its superstates are entered. In there we + can use event _H_ which does a simple internal transition to flip + variable `foo`. Then we simply go back using event _C_. +* Event _A_ is sent again and now _S1_ does a self transition because + guard evaluates true. +* It's also worth to pay attention to how event _H_ is handled in + different states _S0_, _S1_ and _S2_. This is a good example of how + hierarchical states and their event handling works. If state _S2_ is + unable to handle event _H_ due to guard condition, its parent is + checked next. This guarantees that while on state _S2_, `foo` flag + is always flipped around. However in state _S1_ event _H_ always + match to its dummy transtion without guard or action, not never + happens. + == CD Player CD Player is a sample which resembles better use case of most of use have used in a real world. CD Player itself is a really simple entity where @@ -271,3 +337,30 @@ event sent to a state machine. include::samples/demo/cdplayer/Application.java[tags=snippetL] ---- +One other important aspect of a state machines is that they have their +own responsibilies mostly around handling states and all application +level logic should be kept outside. This means that application needs +to have a ways to interact with a state machine and below sample is +how cdplayer does it order to update lcd status. Also pay attention +that we annotated _CdPlayer_ with _@WithStateMachine_ which instructs +state machine to find methods from your pojo which are then called +with various transitions. + +[source,java,indent=0] +---- +include::samples/demo/cdplayer/CdPlayer.java[tags=snippetA] +---- + +In above example we use _@OnTransition_ annotation to hook a callback +when transition happens with a target state _BUSY_. + +[source,java,indent=0] +---- +include::samples/demo/cdplayer/CdPlayer.java[tags=snippetB] +---- + +_@OnTransition_ we used above can only be used with strings which are +matched from enums. _@StatesOnTransition_ is then something what user +can create into his own application to get a type safe annotation where +a real enums can be used. + diff --git a/spring-statemachine-samples/cdplayer/src/main/java/demo/cdplayer/CdPlayer.java b/spring-statemachine-samples/cdplayer/src/main/java/demo/cdplayer/CdPlayer.java index 6a2e97cd..01dd4191 100644 --- a/spring-statemachine-samples/cdplayer/src/main/java/demo/cdplayer/CdPlayer.java +++ b/spring-statemachine-samples/cdplayer/src/main/java/demo/cdplayer/CdPlayer.java @@ -63,6 +63,7 @@ public class CdPlayer { return cdStatus + " " + trackStatus; } +//tag::snippetA[] @OnTransition(target = "BUSY") public void busy(ExtendedState extendedState) { Object cd = extendedState.getVariables().get(Variables.CD); @@ -70,6 +71,7 @@ public class CdPlayer { cdStatus = ((Cd)cd).getName(); } } +//end::snippetA[] @StatesOnTransition(target = States.PLAYING) public void playing(ExtendedState extendedState) { @@ -88,6 +90,7 @@ public class CdPlayer { cdStatus = "Open"; } +//tag::snippetB[] @StatesOnTransition(target = States.CLOSED) public void closed(ExtendedState extendedState) { Object cd = extendedState.getVariables().get(Variables.CD); @@ -98,5 +101,6 @@ public class CdPlayer { } trackStatus = ""; } +//end::snippetB[] } diff --git a/spring-statemachine-samples/showcase/src/main/java/demo/showcase/Application.java b/spring-statemachine-samples/showcase/src/main/java/demo/showcase/Application.java index 3757eeb2..3e69b9cd 100644 --- a/spring-statemachine-samples/showcase/src/main/java/demo/showcase/Application.java +++ b/spring-statemachine-samples/showcase/src/main/java/demo/showcase/Application.java @@ -1,5 +1,9 @@ package demo.showcase; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.shell.Bootstrap; @@ -14,6 +18,8 @@ import org.springframework.statemachine.guard.Guard; @Configuration public class Application { + private final static Log log = LogFactory.getLog(Application.class); + @Configuration @EnableStateMachine static class StateMachineConfig @@ -51,7 +57,8 @@ public class Application { .withStates() .parent(States.S21) .initial(States.S211) - .state(States.S211); + .state(States.S211) + .state(States.S212); } //end::snippetAA[] @@ -62,7 +69,7 @@ public class Application { transitions .withExternal() .source(States.S1).target(States.S1).event(Events.A) - .action(fooAction()) + .guard(foo1Guard()) .and() .withExternal() .source(States.S1).target(States.S11).event(Events.B) @@ -111,7 +118,13 @@ public class Application { .source(States.S1).event(Events.H) .and() .withExternal() - .source(States.S11).target(States.S12).event(Events.I); + .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); } //end::snippetAB[] @@ -137,7 +150,7 @@ public class Application { //tag::snippetB[] public static enum States { - S0, S1, S11, S12, S2, S21, S211 + S0, S1, S11, S12, S2, S21, S211, S212 } //end::snippetB[] @@ -152,13 +165,17 @@ public class Application { @Override public void execute(StateContext context) { - Object foo = context.getExtendedState().getVariables().get("foo"); + Map variables = context.getExtendedState().getVariables(); + Object foo = variables.get("foo"); if (foo instanceof Integer && ((Integer)foo) == 0) { - context.getExtendedState().getVariables().put("foo", 1); + log.info("Switch foo to 1"); + variables.put("foo", 1); } else if (foo instanceof Integer && ((Integer)foo) == 1) { - context.getExtendedState().getVariables().put("foo", 0); + log.info("Switch foo to 0"); + variables.put("foo", 0); } else { - context.getExtendedState().getVariables().put("foo", 0); + log.info("Init foo to 0"); + variables.put("foo", 0); } } } diff --git a/spring-statemachine-samples/showcase/src/main/resources/statechartmodel.txt b/spring-statemachine-samples/showcase/src/main/resources/statechartmodel.txt index 63067a88..fd9c48b7 100644 --- a/spring-statemachine-samples/showcase/src/main/resources/statechartmodel.txt +++ b/spring-statemachine-samples/showcase/src/main/resources/statechartmodel.txt @@ -1,39 +1,44 @@ -+---------------------------------------------------------------------------------------------+ -| S0 | -+---------------------------------------------------------------------------------------------+ -| entry/ | -| exit/ | -| H/[foo.equals(0)]; | -| | -| +-------------------------+ +--------------------------------------------+ | -| *-->| S1 | | S2 | | -| +-------------------------+ +--------------------------------------------+ | -| | entry/ | C | entry/ | | -| D | exit/ |----->| exit/ | | -|<----------| H/ | | H/[foo.equals(1)]; | | -| | | | | | -| | +---------------+ | C | +------------------------------+ | | -| | *-->| S11 | |<-----| *-->| S21 | | | -| | +---------------+ | | +------------------------------+ | | -| +--| | entry/ | | F | | entry/ | | | -| A| | | exit/ |<---------| | exit/ | | | -| +->| | | | | | +--------------+ | | | -| A[foo 1]; | B | | | | | *-->| s211 | | | | -| |---->| | | | F | +--------------+ | | | -| | | | |-------------------->| entry/ | | G | | -| | +--| | | | G | | exit/ |----------------->| -| | | | |------------------------>| | | | | -| | | +---------------+ | | | B | | | E | | -| | | | | |------>| |<-----------------| -| | | +---------------+ | | | | | | | | -| | I| | S12 | | | | D | | | | | -| | | +---------------+ | | |<------| | | | | -| | | | entry/ | | | | +--------------+ | | | -| | | | exit/ | | | | | | | -| | | | | | | | | | | -| | +->| | | | | | | | -| | +---------------+ | | +------------------------------+ | | -| | | | | | -| +-------------------------+ +--------------------------------------------+ | -| | -+---------------------------------------------------------------------------------------------+ ++----------------------------------------------------------------------------------------------+ +| S0 | ++----------------------------------------------------------------------------------------------+ +| entry/ | +| exit/ | +| H/[foo.equals(0)]; | +| | +| +-------------------------+ +--------------------------------------------+ | +| *-->| S1 | | S2 | | +| +-------------------------+ +--------------------------------------------+ | +| | entry/ | C | entry/ | | +| D | exit/ |----->| exit/ | | +|<-----------| H/ | | H/[foo.equals(1)]; | | +| | | | | | +| | +---------------+ | C | +------------------------------+ | | +| | *-->| S11 | |<-----| *-->| S21 | | | +| | +---------------+ | | +------------------------------+ | | +| | | entry/ | | F | | entry/ | | | +| | | exit/ |<---------| | exit/ | | | +| | | | | | | +--------------+ | | | +| | B | | | | | *-->| s211 | | | | +| |---->| | | | F | +--------------+ G | | | +| | | | |-------------------->| entry/ |----------------->| +| | +--| | | | G | | exit/ | | | | +| | | | |------------------------>| | | E | | +| | | +---------------+ | | | B | |<-----------------| +| | | | | |------>| | | | | +| | | +---------------+ | | | | | D | | | +| | I| | S12 | | | | +--| |------>| | | +| | | +---------------+ | | | | +--------------+ | | | +| | | | entry/ | | | | | | | | +| | | | exit/ | | | | I| +--------------+ | | | +| | | | | | | | | | s212 | | | | +| | +->| | | | | | +--------------+ | | | +| +--| | | | | | +->| entry/ | | | | +| | | | | | I | | | exit/ | | | | +| | | | |------------------------>| | | | | +| A| | | | | | | +--------------+ | | | +| | | | | | | | | | | +| | | +---------------+ | | +------------------------------+ | | +| +->| | | | | +| +-------------------------+ +--------------------------------------------+ | +| A[foo.equals(1)]; | ++----------------------------------------------------------------------------------------------+ diff --git a/spring-statemachine-samples/showcase/src/test/java/demo/showcase/ShowcaseTests.java b/spring-statemachine-samples/showcase/src/test/java/demo/showcase/ShowcaseTests.java index a6dd0941..494b7a59 100644 --- a/spring-statemachine-samples/showcase/src/test/java/demo/showcase/ShowcaseTests.java +++ b/spring-statemachine-samples/showcase/src/test/java/demo/showcase/ShowcaseTests.java @@ -49,6 +49,20 @@ public class ShowcaseTests { @Test public void testA() throws Exception { + listener.reset(1, 0, 0); + machine.sendEvent(Events.A); + listener.stateChangedLatch.await(1, TimeUnit.SECONDS); + assertThat(machine.getState().getIds(), contains(States.S0, States.S1, States.S11)); + } + + @Test + public void testCHCA() throws Exception { + listener.reset(3, 0, 0); + machine.sendEvent(Events.C); + machine.sendEvent(Events.H); + machine.sendEvent(Events.C); + listener.stateChangedLatch.await(1, TimeUnit.SECONDS); + listener.reset(1, 2, 2, 1); machine.sendEvent(Events.A); listener.stateChangedLatch.await(1, TimeUnit.SECONDS); @@ -137,6 +151,19 @@ public class ShowcaseTests { assertThat(listener.statesExited.get(0).getId(), is(States.S11)); } + @Test + public void testII() throws Exception { + machine.sendEvent(Events.I); + + listener.reset(1, 0, 0); + // TODO: should think if need to bypass + // S211 as initial state and go directly + // to S212. + machine.sendEvent(Events.I); + listener.stateChangedLatch.await(1, TimeUnit.SECONDS); + assertThat(machine.getState().getIds(), contains(States.S0, States.S2, States.S21, States.S212)); + } + @Test public void testH() throws Exception { listener.reset(0, 0, 0, 1); @@ -164,7 +191,7 @@ public class ShowcaseTests { machine.sendEvent(Events.H); listener.transitionLatch.await(1, TimeUnit.SECONDS); assertThat(listener.transitionCount, is(1)); - assertThat(listener.transitions.get(0).getSource().getId(), is(States.S2)); + assertThat(listener.transitions.get(0).getSource().getId(), is(States.S0)); } @Test