Fix proper transition into super state
- Big conceptual changes how state transitions are handled which now allows more easy ways to figure out what to do and where to go. - Exposing more info via State interface - Adding tests to samples - Fixes #30
This commit is contained in:
@@ -52,7 +52,8 @@ public abstract class AbstractSimpleState<S, E> extends AbstractState<S, E> {
|
||||
* @param entryActions the entry actions
|
||||
* @param exitActions the exit actions
|
||||
*/
|
||||
public AbstractSimpleState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions, Collection<? extends Action<S, E>> exitActions) {
|
||||
public AbstractSimpleState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions,
|
||||
Collection<? extends Action<S, E>> exitActions) {
|
||||
this(id, deferred, entryActions, exitActions, null);
|
||||
}
|
||||
|
||||
@@ -86,8 +87,8 @@ public abstract class AbstractSimpleState<S, E> extends AbstractState<S, E> {
|
||||
* @param pseudoState the pseudo state
|
||||
* @param regions the regions
|
||||
*/
|
||||
public AbstractSimpleState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions, Collection<? extends Action<S, E>> exitActions,
|
||||
PseudoState pseudoState, Collection<Region<S, E>> regions) {
|
||||
public AbstractSimpleState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions,
|
||||
Collection<? extends Action<S, E>> exitActions, PseudoState pseudoState, Collection<Region<S, E>> regions) {
|
||||
super(id, deferred, entryActions, exitActions, pseudoState, regions);
|
||||
this.ids = new ArrayList<S>();
|
||||
this.ids.add(id);
|
||||
@@ -103,8 +104,8 @@ public abstract class AbstractSimpleState<S, E> extends AbstractState<S, E> {
|
||||
* @param pseudoState the pseudo state
|
||||
* @param submachine the submachine
|
||||
*/
|
||||
public AbstractSimpleState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions, Collection<? extends Action<S, E>> exitActions,
|
||||
PseudoState pseudoState, StateMachine<S, E> submachine) {
|
||||
public AbstractSimpleState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions,
|
||||
Collection<? extends Action<S, E>> exitActions, PseudoState pseudoState, StateMachine<S, E> submachine) {
|
||||
super(id, deferred, entryActions, exitActions, pseudoState, submachine);
|
||||
this.ids = new ArrayList<S>();
|
||||
this.ids.add(id);
|
||||
@@ -119,8 +120,8 @@ public abstract class AbstractSimpleState<S, E> extends AbstractState<S, E> {
|
||||
* @param exitActions the exit actions
|
||||
* @param pseudoState the pseudo state
|
||||
*/
|
||||
public AbstractSimpleState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions, Collection<? extends Action<S, E>> exitActions,
|
||||
PseudoState pseudoState) {
|
||||
public AbstractSimpleState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions,
|
||||
Collection<? extends Action<S, E>> exitActions, PseudoState pseudoState) {
|
||||
super(id, deferred, entryActions, exitActions, pseudoState);
|
||||
this.ids = new ArrayList<S>();
|
||||
this.ids.add(id);
|
||||
@@ -131,4 +132,11 @@ public abstract class AbstractSimpleState<S, E> extends AbstractState<S, E> {
|
||||
return Collections.unmodifiableCollection(ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<State<S, E>> getStates() {
|
||||
ArrayList<State<S, E>> states = new ArrayList<State<S, E>>();
|
||||
states.add(this);
|
||||
return states;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -70,7 +70,8 @@ public abstract class AbstractState<S, E> implements State<S, E> {
|
||||
* @param entryActions the entry actions
|
||||
* @param exitActions the exit actions
|
||||
*/
|
||||
public AbstractState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions, Collection<? extends Action<S, E>> exitActions) {
|
||||
public AbstractState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions,
|
||||
Collection<? extends Action<S, E>> exitActions) {
|
||||
this(id, deferred, entryActions, exitActions, null);
|
||||
}
|
||||
|
||||
@@ -83,8 +84,8 @@ public abstract class AbstractState<S, E> implements State<S, E> {
|
||||
* @param exitActions the exit actions
|
||||
* @param pseudoState the pseudo state
|
||||
*/
|
||||
public AbstractState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions, Collection<? extends Action<S, E>> exitActions,
|
||||
PseudoState pseudoState) {
|
||||
public AbstractState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions,
|
||||
Collection<? extends Action<S, E>> exitActions, PseudoState pseudoState) {
|
||||
this(id, deferred, entryActions, exitActions, pseudoState, null, null);
|
||||
}
|
||||
|
||||
@@ -98,8 +99,8 @@ public abstract class AbstractState<S, E> implements State<S, E> {
|
||||
* @param pseudoState the pseudo state
|
||||
* @param submachine the submachine
|
||||
*/
|
||||
public AbstractState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions, Collection<? extends Action<S, E>> exitActions,
|
||||
PseudoState pseudoState, StateMachine<S, E> submachine) {
|
||||
public AbstractState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions,
|
||||
Collection<? extends Action<S, E>> exitActions, PseudoState pseudoState, StateMachine<S, E> submachine) {
|
||||
this(id, deferred, entryActions, exitActions, pseudoState, null, submachine);
|
||||
}
|
||||
|
||||
@@ -113,8 +114,8 @@ public abstract class AbstractState<S, E> implements State<S, E> {
|
||||
* @param pseudoState the pseudo state
|
||||
* @param regions the regions
|
||||
*/
|
||||
public AbstractState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions, Collection<? extends Action<S, E>> exitActions,
|
||||
PseudoState pseudoState, Collection<Region<S, E>> regions) {
|
||||
public AbstractState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions,
|
||||
Collection<? extends Action<S, E>> exitActions, PseudoState pseudoState, Collection<Region<S, E>> regions) {
|
||||
this(id, deferred, entryActions, exitActions, pseudoState, regions, null);
|
||||
}
|
||||
|
||||
@@ -129,8 +130,9 @@ public abstract class AbstractState<S, E> implements State<S, E> {
|
||||
* @param regions the regions
|
||||
* @param submachine the submachine
|
||||
*/
|
||||
private AbstractState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions, Collection<? extends Action<S, E>> exitActions,
|
||||
PseudoState pseudoState, Collection<Region<S, E>> regions, StateMachine<S, E> submachine) {
|
||||
private AbstractState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions,
|
||||
Collection<? extends Action<S, E>> exitActions, PseudoState pseudoState, Collection<Region<S, E>> regions,
|
||||
StateMachine<S, E> submachine) {
|
||||
this.id = id;
|
||||
this.deferred = deferred;
|
||||
this.entryActions = entryActions;
|
||||
@@ -164,6 +166,9 @@ public abstract class AbstractState<S, E> implements State<S, E> {
|
||||
@Override
|
||||
public abstract Collection<S> getIds();
|
||||
|
||||
@Override
|
||||
public abstract Collection<State<S, E>> getStates();
|
||||
|
||||
@Override
|
||||
public PseudoState getPseudoState() {
|
||||
return pseudoState;
|
||||
|
||||
@@ -75,8 +75,8 @@ public class RegionState<S, E> extends AbstractState<S, E> {
|
||||
* @param exitActions the exit actions
|
||||
* @param pseudoState the pseudo state
|
||||
*/
|
||||
public RegionState(S id, Collection<Region<S, E>> regions, Collection<E> deferred, Collection<Action<S, E>> entryActions, Collection<Action<S, E>> exitActions,
|
||||
PseudoState pseudoState) {
|
||||
public RegionState(S id, Collection<Region<S, E>> regions, Collection<E> deferred,
|
||||
Collection<Action<S, E>> entryActions, Collection<Action<S, E>> exitActions, PseudoState pseudoState) {
|
||||
super(id, deferred, entryActions, exitActions, pseudoState, regions);
|
||||
}
|
||||
|
||||
@@ -89,7 +89,8 @@ public class RegionState<S, E> extends AbstractState<S, E> {
|
||||
* @param entryActions the entry actions
|
||||
* @param exitActions the exit actions
|
||||
*/
|
||||
public RegionState(S id, Collection<Region<S, E>> regions, Collection<E> deferred, Collection<Action<S, E>> entryActions, Collection<Action<S, E>> exitActions) {
|
||||
public RegionState(S id, Collection<Region<S, E>> regions, Collection<E> deferred,
|
||||
Collection<Action<S, E>> entryActions, Collection<Action<S, E>> exitActions) {
|
||||
super(id, deferred, entryActions, exitActions, null, regions);
|
||||
}
|
||||
|
||||
@@ -150,4 +151,14 @@ public class RegionState<S, E> extends AbstractState<S, E> {
|
||||
return ids;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<State<S, E>> getStates() {
|
||||
ArrayList<State<S, E>> states = new ArrayList<State<S, E>>();
|
||||
states.add(this);
|
||||
for (Region<S, E> r : getRegions()) {
|
||||
states.addAll(r.getStates());
|
||||
}
|
||||
return states;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -70,6 +70,14 @@ public interface State<S, E> {
|
||||
*/
|
||||
Collection<S> getIds();
|
||||
|
||||
/**
|
||||
* Gets all possible states this state knows about including itself
|
||||
* and substates.
|
||||
*
|
||||
* @return all state including itself and nested states
|
||||
*/
|
||||
Collection<State<S, E>> getStates();
|
||||
|
||||
/**
|
||||
* Gets a {@link PseudoState} attached to a {@code State}.
|
||||
* {@link PseudoState} is not required and thus this method return
|
||||
|
||||
@@ -22,7 +22,6 @@ 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;
|
||||
|
||||
@@ -86,7 +85,8 @@ public class StateMachineState<S, E> extends AbstractState<S, E> {
|
||||
* @param exitActions the exit actions
|
||||
* @param pseudoState the pseudo state
|
||||
*/
|
||||
public StateMachineState(S id, StateMachine<S, E> submachine, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions, Collection<? extends Action<S, E>> exitActions,
|
||||
public StateMachineState(S id, StateMachine<S, E> submachine, Collection<E> deferred,
|
||||
Collection<? extends Action<S, E>> entryActions, Collection<? extends Action<S, E>> exitActions,
|
||||
PseudoState pseudoState) {
|
||||
super(id, deferred, entryActions, exitActions, pseudoState, submachine);
|
||||
this.ids = new ArrayList<S>();
|
||||
@@ -102,7 +102,8 @@ public class StateMachineState<S, E> extends AbstractState<S, E> {
|
||||
* @param entryActions the entry actions
|
||||
* @param exitActions the exit actions
|
||||
*/
|
||||
public StateMachineState(S id, StateMachine<S, E> submachine, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions, Collection<? extends Action<S, E>> exitActions) {
|
||||
public StateMachineState(S id, StateMachine<S, E> submachine, Collection<E> deferred,
|
||||
Collection<? extends Action<S, E>> entryActions, Collection<? extends Action<S, E>> exitActions) {
|
||||
super(id, deferred, entryActions, exitActions, null, submachine);
|
||||
this.ids = new ArrayList<S>();
|
||||
this.ids.add(id);
|
||||
@@ -119,13 +120,22 @@ public class StateMachineState<S, E> extends AbstractState<S, E> {
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<State<S, E>> getStates() {
|
||||
ArrayList<State<S, E>> states = new ArrayList<State<S, E>>();
|
||||
states.add(this);
|
||||
for (State<S, E> s : getSubmachine().getStates()) {
|
||||
states.addAll(s.getStates());
|
||||
}
|
||||
return states;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exit(E event, StateContext<S, E> context) {
|
||||
getSubmachine().getState().exit(event, context);
|
||||
// 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()) {
|
||||
if (getSubmachine().getState() != null && context.getTransition().getSource().getId() != getSubmachine().getState().getId()) {
|
||||
getSubmachine().stop();
|
||||
}
|
||||
Collection<? extends Action<S, E>> actions = getExitActions();
|
||||
@@ -144,14 +154,9 @@ public class StateMachineState<S, E> extends AbstractState<S, E> {
|
||||
action.execute(context);
|
||||
}
|
||||
}
|
||||
|
||||
if (getPseudoState() != null && getPseudoState().getKind() == PseudoStateKind.INITIAL) {
|
||||
if (((LifecycleObjectSupport)getSubmachine()).isRunning()) {
|
||||
getSubmachine().getState().entry(event, context);
|
||||
} else {
|
||||
getSubmachine().start();
|
||||
}
|
||||
} else {
|
||||
getSubmachine().getState().entry(event, context);
|
||||
getSubmachine().start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
@@ -56,7 +55,6 @@ import org.springframework.statemachine.state.State;
|
||||
import org.springframework.statemachine.transition.Transition;
|
||||
import org.springframework.statemachine.transition.TransitionKind;
|
||||
import org.springframework.statemachine.trigger.DefaultTriggerContext;
|
||||
import org.springframework.statemachine.trigger.EventTrigger;
|
||||
import org.springframework.statemachine.trigger.TimerTrigger;
|
||||
import org.springframework.statemachine.trigger.Trigger;
|
||||
import org.springframework.statemachine.trigger.TriggerListener;
|
||||
@@ -259,6 +257,19 @@ public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport
|
||||
return transitions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
ArrayList<State<S, E>> all = new ArrayList<State<S,E>>();
|
||||
for (State<S, E> s : states) {
|
||||
all.addAll(s.getStates());
|
||||
}
|
||||
StringBuilder buf = new StringBuilder();
|
||||
for (State<S, E> s : all) {
|
||||
buf.append(s.getId() + " ");
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
protected boolean acceptEvent(Message<E> event) {
|
||||
|
||||
boolean accepted = currentState.sendEvent(event);
|
||||
@@ -294,7 +305,7 @@ public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport
|
||||
}
|
||||
|
||||
private void switchToState(State<S,E> state, Message<E> event, Transition<S,E> transition) {
|
||||
setCurrentState(state, event, transition);
|
||||
setCurrentState(state, event, transition, true);
|
||||
|
||||
// TODO: should handle triggerles transition some how differently
|
||||
for (Transition<S,E> t : transitions) {
|
||||
@@ -307,19 +318,79 @@ public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport
|
||||
|
||||
}
|
||||
|
||||
void setCurrentState(State<S, E> state, Message<E> event, Transition<S, E> transition) {
|
||||
private State<S, E> findDeepParent(State<S, E> state) {
|
||||
for (State<S, E> s : states) {
|
||||
if (s.getStates().contains(state)) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void setCurrentState(State<S, E> statex, Message<E> event, Transition<S, E> transition, boolean exit) {
|
||||
|
||||
State<S, E> state = statex;
|
||||
State<S, E> findDeep = findDeepParent(state);
|
||||
boolean isTargetSubOf = false;
|
||||
if (transition != null) {
|
||||
isTargetSubOf = isSubstate(state, transition.getSource());
|
||||
if (isTargetSubOf && currentState == transition.getTarget()) {
|
||||
state = transition.getSource();
|
||||
}
|
||||
}
|
||||
|
||||
if (states.contains(state)) {
|
||||
exitFromState(currentState, event, transition);
|
||||
if (exit) {
|
||||
exitCurrentState(state, event, transition);
|
||||
}
|
||||
State<S, E> notifyFrom = currentState;
|
||||
currentState = state;
|
||||
entryToState(state, event, transition);
|
||||
notifyStateChanged(notifyFrom, state);
|
||||
} else if (currentState.isSubmachineState()) {
|
||||
// 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);
|
||||
} else if (currentState != null && currentState.isSubmachineState()) {
|
||||
if (findDeep != null) {
|
||||
if (exit) {
|
||||
exitCurrentState(state, event, transition);
|
||||
}
|
||||
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);
|
||||
}
|
||||
currentState = findDeep;
|
||||
((AbstractStateMachine<S, E>)submachine).setCurrentState(state, event, transition, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
currentState = findDeep;
|
||||
entryToState(currentState, event, transition);
|
||||
StateMachine<S, E> submachine = ((AbstractState<S, E>)currentState).getSubmachine();
|
||||
((AbstractStateMachine<S, E>)submachine).setCurrentState(state, event, transition, false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void exitCurrentState(State<S, E> state, Message<E> event, Transition<S, E> transition) {
|
||||
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);
|
||||
} else {
|
||||
exitFromState(currentState, event, transition);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSubstate(State<S, E> left, State<S, E> right) {
|
||||
Collection<State<S, E>> c = left.getStates();
|
||||
c.remove(left);
|
||||
return c.contains(right);
|
||||
}
|
||||
|
||||
private void exitFromState(State<S, E> state, Message<E> event, Transition<S, E> transition) {
|
||||
@@ -329,22 +400,22 @@ public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport
|
||||
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();
|
||||
}
|
||||
boolean isSubOfSource = isSubstate(transition.getSource(), currentState);
|
||||
boolean isSubOfTarget = isSubstate(transition.getTarget(), currentState);
|
||||
if (currentState == transition.getSource() && currentState == transition.getTarget()) {
|
||||
} else if (!isSubOfSource && !isSubOfTarget && currentState == transition.getSource()) {
|
||||
} else if (!isSubOfSource && !isSubOfTarget && currentState == transition.getTarget()) {
|
||||
} else if (!isSubOfSource && !isSubOfTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (toNotify != null) {
|
||||
notifyStateExited(toNotify);
|
||||
boolean isTargetSubOfSource = isSubstate(transition.getSource(), transition.getTarget());
|
||||
if (transition.getSource() == currentState && isTargetSubOfSource) {
|
||||
return;
|
||||
}
|
||||
|
||||
state.exit(event != null ? event.getPayload() : null, stateContext);
|
||||
notifyStateExited(state);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,21 +425,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);
|
||||
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();
|
||||
if (transition != null) {
|
||||
boolean isSubOfSource = isSubstate(transition.getSource(), currentState);
|
||||
boolean isSubOfTarget = isSubstate(transition.getTarget(), currentState);
|
||||
if (currentState == transition.getSource() && currentState == transition.getTarget()) {
|
||||
} else if (!isSubOfSource && !isSubOfTarget && currentState == transition.getTarget()) {
|
||||
} else if (isSubOfSource && !isSubOfTarget && currentState == transition.getTarget()) {
|
||||
return;
|
||||
} else if (!isSubOfSource && !isSubOfTarget) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
notifyStateEntered(state);
|
||||
state.entry(event != null ? event.getPayload() : null, stateContext);
|
||||
if (toNotify != null) {
|
||||
notifyStateEntered(toNotify);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,14 +6,24 @@ import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.statemachine.EnumStateMachine;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.StateMachineSystemConstants;
|
||||
import org.springframework.statemachine.listener.StateMachineListener;
|
||||
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
|
||||
import org.springframework.statemachine.state.State;
|
||||
import org.springframework.statemachine.transition.Transition;
|
||||
|
||||
import demo.CommonConfiguration;
|
||||
import demo.cdplayer.Application.Events;
|
||||
@@ -29,95 +39,150 @@ public class CdPlayerTests {
|
||||
|
||||
private Library library;
|
||||
|
||||
private TestListener listener;
|
||||
|
||||
@Test
|
||||
public void testInitialState() throws InterruptedException {
|
||||
listener.stateChangedLatch.await(1, TimeUnit.SECONDS);
|
||||
assertThat(listener.stateChangedCount, is(2));
|
||||
assertThat(machine.getState().getIds(), contains(States.IDLE, States.CLOSED));
|
||||
assertLcdStatusStartsWith("No CD");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEjectTwice() throws Exception {
|
||||
listener.reset(1, 0, 0);
|
||||
player.eject();
|
||||
Thread.sleep(100);
|
||||
listener.stateChangedLatch.await(1, TimeUnit.SECONDS);
|
||||
assertThat(listener.stateChangedCount, is(1));
|
||||
assertThat(machine.getState().getIds(), contains(States.IDLE, States.OPEN));
|
||||
listener.reset(1, 0, 0);
|
||||
player.eject();
|
||||
Thread.sleep(100);
|
||||
listener.stateChangedLatch.await(1, TimeUnit.SECONDS);
|
||||
assertThat(listener.stateChangedCount, is(1));
|
||||
assertThat(machine.getState().getIds(), contains(States.IDLE, States.CLOSED));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlayWithCdLoaded() throws Exception {
|
||||
listener.reset(4, 0, 0);
|
||||
player.eject();
|
||||
player.load(library.getCollection().get(0));
|
||||
player.eject();
|
||||
player.play();
|
||||
Thread.sleep(100);
|
||||
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() {
|
||||
public void testPlayWithNoCdLoaded() throws Exception {
|
||||
listener.reset(0, 0, 0);
|
||||
player.play();
|
||||
listener.stateChangedLatch.await(1, TimeUnit.SECONDS);
|
||||
assertThat(listener.stateChangedCount, is(0));
|
||||
assertThat(machine.getState().getIds(), contains(States.IDLE, States.CLOSED));
|
||||
assertLcdStatusStartsWith("No CD");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlayLcdTimeChanges() throws Exception {
|
||||
listener.reset(4, 0, 0);
|
||||
player.eject();
|
||||
player.load(library.getCollection().get(0));
|
||||
player.eject();
|
||||
player.play();
|
||||
listener.stateChangedLatch.await(1, TimeUnit.SECONDS);
|
||||
assertThat(listener.stateChangedCount, is(4));
|
||||
assertThat(machine.getState().getIds(), contains(States.BUSY, States.PLAYING));
|
||||
assertLcdStatusContains("cd1");
|
||||
Thread.sleep(1000);
|
||||
assertLcdStatusContains("00:01");
|
||||
Thread.sleep(1000);
|
||||
assertLcdStatusContains("00:02");
|
||||
Thread.sleep(1000);
|
||||
assertLcdStatusContains("00:03");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlayPause() throws Exception {
|
||||
player.eject();
|
||||
player.load(library.getCollection().get(0));
|
||||
player.eject();
|
||||
player.play();
|
||||
Thread.sleep(100);
|
||||
assertThat(machine.getState().getIds(), contains(States.BUSY, States.PLAYING));
|
||||
assertLcdStatusContains("cd1");
|
||||
Thread.sleep(1000);
|
||||
assertLcdStatusIs("cd1 00:01");
|
||||
Thread.sleep(1000);
|
||||
listener.reset(0, 0, 0, 1);
|
||||
listener.transitionLatch.await(1, TimeUnit.SECONDS);
|
||||
assertThat(listener.transitionCount, is(1));
|
||||
assertLcdStatusContains("00:01");
|
||||
|
||||
listener.reset(0, 0, 0, 1);
|
||||
listener.transitionLatch.await(1, TimeUnit.SECONDS);
|
||||
assertLcdStatusContains("00:02");
|
||||
player.pause();
|
||||
Thread.sleep(2000);
|
||||
assertLcdStatusContains("00:02");
|
||||
player.pause();
|
||||
assertLcdStatusContains("00:03");
|
||||
Thread.sleep(1000);
|
||||
assertThat(listener.transitionCount, is(1));
|
||||
|
||||
listener.reset(0, 0, 0, 2);
|
||||
listener.transitionLatch.await(3, TimeUnit.SECONDS);
|
||||
assertThat(listener.transitionCount, is(2));
|
||||
assertLcdStatusContains("00:04");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlayStop() throws Exception {
|
||||
public void testPlayPause() throws Exception {
|
||||
listener.reset(4, 0, 0);
|
||||
player.eject();
|
||||
player.load(library.getCollection().get(0));
|
||||
player.eject();
|
||||
player.play();
|
||||
Thread.sleep(100);
|
||||
listener.stateChangedLatch.await(2, TimeUnit.SECONDS);
|
||||
assertThat(listener.stateChangedCount, is(4));
|
||||
assertThat(machine.getState().getIds(), contains(States.BUSY, States.PLAYING));
|
||||
assertLcdStatusContains("cd1");
|
||||
|
||||
listener.reset(0, 0, 0, 1);
|
||||
listener.transitionLatch.await(2, TimeUnit.SECONDS);
|
||||
assertThat(listener.transitionCount, is(1));
|
||||
assertLcdStatusContains("00:01");
|
||||
|
||||
listener.reset(0, 0, 0, 1);
|
||||
listener.transitionLatch.await(2, TimeUnit.SECONDS);
|
||||
assertLcdStatusContains("00:02");
|
||||
assertThat(listener.transitionCount, is(1));
|
||||
|
||||
|
||||
listener.reset(1, 0, 0, 0);
|
||||
player.pause();
|
||||
listener.stateChangedLatch.await(2, TimeUnit.SECONDS);
|
||||
assertThat(listener.stateChangedCount, is(1));
|
||||
assertLcdStatusContains("00:02");
|
||||
|
||||
listener.reset(1, 0, 0, 1);
|
||||
player.pause();
|
||||
listener.stateChangedLatch.await(2, TimeUnit.SECONDS);
|
||||
listener.transitionLatch.await(2, TimeUnit.SECONDS);
|
||||
assertThat(listener.stateChangedCount, is(1));
|
||||
assertThat(listener.transitionCount, is(1));
|
||||
assertLcdStatusContains("00:03");
|
||||
|
||||
listener.reset(0, 0, 0, 2);
|
||||
listener.transitionLatch.await(2, TimeUnit.SECONDS);
|
||||
assertThat(listener.transitionCount, is(2));
|
||||
assertLcdStatusContains("00:05");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlayStop() throws Exception {
|
||||
listener.reset(4, 0, 0);
|
||||
player.eject();
|
||||
player.load(library.getCollection().get(0));
|
||||
player.eject();
|
||||
player.play();
|
||||
|
||||
listener.stateChangedLatch.await(1, TimeUnit.SECONDS);
|
||||
assertThat(listener.stateChangedCount, is(4));
|
||||
assertThat(machine.getState().getIds(), contains(States.BUSY, States.PLAYING));
|
||||
|
||||
listener.reset(2, 0, 0);
|
||||
player.stop();
|
||||
Thread.sleep(100);
|
||||
listener.stateChangedLatch.await(1, TimeUnit.SECONDS);
|
||||
assertThat(listener.stateChangedCount, is(2));
|
||||
assertLcdStatusIs("cd1 ");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlayDeckOpenNoCd() throws Exception {
|
||||
listener.reset(2, 0, 0);
|
||||
player.eject();
|
||||
player.play();
|
||||
listener.stateChangedLatch.await(1, TimeUnit.SECONDS);
|
||||
assertThat(listener.stateChangedCount, is(2));
|
||||
assertThat(machine.getState().getIds(), contains(States.IDLE, States.CLOSED));
|
||||
}
|
||||
|
||||
@@ -142,6 +207,7 @@ public class CdPlayerTests {
|
||||
machine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class);
|
||||
player = context.getBean(CdPlayer.class);
|
||||
library = context.getBean(Library.class);
|
||||
listener = context.getBean(TestListener.class);
|
||||
machine.start();
|
||||
}
|
||||
|
||||
@@ -153,10 +219,21 @@ public class CdPlayerTests {
|
||||
machine = null;
|
||||
player = null;
|
||||
library = null;
|
||||
listener = null;
|
||||
}
|
||||
|
||||
static class TestConfig {
|
||||
|
||||
@Autowired
|
||||
private StateMachine<States,Events> machine;
|
||||
|
||||
@Bean
|
||||
public StateMachineListener<States, Events> stateMachineListener() {
|
||||
TestListener listener = new TestListener();
|
||||
machine.addStateListener(listener);
|
||||
return listener;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Library library() {
|
||||
// override library to make it easier to test
|
||||
@@ -171,4 +248,56 @@ public class CdPlayerTests {
|
||||
|
||||
}
|
||||
|
||||
static class TestListener extends StateMachineListenerAdapter<States, Events> {
|
||||
|
||||
volatile CountDownLatch stateChangedLatch = new CountDownLatch(1);
|
||||
volatile CountDownLatch stateEnteredLatch = new CountDownLatch(2);
|
||||
volatile CountDownLatch stateExitedLatch = new CountDownLatch(0);
|
||||
volatile CountDownLatch transitionLatch = new CountDownLatch(0);
|
||||
volatile int stateChangedCount = 0;
|
||||
volatile int transitionCount = 0;
|
||||
List<State<States, Events>> statesEntered = new ArrayList<State<States,Events>>();
|
||||
List<State<States, Events>> statesExited = new ArrayList<State<States,Events>>();
|
||||
|
||||
@Override
|
||||
public void stateChanged(State<States, Events> from, State<States, Events> to) {
|
||||
stateChangedLatch.countDown();
|
||||
stateChangedCount++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateEntered(State<States, Events> state) {
|
||||
statesEntered.add(state);
|
||||
stateEnteredLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateExited(State<States, Events> state) {
|
||||
statesExited.add(state);
|
||||
stateExitedLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transitionEnded(Transition<States, Events> transition) {
|
||||
transitionLatch.countDown();
|
||||
transitionCount++;
|
||||
}
|
||||
|
||||
public void reset(int c1, int c2, int c3) {
|
||||
reset(c1, c2, c3, 0);
|
||||
}
|
||||
|
||||
public void reset(int c1, int c2, int c3, int c4) {
|
||||
stateChangedLatch = new CountDownLatch(c1);
|
||||
stateEnteredLatch = new CountDownLatch(c2);
|
||||
stateExitedLatch = new CountDownLatch(c3);
|
||||
transitionLatch = new CountDownLatch(c4);
|
||||
stateChangedCount = 0;
|
||||
transitionCount = 0;
|
||||
statesEntered.clear();
|
||||
statesExited.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ public class Application {
|
||||
throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(States.S0)
|
||||
.initial(States.S0, fooAction())
|
||||
.state(States.S0)
|
||||
.and()
|
||||
.withStates()
|
||||
@@ -95,9 +95,18 @@ public class Application {
|
||||
.withExternal()
|
||||
.source(States.S211).target(States.S0).event(Events.G)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(States.S21).target(States.S21).event(Events.H)
|
||||
.guard(fooGuard())
|
||||
.withInternal()
|
||||
.source(States.S0).event(Events.H)
|
||||
.guard(foo0Guard())
|
||||
.action(fooAction())
|
||||
.and()
|
||||
.withInternal()
|
||||
.source(States.S2).event(Events.H)
|
||||
.guard(foo1Guard())
|
||||
.action(fooAction())
|
||||
.and()
|
||||
.withInternal()
|
||||
.source(States.S1).event(Events.H)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(States.S11).target(States.S12).event(Events.I);
|
||||
@@ -105,8 +114,13 @@ public class Application {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FooGuard fooGuard() {
|
||||
return new FooGuard();
|
||||
public FooGuard foo0Guard() {
|
||||
return new FooGuard(0);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FooGuard foo1Guard() {
|
||||
return new FooGuard(1);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -134,7 +148,14 @@ public class Application {
|
||||
|
||||
@Override
|
||||
public void execute(StateContext<States, Events> context) {
|
||||
context.getExtendedState().getVariables().put("foo", 1);
|
||||
Object foo = context.getExtendedState().getVariables().get("foo");
|
||||
if (foo instanceof Integer && ((Integer)foo) == 0) {
|
||||
context.getExtendedState().getVariables().put("foo", 1);
|
||||
} else if (foo instanceof Integer && ((Integer)foo) == 1) {
|
||||
context.getExtendedState().getVariables().put("foo", 0);
|
||||
} else {
|
||||
context.getExtendedState().getVariables().put("foo", 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,10 +164,16 @@ public class Application {
|
||||
// tag::snippetE[]
|
||||
private static class FooGuard implements Guard<States, Events> {
|
||||
|
||||
private final int match;
|
||||
|
||||
public FooGuard(int match) {
|
||||
this.match = match;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean evaluate(StateContext<States, Events> context) {
|
||||
Object foo = context.getExtendedState().getVariables().get("foo");
|
||||
return !(foo == null || !foo.equals(1));
|
||||
return !(foo == null || !foo.equals(match));
|
||||
}
|
||||
}
|
||||
// end::snippetE[]
|
||||
|
||||
@@ -3,14 +3,17 @@
|
||||
+---------------------------------------------------------------------------------------------+
|
||||
| entry/ |
|
||||
| exit/ |
|
||||
| H/[foo.equals(0)]; |
|
||||
| |
|
||||
| +-------------------------+ +--------------------------------------------+ |
|
||||
| *-->| S1 | | S2 | |
|
||||
| +-------------------------+ +--------------------------------------------+ |
|
||||
| | entry/ | C | entry/ H | |
|
||||
| D | exit/ |----->| exit/ +-------------------+ | |
|
||||
|<----------| | | | H[foo.equals(1)]; | | |
|
||||
| | +---------------+ | C | +------------------------------+ | | |
|
||||
| | *-->| S11 | |<-----| *-->| S21 |<--+ | |
|
||||
| | entry/ | C | entry/ | |
|
||||
| D | exit/ |----->| exit/ | |
|
||||
|<----------| H/ | | H/[foo.equals(1)]; | |
|
||||
| | | | | |
|
||||
| | +---------------+ | C | +------------------------------+ | |
|
||||
| | *-->| S11 | |<-----| *-->| S21 | | |
|
||||
| | +---------------+ | | +------------------------------+ | |
|
||||
| +--| | entry/ | | F | | entry/ | | |
|
||||
| A| | | exit/ |<---------| | exit/ | | |
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.springframework.statemachine.StateMachineSystemConstants;
|
||||
import org.springframework.statemachine.listener.StateMachineListener;
|
||||
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
|
||||
import org.springframework.statemachine.state.State;
|
||||
import org.springframework.statemachine.transition.Transition;
|
||||
|
||||
import demo.CommonConfiguration;
|
||||
import demo.showcase.Application.Events;
|
||||
@@ -48,11 +49,12 @@ public class ShowcaseTests {
|
||||
|
||||
@Test
|
||||
public void testA() throws Exception {
|
||||
listener.reset(1, 2, 2);
|
||||
listener.reset(1, 2, 2, 1);
|
||||
machine.sendEvent(Events.A);
|
||||
listener.stateChangedLatch.await(1, TimeUnit.SECONDS);
|
||||
listener.stateEnteredLatch.await(1, TimeUnit.SECONDS);
|
||||
listener.stateExitedLatch.await(1, TimeUnit.SECONDS);
|
||||
listener.transitionLatch.await(1, TimeUnit.SECONDS);
|
||||
assertThat(machine.getState().getIds(), contains(States.S0, States.S1, States.S11));
|
||||
assertThat(listener.statesEntered.size(), is(2));
|
||||
assertThat(listener.statesEntered.get(0).getId(), is(States.S1));
|
||||
@@ -60,6 +62,7 @@ public class ShowcaseTests {
|
||||
assertThat(listener.statesExited.size(), is(2));
|
||||
assertThat(listener.statesExited.get(0).getId(), is(States.S11));
|
||||
assertThat(listener.statesExited.get(1).getId(), is(States.S1));
|
||||
assertThat(listener.transitionCount, is(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -91,6 +94,20 @@ public class ShowcaseTests {
|
||||
assertThat(listener.statesEntered.get(1).getId(), is(States.S11));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testD() throws Exception {
|
||||
listener.reset(3, 3, 0);
|
||||
machine.sendEvent(Events.D);
|
||||
listener.stateChangedLatch.await(1, TimeUnit.SECONDS);
|
||||
listener.stateEnteredLatch.await(1, TimeUnit.SECONDS);
|
||||
assertThat(machine.getState().getIds(), contains(States.S0, States.S1, States.S11));
|
||||
assertThat(listener.statesEntered.size(), is(3));
|
||||
assertThat(listener.statesEntered.get(0).getId(), is(States.S0));
|
||||
assertThat(listener.statesEntered.get(1).getId(), is(States.S1));
|
||||
assertThat(listener.statesEntered.get(2).getId(), is(States.S11));
|
||||
assertThat(listener.statesExited.size(), is(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCD() throws Exception {
|
||||
listener.reset(1, 3, 0);
|
||||
@@ -120,6 +137,46 @@ public class ShowcaseTests {
|
||||
assertThat(listener.statesExited.get(0).getId(), is(States.S11));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testH() throws Exception {
|
||||
listener.reset(0, 0, 0, 1);
|
||||
machine.sendEvent(Events.H);
|
||||
listener.transitionLatch.await(1, TimeUnit.SECONDS);
|
||||
assertThat(listener.transitionCount, is(1));
|
||||
assertThat(listener.transitions.get(0).getSource().getId(), is(States.S1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCH() throws Exception {
|
||||
machine.sendEvent(Events.C);
|
||||
listener.reset(0, 0, 0, 1);
|
||||
machine.sendEvent(Events.H);
|
||||
listener.transitionLatch.await(1, TimeUnit.SECONDS);
|
||||
assertThat(listener.transitionCount, is(1));
|
||||
assertThat(listener.transitions.get(0).getSource().getId(), is(States.S0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testACH() throws Exception {
|
||||
machine.sendEvent(Events.A);
|
||||
machine.sendEvent(Events.C);
|
||||
listener.reset(0, 0, 0, 1);
|
||||
machine.sendEvent(Events.H);
|
||||
listener.transitionLatch.await(1, TimeUnit.SECONDS);
|
||||
assertThat(listener.transitionCount, is(1));
|
||||
assertThat(listener.transitions.get(0).getSource().getId(), is(States.S2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testE() throws Exception {
|
||||
listener.reset(1, 2, 3, 0);
|
||||
machine.sendEvent(Events.E);
|
||||
listener.stateChangedLatch.await(1, TimeUnit.SECONDS);
|
||||
assertThat(machine.getState().getIds(), contains(States.S0, States.S2, States.S21, States.S211));
|
||||
assertThat(listener.statesExited.size(), is(2));
|
||||
assertThat(listener.statesEntered.size(), is(3));
|
||||
}
|
||||
|
||||
static class Config {
|
||||
|
||||
@Autowired
|
||||
@@ -138,8 +195,11 @@ public class ShowcaseTests {
|
||||
volatile CountDownLatch stateChangedLatch = new CountDownLatch(1);
|
||||
volatile CountDownLatch stateEnteredLatch = new CountDownLatch(3);
|
||||
volatile CountDownLatch stateExitedLatch = new CountDownLatch(0);
|
||||
volatile CountDownLatch transitionLatch = new CountDownLatch(0);
|
||||
volatile List<Transition<States, Events>> transitions = new ArrayList<Transition<States,Events>>();
|
||||
List<State<States, Events>> statesEntered = new ArrayList<State<States,Events>>();
|
||||
List<State<States, Events>> statesExited = new ArrayList<State<States,Events>>();
|
||||
volatile int transitionCount = 0;
|
||||
|
||||
@Override
|
||||
public void stateChanged(State<States, Events> from, State<States, Events> to) {
|
||||
@@ -158,12 +218,26 @@ public class ShowcaseTests {
|
||||
stateExitedLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transition(Transition<States, Events> transition) {
|
||||
transitions.add(transition);
|
||||
transitionLatch.countDown();
|
||||
transitionCount++;
|
||||
}
|
||||
|
||||
public void reset(int c1, int c2, int c3) {
|
||||
reset(c1, c2, c3, 0);
|
||||
}
|
||||
|
||||
public void reset(int c1, int c2, int c3, int c4) {
|
||||
stateChangedLatch = new CountDownLatch(c1);
|
||||
stateEnteredLatch = new CountDownLatch(c2);
|
||||
stateExitedLatch = new CountDownLatch(c3);
|
||||
transitionLatch = new CountDownLatch(c4);
|
||||
statesEntered.clear();
|
||||
statesExited.clear();
|
||||
transitionCount = 0;
|
||||
transitions.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user