Fixing various aspects of state handling

- Factory had a problem to build machines
  with deep nested states.
- Fixing listener handling for substates, entry
  and exit events.
This commit is contained in:
Janne Valkealahti
2015-03-22 18:25:39 +00:00
parent 073df4e438
commit 6e10433b1b
4 changed files with 97 additions and 14 deletions

View File

@@ -120,14 +120,23 @@ public class EnumStateMachineFactory<S extends Enum<S>, E extends Enum<E>> exten
continue;
}
if (stateData != null && ObjectUtils.nullSafeEquals(peek.getParent(), stateData.getParent())) {
boolean stackContainsSameParent = false;
Iterator<StateData<S, E>> ii = stateStack.iterator();
while (ii.hasNext()) {
StateData<S, E> sd = ii.next();
if (stateData != null && ObjectUtils.nullSafeEquals(stateData.getState(), sd.getParent())) {
stackContainsSameParent = true;
break;
}
}
if (stateData != null && !stackContainsSameParent) {
stateStack.push(stateData);
continue;
}
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
@@ -263,7 +272,6 @@ public class EnumStateMachineFactory<S extends Enum<S>, E extends Enum<E>> exten
} else if (stateDatas.size() == 1) {
initialState = state;
}
// initialState = state;
states.add(state);
} else {
PseudoState pseudoState = null;

View File

@@ -22,6 +22,7 @@ import org.springframework.messaging.Message;
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.support.LifecycleObjectSupport;
import org.springframework.statemachine.transition.Transition;
import org.springframework.statemachine.transition.TransitionKind;
@@ -144,7 +145,11 @@ public class StateMachineState<S, E> extends AbstractState<S, E> {
}
}
if (getPseudoState() != null && getPseudoState().getKind() == PseudoStateKind.INITIAL) {
getSubmachine().start();
if (((LifecycleObjectSupport)getSubmachine()).isRunning()) {
getSubmachine().getState().entry(event, context);
} else {
getSubmachine().start();
}
} else {
getSubmachine().getState().entry(event, context);
}

View File

@@ -206,6 +206,12 @@ public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport
triggerToTransitionMap.put(trigger, transition);
}
}
for (State<S, E> state : states) {
if (state.isSubmachineState()) {
StateMachine<S, E> submachine = ((AbstractState<S, E>)state).getSubmachine();
submachine.addStateListener(new StateMachineListenerRelay());
}
}
}
@Override
@@ -278,8 +284,6 @@ public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport
}
private void switchToState(State<S,E> state, Message<E> event, Transition<S,E> transition) {
exitFromState(currentState, event, transition);
notifyStateChanged(currentState, state);
setCurrentState(state, event, transition);
// TODO: should handle triggerles transition some how differently
@@ -289,19 +293,18 @@ public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport
if (t.getTrigger() == null && source.equals(currentState)) {
switchToState(target, event, t);
}
}
}
void setCurrentState(State<S, E> state, Message<E> event, Transition<S, E> transition) {
if (states.contains(state)) {
exitFromState(currentState, event, transition);
State<S, E> notifyFrom = currentState;
currentState = state;
entryToState(state, event, transition);
notifyStateChanged(notifyFrom, state);
} 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();
@@ -315,8 +318,23 @@ public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport
MessageHeaders messageHeaders = event != null ? event.getHeaders() : new MessageHeaders(
new HashMap<String, Object>());
StateContext<S, E> stateContext = new DefaultStateContext<S, E>(messageHeaders, extendedState, transition, this);
// TODO: we use this trick not to double notify
State<S, E> toNotify = null;
if (state.isSubmachineState()) {
StateMachine<S, E> submachine = ((AbstractState<S, E>)state).getSubmachine();
if (((LifecycleObjectSupport)submachine).isRunning()) {
toNotify = submachine.getState();
}
}
if (toNotify != null) {
notifyStateExited(toNotify);
}
state.exit(event != null ? event.getPayload() : null, stateContext);
notifyStateExited(state);
}
}
@@ -326,8 +344,21 @@ public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport
MessageHeaders messageHeaders = event != null ? event.getHeaders() : new MessageHeaders(
new HashMap<String, Object>());
StateContext<S, E> stateContext = new DefaultStateContext<S, E>(messageHeaders, extendedState, transition, this);
state.entry(event != null ? event.getPayload() : null, stateContext);
notifyStateEntered(state);
// TODO: we use this trick not to double notify
State<S, E> toNotify = null;
if (state.isSubmachineState()) {
StateMachine<S, E> submachine = ((AbstractState<S, E>)state).getSubmachine();
if (((LifecycleObjectSupport)submachine).isRunning()) {
toNotify = submachine.getState();
}
}
state.entry(event != null ? event.getPayload() : null, stateContext);
if (toNotify != null) {
notifyStateEntered(toNotify);
}
}
}
@@ -595,4 +626,43 @@ public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport
}
}
/**
* This class is used to relay listener events from a submachines which works
* as its own listener context. User only connects to main root machine and
* expects to get events for all machines from there.
*/
private class StateMachineListenerRelay implements StateMachineListener<S,E> {
@Override
public void stateChanged(State<S, E> from, State<S, E> to) {
stateListener.stateChanged(from, to);
}
@Override
public void stateEntered(State<S, E> state) {
stateListener.stateEntered(state);
}
@Override
public void stateExited(State<S, E> state) {
stateListener.stateExited(state);
}
@Override
public void transition(Transition<S, E> transition) {
stateListener.transition(transition);
}
@Override
public void transitionStarted(Transition<S, E> transition) {
stateListener.transitionStarted(transition);
}
@Override
public void transitionEnded(Transition<S, E> transition) {
stateListener.transitionEnded(transition);
}
}
}

View File

@@ -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(true));
assertThat(exitActionS1.onExecuteLatch.await(1, TimeUnit.SECONDS), is(false));
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(2));
assertThat(exitActionS1.stateContexts.size(), is(1));
assertThat(entryActionS1.stateContexts.size(), is(1));
assertThat(exitActionS1.stateContexts.size(), is(0));
}