package org.squirrelframework.foundation.fsm; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertNull; import org.junit.Before; import org.junit.Test; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.squirrelframework.foundation.fsm.annotation.LogExecTime; 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; public class DeclarativeStateMachineTest extends AbstractStateMachineTest { @States({ @State(name="A", entryCallMethod="entryStateA", exitCallMethod="exitStateA"), @State(name="B", entryCallMethod="entryStateB", exitCallMethod="exitStateB"), @State(name="C", alias="StateC"), @State(name="D", entryCallMethod="entryStateD", exitCallMethod="exitStateD") }) @Transitions({ @Transit(from="A", to="B", on="ToB", callMethod="fromStateAToStateBOnGotoB"), @Transit(from="A", to="A", on="InternalA", callMethod="fromStateAToStateAOnInternalA", type=TransitionType.INTERNAL), @Transit(from="B", to="D", on="ToC"), @Transit(from="B", to="#StateC", on="ToC", priority=TransitionPriority.HIGH), @Transit(from="C", to="D", on="ToD", when=ExcellentCondition.class), @Transit(from="C", to="D", on="ToD", whenMvel="FailedCondition:::(context!=null && context>=0 && context<60)"), @Transit(from="D", to="A", on="ToA", callMethod="transitionWithException"), @Transit(from="D", to="Final", on="ToEnd", callMethod="fromStateDToFinalOnToEnd", isTargetFinal=true) }) interface DeclarativeStateMachine extends StateMachine<DeclarativeStateMachine, TestState, TestEvent, Integer> { // entry states void entryStateA(TestState from, TestState to, TestEvent event, Integer context); void entryStateB(TestState from, TestState to, TestEvent event, Integer context); void entryC(TestState from, TestState to, TestEvent event, Integer context); void entryStateD(TestState from, TestState to, TestEvent event, Integer context); // transitions void fromStateAToStateBOnGotoB(TestState from, TestState to, TestEvent event, Integer context); void fromStateAToStateAOnInternalA(TestState from, TestState to, TestEvent event, Integer context); void transitFromBToCOnToC(TestState from, TestState to, TestEvent event, Integer context); void transitFromCToDOnToDWhenExcellentCondition(TestState from, TestState to, TestEvent event, Integer context); void transitFromCToDOnToDWhenFailedCondition(TestState from, TestState to, TestEvent event, Integer context); void transitionWithException(TestState from, TestState to, TestEvent event, Integer context); void fromStateDToFinalOnToEnd(TestState from, TestState to, TestEvent event, Integer context); // exit states void exitStateA(TestState from, TestState to, TestEvent event, Integer context); void exitStateB(TestState from, TestState to, TestEvent event, Integer context); void exitC(TestState from, TestState to, TestEvent event, Integer context); void exitStateD(TestState from, TestState to, TestEvent event, Integer context); void beforeTransitionBegin(TestState from, TestEvent event, Integer context); void afterTransitionCompleted(TestState from, TestState to, TestEvent event, Integer context); void afterTransitionDeclined(TestState from, TestEvent event, Integer context); void afterTransitionCausedException(TestState fromState, TestState toState, TestEvent event, Integer context); void start(Integer context); void terminate(Integer context); } static class ExcellentCondition extends AnonymousCondition<Integer> { @Override public boolean isSatisfied(Integer context) { return context!=null && context>80; } } @SuppressWarnings("serial") static class DeclarativeStateMachineException extends RuntimeException { } public static class DeclarativeStateMachineImpl extends AbstractStateMachine<DeclarativeStateMachine, TestState, TestEvent, Integer> implements DeclarativeStateMachine { private DeclarativeStateMachine monitor; protected void postConstruct(DeclarativeStateMachine delegator) { this.monitor = delegator; } @Override public void entryStateA(TestState from, TestState to, TestEvent event, Integer context) { monitor.entryStateA(from, to, event, context); } @Override public void entryStateB(TestState from, TestState to, TestEvent event, Integer context) { monitor.entryStateB(from, to, event, context); } @Override public void entryC(TestState from, TestState to, TestEvent event, Integer context) { monitor.entryC(from, to, event, context); } @Override public void entryStateD(TestState from, TestState to, TestEvent event, Integer context) { monitor.entryStateD(from, to, event, context); } @Override public void fromStateAToStateBOnGotoB(TestState from, TestState to, TestEvent event, Integer context) { monitor.fromStateAToStateBOnGotoB(from, to, event, context); } @Override public void fromStateAToStateAOnInternalA(TestState from, TestState to, TestEvent event, Integer context) { monitor.fromStateAToStateAOnInternalA(from, to, event, context); } @Override @LogExecTime public void transitFromBToCOnToC(TestState from, TestState to, TestEvent event, Integer context) { monitor.transitFromBToCOnToC(from, to, event, context); try { Thread.sleep(200); } catch (InterruptedException e) { System.out.println("How dare you to wake me up?"); } } @Override public void transitFromCToDOnToDWhenExcellentCondition(TestState from, TestState to, TestEvent event, Integer context) { monitor.transitFromCToDOnToDWhenExcellentCondition(from, to, event, context); } @Override public void transitionWithException(TestState from, TestState to, TestEvent event, Integer context) { monitor.transitionWithException(from, to, event, context); throw new IllegalArgumentException("This exception is thrown on purpose."); } @Override public void fromStateDToFinalOnToEnd(TestState from, TestState to, TestEvent event, Integer context) { monitor.fromStateDToFinalOnToEnd(from, to, event, context); } @Override public void exitStateA(TestState from, TestState to, TestEvent event, Integer context) { monitor.exitStateA(from, to, event, context); } @Override public void exitStateB(TestState from, TestState to, TestEvent event, Integer context) { monitor.exitStateB(from, to, event, context); } @Override public void exitC(TestState from, TestState to, TestEvent event, Integer context) { monitor.exitC(from, to, event, context); } @Override public void exitStateD(TestState from, TestState to, TestEvent event, Integer context) { monitor.exitStateD(from, to, event, context); } @Override public void beforeTransitionBegin(TestState from, TestEvent event, Integer context) { super.beforeTransitionBegin(from, event, context); monitor.beforeTransitionBegin(from, event, context); } @Override public void afterTransitionCompleted(TestState from, TestState to, TestEvent event, Integer context) { super.afterTransitionCompleted(from, to, event, context); monitor.afterTransitionCompleted(from, to, event, context); } @Override public void afterTransitionDeclined(TestState from, TestEvent event, Integer context) { super.afterTransitionDeclined(from, event, context); monitor.afterTransitionDeclined(from, event, context); } @Override public void afterTransitionCausedException(TestState fromState, TestState toState, TestEvent event, Integer context) { if(getLastException().getTargetException().getMessage().equals("This exception is thrown on purpose.")) return; super.afterTransitionCausedException(fromState, toState, event, context); } @Override public void start(Integer context) { super.start(context); monitor.start(context); } @Override public void terminate(Integer context) { super.terminate(context); monitor.terminate(context); } @Override public void transitFromCToDOnToDWhenFailedCondition(TestState from, TestState to, TestEvent event, Integer context) { monitor.transitFromCToDOnToDWhenFailedCondition(from, to, event, context); } } @Mock private DeclarativeStateMachine monitor; private DeclarativeStateMachine stateMachine; /** * Initializes a test. */ @Before public void setup() { MockitoAnnotations.initMocks(this); StateMachineBuilder<DeclarativeStateMachine, TestState, TestEvent, Integer> builder = StateMachineBuilderFactory.<DeclarativeStateMachine, TestState, TestEvent, Integer> create(DeclarativeStateMachineImpl.class, TestState.class, TestEvent.class, Integer.class, DeclarativeStateMachine.class); stateMachine = builder.newStateMachine(TestState.A, monitor); StateMachineLogger fsmLogger = new StateMachineLogger(stateMachine); fsmLogger.startLogging(); } @Test public void testInternalTransition() { InOrder callSequence = Mockito.inOrder(monitor); stateMachine.fire(TestEvent.InternalA, null); callSequence.verify(monitor, Mockito.times(1)).beforeTransitionBegin( TestState.A, TestEvent.InternalA, null); callSequence.verify(monitor, Mockito.times(0)).exitStateA( TestState.A, null, TestEvent.InternalA, null); callSequence.verify(monitor, Mockito.times(1)).fromStateAToStateAOnInternalA( TestState.A, TestState.A, TestEvent.InternalA, null); callSequence.verify(monitor, Mockito.times(0)).entryStateA( null, TestState.A, TestEvent.InternalA, null); callSequence.verify(monitor, Mockito.times(1)).afterTransitionCompleted( TestState.A, TestState.A, TestEvent.InternalA, null); assertThat(stateMachine.getCurrentState(), equalTo(TestState.A)); } @Test public void testExternalTransition() { InOrder callSequence = Mockito.inOrder(monitor); assertNull(stateMachine.getCurrentState()); stateMachine.fire(TestEvent.ToB, null); callSequence.verify(monitor, Mockito.times(1)).beforeTransitionBegin( TestState.A, TestEvent.ToB, null); callSequence.verify(monitor, Mockito.times(1)).exitStateA( TestState.A, null, TestEvent.ToB, null); callSequence.verify(monitor, Mockito.times(1)).fromStateAToStateBOnGotoB( TestState.A, TestState.B, TestEvent.ToB, null); callSequence.verify(monitor, Mockito.times(1)).entryStateB( null, TestState.B, TestEvent.ToB, null); callSequence.verify(monitor, Mockito.times(1)).afterTransitionCompleted( TestState.A, TestState.B, TestEvent.ToB, null); assertThat(stateMachine.getCurrentState(), equalTo(TestState.B)); } @Test public void testDeclinedTransition() { InOrder callSequence = Mockito.inOrder(monitor); stateMachine.fire(TestEvent.ToB, null); stateMachine.fire(TestEvent.ToB, null); callSequence.verify(monitor, Mockito.times(1)).beforeTransitionBegin( TestState.B, TestEvent.ToB, null); callSequence.verify(monitor, Mockito.times(0)).exitStateB( TestState.B, null, TestEvent.ToB, null); callSequence.verify(monitor, Mockito.times(1)).afterTransitionDeclined( TestState.B, TestEvent.ToB, null); assertThat(stateMachine.getCurrentState(), equalTo(TestState.B)); } @Test public void testInvokeExtensionMethod() { InOrder callSequence = Mockito.inOrder(monitor); stateMachine.fire(TestEvent.ToB, null); stateMachine.fire(TestEvent.ToC, null); callSequence.verify(monitor, Mockito.times(1)).beforeTransitionBegin( TestState.B, TestEvent.ToC, null); callSequence.verify(monitor, Mockito.times(1)).exitStateB( TestState.B, null, TestEvent.ToC, null); callSequence.verify(monitor, Mockito.times(1)).transitFromBToCOnToC( TestState.B, TestState.C, TestEvent.ToC, null); callSequence.verify(monitor, Mockito.times(1)).entryC( null, TestState.C, TestEvent.ToC, null); callSequence.verify(monitor, Mockito.times(1)).afterTransitionCompleted( TestState.B, TestState.C, TestEvent.ToC, null); assertThat(stateMachine.getCurrentState(), equalTo(TestState.C)); } @Test public void testConditionTransition() { InOrder callSequence = Mockito.inOrder(monitor); stateMachine.fire(TestEvent.ToB, null); stateMachine.fire(TestEvent.ToC, null); stateMachine.fire(TestEvent.ToD, -10); callSequence.verify(monitor, Mockito.times(1)).beforeTransitionBegin( TestState.C, TestEvent.ToD, -10); callSequence.verify(monitor, Mockito.times(1)).afterTransitionDeclined( TestState.C, TestEvent.ToD, -10); assertThat(stateMachine.getCurrentState(), equalTo(TestState.C)); stateMachine.fire(TestEvent.ToD, 81); callSequence.verify(monitor, Mockito.times(1)).beforeTransitionBegin( TestState.C, TestEvent.ToD, 81); callSequence.verify(monitor, Mockito.times(1)).exitC( TestState.C, null, TestEvent.ToD, 81); callSequence.verify(monitor, Mockito.times(1)).transitFromCToDOnToDWhenExcellentCondition( TestState.C, TestState.D, TestEvent.ToD, 81); callSequence.verify(monitor, Mockito.times(1)).entryStateD( null, TestState.D, TestEvent.ToD, 81); callSequence.verify(monitor, Mockito.times(1)).afterTransitionCompleted( TestState.C, TestState.D, TestEvent.ToD, 81); assertThat(stateMachine.getCurrentState(), equalTo(TestState.D)); } @Test public void testConditionMvelTransition() { InOrder callSequence = Mockito.inOrder(monitor); stateMachine.fire(TestEvent.ToB, null); stateMachine.fire(TestEvent.ToC, null); stateMachine.fire(TestEvent.ToD, 41); callSequence.verify(monitor, Mockito.times(1)).beforeTransitionBegin( TestState.C, TestEvent.ToD, 41); callSequence.verify(monitor, Mockito.times(1)).exitC( TestState.C, null, TestEvent.ToD, 41); callSequence.verify(monitor, Mockito.times(1)).transitFromCToDOnToDWhenFailedCondition( TestState.C, TestState.D, TestEvent.ToD, 41); callSequence.verify(monitor, Mockito.times(1)).entryStateD( null, TestState.D, TestEvent.ToD, 41); callSequence.verify(monitor, Mockito.times(1)).afterTransitionCompleted( TestState.C, TestState.D, TestEvent.ToD, 41); assertThat(stateMachine.getCurrentState(), equalTo(TestState.D)); } @Test public void testTransitionWithException() { InOrder callSequence = Mockito.inOrder(monitor); assertThat(stateMachine.getInitialRawState().getAcceptableEvents(), containsInAnyOrder(TestEvent.InternalA, TestEvent.ToB)); stateMachine.fire(TestEvent.ToB, null); stateMachine.fire(TestEvent.ToC, null); stateMachine.fire(TestEvent.ToD, 81); stateMachine.fire(TestEvent.ToA, 50); callSequence.verify(monitor, Mockito.times(1)).beforeTransitionBegin( TestState.D, TestEvent.ToA, 50); callSequence.verify(monitor, Mockito.times(1)).exitStateD( TestState.D, null, TestEvent.ToA, 50); callSequence.verify(monitor, Mockito.times(1)).transitionWithException( TestState.D, TestState.A, TestEvent.ToA, 50); assertThat(stateMachine.getCurrentState(), equalTo(TestState.D)); } @Test public void testTransitToFinalState() { InOrder callSequence = Mockito.inOrder(monitor); stateMachine.fire(TestEvent.ToB, 0); callSequence.verify(monitor, Mockito.times(1)).start(0); stateMachine.fire(TestEvent.ToC, null); stateMachine.fire(TestEvent.ToD, 81); stateMachine.fire(TestEvent.ToEnd, -1); callSequence.verify(monitor, Mockito.times(1)).beforeTransitionBegin( TestState.D, TestEvent.ToEnd, -1); callSequence.verify(monitor, Mockito.times(1)).exitStateD( TestState.D, null, TestEvent.ToEnd, -1); callSequence.verify(monitor, Mockito.times(1)).fromStateDToFinalOnToEnd( TestState.D, TestState.Final, TestEvent.ToEnd, -1); callSequence.verify(monitor, Mockito.times(1)).afterTransitionCompleted( TestState.D, TestState.Final, TestEvent.ToEnd, -1); callSequence.verify(monitor, Mockito.times(1)).terminate(-1); assertThat(stateMachine.getStatus(), equalTo(StateMachineStatus.TERMINATED)); } }