From 85cc2df29ec29d44bd26981a0686d893d08903a5 Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Sat, 28 Mar 2015 18:51:19 +0000 Subject: [PATCH] Ref doc updates --- build.gradle | 1 + docs/src/reference/asciidoc/appendix.adoc | 74 ++++++++++- .../reference/asciidoc/images/statechart0.png | Bin 0 -> 4822 bytes .../reference/asciidoc/images/statechart2.png | Bin 17099 -> 19109 bytes .../reference/asciidoc/images/statechart3.png | Bin 0 -> 18668 bytes docs/src/reference/asciidoc/introduction.adoc | 47 ++++++- docs/src/reference/asciidoc/preface.adoc | 6 - docs/src/reference/asciidoc/sm-examples.adoc | 122 +++++++++++++++++- docs/src/reference/asciidoc/sm.adoc | 105 +++++++++++++-- docs/src/statecharts/statechart0.txt | 16 +++ docs/src/statecharts/statechart1.txt | 19 --- .../docs/DocsConfigurationSampleTests.java | 89 ++++++++++++- .../statemachine/docs/IntroSample.java | 79 ++++++++++++ .../main/java/demo/cdplayer/Application.java | 89 ++++++++----- 14 files changed, 561 insertions(+), 86 deletions(-) create mode 100644 docs/src/reference/asciidoc/images/statechart0.png create mode 100644 docs/src/reference/asciidoc/images/statechart3.png create mode 100644 docs/src/statecharts/statechart0.txt delete mode 100644 docs/src/statecharts/statechart1.txt create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/docs/IntroSample.java 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 0000000000000000000000000000000000000000..18d97d9f1e4f8c607685a6c470c2098154986b04 GIT binary patch literal 4822 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 Qdg9Pf(^AbmarO590tMacoB#j- literal 17099 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

_d)xxboY{TuiV@=z++ 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Π_>53Z1sF~ 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 diff --git a/docs/src/reference/asciidoc/images/statechart3.png b/docs/src/reference/asciidoc/images/statechart3.png new file mode 100644 index 0000000000000000000000000000000000000000..53924f7114057429e84a275b4f7a4449f91b85c5 GIT binary patch literal 18668 zcmch92UwF?*1w~pt~!bpM(R3>Fak!UcN?I9ASz9eC@5VJf+8hE$HE{66oi13hzN+3 zi1e0NC?dT>K%!IuL#Uwy!vDMpD9-Hex8L)8&wrm?);Gy}-+RwJ<#&GP+}H1s0zjob8#_9gLB~O81Za zvVY^cv)-MK3?hiNa-S%+Lg;wnOhjGU?#pDF7 z|G4>~Mt5uS&&fH5-rcI%w&|g0LrKl5Ogqt*jjwVGX@j}S?$NtSMzmzY3p#MOF;$V_ zf|M~uqDQrYv`3d1%q4kQ$3A3E)YZ$D-FRA0o?nmr|1IWE%a+xs!RPVv;pY62HEc~_V&^8BX+A8vI}OV>%Gb-8?7h=0r}mtYY8)Hm<|b3YC>6myL#9=n zh`n{|*8Ehd`rBFZ9J9^OZw>P7labWPW(r%jf>DAOK;z~nc+_VPB8!G`*t31=^K@q% zi!sk;;MikC8g^2MzGB(3Cs<^Wiz|S~Cjay4slma)`hK_F!PCVkpNF*EH=$Byu4a~s zt+uv^043G!9UNjyoj&GG41a!CRaIq5vG%E(!=b>5fSz#1+o#>b0szC>wKm@`;sW^8IU?`3vO-@J^u@cly7I`o{BxQdn6^y z9TIJ6u7Kv(5S4^zPle*3rSr@F{5sdF_Xft7>9>4YUqJYZDj}-a+8dbF z)*EWnmi#M0^QXQlZ5tXHOT*0?$3WEzvhMWa0tuvr~znF!Z);)TFtG(~e%}?E(UAnGVLggZ(5zLqp|c zWOlVFD=AqLXJ%$*B}(ia9i5%!F@04}M-$ZN`svv;i$c$jq!OX6f zR}kWvqPn`evtXq93H9>M9nY{J6TjurCLYq#(nRmkN~0sTp>s&Byx_h`Y@!cNkf9jgM!9E4%Xs7J5zIrAMjb)Pz~@s$aZ#u{JH^ zZ6?Yj)4p+}tF+R)%pQG(`L(sBg-X?Cjc=oGS*a2?8bp{|TT2cK3NjHE5g~e|@7%d_ z+qP{ZPgr2**OygUr>F4?tZ&avzdqZqk6YX4U(l0vn|S9RWOKTXt&4UV7~}xpKwY~ z5{V3g^z7Hi|1ouLU4g^NUNnj6;Y=hy#Xh}pSOPV#uJz%=2TR##rD~R-60x?nHb*>4 zFX>EQ)nF$BH}7?&oH>e#U3F}Or?tQpZi6F zB5_7dFz!e^oiIxAic3$IHsTRf*f$q;F*7MiA~z#3@%Ly$u|V946)Rk71B8^%yukFy zZhn#NOe4!WG!;>lw(g5W&2JG#rD6r-oeDfBULc#Z?(<-O|C&DkzNBgA-SJ#C>&1!g z3@$^o{rYuK%k+=u5^o0ytT>xyW|V-P38 zbM6MlW;zYaVzMR~5sKh)h`xJY=dO@m4R@T5y@4sM_zBD&F?MhT5T*3-%d3Q>S4S#m z=x<%7;9enkWvGVS8L<3D-;tYNBo3l$)vA{*>)TKvIBd@yfa^Dq0pLRzz&UMb&$5oT z1ky6B21{kdvQO>%F|Tuvu#Wz8W$1OqPk;};|Eod#Q~OPhYe_U%oRk?^c4o8s@rI@* zBQvu>@`{!FG1!$bMdNPSvxbI^t*!m!6?Ex^&njg3AKq)*{_C$-Xg8VRx#Z=rDzjwq zUp8!zla;-6ApFc8Lo}M!+$^6YIzLLF6pU3V_q!=E?VOy1g@vmqZcPNu-Q~f-!K*h2 zilb10ZTAODLep)kd!4c<6!k1eYR2!`Js@Y7F7OXS$~9`z&Bll$_uqV{oPZ)cdURAV z0FOVRukY^WR&-TAN-2cqS9=T0xz?pLP=siR!w^-)%q**Ov|q&~B#32wZAsgit?%IA z;HY@*tBI4&kt0WRbo`Y!WK>s7Y=ga_S!!H>=$nQaS~Jqna3;B_p`qKovob%{tGvI?AP9WposF8yB4(Fu`}~UHkJ+j<&Ni>4&j)@LwLHO_gu!2|hcT zD62G>EvV+LFzKe~aX_fL8AB)uox<}7t6V8vD!OO&{VmhSolkj~$|@Z=uvx>@*qE=p zE$gzs5MsOe8F6uOX=$~EtEe`;Gi#CU-V|HY(6Ci`a&i)7k6*+|F$h zmheiIzaS$cBR{|Ghg1r9NGJ)wM`2;1S(fAPFGVFJB&=j7Qwb~W$2*Jt z(+Xd_*yA(wG2gw3`R60A_1^^5srh+kVldyGu~$OE-$6qP0*OVgYusyo)vE(%JUph` zQAfij`5GGT`49bJBZ< z3~*xU>GgBV`!l%XtgpE-F)?v*#Zk*;*K!PNQYrQ&MB5e* z@Jh`QX=hKL4sQEg6{QSb2-J+3n4EmSr)Bh6N2FP7ZB<%ac6Rg9TOVl0b%Y&JAy(1) zz_I00PP?&Zr%frgpZV$i{eEU%!=G*{ue9IF5f&_UN0eS^)jG<@Q zlf*<#v$JRU1q43sJQ-WVF)@kj-n62Xj?s8 ziquo`@@D)|MJRMl%a<)%Vsf3OLAhO zDaB_lxs7myC(qiV9lH{l*JQ|FVV-J_R8d}s=OE-XPKCU5ozQn}o26NhhnYQL&ao#c zfkI%5oi=2Yn)lSc3`seQ{A*IJ2V`WnCV{v0_wQ{a&qnpJ2lf@1sCrKg1EjOOYH8W| zwKW~aGmI5>tMu*}H_YjE_8E&n&vx>S6rrNohG))nN3*0(8iBh9W9{kbaS55)UY;iE z-rv$<(tgbK)=2WJn3xM4I7Tw+UAd~EUP8UgugdO2U;FzX9zp!c9zb$Kc%}-D?X93S zelACEM~d-v{Hi3Q6eXY8slht(Gm$Nd5+_-5jx8IN&2z5o@F{U+YY_IHyN8dEch)nC z)wP-cABk~RG))B~l_Q3Shv&b8>{YNlDk{qL7^}j&m}#hjN+FSWq?NA@G>nhCxoN0` zd%s|6BwR{w&2(tqUA?EvMzS^6ix>_;>$I>_? zX!LL-g7e*&+43uo3Kok^t7&YMWiU>JdU<(u_2JlkxT=pkc8ipw*iYg#V@RRSXpNgi zDaFCBmnTWaq@+kC652zb*;Cuvf*TRMT!{<(kZzAY?C$RFxMn_>BgP$MXUg0)91h10 zeck|&Oi~kMXiQSW^j0J_lR@BH+4g7^DZS*qp|I9!PxJ7{k6&Obs(qO0Q{nR}*Hf9r zhfkkAtr8+4EF7JAWR&?vtnviDPE8uU1{+ckzRQDf?DIV%1A|R%W94F=zh?pb5=(Mu zPQ^Dm%C%9c{F+mh@-!79^NvCqABcWv|4Fo}HdgaOtm$I}st*!x{)aK-t#pa#DfS!#7g#s_PK5oy+sw}G zOK)Waz=>(sowZDNI`FP?pq&B;FXqN{{<39GC2OfUj(~X0RGoqVegg( zi5fTcJ|1@pz~B1y>vM0JPIZNHeO{TEjFsYiSLifL3@6K*I$931<~C$ioeuo_ zrkk%-fWVz*JC=BsUg5^%6%i9X_9K`kLQ)Ufmp*5Y0ph8!t0CPChf4>x0}`(>hWHh}N|&7}GzQopSltogK0lL* zXrCb~NImlLJl_WExz7zAnWT)2h6>S*NVZS1d)9d^zgThoby9_^qPwP+RxrgZ?X;3NQ+r>gW?4 z&*k^++gSS2sx;tErgM{+hwt6HypWiN>--ZT4AAvA%20k*9l02rt5>f=4ootOOw@h~K;IVi5bfe>lW0~U)_U)^S)-(^o%k1)V80dqy;~ntS3GQ0gE;T`MP5he zNm)J6K4c9wJYmY|1q?=|o`vl%ce&Bn*ogKXxf4Dp0nh*h(P(?`R&fSp<-fUkc^+Sun^AP-i((5aaDeF1Xu6a3UrK9 zd2Os#SBZ_ab$88vlmmU8QOen?vr{gh8dX@%JB~jd&J54h;;x%Yl`1}f=%r|?I2SX+8&x?Bo~2q5T%t0P=1R( zRcgQ0{nZi4ikVn509Ay1=1Oj?x({ORQv?6onSUmJ6JBoe^B&_z`u1fBO<$5}u2aLG z+v|I}%iL7Z)#v!lI_1@_4_`08DnqGoNVT}&6^dc2vNaG=3c(7H34>EQAZeM4g z$tM;h^9ya??)Mb39RHrnZCS+|67-2x=csjcbuBI8z5%&m7Ign(sNvQPi*i$OE4*tb zBP&~61)j_QZ0s8^Mj>At$~&4H`uAmD& z?Vv2xN17eNN@{9O?hZ~&Nh!p#t|bHXn`AN(a_V(xGQgF__IwN8=d}pzW&%2!$_1!x z+Sc`RFpT*#H5(!$2#AcpE_>*%_KCf(97M_)YM$UJ}onI;zNkU%BC+s+QD)I5-$Fr0jS+D!HZ5CF!{=oYu*I7Xkk?PC(Y{AylbNQq0KJ#;vR#xWENZk(r z#lxe;nrtIg(p=7;e+}rK_XsPfXE>{byd1ZBobqAu>4#@M~ATdHUSP$=~gS3 zn@rm1+MVh;{-ri&L=2lo7F4pf`BJ`_@bKZoqg87g$iSuTLZ95M4#D854qRT1M}Y?$Bb`=Oz{usJ4;5(S68#X#K%-axgPdmReuGC7b!_%?f1JXXsw> zlT4s5P*rmigUx+eMXrv1^y&PMIW9YM9}YH?1{Zf>j$^O|!vdyi?5VDnguFZ@A7OQ$ z*>L*>$_3eDeU>XT9kSTv>SYn$VOD#mjd26`FN;I8%D^fu``B!@7=5GuWUPI2>N#aO zIbLK_Jm%lZ^3%=Fo~3^LczHBnyMF?<+Qn{kwawW43Ylr1PE(PuCic_>QY0&bM>x;I zmLI68aNYQ)As&OuSY!oYMygC?)(rW zbKfr58zLt@ZYU%RjZtI*({cVsM!JkR#3e}H5WMa?o0vtr-dM02gOZn^Pc-$;!#?1=qdC4e^qFG3CfA%FA9gljUjN zWA7jCT!#zx|Cy7wjn!P@Jn5o`0}V=OB#Xj5H@RVwv-I-g`yFTi6xYp4T;BYJ>%$3L zQC&*JhJ=LlRTBx^833>YV=<_pW^-v}ohKL~$0H$f7I&owALQZ%4(nh`{Q0p8gtdF` zI8$3gzlGq91O@IQYShA176GYZ40aK3p`d?Pka+39FYFn&@|D<4&Thj7_*&RKRBc_Dg-aOsn!1`ZsqdG-Bs_sx93f@A zkG4a2?Gf4a*T01D!i`*9d_mj(%-M_j%X@0}*Q*)qTYvgg>;u&`j~2D%IQ}q`yHx@U~FmiH8|&g>^1!=I@CH@1wcW{Wn6GjQu8j&Xj5qal>A88R~Z7 zOC<|>wsCn&fptLi3=Ivjp%6Xd$^$}y^5DW_$r;YaZPA8?hBh`flDCKFdgUo=rem>H zLN?;`TYxrB3i>BuNzn>DoxcL`<7K>78CP=uwqyBE6GlqrFIv#FjnwDH>mieIQ<@ti z&KFQ9^N@1^oHi2R=XdHT2**W0RTm&sxbE%{?Z)Qj$%%=|yu`AyvK6a0K+0XZp^O=; z6=Q!jKHe8E?wXgstDKdQp)&FM56HBO zOBC*#_>6xsQ0Qq&F;nHP?J#HO@Pv4*-*c$Cb(2~QE{hT zuJz34Z7gbOX=^k4lPO9Wg5+P=GfFw5S_VinQ16nbw&=4JWXlvlf7L18&?+{Tqej^sU7+@AIIo|97 z;+?)5^lOg4S)9&)aw+>nW!Ek`G4B(xzs6_4&N3+T7WhAyimqJjb|UtD!d7KtV`JP% zF`xa`jOvHw3ocP36}tvF*T~4oGqF#w0p=V~gsungyqJ_F8q}zL^Je$VS$`NN8G!|u z!|-7FKq>$=V9-q^)U>yIPY#`vT?=ww@UWT>9ndd5CkCW#s-7dGEV{kW@kbVsHRLT~ zdOv)8uXU4vfPf7)>j-CTZoV0BaOPX$FeIP*;|*FYIj8pP69a~vr!M$AO>ThVo07N; zOVwX~0wJ>j247OIgL3d#Uv(19MQbkyX_D-=N<#&Jf}1DvucTwT7lwe$cQGDugV-U~ zu0ohsa&bwGyXQyi4uA7Aue;;!AOaiUaw$tE%z4^Q!a~4! zqPh4mh!;S)|FS4ggpL7xa+vi4AJ0)|BtUh?Y3SLCaG8MDn~Adkw;)U-9}io2qJd>uq!#Ri}*T*6lIUK)k8S1Ul!6gmH4ND<>=&+zzH( z9W;W%yMV_r|HvA(hoG{Yl7M(t8BxK-5+behtp2kfpHpiw9g`sI6%obA5N^h3xV z_Bt~ zB1?{q3#Hu7u>m#cm*5}=J-HvR5Z&fxK(*waZRQ1g5iKOwA)xnUfbngS1264Z8{)kB{Sh|MtL{wJ<$o7I?Doe=f9K znVXqN8v$ajEEFn^Cg-x9&Y5@nG#D>T+3K0bPSaT z9VVO-+be2K&Bi1?l!cr6fW&)6`WBSdIz`+ZmEZ_uwrFd{CF9(&8?l?p>bu;1ibMMz_c66-EIIYnm{LRC1cG%iCQ z4SGPlu6XwS^k_ZVbatGd?V58G! z-mDhsW}Jq6ax@HBowu2ckM1;l&l2W&>+?vy>k*JknWS6e#a2PDN~SX)5EnZfPMsc% z*q~7)b8dV}UBOZ|xU4(Nob0VQ?enU8UBxJccBO@*=_w?PN^#c8mpyrA5 zJia7N7KEVAx-Y6!F~qh~%;^$RQk9q2eD;@uMA|sj-0x0zR266}h|o$gq7rCUCX?wi zRw=*sOcfuw5~7!}k&)->Lf>~pi$*Oa=<2*V|MQMV7^<&jJsGP0l{5MsfO!G8LYd;C zEU)F&ix;19da>%DwC^e^U<_qN(C_Cp=ny~9In z?*Q;vw5jPnA~X$@ln{j}f-N6Eo-;Fx@YIzovL8Ks{v~t_fySjK10xVgEt15Yzg z@RZWR@6}bYnQA2Yi0;U6cDhv9PN{Q&_5;W`9OcXaiyCG@#{V3JY_@)<|4G&jQHxlZ z>c~FH!VJ>JULU@nU?vbvR!b)EM}>t=^`LP50Z*{&Kx7!8a&s)byz=wSxWX7Zl>CUl z7$`J1rE>-bBhb~V6fKsM-@_T8Pxxm#Mx8f>iL^IB_*)CRxmpnjR%j#5sGd8v=*n*} zb4Bb#;Gs7mVnBG{e8F)_NaWbO9B%3CvE9*E_r>SY#sx#ELK z>qm8kBPkTp-re8dpPTt>(c8DGVU$abj*`*)Z4QLzv;wVt_wL<0L|nZZAb{a}nJ9Cy zz2HoJMrcsD6M1+lwmkWW*zVm>Qns47#iRC~mH@`sy&q_!I}Wsb23epS^3XKzvEE8m zFQ&@rn{~)IFI%YDP^{0%pimI11LCN$rLW$J6UFwsgUc@GdQZDL+S%EyarVCh+zm>r(7EU&4lfpVnf4vz%P4r=x_ zI@JUkKvRG;FDwKKXFX6`89b=CR~AhiukiWYQSJ~Lao9j%)yEUWg4oztsBlb7OqAJ6 z77ajAV+YlJp-QYRm)cTapMG!?R~Vf$J&g#&5t3T#IUs0lbnYDXOHGXV8lUl6t+cs% zef1MY`q3)e!PjydH2oc*OQ1W1@^;-Lq=VD9xQX0dh~?XB&V?1VAssRQ(FvSDI$qz^ z)v6(V(MLbj4%EzRK|fCWVMlxWdw3{A1oS{#yNMc9Nyn{%8lgw4JsL_pQIV1Ndv4ym znPUr}1Oi*FnxqgN_+IqM#=5!#Dk`(}6iPN~z<*&QjC{$^*5_gXF`r#htv;_J2mfJ@ zPxZV8MguB>4OY;w8_c)-R+O?k6zV`rS}0Iojg9RB-e8*2;KiBcm|;`-oP-cirVf`c zS6ZhdMWahdcgPHYTLeSZKog?mIk0U6Ysn7S9^(7t^XP%46_l}o$Z(m=%+4-X1ln#9 zlKp-bDli%v#c=pw5Kaz&1==_K1-sdA1?ct@5=yo4haopwG;+kup%kw541gkn{9zj83&QO{Bb6Qjw2rkpGS;0B=1f44luhw1Mx4Eof074bdeoVpcs zaMgXfciYPd7(enKNVYY2@fP}QC|d=xN#IsMK#`GKxg1<9mIHY8mrra1c|sQ} zLaI`@RZ~+FEWfI$#8r{K6Lf!)mGEcbm7JP`a6^c z*%~f-!cc`aiin7Sy6MMsn19Ofne5EW;*<#{vzJqQ=zMJU&yjK2(5^KXcTk(BenlR| z3Fv&f|IwR8vP44fU|nwd2PJ&{j2kkA`;4x?kl3@QvuTw0^X?Es3k&uiRQH|_Aych1 z8dOJ-I4_~M)Xkkd8H34CwZqylA+AgLyQPuwxc7($c|SsFIde=3A9P>w473q9-zx zkq-959T%k;8gK-M*#RD!WJ}Y>sFf+p$Yi`0^B^8!L5Gmq;JgT@@<+`+m4Wi$|JdFh zNlAkQK`vSis}!oNtZX&?`sRxIz~e^wd3mm0(849>F*fn~^+EHk)z65Dk9~c8A#NT) zd@70S_aJ4{c1FGZ|2n_wxtDIV$f1Z7sPZ zxv22){-L4Nl#~;2kib&bW5+&3#kq|MbFon%PAG>?K4V82zA4kWHL^?1X_tZSq$KRv zsr3sMJeiyU`7o!GuNmxl50bl-#VlAm{~eYAx(p$)z=jc@$mOCS!GA9J0`VunYak58 zLOT9Eoew!e6%*r-elYb($?U*p#K+YebJP01)^wn>7H|I{o98?!&xVui=h#5DO`FZ; zVT6o4(Sf)C@#}PKwAC9`i63W)jErzfNS)>+c+Jh?9B-i}yOu)$EpiD^;kgMbK=={B ztTelVqUt{~Gcy~Tn!r6lVAn@;h!1EDZnotx>`4C@h-uLsZo}Y9*6?Oa@7woJU>_h5 zV&f3%1{%<>vi{DN+m|7%1*2e0Pbn$@3+=$L<723(iA-YIXm@w_%@wPmPTnQgm6z57 zt&O=6bOs|(Dnjn^hs%ndT1T%&wuvpSx{bB zZ2}2o3Nm+2bHe=(t?nhQL2a9xfedy@0qiBZqd@|vwyXFb+TEet z2llndzmSXb=G>Hol-2uB>_H`IYH4ad6(1ZJu=D`rnf9)~J^@33T7Zn4+>aYZ`>n$V z5bD=<8jbQ?mcL-K!z*nL@}t^>+K=#g8Pj$cfauVW6Y#X43_xIU*eJ82HW7aQ4;K%B z5|ALU6p7Qg>n??cd!x6Z%e)=pWMRPiv0g!+C+i`s==EDhu}wIx;D z2BPB4ktN?-2Kc0P6 zAC|4z)$NCeWD7*{()F7K(fpfiU)ndm=qe4QfRF_R0b+uj6VJSW`U#1IayNj6)Alot z6_@>hC}K&rwUZ9Nf0+M)(?LWWFP=dpY?x;MO)vOTYHve{DBqnyo?5g5r_0~0VE&y0 z?DKKwXcdnG&`mDs3`a$nk(2$S1=Z0|(Bi85g_YmOBbM>epo|BHTm zV?-cG`5aLqnszgpPt1P2$Zrcu0S_%}lc8PHfJq=(DxZsb3PeKnoUi1`Sg=DE2WZE9 z@JH-!40}!jY$?*zxrSQwN-Gs4Q$`tUGL%mO{%8J zCDq+gGQjqXF|DC$l&txAH$o2HZ&dJr4lpZ3+6&!6)#zAm(~q!fy-a88Fa)J?AZX6k z-h#{u=$PYs;p~Y&(neiHk1UAu>_8p;hhCHtpqS{kmc~X1>rnOmVZdp9|7#Hw9o?7q z<2gP3P>?QJ3l5pn?xgQcB*JM$8U;s_fti$-)lozyLY7p;O*SpE{oDqD#iGeVH^P?h z)oExq09qL;Nvn}QeUub%+}ujxLLq#ikQG({K@`e2rMgHlkW*XWv<)Ebg8%0gk9{6? z-wTi2>c`0qLeiYxM0glf8#tXrCszDWf`T4_|6Rqy`HoF*t|>5cpq};(a6ADivz)Yk zc894b3azMHh2M&vv_?X%VEj+AYk58gm@iyG>1O+qFaLgN3dH@zy8vny&OY2`mfeSk zaF085?ug`G>aBrROC5jTD{CHY2UCmO%xR6~`MgzdO_Hj!%f4Ow+T=HY&#Kmpqq3;uhDG@86T(Gds!f#I;u?829(GEj4TIIIR6Xe^z zNBh=4H?SK)9<&o62-1@KH#xxkIGBi2RpLzd+ohx!WSEb(1E~F`Y1s_{!^c-E9yn0f z(P07lt{n#_YDz;*S>B^%Vr5cAZ`&jA67lu@_wL<;gqb-${{9z;*Na^zHDu8Q?1k&C zjzCmoLbB}Nh;;a03Y`W_6RHxQnmTk`Uf`|^&ZTj@$Y9^WbQXXi9qlcH9uXd1W}S{f|Afc8)E3BlO$}dQAlOGA&uJ@C z+>ZxgrgL!qqxs_G?_mpC(=V##l}ilp56FApUK|2`dfVQ6K;^v1)fheZ?r@o_)SW#Ms4<`RE#G=m3eGqO0_T_mI1 zk9)aLFz2q5`{1Poa{y+DV*w`%YyMs^U#OhIFfcWwFrE!<%+UJ*KZnCoh=&M|;8ei5 zaAOcKEeP)WD3z6!P*!T>)?aN!76ySD3sDiH33CA-^aKlwF{wN4>9Wwb%UK3AL*?3S zP2UOhRn?56FE}Vz%|X`r-oMn!!7m_B%;14(7soE1I_CZyoF*LKLVB}s^X!#u?T96# zq>LF$0(r#Ipu3Jq;kc7WRPCIK}Zii#_Dfmc`k)g(g_U zJYhkL?GH$Q-In@&KQA_1A(p>?J#-#ng2a*2W>6ZgzX9{=T!&oj$8ay;w7}8}j_z?r zaJmy(d^w4V{no`}%gABn{g`E74ai|-t6q3X1@|NF>1NJR>b?zd!hhNK1Jny= zqB)1E!#Jm}`Bz3N@8cZJmR*Y+(L;_CBhLs^MELjb13uxN$mYD9VOgZI^#%oKsuEv@ z{O}XB0X>DY+23#Go~!=xG__SP=YSQ+$_krATzP}9umiP1G$?yL#aY&h#yio#P ztCIXA;o`q|H^tvyd-B7JTYh}|#sAT3G5W|bA9!8K(m$|a$op%^#;O0`-z9Q(4^>`P zcH3tD(yekc6V2#(> +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); }