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:
Janne Valkealahti
2015-04-07 22:18:03 +01:00
parent 45ebce9c64
commit 487911b4de
4 changed files with 48 additions and 29 deletions

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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);