StateContext may use wrong statemachine
- Tweaking to keep correct statemachine ref around so that StateContext passed to actions can be used to send events within submachines. - This was missed due to missing test and i.e. cdplayer should transition into playing state directly via and event sent from action which when closed is checking if cd is inserted. - Fixes #33
This commit is contained in:
@@ -37,6 +37,7 @@ import org.springframework.statemachine.state.PseudoStateKind;
|
||||
import org.springframework.statemachine.state.RegionState;
|
||||
import org.springframework.statemachine.state.State;
|
||||
import org.springframework.statemachine.state.StateMachineState;
|
||||
import org.springframework.statemachine.support.DefaultExtendedState;
|
||||
import org.springframework.statemachine.support.LifecycleObjectSupport;
|
||||
import org.springframework.statemachine.support.tree.Tree;
|
||||
import org.springframework.statemachine.support.tree.Tree.Node;
|
||||
@@ -82,6 +83,10 @@ public class EnumStateMachineFactory<S extends Enum<S>, E extends Enum<E>> exten
|
||||
|
||||
@Override
|
||||
public StateMachine<S, E> getStateMachine() {
|
||||
|
||||
// shared
|
||||
DefaultExtendedState defaultExtendedState = new DefaultExtendedState();
|
||||
|
||||
StateMachine<S, E> machine = null;
|
||||
|
||||
// we store mappings from state id's to states which gets
|
||||
@@ -142,7 +147,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);
|
||||
|
||||
machine = buildMachine(machineMap, stateMap, stateDatas, transitionsData, getBeanFactory(), contextEvents);
|
||||
machine = buildMachine(machineMap, stateMap, stateDatas, transitionsData, getBeanFactory(), contextEvents, defaultExtendedState);
|
||||
// TODO: last part in if feels a bit hack
|
||||
// (!peek.isInitial() && !machineMap.containsKey(peek.getParent()))
|
||||
if (peek.isInitial() || (!peek.isInitial() && !machineMap.containsKey(peek.getParent()))) {
|
||||
@@ -170,7 +175,7 @@ public class EnumStateMachineFactory<S extends Enum<S>, E extends Enum<E>> exten
|
||||
}
|
||||
|
||||
if (initials == 1) {
|
||||
machine = buildMachine(machineMap, stateMap, stateDatas, transitionsData, getBeanFactory(), contextEvents);
|
||||
machine = buildMachine(machineMap, stateMap, stateDatas, transitionsData, getBeanFactory(), contextEvents, defaultExtendedState);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +190,8 @@ public class EnumStateMachineFactory<S extends Enum<S>, E extends Enum<E>> exten
|
||||
RegionState<S, E> rstate = new RegionState<S, E>(null, regions);
|
||||
Collection<State<S, E>> states = new ArrayList<State<S, E>>();
|
||||
states.add(rstate);
|
||||
EnumStateMachine<S, E> m = new EnumStateMachine<S, E>(states, null, rstate, null);
|
||||
EnumStateMachine<S, E> m = new EnumStateMachine<S, E>(states, null, rstate,
|
||||
null, null, null, defaultExtendedState);
|
||||
if (contextEvents != null) {
|
||||
m.setContextEventsEnabled(contextEvents);
|
||||
}
|
||||
@@ -266,8 +272,9 @@ public class EnumStateMachineFactory<S extends Enum<S>, E extends Enum<E>> exten
|
||||
|
||||
|
||||
private static <S extends Enum<S>, E extends Enum<E>> StateMachine<S, E> buildMachine(
|
||||
Map<Object, StateMachine<S, E>> machineMap, Map<S, State<S, E>> stateMap, Collection<StateData<S, E>> stateDatas,
|
||||
Collection<TransitionData<S, E>> transitionsData, BeanFactory beanFactory, Boolean contextEvents) {
|
||||
Map<Object, StateMachine<S, E>> machineMap, Map<S, State<S, E>> stateMap,
|
||||
Collection<StateData<S, E>> stateDatas, Collection<TransitionData<S, E>> transitionsData,
|
||||
BeanFactory beanFactory, Boolean contextEvents, DefaultExtendedState defaultExtendedState) {
|
||||
State<S, E> state = null;
|
||||
State<S, E> initialState = null;
|
||||
Action<S, E> initialAction = null;
|
||||
@@ -348,7 +355,7 @@ public class EnumStateMachineFactory<S extends Enum<S>, E extends Enum<E>> exten
|
||||
}
|
||||
|
||||
EnumStateMachine<S, E> machine = new EnumStateMachine<S, E>(states, transitions, initialState,
|
||||
initialTransition, endState, null, null);
|
||||
initialTransition, endState, null, defaultExtendedState);
|
||||
if (contextEvents != null) {
|
||||
machine.setContextEventsEnabled(contextEvents);
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@ public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport
|
||||
protected void doStart() {
|
||||
super.doStart();
|
||||
registerTriggerListener();
|
||||
switchToState(initialState, initialEvent, null);
|
||||
switchToState(initialState, initialEvent, null, this);
|
||||
// TODO: for now execute outside of switchToState
|
||||
if (initialTransition != null) {
|
||||
StateContext<S, E> stateContext = new DefaultStateContext<S, E>(
|
||||
@@ -322,15 +322,15 @@ public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport
|
||||
return false;
|
||||
}
|
||||
|
||||
private void switchToState(State<S,E> state, Message<E> event, Transition<S,E> transition) {
|
||||
setCurrentState(state, event, transition, true);
|
||||
private void switchToState(State<S,E> state, Message<E> event, Transition<S,E> transition, StateMachine<S, E> stateMachine) {
|
||||
setCurrentState(state, event, transition, true, stateMachine);
|
||||
|
||||
// TODO: should handle triggerles transition some how differently
|
||||
for (Transition<S,E> t : transitions) {
|
||||
State<S,E> source = t.getSource();
|
||||
State<S,E> target = t.getTarget();
|
||||
if (t.getTrigger() == null && source.equals(currentState)) {
|
||||
switchToState(target, event, t);
|
||||
switchToState(target, event, t, stateMachine);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,7 +345,7 @@ public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport
|
||||
return null;
|
||||
}
|
||||
|
||||
void setCurrentState(State<S, E> state, Message<E> event, Transition<S, E> transition, boolean exit) {
|
||||
void setCurrentState(State<S, E> state, Message<E> event, Transition<S, E> transition, boolean exit, StateMachine<S, E> stateMachine) {
|
||||
State<S, E> findDeep = findDeepParent(state);
|
||||
boolean isTargetSubOf = false;
|
||||
if (transition != null) {
|
||||
@@ -357,49 +357,49 @@ public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport
|
||||
|
||||
if (states.contains(state)) {
|
||||
if (exit) {
|
||||
exitCurrentState(state, event, transition);
|
||||
exitCurrentState(state, event, transition, stateMachine);
|
||||
}
|
||||
State<S, E> notifyFrom = currentState;
|
||||
currentState = state;
|
||||
entryToState(state, event, transition);
|
||||
entryToState(state, event, transition, stateMachine);
|
||||
notifyStateChanged(notifyFrom, state);
|
||||
} else if (currentState != null && currentState.isSubmachineState()) {
|
||||
if (findDeep != null) {
|
||||
if (exit) {
|
||||
exitCurrentState(state, event, transition);
|
||||
exitCurrentState(state, event, transition, stateMachine);
|
||||
}
|
||||
if (currentState == findDeep) {
|
||||
StateMachine<S, E> submachine = ((AbstractState<S, E>)currentState).getSubmachine();
|
||||
if (submachine.getState() == state) {
|
||||
if (currentState == findDeep) {
|
||||
if (isTargetSubOf) {
|
||||
entryToState(currentState, event, transition);
|
||||
entryToState(currentState, event, transition, stateMachine);
|
||||
}
|
||||
currentState = findDeep;
|
||||
((AbstractStateMachine<S, E>)submachine).setCurrentState(state, event, transition, false);
|
||||
((AbstractStateMachine<S, E>)submachine).setCurrentState(state, event, transition, false, stateMachine);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
currentState = findDeep;
|
||||
entryToState(currentState, event, transition);
|
||||
entryToState(currentState, event, transition, stateMachine);
|
||||
StateMachine<S, E> submachine = ((AbstractState<S, E>)currentState).getSubmachine();
|
||||
((AbstractStateMachine<S, E>)submachine).setCurrentState(state, event, transition, false);
|
||||
((AbstractStateMachine<S, E>)submachine).setCurrentState(state, event, transition, false, stateMachine);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void exitCurrentState(State<S, E> state, Message<E> event, Transition<S, E> transition) {
|
||||
void exitCurrentState(State<S, E> state, Message<E> event, Transition<S, E> transition, StateMachine<S, E> stateMachine) {
|
||||
if (currentState == null) {
|
||||
return;
|
||||
}
|
||||
if (currentState.isSubmachineState()) {
|
||||
StateMachine<S, E> submachine = ((AbstractState<S, E>)currentState).getSubmachine();
|
||||
((AbstractStateMachine<S, E>)submachine).exitCurrentState(state, event, transition);
|
||||
exitFromState(currentState, event, transition);
|
||||
((AbstractStateMachine<S, E>)submachine).exitCurrentState(state, event, transition, stateMachine);
|
||||
exitFromState(currentState, event, transition, stateMachine);
|
||||
} else {
|
||||
exitFromState(currentState, event, transition);
|
||||
exitFromState(currentState, event, transition, stateMachine);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -409,12 +409,12 @@ public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport
|
||||
return c.contains(right);
|
||||
}
|
||||
|
||||
private void exitFromState(State<S, E> state, Message<E> event, Transition<S, E> transition) {
|
||||
private void exitFromState(State<S, E> state, Message<E> event, Transition<S, E> transition, StateMachine<S, E> stateMachine) {
|
||||
if (state != null) {
|
||||
log.trace("Exit state=[" + state + "]");
|
||||
MessageHeaders messageHeaders = event != null ? event.getHeaders() : new MessageHeaders(
|
||||
new HashMap<String, Object>());
|
||||
StateContext<S, E> stateContext = new DefaultStateContext<S, E>(messageHeaders, extendedState, transition, this);
|
||||
StateContext<S, E> stateContext = new DefaultStateContext<S, E>(messageHeaders, extendedState, transition, stateMachine);
|
||||
|
||||
State<S, E> findDeep = findDeepParent(transition.getTarget());
|
||||
boolean isTargetSubOfOtherState = findDeep != null && findDeep != currentState;
|
||||
@@ -439,12 +439,12 @@ public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport
|
||||
}
|
||||
}
|
||||
|
||||
private void entryToState(State<S, E> state, Message<E> event, Transition<S, E> transition) {
|
||||
private void entryToState(State<S, E> state, Message<E> event, Transition<S, E> transition, StateMachine<S, E> stateMachine) {
|
||||
if (state != null) {
|
||||
log.trace("Enter state=[" + state + "]");
|
||||
MessageHeaders messageHeaders = event != null ? event.getHeaders() : new MessageHeaders(
|
||||
new HashMap<String, Object>());
|
||||
StateContext<S, E> stateContext = new DefaultStateContext<S, E>(messageHeaders, extendedState, transition, this);
|
||||
StateContext<S, E> stateContext = new DefaultStateContext<S, E>(messageHeaders, extendedState, transition, stateMachine);
|
||||
|
||||
if (transition != null) {
|
||||
State<S, E> findDeep1 = findDeepParent(transition.getTarget());
|
||||
@@ -591,7 +591,7 @@ public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport
|
||||
notifyTransitionStart(t);
|
||||
callHandlers(t.getSource(), t.getTarget(), queuedEvent);
|
||||
if (t.getKind() != TransitionKind.INTERNAL) {
|
||||
switchToState(t.getTarget(), queuedEvent, t);
|
||||
switchToState(t.getTarget(), queuedEvent, t, this);
|
||||
}
|
||||
notifyTransition(t);
|
||||
notifyTransitionEnd(t);
|
||||
|
||||
@@ -196,8 +196,8 @@ public class Application {
|
||||
@Override
|
||||
public void execute(StateContext<States, Events> context) {
|
||||
if (context.getTransition() != null
|
||||
&& context.getTransition().getSource().getId() == States.CLOSED
|
||||
&& context.getMessageHeader(Variables.CD) != null) {
|
||||
&& context.getTransition().getTarget().getId() == States.CLOSED
|
||||
&& context.getExtendedState().getVariables().get(Variables.CD) != null) {
|
||||
context.getStateMachine().sendEvent(Events.PLAY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,18 @@ public class CdPlayerTests {
|
||||
assertLcdStatusContains("cd1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlayWithCdLoadedDeckOpen() throws Exception {
|
||||
listener.reset(3, 0, 0);
|
||||
player.eject();
|
||||
player.load(library.getCollection().get(0));
|
||||
player.play();
|
||||
listener.stateChangedLatch.await(5, TimeUnit.SECONDS);
|
||||
assertThat(listener.stateChangedCount, is(4));
|
||||
assertThat(machine.getState().getIds(), contains(States.BUSY, States.PLAYING));
|
||||
assertLcdStatusContains("cd1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlayWithNoCdLoaded() throws Exception {
|
||||
listener.reset(0, 0, 0);
|
||||
|
||||
Reference in New Issue
Block a user