package org.squirrelframework.foundation.fsm.samples; import com.google.common.collect.Lists; import junit.framework.Assert; import org.junit.Before; import org.junit.Test; import org.squirrelframework.foundation.fsm.StateMachineBuilderFactory; import org.squirrelframework.foundation.fsm.UntypedAnonymousAction; import org.squirrelframework.foundation.fsm.UntypedStateMachine; import org.squirrelframework.foundation.fsm.UntypedStateMachineBuilder; import org.squirrelframework.foundation.fsm.annotation.StateMachineParameters; import org.squirrelframework.foundation.fsm.impl.AbstractUntypedStateMachine; /** * Created by kailianghe on 7/12/14. */ public class DecisionStateSampleTest { enum DecisionState { A, _A, B, C, D } enum DecisionEvent { A2ANY, A2B, A2C, A2D, ANY2A } static StringBuilder logger; @Before public void setUp() { logger = new StringBuilder(); } @StateMachineParameters(stateType = DecisionState.class, eventType = DecisionEvent.class, contextType = Integer.class) static class DecisionStateMachine extends AbstractUntypedStateMachine { public void enterA(DecisionState from, DecisionState to, DecisionEvent event, Integer context) { logger.append("enterA"); } public void leftA(DecisionState from, DecisionState to, DecisionEvent event, Integer context) { logger.append("leftA"); } public void a2b(DecisionState from, DecisionState to, DecisionEvent event, Integer context) { logger.append("a2b"); } public void a2c(DecisionState from, DecisionState to, DecisionEvent event, Integer context) { logger.append("a2c"); } public void a2d(DecisionState from, DecisionState to, DecisionEvent event, Integer context) { logger.append("a2d"); } String consumeLog() { final String result = logger.toString(); logger = new StringBuilder(); return result; } @Override protected void beforeActionInvoked(Object fromState, Object toState, Object event, Object context) { if (logger.length() > 0) { logger.append('.'); } } } static class DecisionMaker extends UntypedAnonymousAction { // wrap any local state in action int transitionCount = 0; final String name; DecisionMaker(String name) { this.name = name; } @Override public void execute(Object from, Object to, Object event, Object context, UntypedStateMachine stateMachine) { DecisionState typedFrom = (DecisionState)from; DecisionState typedTo = (DecisionState)to; DecisionEvent typedEvent = (DecisionEvent)event; if(typedFrom!=null && typedTo==null) { logger.append("leftMakeDecision"); // local state clean up at some situation, e.g. at some criteria restore transitionCount } else if(typedFrom==null && typedTo!=null) { logger.append("enterMakeDecision"); // local state initialize at some situation } else if(typedFrom!=null && typedFrom!=null && typedEvent==DecisionEvent.A2ANY) { // perform dynamic transitions Integer typedContext = (Integer)context; if(typedContext < 10) { stateMachine.fire(DecisionEvent.A2B, context); transitionCount++; } else if(typedContext < 20) { stateMachine.fire(DecisionEvent.A2C, context); transitionCount++; } else if(typedContext < 40) { stateMachine.fire(DecisionEvent.A2D, context); transitionCount++; } else { stateMachine.fire(DecisionEvent.ANY2A, context); } // e.g. if transitionCount>10 then change transition router rules } } @Override public String name() { return name; } } DecisionStateMachine buildStateMachine() { DecisionStateMachine fsm; final UntypedStateMachineBuilder builder = StateMachineBuilderFactory.create(DecisionStateMachine.class); final DecisionMaker decisionMaker = new DecisionMaker("DecisionMaker"); // _A is decision state for A and it is invisible to user builder.defineNoInitSequentialStatesOn(DecisionState.A, DecisionState._A); builder.onEntry(DecisionState.A).callMethod("enterA"); builder.onExit(DecisionState.A).callMethod("leftA"); builder.onEntry(DecisionState._A).perform(decisionMaker); builder.onExit(DecisionState._A).perform(decisionMaker); // transition to left state A are all started with _A which means all transition cause exit state A must be router by _A builder.transitions().from(DecisionState._A).toAmong(DecisionState.B, DecisionState.C, DecisionState.D). onEach(DecisionEvent.A2B, DecisionEvent.A2C, DecisionEvent.A2D).callMethod("a2b|a2c|_"); builder.transitions().fromAmong(DecisionState.B, DecisionState.C, DecisionState.D). to(DecisionState.A).on(DecisionEvent.ANY2A); // use local transition avoid invoking state A exit functions when entering its decision state builder.localTransitions().between(DecisionState.A).and(DecisionState._A). onMutual(DecisionEvent.A2ANY, DecisionEvent.ANY2A). perform(Lists.newArrayList(decisionMaker, null)); fsm = builder.newUntypedStateMachine(DecisionState.A); return fsm; } @Test public void testDecisionStateMachine() { DecisionStateMachine fsm = buildStateMachine(); fsm.start(); Assert.assertTrue(fsm.getCurrentState().equals(DecisionState.A)); Assert.assertTrue("enterA".equals(fsm.consumeLog())); fsm.fire(DecisionEvent.A2B, 30); Assert.assertTrue(fsm.getCurrentState().equals(DecisionState.A)); Assert.assertTrue("".equals(fsm.consumeLog())); fsm.fire(DecisionEvent.A2ANY, 100); Assert.assertTrue(fsm.getCurrentState().equals(DecisionState.A)); Assert.assertTrue("enterMakeDecision.leftMakeDecision".equals(fsm.consumeLog())); fsm.fire(DecisionEvent.A2ANY, 5); Assert.assertTrue(fsm.getCurrentState().equals(DecisionState.B)); Assert.assertTrue("enterMakeDecision.leftMakeDecision.leftA.a2b".equals(fsm.consumeLog())); fsm.fire(DecisionEvent.ANY2A, 1000); Assert.assertTrue(fsm.getCurrentState().equals(DecisionState.A)); Assert.assertTrue("enterA".equals(fsm.consumeLog())); fsm.fire(DecisionEvent.A2ANY, 15); Assert.assertTrue(fsm.getCurrentState().equals(DecisionState.C)); Assert.assertTrue("enterMakeDecision.leftMakeDecision.leftA.a2c".equals(fsm.consumeLog())); fsm.fire(DecisionEvent.ANY2A, 1000); Assert.assertTrue(fsm.getCurrentState().equals(DecisionState.A)); Assert.assertTrue("enterA".equals(fsm.consumeLog())); fsm.fire(DecisionEvent.A2ANY, 30); Assert.assertTrue(fsm.getCurrentState().equals(DecisionState.D)); Assert.assertTrue("enterMakeDecision.leftMakeDecision.leftA".equals(fsm.consumeLog())); fsm.terminate(); // System.out.println(fsm.exportXMLDefinition(true)); } }