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:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user