Fix state handling with substates
- Fixes #23 - Transition with submachine now exists and enters super state if transition is external.
This commit is contained in:
@@ -127,6 +127,7 @@ public class EnumStateMachineFactory<S extends Enum<S>, E extends Enum<E>> exten
|
||||
|
||||
Collection<StateData<S, E>> stateDatas = popSameParents(stateStack);
|
||||
Collection<TransitionData<S, E>> transitionsData = getTransitionData(iterator.hasNext(), stateDatas);
|
||||
// Collection<TransitionData<S, E>> transitionsData = getTransitionData(false, null);
|
||||
|
||||
machine = buildMachine(machineMap, stateMap, stateDatas, transitionsData, getBeanFactory());
|
||||
// TODO: last part in if feels a bit hack
|
||||
|
||||
@@ -204,11 +204,21 @@ public abstract class AbstractState<S, E> implements State<S, E> {
|
||||
return submachine != null;
|
||||
}
|
||||
|
||||
protected StateMachine<S, E> getSubmachine() {
|
||||
/**
|
||||
* Gets the submachine.
|
||||
*
|
||||
* @return the submachine or null if not set
|
||||
*/
|
||||
public StateMachine<S, E> getSubmachine() {
|
||||
return submachine;
|
||||
}
|
||||
|
||||
protected Collection<Region<S, E>> getRegions() {
|
||||
/**
|
||||
* Gets the regions.
|
||||
*
|
||||
* @return the regions or empty collection if no regions
|
||||
*/
|
||||
public Collection<Region<S, E>> getRegions() {
|
||||
return regions;
|
||||
}
|
||||
|
||||
|
||||
@@ -121,7 +121,12 @@ public class StateMachineState<S, E> extends AbstractState<S, E> {
|
||||
@Override
|
||||
public void exit(E event, StateContext<S, E> context) {
|
||||
getSubmachine().getState().exit(event, context);
|
||||
getSubmachine().stop();
|
||||
// don't stop if it looks like we're coming back
|
||||
// stop would cause start with entry which would
|
||||
// enable default transition and state
|
||||
if (context.getTransition().getSource().getId() != getSubmachine().getState().getId()) {
|
||||
getSubmachine().stop();
|
||||
}
|
||||
Collection<? extends Action<S, E>> actions = getExitActions();
|
||||
if (actions != null && !isLocal(context)) {
|
||||
for (Action<S, E> action : actions) {
|
||||
|
||||
@@ -46,6 +46,7 @@ import org.springframework.statemachine.listener.StateMachineListener;
|
||||
import org.springframework.statemachine.processor.StateMachineHandler;
|
||||
import org.springframework.statemachine.processor.StateMachineOnTransitionHandler;
|
||||
import org.springframework.statemachine.processor.StateMachineRuntime;
|
||||
import org.springframework.statemachine.state.AbstractState;
|
||||
import org.springframework.statemachine.state.PseudoStateKind;
|
||||
import org.springframework.statemachine.state.State;
|
||||
import org.springframework.statemachine.transition.Transition;
|
||||
@@ -279,8 +280,7 @@ public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport
|
||||
|
||||
callHandlers(currentState, state, event);
|
||||
|
||||
currentState = state;
|
||||
entryToState(state, event, transition);
|
||||
setCurrentState(state, event, transition);
|
||||
|
||||
// TODO: should handle triggerles transition some how differently
|
||||
for (Transition<S,E> t : transitions) {
|
||||
@@ -294,6 +294,21 @@ public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport
|
||||
|
||||
}
|
||||
|
||||
void setCurrentState(State<S, E> state, Message<E> event, Transition<S, E> transition) {
|
||||
if (states.contains(state)) {
|
||||
currentState = state;
|
||||
entryToState(state, event, transition);
|
||||
} else if (currentState.isSubmachineState()) {
|
||||
if (transition != null && transition.getKind() == TransitionKind.EXTERNAL) {
|
||||
entryToState(currentState, event, transition);
|
||||
}
|
||||
// TODO: should find a better way to trick setting state for submachine
|
||||
// without a need to access package protected method via casting
|
||||
StateMachine<S, E> submachine = ((AbstractState<S, E>)currentState).getSubmachine();
|
||||
((AbstractStateMachine<S, E>)submachine).setCurrentState(state, event, transition);
|
||||
}
|
||||
}
|
||||
|
||||
private void exitFromState(State<S, E> state, Message<E> event, Transition<S, E> transition) {
|
||||
if (state != null) {
|
||||
log.trace("Exit state=[" + state + "]");
|
||||
|
||||
@@ -73,6 +73,15 @@ public abstract class AbstractStateMachineTests {
|
||||
E1,E2,E3,E4,EF
|
||||
}
|
||||
|
||||
public static enum TestStates2 {
|
||||
BUSY, PLAYING, PAUSED,
|
||||
IDLE, CLOSED, OPEN
|
||||
}
|
||||
|
||||
public static enum TestEvents2 {
|
||||
PLAY, STOP, PAUSE, EJECT, LOAD
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class BaseConfig {
|
||||
|
||||
|
||||
@@ -152,7 +152,7 @@ public class SubStateMachineTests extends AbstractStateMachineTests {
|
||||
assertThat(exitActionS1.stateContexts.size(), is(1));
|
||||
}
|
||||
|
||||
// @Test
|
||||
@Test
|
||||
public void testExternalTransition2() throws Exception {
|
||||
|
||||
/**
|
||||
@@ -190,7 +190,7 @@ public class SubStateMachineTests extends AbstractStateMachineTests {
|
||||
Collection<Action<TestStates, TestEvents>> entryActionsS112 = new ArrayList<Action<TestStates, TestEvents>>();
|
||||
entryActionsS112.add(entryActionS112);
|
||||
Collection<Action<TestStates, TestEvents>> exitActionsS112 = new ArrayList<Action<TestStates, TestEvents>>();
|
||||
exitActionsS111.add(exitActionS112);
|
||||
exitActionsS112.add(exitActionS112);
|
||||
State<TestStates,TestEvents> stateS112 = new EnumState<TestStates,TestEvents>(TestStates.S112, null, entryActionsS112, exitActionsS112, null);
|
||||
|
||||
// submachine 1
|
||||
@@ -231,14 +231,14 @@ public class SubStateMachineTests extends AbstractStateMachineTests {
|
||||
assertThat(entryActionS112.onExecuteLatch.await(1, TimeUnit.SECONDS), is(true));
|
||||
assertThat(exitActionS112.onExecuteLatch.await(1, TimeUnit.SECONDS), is(false));
|
||||
assertThat(entryActionS1.onExecuteLatch.await(1, TimeUnit.SECONDS), is(true));
|
||||
assertThat(exitActionS1.onExecuteLatch.await(1, TimeUnit.SECONDS), is(false));
|
||||
assertThat(exitActionS1.onExecuteLatch.await(1, TimeUnit.SECONDS), is(true));
|
||||
|
||||
assertThat(entryActionS111.stateContexts.size(), is(1));
|
||||
assertThat(exitActionS111.stateContexts.size(), is(1));
|
||||
assertThat(entryActionS112.stateContexts.size(), is(1));
|
||||
assertThat(exitActionS112.stateContexts.size(), is(0));
|
||||
assertThat(entryActionS1.stateContexts.size(), is(1));
|
||||
assertThat(exitActionS1.stateContexts.size(), is(0));
|
||||
assertThat(entryActionS1.stateContexts.size(), is(2));
|
||||
assertThat(exitActionS1.stateContexts.size(), is(1));
|
||||
}
|
||||
|
||||
|
||||
@@ -389,6 +389,21 @@ public class SubStateMachineTests extends AbstractStateMachineTests {
|
||||
assertThat(machine.getState().getIds(), contains(TestStates.S1, TestStates.S10));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStateChangeWithinMachine() {
|
||||
context.register(BaseConfig.class, StateMachineEventPublisherConfiguration.class, Config3.class);
|
||||
context.refresh();
|
||||
assertTrue(context.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE));
|
||||
@SuppressWarnings("unchecked")
|
||||
EnumStateMachine<TestStates2,TestEvents2> machine =
|
||||
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class);
|
||||
machine.start();
|
||||
assertThat(machine, notNullValue());
|
||||
assertThat(machine.getState().getIds(), contains(TestStates2.IDLE, TestStates2.CLOSED));
|
||||
machine.sendEvent(TestEvents2.EJECT);
|
||||
assertThat(machine.getState().getIds(), contains(TestStates2.IDLE, TestStates2.OPEN));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
public static class Config1 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
|
||||
@@ -473,4 +488,48 @@ public class SubStateMachineTests extends AbstractStateMachineTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
static class Config3 extends EnumStateMachineConfigurerAdapter<TestStates2, TestEvents2> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<TestStates2, TestEvents2> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(TestStates2.IDLE)
|
||||
.state(TestStates2.IDLE)
|
||||
.and()
|
||||
.withStates()
|
||||
.parent(TestStates2.IDLE)
|
||||
.initial(TestStates2.CLOSED)
|
||||
.state(TestStates2.CLOSED)
|
||||
.state(TestStates2.OPEN)
|
||||
.and()
|
||||
.withStates()
|
||||
.state(TestStates2.BUSY)
|
||||
.and()
|
||||
.withStates()
|
||||
.parent(TestStates2.BUSY)
|
||||
.initial(TestStates2.PLAYING)
|
||||
.state(TestStates2.PLAYING)
|
||||
.state(TestStates2.PAUSED);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<TestStates2, TestEvents2> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source(TestStates2.CLOSED)
|
||||
.target(TestStates2.OPEN)
|
||||
.event(TestEvents2.EJECT)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(TestStates2.OPEN)
|
||||
.target(TestStates2.CLOSED)
|
||||
.event(TestEvents2.EJECT);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user