package org.squirrelframework.foundation.fsm;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import junit.framework.Assert;
import org.junit.Before;
import org.junit.Test;
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 LinkedStateMachineTest {
enum LState {
A, B, C, D, A1, A2, A3
}
enum LEvent {
A2B, B2C, C2D, D2A, A12A2, A22A3, A32A1
}
@States({
@State(name = "A", entryCallMethod = "enterA", exitCallMethod = "leftA"),
@State(name = "B", entryCallMethod = "enterB", exitCallMethod = "leftB"),
@State(name = "C", entryCallMethod = "enterC", exitCallMethod = "leftC"),
@State(name = "C", entryCallMethod = "enterD", exitCallMethod = "leftD") })
@Transitions({
@Transit(from = "A", to = "B", on = "A2B", callMethod = "transitA2B"),
@Transit(from = "B", to = "C", on = "B2C", callMethod = "transitB2C"),
@Transit(from = "C", to = "D", on = "C2D", callMethod = "transitC2D"),
@Transit(from = "D", to = "A", on = "D2A", callMethod = "transitD2A") })
static class TestStateMachine extends
AbstractStateMachine<TestStateMachine, LState, LEvent, Integer> {
private StringBuilder logger;
protected TestStateMachine(StringBuilder logger) {
this.logger = logger;
}
public void transitA2B(LState from, LState to, LEvent event,
Integer context) {
addOptionalDot();
logger.append("transitA2B");
}
public void transitB2C(LState from, LState to, LEvent event,
Integer context) {
addOptionalDot();
logger.append("transitB2C");
}
public void transitC2D(LState from, LState to, LEvent event,
Integer context) {
addOptionalDot();
logger.append("transitC2D");
}
public void transitD2A(LState from, LState to, LEvent event,
Integer context) {
addOptionalDot();
logger.append("transitD2A");
}
public void enterA(LState from, LState to, LEvent event, Integer context) {
addOptionalDot();
logger.append("enterA");
}
public void leftA(LState from, LState to, LEvent event, Integer context) {
addOptionalDot();
logger.append("leftA");
}
public void enterB(LState from, LState to, LEvent event, Integer context) {
addOptionalDot();
logger.append("enterB");
}
public void leftB(LState from, LState to, LEvent event, Integer context) {
addOptionalDot();
logger.append("leftB");
}
public void enterC(LState from, LState to, LEvent event, Integer context) {
addOptionalDot();
logger.append("enterC");
}
public void leftC(LState from, LState to, LEvent event, Integer context) {
addOptionalDot();
logger.append("leftC");
}
public void enterD(LState from, LState to, LEvent event, Integer context) {
addOptionalDot();
logger.append("enterA");
}
public void leftD(LState from, LState to, LEvent event, Integer context) {
addOptionalDot();
logger.append("leftA");
}
@Override
public void start(Integer context) {
logger.append("start1");
super.start(context);
}
@Override
public void terminate(Integer context) {
addOptionalDot();
logger.append("terminate1");
super.terminate(context);
}
private void addOptionalDot() {
if (logger.length() > 0) {
logger.append('.');
}
}
}
@States({
@State(name = "A1", entryCallMethod = "enterA1", exitCallMethod = "leftA1"),
@State(name = "A2", entryCallMethod = "enterA2", exitCallMethod = "leftA2"),
@State(name = "A3", entryCallMethod = "enterA3", exitCallMethod = "leftA3") })
@Transitions({
@Transit(from = "A1", to = "A2", on = "A12A2", callMethod = "transitA12A2"),
@Transit(from = "A2", to = "A3", on = "A22A3", callMethod = "transitA22A3"),
@Transit(from = "A3", to = "A1", on = "A32A1", callMethod = "transitA32A1") })
static class LinkedStateMachine extends
AbstractStateMachine<LinkedStateMachine, LState, LEvent, Integer> {
private StringBuilder logger;
protected LinkedStateMachine(StringBuilder logger) {
this.logger = logger;
}
public void transitA12A2(LState from, LState to, LEvent event,
Integer context) {
logger.append("transitA12A2");
}
public void transitA22A3(LState from, LState to, LEvent event,
Integer context) {
logger.append("transitA22A3");
}
public void transitA32A1(LState from, LState to, LEvent event,
Integer context) {
logger.append("transitA32A1");
}
public void enterA1(LState from, LState to, LEvent event,
Integer context) {
logger.append("enterA1");
}
public void leftA1(LState from, LState to, LEvent event, Integer context) {
logger.append("leftA1");
}
public void enterA2(LState from, LState to, LEvent event,
Integer context) {
logger.append("enterA2");
}
public void leftA2(LState from, LState to, LEvent event, Integer context) {
logger.append("leftA2");
}
public void enterA3(LState from, LState to, LEvent event,
Integer context) {
logger.append("enterA3");
}
public void leftA3(LState from, LState to, LEvent event, Integer context) {
logger.append("leftA3");
}
@Override
protected void beforeActionInvoked(LState from, LState to, LEvent event, Integer context) {
addOptionalDot();
}
@Override
public void start(Integer context) {
addOptionalDot();
logger.append("start2");
super.start(context);
}
@Override
public void terminate(Integer context) {
addOptionalDot();
logger.append("terminate2");
super.terminate(context);
}
private void addOptionalDot() {
if (logger.length() > 0) {
logger.append('.');
}
}
}
TestStateMachine stateMachine;
TestStateMachine stateMachine2;
StringBuilder logger;
@Before
public void setup() {
logger = new StringBuilder();
StateMachineBuilder<LinkedStateMachine, LState, LEvent, Integer> builderOfLinkedStateMachine =
StateMachineBuilderFactory.create(LinkedStateMachine.class, LState.class, LEvent.class,
Integer.class, new Class<?>[] { StringBuilder.class });
StateMachineBuilder<TestStateMachine, LState, LEvent, Integer> builderOfTestStateMachine =
StateMachineBuilderFactory.create(TestStateMachine.class, LState.class, LEvent.class,
Integer.class, new Class<?>[] { StringBuilder.class });
// defined linked state
builderOfTestStateMachine.defineLinkedState(LState.A, builderOfLinkedStateMachine, LState.A1, logger);
builderOfTestStateMachine.defineLinkedState(LState.C, builderOfLinkedStateMachine, LState.A2, logger);
stateMachine = builderOfTestStateMachine.newStateMachine(LState.A, logger);
stateMachine2 = builderOfTestStateMachine.newStateMachine(LState.A, logger);
}
@Test
public void testInitialLinkedState() {
doTestInitialLinkedState(stateMachine, logger);
logger.append("|");
doTestInitialLinkedState(stateMachine2, logger);
assertThat(logger.toString(), equalTo("start1.enterA.start2.enterA1|start1.enterA.start2.enterA1"));
}
private void doTestInitialLinkedState(TestStateMachine stateMachine, StringBuilder logger) {
stateMachine.start(0);
assertThat(stateMachine.getCurrentState(), equalTo(LState.A1));
assertThat(stateMachine.getCurrentRawState().getStateId(), equalTo(LState.A1));
}
@Test
public void testLinkedStateMachineProcessEvent() {
stateMachine.fire(LEvent.A12A2, 0);
assertThat(
logger.toString(),
equalTo("start1.enterA.start2.enterA1.leftA1.transitA12A2.enterA2"));
}
@Test
public void testTestStateMachineProcessEvent() {
stateMachine.fire(LEvent.A2B, 0);
assertThat(
logger.toString(),
equalTo("start1.enterA.start2.enterA1.terminate2.leftA1.leftA.transitA2B.enterB"));
}
@Test
public void testInitialLinkedState2() {
stateMachine.fire(LEvent.A2B, 0);
stateMachine.fire(LEvent.B2C, 0);
stateMachine.fire(LEvent.A22A3, 0);
assertThat(stateMachine.getCurrentState(), equalTo(LState.A3));
assertThat(stateMachine.getCurrentRawState().getStateId(), equalTo(LState.A3));
}
@Test
public void testSavedData() {
stateMachine.fire(LEvent.A12A2, 0);
assertThat(stateMachine.getCurrentState(), equalTo(LState.A2));
StateMachineData.Reader<TestStateMachine, LState, LEvent, Integer> savedData =
stateMachine.dumpSavedData();
assertThat(savedData.linkedStates(), contains(LState.A));
stateMachine.terminate(null);
try {
// use buffering
OutputStream file = new FileOutputStream("data.sqr");
OutputStream buffer = new BufferedOutputStream(file);
OutputStreamWriter osw = new OutputStreamWriter(buffer, "UTF-8");
osw.write(ObjectSerializableSupport.serialize(savedData));
osw.flush();
} catch (IOException ex) {
Assert.fail();
}
setup();
try {
// use buffering
InputStream file = new FileInputStream("data.sqr");
InputStream buffer = new BufferedInputStream(file);
InputStreamReader isr = new InputStreamReader(buffer, "UTF-8");
BufferedReader br = new BufferedReader(isr);
String fileContent = br.readLine();
// deserialize the List
StateMachineData.Reader<TestStateMachine, LState, LEvent, Integer> loadedSavedData =
ObjectSerializableSupport.deserialize(fileContent);
stateMachine.loadSavedData(loadedSavedData);
stateMachine.fire(LEvent.A22A3, 0);
assertThat(stateMachine.getCurrentState(), equalTo(LState.A3));
} catch (Exception ex) {
ex.printStackTrace();
Assert.fail();
}
}
}