package org.squirrelframework.foundation.fsm; import org.junit.*; import org.squirrelframework.foundation.component.SquirrelPostProcessorProvider; import org.squirrelframework.foundation.component.SquirrelProvider; import org.squirrelframework.foundation.fsm.annotation.State; import org.squirrelframework.foundation.fsm.annotation.States; import org.squirrelframework.foundation.fsm.annotation.Transit; import org.squirrelframework.foundation.fsm.annotation.Transitions; import org.squirrelframework.foundation.fsm.impl.AbstractStateMachine; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; public class HierarchicalStateMachineTest { public enum HState { A, A1, A1a, A1a1, A2, A2a, A3, A4, B, B1, B2, B2a, B3, D, E, E1, C } public enum HEvent { A2B, B2A, Finish, A12A2, A12A3, A12A4, A12A1a, A12A1a1, A1a12A1, A1a2A1a1, A1a12A1a, A32A1, A12B3, A22A2a, B12B2, B22B2a, B22A, A2D, D2E1 } @States({ @State(parent="A", name="A3", entryCallMethod="enterA3", exitCallMethod="leftA3"), @State(parent="A", name="A4", entryCallMethod="enterA4", exitCallMethod="leftA4", isFinal=true), @State(parent="B", name="B3", entryCallMethod="enterB3", exitCallMethod="leftB3"), @State(name="C", entryCallMethod="enterC", exitCallMethod="leftC"), @State(parent="A1", name="A1a", entryCallMethod="enterA1a", exitCallMethod="leftA1a"), @State(parent="A1a", name="A1a1", entryCallMethod="enterA1a1", exitCallMethod="leftA1a1"), @State(name="A2", historyType=HistoryType.DEEP), @State(parent="A2", name="A2a", entryCallMethod="enterA2a", exitCallMethod="leftA2a"), @State(name="B2", historyType=HistoryType.DEEP), @State(parent="B2", name="B2a", entryCallMethod="enterB2a", exitCallMethod="leftB2a"), @State(name="D", entryCallMethod="enterD", exitCallMethod="leftD"), @State(name="E", entryCallMethod="enterE", exitCallMethod="leftE"), @State(parent="E", name="E1", entryCallMethod="enterE1", exitCallMethod="leftE1"), }) @Transitions({ @Transit(from="A", to="C", on="Finish", callMethod="transitA2C"), @Transit(from="A1", to="A3", on="A12A3", callMethod="transitA12A3"), @Transit(from="A1", to="A4", on="A12A4", callMethod="transitA12A4"), @Transit(from="A3", to="A1", on="A32A1", callMethod="transitA32A1"), @Transit(from="A1", to="B3", on="A12B3", callMethod="transitA12B3"), @Transit(from="A1", to="A1a1", on="A12A1a1", callMethod="transitA12A1a1"), @Transit(from="A1a1", to="A1", on="A1a12A1", callMethod="transitA1a12A1"), @Transit(from="A1", to="A1a", on="A12A1a", callMethod="transitA12A1a"), @Transit(from="A1a", to="A1a1", on="A1a2A1a1", callMethod="transitA1a2A1a1", type=TransitionType.LOCAL), @Transit(from="A1a1", to="A1a", on="A1a12A1a", callMethod="transitA1a12A1a", type=TransitionType.LOCAL), @Transit(from="A2", to="A2a", on="A22A2a", callMethod="transitA22A2a", type=TransitionType.LOCAL), @Transit(from="B2", to="B2a", on="B22B2a", callMethod="transitB22B2a", type=TransitionType.LOCAL), @Transit(from="A", to="D", on="A2D", callMethod="transitA2D"), @Transit(from="D", to="E1", on="D2E1", callMethod="transitD2E1"), @Transit(from="A", to="C", on="Finish", callMethod="transitA2C"), }) static class HierachicalStateMachine extends AbstractStateMachine<HierachicalStateMachine, HState, HEvent, Integer> { private StringBuilder logger = new StringBuilder(); public void entryA(HState from, HState to, HEvent event, Integer context) { logger.append("entryA"); } public void exitA(HState from, HState to, HEvent event, Integer context) { logger.append("exitA"); } public void transitFromAToBOnA2B(HState from, HState to, HEvent event, Integer context) { logger.append("transitFromAToBOnA2B"); } public void entryA1(HState from, HState to, HEvent event, Integer context) { logger.append("entryA1"); } public void exitA1(HState from, HState to, HEvent event, Integer context) { logger.append("exitA1"); } public void transitFromA1ToA2OnA12A2(HState from, HState to, HEvent event, Integer context) { logger.append("transitFromA1ToA2OnA12A2"); } public void entryA2(HState from, HState to, HEvent event, Integer context) { logger.append("entryA2"); } public void exitA2(HState from, HState to, HEvent event, Integer context) { logger.append("exitA2"); } public void entryB(HState from, HState to, HEvent event, Integer context) { logger.append("entryB"); } public void exitB(HState from, HState to, HEvent event, Integer context) { logger.append("exitB"); } public void entryB1(HState from, HState to, HEvent event, Integer context) { logger.append("entryB1"); } public void exitB1(HState from, HState to, HEvent event, Integer context) { logger.append("exitB1"); } public void entryB2(HState from, HState to, HEvent event, Integer context) { logger.append("entryB2"); } public void exitB2(HState from, HState to, HEvent event, Integer context) { logger.append("exitB2"); } public void transitFromBToAOnB2A(HState from, HState to, HEvent event, Integer context) { logger.append("transitFromBToAOnB2A"); } public void transitFromB1ToB2OnB12B2(HState from, HState to, HEvent event, Integer context) { logger.append("transitFromB1ToB2OnB12B2"); } public void transitFromB2ToAOnB22A(HState from, HState to, HEvent event, Integer context) { logger.append("transitFromB2ToAOnB22A"); } public void enterA3(HState from, HState to, HEvent event, Integer context) { logger.append("enterA3"); } public void leftA3(HState from, HState to, HEvent event, Integer context) { logger.append("leftA3"); } public void transitA12A3(HState from, HState to, HEvent event, Integer context) { logger.append("transitA12A3"); } public void transitA32A1(HState from, HState to, HEvent event, Integer context) { logger.append("transitA32A1"); } public void enterB3(HState from, HState to, HEvent event, Integer context) { logger.append("enterB3"); } public void leftB3(HState from, HState to, HEvent event, Integer context) { logger.append("leftB3"); } public void transitA12B3(HState from, HState to, HEvent event, Integer context) { logger.append("transitA12B3"); } public void enterA1a(HState from, HState to, HEvent event, Integer context) { logger.append("enterA1a"); } public void leftA1a(HState from, HState to, HEvent event, Integer context) { logger.append("leftA1a"); } public void enterA1a1(HState from, HState to, HEvent event, Integer context) { logger.append("enterA1a1"); } public void leftA1a1(HState from, HState to, HEvent event, Integer context) { logger.append("leftA1a1"); } public void transitA12A1a1(HState from, HState to, HEvent event, Integer context) { logger.append("transitA12A1a1"); } public void transitA1a12A1(HState from, HState to, HEvent event, Integer context) { logger.append("transitA1a12A1"); } public void transitA1a12A1a(HState from, HState to, HEvent event, Integer context) { logger.append("transitA1a12A1a"); } public void transitA12A1a(HState from, HState to, HEvent event, Integer context) { logger.append("transitA12A1a"); } public void transitA1a2A1a1(HState from, HState to, HEvent event, Integer context) { logger.append("transitA1a2A1a1"); } public void enterA2a(HState from, HState to, HEvent event, Integer context) { logger.append("enterA2a"); } public void leftA2a(HState from, HState to, HEvent event, Integer context) { logger.append("leftA2a"); } public void transitA22A2a(HState from, HState to, HEvent event, Integer context) { logger.append("transitA22A2a"); } public void enterB2a(HState from, HState to, HEvent event, Integer context) { logger.append("enterB2a"); } public void leftB2a(HState from, HState to, HEvent event, Integer context) { logger.append("leftB2a"); } public void transitB22B2a(HState from, HState to, HEvent event, Integer context) { logger.append("transitB22B2a"); } public void enterA4(HState from, HState to, HEvent event, Integer context) { logger.append("enterA4"); } public void leftA4(HState from, HState to, HEvent event, Integer context) { logger.append("leftA4"); } public void transitA12A4(HState from, HState to, HEvent event, Integer context) { logger.append("transitA12A4"); } public void transitA2C(HState from, HState to, HEvent event, Integer context) { logger.append("transitA2C"); } public void enterC(HState from, HState to, HEvent event, Integer context) { logger.append("enterC"); } public void leftC(HState from, HState to, HEvent event, Integer context) { logger.append("leftC"); } public void enterD(HState from, HState to, HEvent event, Integer context) { logger.append("enterD"); } public void leftD(HState from, HState to, HEvent event, Integer context) { logger.append("leftD"); } public void transitA2D(HState from, HState to, HEvent event, Integer context) { logger.append("transitA2D"); } public void enterE(HState from, HState to, HEvent event, Integer context) { logger.append("enterE"); } public void leftE(HState from, HState to, HEvent event, Integer context) { logger.append("leftE"); } public void enterE1(HState from, HState to, HEvent event, Integer context) { logger.append("enterE1"); } public void leftE1(HState from, HState to, HEvent event, Integer context) { logger.append("leftE1"); } public void transitD2E1(HState from, HState to, HEvent event, Integer context) { logger.append("transitD2E1"); } @Override protected void beforeActionInvoked(HState from, HState to, HEvent event, Integer context) { addOptionalDot(); } private void addOptionalDot() { if (logger.length() > 0) { logger.append('.'); } } public String consumeLog() { final String result = logger.toString(); logger = new StringBuilder(); return result; } } HierachicalStateMachine stateMachine; StateMachineLogger fsmLogger; @BeforeClass public static void beforeTest() { } @AfterClass public static void afterTest() { ConverterProvider.INSTANCE.clearRegistry(); SquirrelPostProcessorProvider.getInstance().clearRegistry(); } @After public void teardown() { if(stateMachine.getStatus()!=StateMachineStatus.TERMINATED) stateMachine.terminate(null); System.out.println("-------------------------------------------------"); } @Before public void setup() { StateMachineBuilder<HierachicalStateMachine, HState, HEvent, Integer> builder = StateMachineBuilderFactory.create(HierachicalStateMachine.class, HState.class, HEvent.class, Integer.class, new Class<?>[0]); builder.externalTransition().from(HState.A).to(HState.B).on(HEvent.A2B); builder.externalTransition().from(HState.B).to(HState.A).on(HEvent.B2A); builder.defineSequentialStatesOn(HState.A, HistoryType.DEEP, HState.A1, HState.A2); builder.externalTransition().from(HState.A1).to(HState.A2).on(HEvent.A12A2); builder.defineSequentialStatesOn(HState.B, HistoryType.SHALLOW, HState.B1, HState.B2); builder.externalTransition().from(HState.B1).to(HState.B2).on(HEvent.B12B2); builder.externalTransition().from(HState.B2).to(HState.A).on(HEvent.B22A); builder.defineFinishEvent(HEvent.Finish); stateMachine = builder.newStateMachine(HState.A, StateMachineConfiguration.create().enableDebugMode(true), new Object[0]); } @Test public void testBasicHierarchicalState() { stateMachine.start(); assertThat(stateMachine.consumeLog(), is(equalTo("entryA.entryA1"))); assertThat(stateMachine.getCurrentState(), is(equalTo(HState.A1))); stateMachine.fire(HEvent.A12A2, 1); assertThat(stateMachine.consumeLog(), is(equalTo("exitA1.transitFromA1ToA2OnA12A2.entryA2"))); assertThat(stateMachine.getCurrentState(), is(equalTo(HState.A2))); stateMachine.fire(HEvent.A2B, 1); assertThat(stateMachine.consumeLog(), is(equalTo("exitA2.exitA.transitFromAToBOnA2B.entryB.entryB1"))); assertThat(stateMachine.getCurrentState(), is(equalTo(HState.B1))); stateMachine.fire(HEvent.B12B2, 1); assertThat(stateMachine.consumeLog(), is(equalTo("exitB1.transitFromB1ToB2OnB12B2.entryB2"))); assertThat(stateMachine.getCurrentState(), is(equalTo(HState.B2))); stateMachine.fire(HEvent.B22A, 1); // enter A2 by history assertThat(stateMachine.consumeLog(), is(equalTo("exitB2.exitB.transitFromB2ToAOnB22A.entryA.entryA2"))); assertThat(stateMachine.getCurrentState(), is(equalTo(HState.A2))); stateMachine.terminate(); assertThat(stateMachine.consumeLog(), is(equalTo("exitA2.exitA"))); } @Test public void testTestEvent() { HState testResult = stateMachine.test(HEvent.A12A2, 1); assertThat(testResult, is(equalTo(HState.A2))); assertThat(stateMachine.consumeLog(), is(equalTo(""))); assertThat(stateMachine.getStatus(), is(equalTo(StateMachineStatus.INITIALIZED))); stateMachine.start(); assertThat(stateMachine.consumeLog(), is(equalTo("entryA.entryA1"))); assertThat(stateMachine.getCurrentState(), is(equalTo(HState.A1))); stateMachine.fire(HEvent.A12A2, 1); assertThat(stateMachine.consumeLog(), is(equalTo("exitA1.transitFromA1ToA2OnA12A2.entryA2"))); assertThat(stateMachine.getCurrentState(), is(equalTo(HState.A2))); testResult = stateMachine.test(HEvent.A2B, 1); assertThat(stateMachine.consumeLog(), is(equalTo(""))); assertThat(testResult, is(equalTo(HState.B1))); } @Test public void testDeclarativeHierarchicalState() { stateMachine.fire(HEvent.A12A3, 1); assertThat(stateMachine.consumeLog(), is(equalTo("entryA.entryA1.exitA1.transitA12A3.enterA3"))); assertThat(stateMachine.getCurrentState(), is(equalTo(HState.A3))); stateMachine.fire(HEvent.A32A1, 1); assertThat(stateMachine.consumeLog(), is(equalTo("leftA3.transitA32A1.entryA1"))); assertThat(stateMachine.getCurrentState(), is(equalTo(HState.A1))); } @Test public void testTransitionBetweenInnerStates() { stateMachine.fire(HEvent.A12B3, 1); assertThat(stateMachine.consumeLog(), is(equalTo("entryA.entryA1.exitA1.exitA.transitA12B3.entryB.enterB3"))); assertThat(stateMachine.getCurrentState(), is(equalTo(HState.B3))); } @Test public void testExternalTransitionBetweenParentAndChild() { stateMachine.start(); assertThat(stateMachine.consumeLog(), is(equalTo("entryA.entryA1"))); assertThat(stateMachine.getCurrentState(), is(equalTo(HState.A1))); stateMachine.fire(HEvent.A12A1a1, 1); assertThat(stateMachine.consumeLog(), is(equalTo("exitA1.entryA1.transitA12A1a1.enterA1a.enterA1a1"))); assertThat(stateMachine.getCurrentState(), is(equalTo(HState.A1a1))); stateMachine.fire(HEvent.A1a12A1, 1); assertThat(stateMachine.consumeLog(), is(equalTo("leftA1a1.leftA1a.exitA1.transitA1a12A1.entryA1"))); assertThat(stateMachine.getCurrentState(), is(equalTo(HState.A1))); } @Test public void testLocalTransitionBetweenParentAndChild() { stateMachine.start(); stateMachine.consumeLog(); stateMachine.fire(HEvent.A12A1a, 1); assertThat(stateMachine.consumeLog(), is(equalTo("exitA1.entryA1.transitA12A1a.enterA1a"))); stateMachine.fire(HEvent.A1a2A1a1, 1); assertThat(stateMachine.consumeLog(), is(equalTo("transitA1a2A1a1.enterA1a1"))); stateMachine.fire(HEvent.A1a12A1a, 1); assertThat(stateMachine.consumeLog(), is(equalTo("leftA1a1.transitA1a12A1a"))); assertThat(stateMachine.getCurrentState(), is(equalTo(HState.A1a))); } @Test public void testParentTransition() { stateMachine.start(); stateMachine.consumeLog(); stateMachine.fire(HEvent.A12A1a1, 1); stateMachine.consumeLog(); stateMachine.fire(HEvent.A12B3, 1); assertThat(stateMachine.consumeLog(), is(equalTo("leftA1a1.leftA1a.exitA1.exitA.transitA12B3.entryB.enterB3"))); assertThat(stateMachine.getCurrentState(), is(equalTo(HState.B3))); } @Test public void testSavedData() { stateMachine.start(); stateMachine.fire(HEvent.A12A3, 1); stateMachine.fire(HEvent.A32A1, 0); StateMachineData.Reader<HierachicalStateMachine, HState, HEvent, Integer> savedData = stateMachine.dumpSavedData(); stateMachine.terminate(); assertThat(savedData.currentState(), is(equalTo(HState.A1))); assertThat(savedData.initialState(), is(equalTo(HState.A))); assertThat(savedData.lastState(), is(equalTo(HState.A3))); assertThat(savedData.lastActiveChildStateOf(HState.A), is(equalTo(HState.A3))); setup(); stateMachine.loadSavedData(savedData); StateMachineData.Reader<HierachicalStateMachine, HState, HEvent, Integer> savedData2 = stateMachine.dumpSavedData(); assertThat(savedData2.lastActiveChildStateOf(HState.A), is(equalTo(HState.A3))); } @Test public void testDeepHistoryState() { stateMachine.start(); stateMachine.consumeLog(); stateMachine.fire(HEvent.A12A2, 1); stateMachine.consumeLog(); stateMachine.fire(HEvent.A22A2a, 1); stateMachine.consumeLog(); assertThat(stateMachine.getCurrentState(), is(equalTo(HState.A2a))); stateMachine.fire(HEvent.A2B, 1); assertThat(stateMachine.consumeLog(), is(equalTo("leftA2a.exitA2.exitA.transitFromAToBOnA2B.entryB.entryB1"))); assertThat(stateMachine.getCurrentState(), is(equalTo(HState.B1))); stateMachine.fire(HEvent.B2A, 1); assertThat(stateMachine.consumeLog(), is(equalTo("exitB1.exitB.transitFromBToAOnB2A.entryA.entryA2.enterA2a"))); assertThat(stateMachine.getCurrentState(), is(equalTo(HState.A2a))); } @Test public void testShallowHistoryState() { stateMachine.fire(HEvent.A2B, 1); stateMachine.fire(HEvent.B12B2, 1); stateMachine.fire(HEvent.B22B2a, 1); stateMachine.consumeLog(); assertThat(stateMachine.getCurrentState(), is(equalTo(HState.B2a))); stateMachine.fire(HEvent.B2A, 1); assertThat(stateMachine.consumeLog(), is(equalTo("leftB2a.exitB2.exitB.transitFromBToAOnB2A.entryA.entryA1"))); assertThat(stateMachine.getCurrentState(), is(equalTo(HState.A1))); stateMachine.fire(HEvent.A2B, 1); assertThat(stateMachine.consumeLog(), is(equalTo("exitA1.exitA.transitFromAToBOnA2B.entryB.entryB2"))); assertThat(stateMachine.getCurrentState(), is(equalTo(HState.B2))); } @Test public void testNestedFinalState() { stateMachine.start(); stateMachine.consumeLog(); stateMachine.fire(HEvent.A12A4, 1); assertThat(stateMachine.consumeLog(), is(equalTo("exitA1.transitA12A4.enterA4.exitA.transitA2C.enterC"))); assertThat(stateMachine.getCurrentState(), is(equalTo(HState.C))); } @Test public void testExportAndImportHierarchicalStateMachine() { SCXMLVisitor visitor = SquirrelProvider.getInstance().newInstance(SCXMLVisitor.class); stateMachine.accept(visitor); // visitor.convertSCXMLFile("HierarchicalStateMachine", true); String xmlDef = visitor.getScxml(false); UntypedStateMachineBuilder builder = new UntypedStateMachineImporter().importDefinition(xmlDef); stateMachine = builder.newAnyStateMachine(HState.A); HState testResult = stateMachine.test(HEvent.A12A2, 1); assertThat(testResult, is(equalTo(HState.A2))); assertThat(stateMachine.consumeLog(), is(equalTo(""))); assertThat(stateMachine.getStatus(), is(equalTo(StateMachineStatus.INITIALIZED))); stateMachine.start(); assertThat(stateMachine.consumeLog(), is(equalTo("entryA.entryA1"))); assertThat(stateMachine.getCurrentState(), is(equalTo(HState.A1))); stateMachine.fire(HEvent.A12A2, 1); assertThat(stateMachine.consumeLog(), is(equalTo("exitA1.transitFromA1ToA2OnA12A2.entryA2"))); assertThat(stateMachine.getCurrentState(), is(equalTo(HState.A2))); testResult = stateMachine.test(HEvent.A2B, 1); assertThat(stateMachine.consumeLog(), is(equalTo(""))); assertThat(testResult, is(equalTo(HState.B1))); } @Test public void testExportDotHierarchicalStateMachine() { DotVisitor visitor = SquirrelProvider.getInstance().newInstance(DotVisitor.class); stateMachine.accept(visitor); visitor.convertDotFile("HierarchicalStateMachine"); } @Test public void testChildTransition() { stateMachine.start(); System.out.println(stateMachine.consumeLog()); stateMachine.fire(HEvent.A2D, 1); System.out.println(stateMachine.consumeLog()); stateMachine.fire(HEvent.D2E1, 2); assertThat(stateMachine.consumeLog(), is(equalTo("leftD.transitD2E1.enterE.enterE1"))); assertThat(stateMachine.getCurrentState(), is(equalTo(HState.E1))); } }