package org.squirrelframework.foundation.fsm;
import com.google.common.collect.Lists;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import org.squirrelframework.foundation.component.SquirrelProvider;
import org.squirrelframework.foundation.fsm.StateMachine.TransitionDeclinedEvent;
import org.squirrelframework.foundation.fsm.annotation.*;
import org.squirrelframework.foundation.fsm.impl.AbstractStateMachine;
import java.util.List;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
public class ParallelStateMachineTest {
enum PState {
Total, A, A1, A1a, A1b, A1c, A2, A2a, A2b, A2c, B, C
}
enum PEvent {
A1a2A1b, A1a2B, A1b2A1a, A1b2A1c, A2a2A2b, A2b2A2a, A2b2A2c, A2B, B2A, Finish
}
@States({
@State(name="Total", entryCallMethod="enterTotal", exitCallMethod="exitTotal"),
@State(parent="Total", name="A", entryCallMethod="enterA", exitCallMethod="exitA",
compositeType=StateCompositeType.PARALLEL, historyType=HistoryType.DEEP),
@State(parent="A", name="A1", entryCallMethod="enterA1", exitCallMethod="exitA1", historyType=HistoryType.DEEP),
@State(parent="A1", name="A1a", entryCallMethod="enterA1a", exitCallMethod="exitA1a", initialState=true),
@State(parent="A1", name="A1b", entryCallMethod="enterA1b", exitCallMethod="exitA1b"),
@State(parent="A1", name="A1c", entryCallMethod="enterA1c", exitCallMethod="exitA1c", isFinal=true),
@State(parent="A", name="A2", entryCallMethod="enterA2", exitCallMethod="exitA2", historyType=HistoryType.DEEP),
@State(parent="A2", name="A2a", entryCallMethod="enterA2a", exitCallMethod="exitA2a"),
@State(parent="A2", name="A2b", entryCallMethod="enterA2b", exitCallMethod="exitA2b", initialState=true),
@State(parent="A2", name="A2c", entryCallMethod="enterA2c", exitCallMethod="exitA2c", isFinal=true),
@State(parent="Total", name="B", entryCallMethod="enterB", exitCallMethod="exitB"),
@State(parent="Total", name="C", entryCallMethod="enterC", exitCallMethod="exitC"),
})
@Transitions({
@Transit(from="A", to="B", on="A2B", callMethod="transitA2B"),
@Transit(from="B", to="A", on="B2A", callMethod="transitB2A"),
@Transit(from="A1a", to="A1b", on="A1a2A1b", callMethod="transitA1a2A1b"),
@Transit(from="A1a", to="B", on="A1a2B", callMethod="transitA1a2B"),
@Transit(from="A1b", to="A1a", on="A1b2A1a", callMethod="transitA1b2A1a"),
@Transit(from="A1b", to="A1c", on="A1b2A1c", callMethod="transitA1b2A1c"),
@Transit(from="A2a", to="A2b", on="A2a2A2b", callMethod="transitA2a2A2b"),
@Transit(from="A2b", to="A2a", on="A2b2A2a", callMethod="transitA2b2A2a"),
@Transit(from="A2b", to="A2c", on="A2b2A2c", callMethod="transitA2b2A2c"),
@Transit(from="A", to="C", on="Finish", callMethod="transitA2C"),
})
@ContextEvent(finishEvent="Finish")
static class ParallelStateMachine extends AbstractStateMachine<ParallelStateMachine, PState, PEvent, Integer> {
private StringBuilder logger = new StringBuilder();
public void transitA1a2B(PState from, PState to, PEvent event, Integer context) {
logger.append("transitA1a2B");
}
public void transitA2C(PState from, PState to, PEvent event, Integer context) {
logger.append("transitA2C");
}
public void transitA1b2A1c(PState from, PState to, PEvent event, Integer context) {
logger.append("transitA1b2A1c");
}
public void transitA2b2A2c(PState from, PState to, PEvent event, Integer context) {
logger.append("transitA2b2A2c");
}
public void transitA2a2A2b(PState from, PState to, PEvent event, Integer context) {
logger.append("transitA2a2A2b");
}
public void transitA2b2A2a(PState from, PState to, PEvent event, Integer context) {
logger.append("transitA2b2A2a");
}
public void transitA1b2A1a(PState from, PState to, PEvent event, Integer context) {
logger.append("transitA1b2A1a");
}
public void transitA1a2A1b(PState from, PState to, PEvent event, Integer context) {
logger.append("transitA1a2A1b");
}
public void transitB2A(PState from, PState to, PEvent event, Integer context) {
logger.append("transitB2A");
}
public void transitA2B(PState from, PState to, PEvent event, Integer context) {
logger.append("transitA2B");
}
public void enterTotal(PState from, PState to, PEvent event, Integer context) {
logger.append("enterTotal");
}
public void exitTotal(PState from, PState to, PEvent event, Integer context) {
logger.append("exitTotal");
}
public void enterA(PState from, PState to, PEvent event, Integer context) {
logger.append("enterA");
}
public void exitA(PState from, PState to, PEvent event, Integer context) {
logger.append("exitA");
}
public void enterA1(PState from, PState to, PEvent event, Integer context) {
logger.append("enterA1");
}
public void exitA1(PState from, PState to, PEvent event, Integer context) {
logger.append("exitA1");
}
public void enterA1a(PState from, PState to, PEvent event, Integer context) {
logger.append("enterA1a");
}
public void exitA1a(PState from, PState to, PEvent event, Integer context) {
logger.append("exitA1a");
}
public void enterA1b(PState from, PState to, PEvent event, Integer context) {
logger.append("enterA1b");
}
public void exitA1b(PState from, PState to, PEvent event, Integer context) {
logger.append("exitA1b");
}
public void enterA1c(PState from, PState to, PEvent event, Integer context) {
logger.append("enterA1c");
}
public void exitA1c(PState from, PState to, PEvent event, Integer context) {
logger.append("exitA1c");
}
public void enterA2(PState from, PState to, PEvent event, Integer context) {
logger.append("enterA2");
}
public void exitA2(PState from, PState to, PEvent event, Integer context) {
logger.append("exitA2");
}
public void enterA2a(PState from, PState to, PEvent event, Integer context) {
logger.append("enterA2a");
}
public void exitA2a(PState from, PState to, PEvent event, Integer context) {
logger.append("exitA2a");
}
public void enterA2b(PState from, PState to, PEvent event, Integer context) {
logger.append("enterA2b");
}
public void exitA2b(PState from, PState to, PEvent event, Integer context) {
logger.append("exitA2b");
}
public void enterA2c(PState from, PState to, PEvent event, Integer context) {
logger.append("enterA2c");
}
public void exitA2c(PState from, PState to, PEvent event, Integer context) {
logger.append("exitA2c");
}
public void enterB(PState from, PState to, PEvent event, Integer context) {
logger.append("enterB");
}
public void exitB(PState from, PState to, PEvent event, Integer context) {
logger.append("exitB");
}
public void enterC(PState from, PState to, PEvent event, Integer context) {
logger.append("enterC");
}
public void exitC(PState from, PState to, PEvent event, Integer context) {
logger.append("exitC");
}
@Override
protected void beforeActionInvoked(PState from, PState to, PEvent 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;
}
}
private ParallelStateMachine stateMachine;
@AfterClass
public static void afterTest() {
ConverterProvider.INSTANCE.clearRegistry();
}
@After
public void teardown() {
stateMachine.terminate(null);
}
@Before
public void setup() {
StateMachineBuilder<ParallelStateMachine, PState, PEvent, Integer> builder = StateMachineBuilderFactory.create
(ParallelStateMachine.class, PState.class, PEvent.class, Integer.class);
stateMachine = builder.newStateMachine(PState.A);
}
@Test
public void testInitialParallelStates() {
stateMachine.start();
assertThat(stateMachine.consumeLog(), is(equalTo("enterTotal.enterA.enterA1.enterA1a.enterA2.enterA2b")));
assertThat(stateMachine.getCurrentState(), is(equalTo(PState.A)));
assertThat(stateMachine.getSubStatesOn(PState.A), contains(PState.A1a, PState.A2b));
}
@Test
public void testReceiveSubStateEvent() {
stateMachine.addTransitionDeclinedListener(new StateMachine.TransitionDeclinedListener<ParallelStateMachine, PState, PEvent, Integer>() {
@Override
public void transitionDeclined(TransitionDeclinedEvent<ParallelStateMachine, PState, PEvent, Integer> event) {
stateMachine.consumeLog();
}
});
stateMachine.start();
stateMachine.consumeLog();
stateMachine.fire(PEvent.A1a2A1b, 1);
assertThat(stateMachine.consumeLog(), is(equalTo("exitA1a.transitA1a2A1b.enterA1b")));
assertThat(stateMachine.getCurrentState(), is(equalTo(PState.A)));
assertThat(stateMachine.getSubStatesOn(PState.A), contains(PState.A2b, PState.A1b));
stateMachine.terminate(null);
assertThat(stateMachine.consumeLog(), is(equalTo("exitA2b.exitA2.exitA1b.exitA1.exitA.exitTotal")));
}
@Test
public void testEnterSubFinalState() {
stateMachine.start();
stateMachine.consumeLog();
stateMachine.fire(PEvent.A1a2A1b, 1);
stateMachine.consumeLog();
stateMachine.fire(PEvent.A1b2A1c, 1);
assertThat(stateMachine.consumeLog(), is(equalTo("exitA1b.transitA1b2A1c.enterA1c")));
assertThat(stateMachine.getCurrentState(), is(equalTo(PState.A)));
assertThat(stateMachine.getSubStatesOn(PState.A), contains(PState.A2b, PState.A1c));
stateMachine.fire(PEvent.A2b2A2c, 1);
assertThat(stateMachine.consumeLog(), is(equalTo("exitA2b.transitA2b2A2c.enterA2c.exitA1.exitA2.exitA.transitA2C.enterC")));
assertThat(stateMachine.getCurrentState(), is(equalTo(PState.C)));
stateMachine.terminate(null);
assertThat(stateMachine.consumeLog(), is(equalTo("exitC.exitTotal")));
}
@Test
public void testSavedData() {
stateMachine.start();
stateMachine.fire(PEvent.A1a2A1b, 1);
stateMachine.fire(PEvent.A1b2A1c, 1);
List<PState> subStates = stateMachine.getSubStatesOn(stateMachine.getCurrentState());
assertEquals("Total/A/A2/A2b", stateMachine.getRawStateFrom(subStates.get(0)).getPath());
assertEquals("Total/A/A1/A1c", stateMachine.getRawStateFrom(subStates.get(1)).getPath());
assertEquals("Total/A", stateMachine.getCurrentRawState().getPath());
StateMachineData.Reader<ParallelStateMachine, PState, PEvent, Integer> savedData =
stateMachine.dumpSavedData();
stateMachine.terminate(null);
assertThat(savedData.currentState(), is(equalTo(PState.A)));
assertThat(savedData.lastActiveChildStateOf(PState.A1), is(equalTo(PState.A1b)));
List<PState> expectedResult = Lists.newArrayList(PState.A2b, PState.A1c);
assertThat(savedData.subStatesOn(PState.A), is(equalTo(expectedResult)));
stateMachine = StateMachineBuilderFactory.create(savedData.typeOfStateMachine(), savedData.typeOfState(),
savedData.typeOfEvent(), savedData.typeOfContext()).newStateMachine(PState.A);
stateMachine.loadSavedData(savedData);
stateMachine.fire(PEvent.A2b2A2c, 1);
assertThat(stateMachine.consumeLog(), is(equalTo("exitA2b.transitA2b2A2c.enterA2c.exitA1.exitA2.exitA.transitA2C.enterC")));
assertThat(stateMachine.getCurrentState(), is(equalTo(PState.C)));
stateMachine.terminate();
assertThat(stateMachine.consumeLog(), is(equalTo("exitC.exitTotal")));
}
@Test
public void testParallelSubStateExit() {
stateMachine.start();
stateMachine.consumeLog();
stateMachine.fire(PEvent.A1a2B, 1);
assertThat(stateMachine.consumeLog(), is(equalTo("exitA1a.exitA1.exitA2b.exitA2.exitA.transitA1a2B.enterB")));
assertThat(stateMachine.getCurrentState(), is(equalTo(PState.B)));
}
@Test
public void testHistoricalState() {
stateMachine.start();
assertThat(stateMachine.getCurrentState(), is(equalTo(PState.A)));
assertThat(stateMachine.getSubStatesOn(PState.A), contains(PState.A1a, PState.A2b));
stateMachine.fire(PEvent.A1a2A1b, 1);
assertThat(stateMachine.getCurrentState(), is(equalTo(PState.A)));
assertThat(stateMachine.getSubStatesOn(PState.A), contains(PState.A2b, PState.A1b));
stateMachine.fire(PEvent.A2b2A2a, 1);
assertThat(stateMachine.getCurrentState(), is(equalTo(PState.A)));
assertThat(stateMachine.getSubStatesOn(PState.A), contains(PState.A1b, PState.A2a));
stateMachine.fire(PEvent.A2B, 1);
assertThat(stateMachine.getCurrentState(), is(equalTo(PState.B)));
assertThat(stateMachine.getSubStatesOn(PState.A), is(empty()));
assertThat(stateMachine.getSubStatesOn(PState.B), is(empty()));
stateMachine.fire(PEvent.B2A, 1);
assertThat(stateMachine.getCurrentState(), is(equalTo(PState.A)));
assertThat(stateMachine.getSubStatesOn(PState.A), contains(PState.A1b, PState.A2a));
}
@Test
public void testExportAndImportParallelState() {
SCXMLVisitor visitor = SquirrelProvider.getInstance().newInstance(SCXMLVisitor.class);
stateMachine.accept(visitor);
// visitor.convertSCXMLFile("ParallelStateMachine", true);
String xmlDef = visitor.getScxml(false);
UntypedStateMachineBuilder builder = new UntypedStateMachineImporter().importDefinition(xmlDef);
stateMachine = builder.newAnyStateMachine(PState.A);
stateMachine.start();
assertThat(stateMachine.consumeLog(), is(equalTo("enterTotal.enterA.enterA1.enterA1a.enterA2.enterA2b")));
assertThat(stateMachine.getCurrentState(), is(equalTo(PState.A)));
assertThat(stateMachine.getSubStatesOn(PState.A), contains(PState.A1a, PState.A2b));
stateMachine.fire(PEvent.A1a2A1b, 1);
assertThat(stateMachine.consumeLog(), is(equalTo("exitA1a.transitA1a2A1b.enterA1b")));
assertThat(stateMachine.getCurrentState(), is(equalTo(PState.A)));
assertThat(stateMachine.getSubStatesOn(PState.A), contains(PState.A2b, PState.A1b));
}
}