diff --git a/build.gradle b/build.gradle index 46b35442..06308945 100644 --- a/build.gradle +++ b/build.gradle @@ -181,6 +181,7 @@ configure(rootProject) { from 'spring-statemachine-samples/src/main/java/' from 'spring-statemachine-samples/turnstile/src/main/java/' from 'spring-statemachine-samples/showcase/src/main/java/' + from 'spring-statemachine-samples/cdplayer/src/main/java/' include '**/*.java' into 'docs/src/reference/asciidoc/samples' } diff --git a/docs/src/reference/asciidoc/appendix.adoc b/docs/src/reference/asciidoc/appendix.adoc index e828028b..e0c2cdb3 100644 --- a/docs/src/reference/asciidoc/appendix.adoc +++ b/docs/src/reference/asciidoc/appendix.adoc @@ -24,6 +24,17 @@ include::samples/Events.java[tags=snippetA] == State Machine Concepts This appendix provides generic information about state machines. +=== Quick Example +Assuming we have states _STATE1_, _STATE2_ and events _EVENT1_, +_EVENT2_, logic of state machine can be defined as shown in below +quick example. + +image::images/statechart0.png[] + +[source,java,indent=0] +---- +include::samples/IntroSample.java[tags=snippetA] +---- [glossary] === Glossary @@ -75,28 +86,77 @@ to FALSE. A action is a behaviour executed during the triggering of the transition. +[[crashcourse]] === A State Machines Crash Course This appendix provides generic crash course to a state machine concepts. ==== States -TBD. +A state is a model which a state machine can be in. It is always +easier to describe state as a real world example rather than trying to +abstract concepts with a generic documentation. For example lets take +a simple example of a keyboard most of use are using every single day. +If you have a full keyboard which has normal keys on a left side and +the numeric keypad on a right side you may have noticed that the +numeric keypad may be in a two different stated depending whether +numlock is activated or not. If it is not active then typing will +result navigation using arrows, etc. If numpad is active then typing +will result numbers to be used. Essentially numpad part of a keyboard +can be in two different states. + +To relate state concept to programming it means that instead of using +flags, nested if/else/break clauses or other impractical logic you +simply rely on state, state variables or other interaction with a +state machine. ==== Guard Conditions -TBD. +Guard conditions are expressions which evaluates either to *TRUE* or +*FALSE* based on extended state variables and event parameters. Guards +are used with actions and transitions to dynamically choose if +particular action or transition should be executed. Aspects of guards, +event parameters and extended state variables are simply to make state +machine desing much more simple. ==== Events -TBD. +Event is the most used trigger behaviour to drive a state machine. +There are other ways to trigger behaviour to happen in state machine +like a timer but events are the ones which really allows user to +interact with a state machine. Events are also called as signals to +possibly alter a state machine state. ==== Transitions -TBD. +A transition is a relationship between a source state and a target +state. A switch from a state to another is a _state transition_ caused +by a _trigger_. ==== Actions -TBD. +Actions are the ones which really glues state machine state changes +with a users own code. State machine can execute action on various +changes and steps in a state machine like entering or exiting a state, +or doing a state transition. ==== Hierarchical State Machines -TBD. +Concept of a hierarchical state machine is used to simplify state +design when particular states can only exist together. ==== Regions -TBD. +Regions which are also called as orthogonal regions are usually viewed +as exclusive OR operation applied to a states. Concept of a region in +terms of a state machine is usually a little difficult to understang +but things gets a little simpler with a simple example. + +Some of use have a full size keyboard with main keys on a left side and numeric +keys on a right side. You've probably noticed that both sides really +have their own state which you see if you press a numlock key which +only alters behaviour of numbad itself. If you don't have a full size +keyboard you can buy a simple external usb numbad having only numbad +part of a keys. If left and right side can freely exist without the +other they must have a totally different states which means they are +operating on different state machines. + +It would be a little inconvenient to handle two different +statemachines as totally separate entities because in a sense they are +still working together in a sense. This is why orthogonal regios can +combine together a multiple simultaneous states withing a single state +in a state machine. diff --git a/docs/src/reference/asciidoc/images/statechart0.png b/docs/src/reference/asciidoc/images/statechart0.png new file mode 100644 index 00000000..18d97d9f Binary files /dev/null and b/docs/src/reference/asciidoc/images/statechart0.png differ diff --git a/docs/src/reference/asciidoc/images/statechart2.png b/docs/src/reference/asciidoc/images/statechart2.png index 76f06e44..01548282 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/images/statechart3.png b/docs/src/reference/asciidoc/images/statechart3.png new file mode 100644 index 00000000..53924f71 Binary files /dev/null and b/docs/src/reference/asciidoc/images/statechart3.png differ diff --git a/docs/src/reference/asciidoc/introduction.adoc b/docs/src/reference/asciidoc/introduction.adoc index 835748af..d6433a83 100644 --- a/docs/src/reference/asciidoc/introduction.adoc +++ b/docs/src/reference/asciidoc/introduction.adoc @@ -1,10 +1,49 @@ [[introduction]] = Introduction - Spring State Machine(SSM) is a framework for application developers to -use state machine concepts with Spring. +use traditional state machine concepts with Spring applications. + +Before you continue it's worth to go through appendices <> +and <> to get a generic idea of what state machines are +mostly because rest of a documentation expects reader to be fairly +familiar with state machine concepts. == Requirements - -Spring Statemachine {revnumber} is built and tested with JDK 7, Spring +Spring Statemachine {revnumber} is built and tested with JDK 7 and Spring Framework {spring-version}. + +== Background +State machines are powerful because behaviour is always supposed to be +consistent and relatively easily debugged due to ways how operational +rules are written in stone when machine is started. Idea is that your +application is and may exist in a finite number of states and then something +happens which takes your application from one state to the next. + +It is much easier to design high level logic outside of your +application and then interact witha state machine with a various +different ways. + +Traditionally state machines are added to a existing project when +developer realizes that code base is starting to look like a plate +full of spaghetti. Spaghetti code looks like never ending hierarchical +structure of IFs, ELSEs and BREAK clauses and probably compiler should +ask developer to go home when things are starting to look too complex. + +== Usage Scenarios + +Project is a good candiate to use state machines 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. + something happening asynchronously. + +You are already trying to implement a state machine if: + +* Use of boolean flags or enums to model situations. +* Having variables which only have meaning for some part of your + application lifecycle. +* Looping throught if/else structure and checking if particular flag or + enum is set and then making further exceptions what to do when certain + combination of your flags and enums exists or doesn't exist together. + diff --git a/docs/src/reference/asciidoc/preface.adoc b/docs/src/reference/asciidoc/preface.adoc index c085ffcd..ac0cb2ad 100644 --- a/docs/src/reference/asciidoc/preface.adoc +++ b/docs/src/reference/asciidoc/preface.adoc @@ -10,12 +10,6 @@ Moore presented another paper which is known as a Moore Machine. If you're ever read anything about state machines, names Mealy and Moore should have popped up at some point. -Traditionally state machines are added to a existing project when -developer realizes that code base is starting to look like a plate -full of spaghetti. Spaghetti code looks like never ending hierarchical -structure of IFs, ELSEs and BREAK clauses and probably compiler should -ask developer to go home when things are starting to look too complex. - This reference documentations contains following parts. <> introduction to this reference documentation diff --git a/docs/src/reference/asciidoc/sm-examples.adoc b/docs/src/reference/asciidoc/sm-examples.adoc index ca8c1078..84142d79 100644 --- a/docs/src/reference/asciidoc/sm-examples.adoc +++ b/docs/src/reference/asciidoc/sm-examples.adoc @@ -76,11 +76,10 @@ Event PUSH send ---- == Showcase - Showcase is a complex state machine showing all possible transition topologies up to four levels of state nesting. -image::images/statechart2.png[] +image::images/statechart2.png[width=200] .States [source,java,indent=0] @@ -111,3 +110,122 @@ include::samples/demo/showcase/Application.java[tags=snippetD] ---- include::samples/demo/showcase/Application.java[tags=snippetE] ---- + +== 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 +user can open a deck, inser or change a disk, then drive player +functionality by pressing various buttons like _eject_, _play_, +_stop_, _pause_, _rewind_ and _backward_. + +How many of use have really given a thought of what it will take to +make a code for a CD Player which interacts with a hardware. Yes, +concept of a player is overly simple but if you look behind a scenes +things actually get a bit convoluted. + +You've probably noticed that if your deck is open and you press play, +deck will close and a song will start to play if CD was inserted in +a first place. In a sense when deck is open you first need to close +it and then try to start playing if cd is actually instered. Hopefully +you have now realised that a simple CD Player is not anymore so simple. +Sure you can wrap all this with a simple class with few boolean variables +and probably few nested if/else clauses, that will do the job, but what +about if you need to make all this behaviour much more complex, do you +really want to keep adding more flags and if/else clauses. + +image::images/statechart3.png[] + +Lets go throught how this sample and its state machine is designed and +how those two interacts with each other. + +[source,java,indent=0] +---- +include::samples/demo/cdplayer/Application.java[tags=snippetA] +---- + +What we did in above configuration: + +* We used EnumStateMachineConfigurerAdapter to configure states and + transitions. +* States _CLOSED_ and _OPEN_ are defined as substates of _IDLE_, + states _PLAYING_ and _PAUSED_ are defined as substates of _BUSY_. +* With state _CLOSED_ we added entry action as bean + _closedEntryAction_. +* With transition we mostly mapped events to expected state + transitions like _EJECT_ closing and opening a deck, _PLAY_, _STOP_ + and _PAUSE_ doing their natural transitions. Few words to mention + what we did for other transitions. +** With source state _PLAYING_ we added a timer trigger which is +needed to automatically track elapsed time within a playing track and +to have facility to make a decision when to switch to next track. +** With event _PLAY_ if source state is _IDLE_ and target state is + _BUSY_ we defined action _playAction_ and guard _playGuard_. +** Lastly with event _LOAD_ and state _OPEN_ we defined internal +transition with action _loadAction_ which will insert cd disc into +extended state variables. + +This machine only have six states which are introduced as an enum. +[source,java,indent=0] +---- +include::samples/demo/cdplayer/Application.java[tags=snippetB] +---- + +Events represent, in a sense in this example, what buttons user would +press and if user loads a cd disc into a deck. +[source,java,indent=0] +---- +include::samples/demo/cdplayer/Application.java[tags=snippetC] +---- + +Beans _cdPlayer_ and _library_ are just used with a sample to drive +the application. +[source,java,indent=0] +---- +include::samples/demo/cdplayer/Application.java[tags=snippetD] +---- + +We can define extended state variable key as simple enums. +[source,java,indent=0] +---- +include::samples/demo/cdplayer/Application.java[tags=snippetE] +---- + +We wanted to make this samply type safe so we're defining our own +annotation _@StatesOnTransition_ which have a mandatory meta +annotation _@OnTransition_. +[source,java,indent=0] +---- +include::samples/demo/cdplayer/Application.java[tags=snippetF] +---- + +_ClosedEntryAction_ is a entry action for state _CLOSED_ to simply +send and _PLAY_ event to a statemachine if cd disc is present. +[source,java,indent=0] +---- +include::samples/demo/cdplayer/Application.java[tags=snippetG] +---- + +_LoadAction_ is simply updating extended state variable if event +headers contained information about a cd disc to load. +[source,java,indent=0] +---- +include::samples/demo/cdplayer/Application.java[tags=snippetH] +---- + +_PlayAction_ is simply resetting player elapsed time which is kept as +an extended state variable. +[source,java,indent=0] +---- +include::samples/demo/cdplayer/Application.java[tags=snippetI] +---- + +_PlayGuard_ is used to guard transition from _IDLE_ to _BUSY_ with +event _PLAY_ if extended state variable _CD_ doesn't indicate that cd +disc has been loaded. +[source,java,indent=0] +---- +include::samples/demo/cdplayer/Application.java[tags=snippetJ] +---- + +Now lets see how this cd player works and we can go a little deeper in +its state machine logic. diff --git a/docs/src/reference/asciidoc/sm.adoc b/docs/src/reference/asciidoc/sm.adoc index 51c5922c..4ef176af 100644 --- a/docs/src/reference/asciidoc/sm.adoc +++ b/docs/src/reference/asciidoc/sm.adoc @@ -73,36 +73,115 @@ Configuration for state machine factory is exactly same as you've seen in various examples in this document where state machine configuration is hard coded. +Actually creating a state machine using _@EnableStateMachine_ will +work via factory so _@EnableStateMachineFactory_ is merely exposing +that factory via its interface. + [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetF] ---- -=== Factory Limitations +Now that you've used _@EnableStateMachineFactory_ to create a factory +instead of a state machine bean, it can be injected and used as is to +request new state machines. -xxx +[source,java,indent=0] +---- +include::samples/DocsConfigurationSampleTests.java[tags=snippetL] +---- + +=== Factory Limitations +Current limitation of factory is that all actions and guard it is +associating with created state machine will share a same instances. +This means that from your actions and guard you will need to +specifially handle a case that same bean will be called by a different +state machines. This limitation is something which will be resolved in +future releases. [[sm-listeners]] -== State Machine Listeners - +== Listening State Machine Events There are use cases where you just want to know what is happening with a state machine, react to something or simply get logging for debugging purposes. SSM provides interfaces for adding listeners which then gives an option to get callback when various state changes, actions, etc are happening. -[[sm-context]] -== Context Integration +You basically have two options, either to listen Spring application +context events or directly attach listener to a state machine. Both of +these basically will provide same information where one is producing +events as event classes and other producing callbacks via a listener +interface. Both of these have pros and cons which will be discussed later. -TBD - -=== Annotation Support - -TBD - -=== Context Events +=== Application Context Events +Application context events classes are _OnTransitionStartEvent_, +_OnTransitionEvent_, _OnTransitionEndEvent_, _OnStateExitEvent_, +_OnStateEntryEvent_ and _OnStateChangedEvent_. There can be used as is +as spring typed _ApplicationListener_ class but all also share a +common class _StateMachineEvent_ which can be used to get events for +all. [source,java,indent=0] ---- include::samples/DocsConfigurationSampleTests.java[tags=snippetG] ---- + +=== State Machine Listener +For using _StateMachineListener_ you can either extend it and +implement all callback methods or use _StateMachineListenerAdapter_ +class which contains stub method implementations and choose which ones +to override. + +=== Limitations and Problems +TBD ctx events may create too much traffic, etc. + +[source,java,indent=0] +---- +include::samples/DocsConfigurationSampleTests.java[tags=snippetH] +---- + +[[sm-context]] +== Context Integration +It is a little limited to do interaction with a state machine by +either listening its events or using actions with states and +transitions. Time to time this approach would be too limited and +verbose to create interaction with the application a state machine is +working with. For this specific use case we have made a spring style +context intergration which easily attach state machine functionality +into your beans. + +=== Annotation Support +_@WithStateMachine_ annotation can be used to associate a state +machine with a existing bean. Withing this annotation a propertys +_source_ and _target_ can be used to qualify a transition + +[source,java,indent=0] +---- +include::samples/DocsConfigurationSampleTests.java[tags=snippetI] +---- + +Default _@OnTransition_ annotation can't be used with a state and +event enums user have created due to java language limitations, thus +string representation have to be used. + +However if you want to have a type safe annotation it is possible to +create a new annotation and use _@OnTransition_ as meta annotations. +This user level annotation can make a reference to actual states and +events enums and framework will try to match these in a same way. + +[source,java,indent=0] +---- +include::samples/DocsConfigurationSampleTests.java[tags=snippetJ] +---- + +Above we created a _@StatesOnTransition_ annotation which defines +`source` and `target` as a type safe manner. + +[source,java,indent=0] +---- +include::samples/DocsConfigurationSampleTests.java[tags=snippetK] +---- + +In your own bean you can then use this _@StatesOnTransition_ as is and +use type safe `source` and `target`. + diff --git a/docs/src/statecharts/statechart0.txt b/docs/src/statecharts/statechart0.txt new file mode 100644 index 00000000..903e8ff9 --- /dev/null +++ b/docs/src/statecharts/statechart0.txt @@ -0,0 +1,16 @@ ++----------------------------------------+ +| SM | ++----------------------------------------+ +| | +| +----------+ +----------+ | +| *-->| STATE1 | | STATE2 | | +| +----------+ +----------+ | +| | entry/ | | entry/ | | +| | exit/ | | exit/ | | +| | |--EVENT1->| | | +| | | | | | +| | |<-EVENT2--| | | +| | | | | | +| +----------+ +----------+ | +| | ++----------------------------------------+ diff --git a/docs/src/statecharts/statechart1.txt b/docs/src/statecharts/statechart1.txt deleted file mode 100644 index a551ef9a..00000000 --- a/docs/src/statecharts/statechart1.txt +++ /dev/null @@ -1,19 +0,0 @@ -+----------------------------------------------------------------+ -| SM | -+----------------------------------------------------------------+ -| | -| +----------------+ +----------------+ | -| *-->| LOCKED | | UNLOCKED | | -| +----------------+ +----------------+ | -| +---| entry/ | | entry/ |---+ | -| | | exit/ | | exit/ | | | -| | | | | | | | -| PUSH| | |---COIN-->| | |COIN | -| | | | | | | | -| | | | | | | | -| | | |<--PUSH---| | | | -| +-->| | | |<--+ | -| | | | | | -| +----------------+ +----------------+ | -| | -+----------------------------------------------------------------+ diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests.java index 80f0e74a..3d08a084 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/DocsConfigurationSampleTests.java @@ -15,21 +15,34 @@ */ package org.springframework.statemachine.docs; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.util.EnumSet; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.statemachine.AbstractStateMachineTests; import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.annotation.AnnoStates; +import org.springframework.statemachine.annotation.OnTransition; +import org.springframework.statemachine.annotation.WithStateMachine; import org.springframework.statemachine.config.EnableStateMachine; import org.springframework.statemachine.config.EnableStateMachineFactory; import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; +import org.springframework.statemachine.config.StateMachineFactory; import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; import org.springframework.statemachine.event.StateMachineEvent; import org.springframework.statemachine.guard.Guard; +import org.springframework.statemachine.listener.StateMachineListenerAdapter; +import org.springframework.statemachine.state.State; +import org.springframework.statemachine.transition.Transition; /** * Tests for state machine configuration. @@ -185,7 +198,7 @@ public class DocsConfigurationSampleTests extends AbstractStateMachineTests { // tag::snippetG[] - static class StateMachineEventListener implements ApplicationListener { + static class StateMachineApplicationEventListener implements ApplicationListener { @Override public void onApplicationEvent(StateMachineEvent event) { @@ -193,4 +206,78 @@ public class DocsConfigurationSampleTests extends AbstractStateMachineTests { } // end::snippetG[] +// tag::snippetH[] + static class StateMachineEventListener extends StateMachineListenerAdapter { + + @Override + public void stateChanged(State from, State to) { + } + + @Override + public void stateEntered(State state) { + } + + @Override + public void stateExited(State state) { + } + + @Override + public void transition(Transition transition) { + } + + @Override + public void transitionStarted(Transition transition) { + } + + @Override + public void transitionEnded(Transition transition) { + } + } +// end::snippetH[] + +// tag::snippetI[] + @WithStateMachine + static class Bean1 { + + @OnTransition(source = "S1", target = "S2") + public void fromS1ToS2() { + } + } +// end::snippetI[] + +// tag::snippetJ[] + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + @OnTransition + static @interface StatesOnTransition { + + States[] source() default {}; + + States[] target() default {}; + } +// end::snippetJ[] + +// tag::snippetK[] + @WithStateMachine + static class Bean2 { + + @StatesOnTransition(source = States.S1, target = States.S2) + public void fromS1ToS2() { + } + } +// end::snippetK[] + +// tag::snippetL[] + static class Bean3 { + + @Autowired + StateMachineFactory factory; + + void method() { + StateMachine stateMachine = factory.getStateMachine(); + stateMachine.start(); + } + } +// end::snippetL[] + } diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/IntroSample.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/IntroSample.java new file mode 100644 index 00000000..bc8cdead --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/IntroSample.java @@ -0,0 +1,79 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.statemachine.docs; + +import java.util.EnumSet; + +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.annotation.OnTransition; +import org.springframework.statemachine.annotation.WithStateMachine; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; + +public class IntroSample { + +// tag::snippetA[] + static enum States { + STATE1, STATE2 + } + + static enum Events { + EVENT1, EVENT2 + } + + @Configuration + @EnableStateMachine + static class Config1 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) + throws Exception { + states + .withStates() + .initial(States.STATE1) + .states(EnumSet.allOf(States.class)); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) + throws Exception { + transitions + .withExternal() + .source(States.STATE1).target(States.STATE2) + .event(Events.EVENT1) + .and() + .withExternal() + .source(States.STATE2).target(States.STATE1) + .event(Events.EVENT2); + } + } + + @WithStateMachine + static class MyBean { + + @OnTransition(target = "STATE1") + void toState1() { + } + + @OnTransition(target = "STATE2") + void toState2() { + } + } +// end::snippetA[] + +} diff --git a/spring-statemachine-samples/cdplayer/src/main/java/demo/cdplayer/Application.java b/spring-statemachine-samples/cdplayer/src/main/java/demo/cdplayer/Application.java index 2f8ef71d..ab7f4ef0 100644 --- a/spring-statemachine-samples/cdplayer/src/main/java/demo/cdplayer/Application.java +++ b/spring-statemachine-samples/cdplayer/src/main/java/demo/cdplayer/Application.java @@ -88,54 +88,29 @@ public class Application { } @Bean - public Action closedEntryAction() { - return new Action() { - @Override - public void execute(StateContext context) { - if (context.getTransition() != null && context.getTransition().getSource().getId() == States.CLOSED - && context.getMessageHeader(Variables.CD) != null) { - context.getStateMachine().sendEvent(Events.PLAY); - } - } - }; + public ClosedEntryAction closedEntryAction() { + return new ClosedEntryAction(); } @Bean - public Action loadAction() { - return new Action() { - @Override - public void execute(StateContext context) { - Object cd = context.getMessageHeader(Variables.CD); - context.getExtendedState().getVariables().put(Variables.CD, cd); - } - }; + public LoadAction loadAction() { + return new LoadAction(); } @Bean - public Action playAction() { - return new Action() { - @Override - public void execute(StateContext context) { - context.getExtendedState().getVariables().put(Variables.ELAPSEDTIME, 0l); - } - }; + public PlayAction playAction() { + return new PlayAction(); } @Bean - public Guard playGuard() { - return new Guard() { - - @Override - public boolean evaluate(StateContext context) { - ExtendedState extendedState = context.getExtendedState(); - return extendedState.getVariables().get(Variables.CD) != null; - } - }; + public PlayGuard playGuard() { + return new PlayGuard(); } } //end::snippetA[] + //tag::snippetB[] public static enum States { // super state of PLAYING and PAUSED @@ -186,6 +161,52 @@ public class Application { } //end::snippetF[] +//tag::snippetG[] + public static class ClosedEntryAction implements Action { + + @Override + public void execute(StateContext context) { + if (context.getTransition() != null + && context.getTransition().getSource().getId() == States.CLOSED + && context.getMessageHeader(Variables.CD) != null) { + context.getStateMachine().sendEvent(Events.PLAY); + } + } + } +//end::snippetG[] + +//tag::snippetH[] + public static class LoadAction implements Action { + + @Override + public void execute(StateContext context) { + Object cd = context.getMessageHeader(Variables.CD); + context.getExtendedState().getVariables().put(Variables.CD, cd); + } + } +//end::snippetH[] + +//tag::snippetI[] + public static class PlayAction implements Action { + + @Override + public void execute(StateContext context) { + context.getExtendedState().getVariables().put(Variables.ELAPSEDTIME, 0l); + } + } +//end::snippetI[] + +//tag::snippetJ[] + public static class PlayGuard implements Guard { + + @Override + public boolean evaluate(StateContext context) { + ExtendedState extendedState = context.getExtendedState(); + return extendedState.getVariables().get(Variables.CD) != null; + } + } +//end::snippetJ[] + public static void main(String[] args) throws Exception { Bootstrap.main(args); }