Add hierarchical event processing
- resolves #19 - add base support for handling events in a submachines
This commit is contained in:
@@ -19,6 +19,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
|
||||
@@ -98,7 +99,13 @@ public class EnumStateMachineFactory<S extends Enum<S>, E extends Enum<E>> exten
|
||||
// use two stack, first for states and second for machines
|
||||
Stack<StateMachine<S, E>> machineStack = new Stack<StateMachine<S, E>>();
|
||||
Stack<StateData<S, E>> stateStack = new Stack<StateData<S, E>>();
|
||||
for (Node<StateData<S, E>> node : traverser.postOrderTraversal(tree.getRoot())) {
|
||||
|
||||
Iterable<Node<StateData<S, E>>> postOrderTraversal = traverser.postOrderTraversal(tree.getRoot());
|
||||
Iterator<Node<StateData<S, E>>> iterator = postOrderTraversal.iterator();
|
||||
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
Node<StateData<S, E>> node = iterator.next();
|
||||
StateData<S, E> stateData = node.getData();
|
||||
if (stateStack.isEmpty()) {
|
||||
stateStack.push(stateData);
|
||||
@@ -114,12 +121,20 @@ public class EnumStateMachineFactory<S extends Enum<S>, E extends Enum<E>> exten
|
||||
stateDatas.add(next);
|
||||
}
|
||||
stateStack.clear();
|
||||
|
||||
Collection<TransitionData<S, E>> transitionsData = null;
|
||||
if (iterator.hasNext()) {
|
||||
transitionsData = resolveTransitionData(stateMachineTransitions.getTransitions(), stateDatas);
|
||||
} else {
|
||||
transitionsData = resolveTransitionData2(stateMachineTransitions.getTransitions());
|
||||
}
|
||||
|
||||
if (machineStack.isEmpty()) {
|
||||
machine = buildSimpleMachinexxx(stateMap, stateDatas, stateMachineTransitions, getBeanFactory());
|
||||
machine = buildSimpleMachine(stateMap, stateDatas, transitionsData, getBeanFactory());
|
||||
machineStack.push(machine);
|
||||
} else {
|
||||
StateMachine<S, E> pop = machineStack.pop();
|
||||
machine = buildSubMachinexxx(stateMap, pop, stateDatas, stateMachineTransitions, getBeanFactory());
|
||||
machine = buildSubMachine(stateMap, pop, stateDatas, transitionsData, getBeanFactory());
|
||||
machineStack.push(machine);
|
||||
}
|
||||
stateStack.push(stateData);
|
||||
@@ -129,9 +144,37 @@ public class EnumStateMachineFactory<S extends Enum<S>, E extends Enum<E>> exten
|
||||
return machine;
|
||||
}
|
||||
|
||||
private static <S extends Enum<S>, E extends Enum<E>> StateMachine<S, E> buildSimpleMachinexxx(
|
||||
private Collection<TransitionData<S, E>> resolveTransitionData(Collection<TransitionData<S, E>> in, Collection<StateData<S, E>> stateDatas) {
|
||||
ArrayList<TransitionData<S, E>> out = new ArrayList<TransitionData<S,E>>();
|
||||
|
||||
Collection<Object> states = new ArrayList<Object>();
|
||||
for (StateData<S, E> stateData : stateDatas) {
|
||||
states.add(stateData.getParent());
|
||||
}
|
||||
|
||||
for (TransitionData<S, E> transitionData : in) {
|
||||
S state = transitionData.getState();
|
||||
if (state != null && states.contains(state)) {
|
||||
out.add(transitionData);
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
private Collection<TransitionData<S, E>> resolveTransitionData2(Collection<TransitionData<S, E>> in) {
|
||||
ArrayList<TransitionData<S, E>> out = new ArrayList<TransitionData<S,E>>();
|
||||
for (TransitionData<S, E> transitionData : in) {
|
||||
if (transitionData.getState() == null) {
|
||||
out.add(transitionData);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static <S extends Enum<S>, E extends Enum<E>> StateMachine<S, E> buildSimpleMachine(
|
||||
Map<S, State<S, E>> stateMap, Collection<StateData<S, E>> stateDatas,
|
||||
StateMachineTransitions<S, E> transitionsData, BeanFactory beanFactory) {
|
||||
Collection<TransitionData<S, E>> transitionsData, BeanFactory beanFactory) {
|
||||
State<S, E> initialState = null;
|
||||
State<S, E> endState = null;
|
||||
for (StateData<S, E> stateData : stateDatas) {
|
||||
@@ -153,7 +196,7 @@ public class EnumStateMachineFactory<S extends Enum<S>, E extends Enum<E>> exten
|
||||
}
|
||||
|
||||
Collection<Transition<S, E>> transitions = new ArrayList<Transition<S, E>>();
|
||||
for (TransitionData<S, E> transitionData : transitionsData.getTransitions()) {
|
||||
for (TransitionData<S, E> transitionData : transitionsData) {
|
||||
S source = transitionData.getSource();
|
||||
S target = transitionData.getTarget();
|
||||
E event = transitionData.getEvent();
|
||||
@@ -178,9 +221,9 @@ public class EnumStateMachineFactory<S extends Enum<S>, E extends Enum<E>> exten
|
||||
return machine;
|
||||
}
|
||||
|
||||
private static <S extends Enum<S>, E extends Enum<E>> StateMachine<S, E> buildSubMachinexxx(
|
||||
private static <S extends Enum<S>, E extends Enum<E>> StateMachine<S, E> buildSubMachine(
|
||||
Map<S, State<S, E>> stateMap, StateMachine<S, E> submachine, Collection<StateData<S, E>> stateDatas,
|
||||
StateMachineTransitions<S, E> transitionsData, BeanFactory beanFactory) {
|
||||
Collection<TransitionData<S, E>> transitionsData, BeanFactory beanFactory) {
|
||||
Collection<State<S, E>> states = new ArrayList<State<S,E>>();
|
||||
State<S, E> state = null;
|
||||
for (StateData<S, E> stateData : stateDatas) {
|
||||
@@ -192,7 +235,7 @@ public class EnumStateMachineFactory<S extends Enum<S>, E extends Enum<E>> exten
|
||||
}
|
||||
|
||||
Collection<Transition<S, E>> transitions = new ArrayList<Transition<S, E>>();
|
||||
for (TransitionData<S, E> transitionData : transitionsData.getTransitions()) {
|
||||
for (TransitionData<S, E> transitionData : transitionsData) {
|
||||
S source = transitionData.getSource();
|
||||
S target = transitionData.getTarget();
|
||||
E event = transitionData.getEvent();
|
||||
|
||||
@@ -61,19 +61,19 @@ public class StateMachineTransitionBuilder<S, E>
|
||||
public ExternalTransitionConfigurer<S, E> withExternal() throws Exception {
|
||||
return apply(new DefaultExternalTransitionConfigurer<S, E>());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public InternalTransitionConfigurer<S, E> withInternal() throws Exception {
|
||||
return apply(new DefaultInternalTransitionConfigurer<S, E>());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public LocalTransitionConfigurer<S, E> withLocal() throws Exception {
|
||||
return apply(new DefaultLocalTransitionConfigurer<S, E>());
|
||||
}
|
||||
|
||||
public void add(S source, S target, E event, Collection<Action<S, E>> actions, Guard<S, E> guard, TransitionKind kind) {
|
||||
transitionData.add(new TransitionData<S, E>(source, target, event, actions, guard, kind));
|
||||
public void add(S source, S target, S state, E event, Collection<Action<S, E>> actions, Guard<S, E> guard, TransitionKind kind) {
|
||||
transitionData.add(new TransitionData<S, E>(source, target, state, event, actions, guard, kind));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -36,13 +36,15 @@ public class StateMachineTransitions<S, E> {
|
||||
public static class TransitionData<S, E> {
|
||||
S source;
|
||||
S target;
|
||||
S state;
|
||||
E event;
|
||||
Collection<Action<S, E>> actions;
|
||||
Guard<S, E> guard;
|
||||
TransitionKind kind;
|
||||
public TransitionData(S source, S target, E event, Collection<Action<S, E>> actions, Guard<S, E> guard, TransitionKind kind) {
|
||||
public TransitionData(S source, S target, S state, E event, Collection<Action<S, E>> actions, Guard<S, E> guard, TransitionKind kind) {
|
||||
this.source = source;
|
||||
this.target = target;
|
||||
this.state = state;
|
||||
this.event = event;
|
||||
this.actions = actions;
|
||||
this.guard = guard;
|
||||
@@ -54,6 +56,9 @@ public class StateMachineTransitions<S, E> {
|
||||
public S getTarget() {
|
||||
return target;
|
||||
}
|
||||
public S getState() {
|
||||
return state;
|
||||
}
|
||||
public E getEvent() {
|
||||
return event;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ import org.springframework.statemachine.transition.TransitionKind;
|
||||
|
||||
/**
|
||||
* Default implementation of a {@link ExternalTransitionConfigurer}.
|
||||
*
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
@@ -46,15 +46,17 @@ public class DefaultExternalTransitionConfigurer<S, E>
|
||||
|
||||
private S target;
|
||||
|
||||
private S state;
|
||||
|
||||
private E event;
|
||||
|
||||
private Collection<Action<S, E>> actions = new ArrayList<Action<S, E>>();
|
||||
|
||||
|
||||
private Guard<S, E> guard;
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionBuilder<S, E> builder) throws Exception {
|
||||
builder.add(source, target, event, actions, guard, TransitionKind.EXTERNAL);
|
||||
builder.add(source, target, state, event, actions, guard, TransitionKind.EXTERNAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -69,6 +71,12 @@ public class DefaultExternalTransitionConfigurer<S, E>
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExternalTransitionConfigurer<S, E> state(S state) {
|
||||
this.state = state;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExternalTransitionConfigurer<S, E> event(E event) {
|
||||
this.event = event;
|
||||
@@ -80,13 +88,13 @@ public class DefaultExternalTransitionConfigurer<S, E>
|
||||
actions.add(action);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ExternalTransitionConfigurer<S, E> guard(Guard<S, E> guard) {
|
||||
this.guard = guard;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ExternalTransitionConfigurer<S, E> guardExpression(String expression) {
|
||||
SpelExpressionParser parser = new SpelExpressionParser(
|
||||
|
||||
@@ -32,7 +32,7 @@ import org.springframework.statemachine.transition.TransitionKind;
|
||||
|
||||
/**
|
||||
* Default implementation of a {@link InternalTransitionConfigurer}.
|
||||
*
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
@@ -46,15 +46,17 @@ public class DefaultInternalTransitionConfigurer<S, E>
|
||||
|
||||
private S target;
|
||||
|
||||
private S state;
|
||||
|
||||
private E event;
|
||||
|
||||
private Collection<Action<S, E>> actions = new ArrayList<Action<S, E>>();
|
||||
|
||||
|
||||
private Guard<S, E> guard;
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionBuilder<S, E> builder) throws Exception {
|
||||
builder.add(source, target, event, actions, guard, TransitionKind.INTERNAL);
|
||||
builder.add(source, target, state, event, actions, guard, TransitionKind.INTERNAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -63,6 +65,12 @@ public class DefaultInternalTransitionConfigurer<S, E>
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InternalTransitionConfigurer<S, E> state(S state) {
|
||||
this.state = state;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InternalTransitionConfigurer<S, E> event(E event) {
|
||||
this.event = event;
|
||||
@@ -74,13 +82,13 @@ public class DefaultInternalTransitionConfigurer<S, E>
|
||||
actions.add(action);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public InternalTransitionConfigurer<S, E> guard(Guard<S, E> guard) {
|
||||
this.guard = guard;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public InternalTransitionConfigurer<S, E> guardExpression(String expression) {
|
||||
SpelExpressionParser parser = new SpelExpressionParser(
|
||||
|
||||
@@ -32,7 +32,7 @@ import org.springframework.statemachine.transition.TransitionKind;
|
||||
|
||||
/**
|
||||
* Default implementation of a {@link LocalTransitionConfigurer}.
|
||||
*
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
@@ -46,15 +46,17 @@ public class DefaultLocalTransitionConfigurer<S, E>
|
||||
|
||||
private S target;
|
||||
|
||||
private S state;
|
||||
|
||||
private E event;
|
||||
|
||||
private Collection<Action<S, E>> actions = new ArrayList<Action<S, E>>();
|
||||
|
||||
|
||||
private Guard<S, E> guard;
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionBuilder<S, E> builder) throws Exception {
|
||||
builder.add(source, target, event, actions, guard, TransitionKind.LOCAL);
|
||||
builder.add(source, target, state, event, actions, guard, TransitionKind.LOCAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -69,6 +71,12 @@ public class DefaultLocalTransitionConfigurer<S, E>
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalTransitionConfigurer<S, E> state(S state) {
|
||||
this.state = state;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalTransitionConfigurer<S, E> event(E event) {
|
||||
this.event = event;
|
||||
@@ -80,13 +88,13 @@ public class DefaultLocalTransitionConfigurer<S, E>
|
||||
actions.add(action);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public LocalTransitionConfigurer<S, E> guard(Guard<S, E> guard) {
|
||||
this.guard = guard;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public LocalTransitionConfigurer<S, E> guardExpression(String expression) {
|
||||
SpelExpressionParser parser = new SpelExpressionParser(
|
||||
|
||||
@@ -23,7 +23,7 @@ import org.springframework.statemachine.transition.Transition;
|
||||
|
||||
/**
|
||||
* Base {@code TransitionConfigurer} interface for configuring {@link Transition}s.
|
||||
*
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <T> the type of a transition configurer
|
||||
@@ -41,9 +41,11 @@ public interface TransitionConfigurer<T, S, E> extends
|
||||
*/
|
||||
T source(S source);
|
||||
|
||||
T state(S state);
|
||||
|
||||
/**
|
||||
* Specify event {@code E} for this {@link Transition}.
|
||||
*
|
||||
*
|
||||
* @param event the event for transition
|
||||
* @return configurer for chaining
|
||||
*/
|
||||
@@ -51,7 +53,7 @@ public interface TransitionConfigurer<T, S, E> extends
|
||||
|
||||
/**
|
||||
* Specify {@link Action} for this {@link Transition}.
|
||||
*
|
||||
*
|
||||
* @param action the action
|
||||
* @return configurer for chaining
|
||||
*/
|
||||
@@ -59,7 +61,7 @@ public interface TransitionConfigurer<T, S, E> extends
|
||||
|
||||
/**
|
||||
* Specify a {@link Guard} for this {@link Transition}.
|
||||
*
|
||||
*
|
||||
* @param guard the guard
|
||||
* @return configurer for chaining
|
||||
*/
|
||||
@@ -67,7 +69,7 @@ public interface TransitionConfigurer<T, S, E> extends
|
||||
|
||||
/**
|
||||
* Specify a {@link Guard} backed by a SpEL expression for this {@link Transition}.
|
||||
*
|
||||
*
|
||||
* @param expression the SpEL expression
|
||||
* @return configurer for chaining
|
||||
*/
|
||||
|
||||
@@ -46,15 +46,17 @@ public interface Region<S, E> {
|
||||
* Send an event {@code E} wrapped with a {@link Message} to the region.
|
||||
*
|
||||
* @param event the wrapped event to send
|
||||
* @return true if event was accepted
|
||||
*/
|
||||
void sendEvent(Message<E> event);
|
||||
boolean sendEvent(Message<E> event);
|
||||
|
||||
/**
|
||||
* Send an event {@code E} to the region.
|
||||
*
|
||||
* @param event the event to send
|
||||
* @return true if event was accepted
|
||||
*/
|
||||
void sendEvent(E event);
|
||||
boolean sendEvent(E event);
|
||||
|
||||
/**
|
||||
* Gets the current {@link State}.
|
||||
|
||||
@@ -146,7 +146,8 @@ public abstract class AbstractState<S, E> implements State<S, E> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendEvent(Message<E> event) {
|
||||
public boolean sendEvent(Message<E> event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -94,12 +94,14 @@ public class RegionState<S, E> extends AbstractState<S, E> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendEvent(Message<E> event) {
|
||||
public boolean sendEvent(Message<E> event) {
|
||||
if (getRegions() != null) {
|
||||
for (Region<S, E> r : getRegions()) {
|
||||
r.sendEvent(event);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -35,8 +35,9 @@ public interface State<S, E> {
|
||||
* Send an event {@code E} wrapped with a {@link Message} to the state.
|
||||
*
|
||||
* @param event the wrapped event to send
|
||||
* @return true if event was accepted
|
||||
*/
|
||||
void sendEvent(Message<E> event);
|
||||
boolean sendEvent(Message<E> event);
|
||||
|
||||
/**
|
||||
* Initiate an exit sequence for the state.
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.statemachine.state;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.statemachine.StateContext;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.action.Action;
|
||||
@@ -144,6 +145,15 @@ public class StateMachineState<S, E> extends AbstractState<S, E> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendEvent(Message<E> event) {
|
||||
StateMachine<S, E> machine = getSubmachine();
|
||||
if (machine != null) {
|
||||
return machine.sendEvent(event);
|
||||
}
|
||||
return super.sendEvent(event);
|
||||
}
|
||||
|
||||
private boolean isLocal(StateContext<S, E> context) {
|
||||
Transition<S, E> transition = context.getTransition();
|
||||
if (transition != null && TransitionKind.LOCAL == transition.getKind() && this == transition.getTarget()) {
|
||||
|
||||
@@ -157,9 +157,9 @@ public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendEvent(Message<E> event) {
|
||||
public boolean sendEvent(Message<E> event) {
|
||||
if (isComplete()) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
// TODO: machine header looks weird!
|
||||
event = MessageBuilder.fromMessage(event).setHeader("machine", this).build();
|
||||
@@ -167,18 +167,14 @@ public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport
|
||||
log.debug("Queue event " + event);
|
||||
}
|
||||
|
||||
// TODO: should not do here
|
||||
if (currentState != null) {
|
||||
currentState.sendEvent(event);
|
||||
}
|
||||
|
||||
eventQueue.add(event);
|
||||
boolean accepted = acceptEvent(event);
|
||||
scheduleEventQueueProcessing();
|
||||
return accepted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendEvent(E event) {
|
||||
sendEvent(MessageBuilder.withPayload(event).build());
|
||||
public boolean sendEvent(E event) {
|
||||
return sendEvent(MessageBuilder.withPayload(event).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -228,6 +224,36 @@ public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport
|
||||
return transitions;
|
||||
}
|
||||
|
||||
protected boolean acceptEvent(Message<E> event) {
|
||||
|
||||
boolean accepted = currentState.sendEvent(event);
|
||||
if (accepted) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Message<E> defer = null;
|
||||
for (Transition<S,E> transition : transitions) {
|
||||
State<S,E> source = transition.getSource();
|
||||
Trigger<S, E> trigger = transition.getTrigger();
|
||||
|
||||
if (StateMachineUtils.containsAtleastOne(source.getIds(), currentState.getIds())) {
|
||||
if (trigger != null && trigger.evaluate(event.getPayload())) {
|
||||
eventQueue.add(event);
|
||||
return true;
|
||||
} else if (source.getDeferredEvents() != null && source.getDeferredEvents().contains(event.getPayload())) {
|
||||
defer = event;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (defer != null) {
|
||||
log.info("Deferring event " + defer);
|
||||
deferList.addLast(defer);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void switchToState(State<S,E> state, Message<E> event, Transition<S,E> transition) {
|
||||
exitFromState(currentState, event, transition);
|
||||
notifyStateChanged(currentState, state);
|
||||
|
||||
@@ -27,7 +27,7 @@ import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Base implementation of a {@link Transition}.
|
||||
*
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
@@ -42,7 +42,7 @@ public abstract class AbstractTransition<S, E> implements Transition<S, E> {
|
||||
private final Collection<Action<S, E>> actions;
|
||||
|
||||
private final TransitionKind kind;
|
||||
|
||||
|
||||
private final Guard<S, E> guard;
|
||||
|
||||
private Trigger<S, E> trigger;
|
||||
@@ -50,7 +50,6 @@ public abstract class AbstractTransition<S, E> implements Transition<S, E> {
|
||||
public AbstractTransition(State<S, E> source, State<S, E> target, Collection<Action<S, E>> actions, E event,
|
||||
TransitionKind kind, Guard<S, E> guard) {
|
||||
Assert.notNull(source, "Source must be set");
|
||||
// Assert.notNull(target, "Target must be set");
|
||||
Assert.notNull(kind, "Transition type must be set");
|
||||
this.source = source;
|
||||
this.target = target;
|
||||
|
||||
@@ -62,7 +62,7 @@ public abstract class AbstractStateMachineTests {
|
||||
|
||||
public enum TestStates {
|
||||
SI,S1,S2,S3,S4,SF,
|
||||
S10,S11,S101,S111,S112,S12,S121,S122,
|
||||
S10,S11,S101,S111,S112,S12,S121,S122,S13,
|
||||
S20,S21,S201,S211,
|
||||
S1011,S1012,S2011,S2012
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.statemachine.event;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
@@ -74,6 +75,22 @@ public class StateMachineEventTests extends AbstractStateMachineTests {
|
||||
assertThat(listener.events.size(), is(6));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubmachineHandlesEvent() throws Exception {
|
||||
context.register(BaseConfig.class, StateMachineEventPublisherConfiguration.class, Config2.class);
|
||||
context.refresh();
|
||||
assertTrue(context.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE));
|
||||
@SuppressWarnings("unchecked")
|
||||
EnumStateMachine<TestStates,TestEvents> machine =
|
||||
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class);
|
||||
machine.start();
|
||||
assertThat(machine, notNullValue());
|
||||
|
||||
assertThat(machine.getState().getIds(), contains(TestStates.S1, TestStates.S10));
|
||||
machine.sendEvent(TestEvents.E1);
|
||||
assertThat(machine.getState().getIds(), contains(TestStates.S1, TestStates.S12));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
static class Config1 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
|
||||
@@ -123,6 +140,49 @@ public class StateMachineEventTests extends AbstractStateMachineTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
static class Config2 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(TestStates.S1)
|
||||
.state(TestStates.S1)
|
||||
.and()
|
||||
.withStates()
|
||||
.parent(TestStates.S1)
|
||||
.initial(TestStates.S10)
|
||||
.state(TestStates.S10)
|
||||
.state(TestStates.S11)
|
||||
.state(TestStates.S12)
|
||||
.state(TestStates.S13);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source(TestStates.S10)
|
||||
.target(TestStates.S11)
|
||||
.event(TestEvents.E1)
|
||||
.and()
|
||||
.withExternal()
|
||||
.state(TestStates.S1)
|
||||
.source(TestStates.S10)
|
||||
.target(TestStates.S12)
|
||||
.event(TestEvents.E1)
|
||||
.and()
|
||||
.withExternal()
|
||||
.state(TestStates.S1)
|
||||
.source(TestStates.S10)
|
||||
.target(TestStates.S13)
|
||||
.event(TestEvents.E2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class TestEventListener implements ApplicationListener<AbstractStateMachineEvent> {
|
||||
|
||||
CountDownLatch onEventLatch = new CountDownLatch(6);
|
||||
|
||||
Reference in New Issue
Block a user