From ee2f7b2eb64990e460f86cb8971a0675ec478529 Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Sun, 22 Feb 2015 15:34:10 +0000 Subject: [PATCH] Add hierarchical event processing - resolves #19 - add base support for handling events in a submachines --- .../config/EnumStateMachineFactory.java | 61 ++++++++++++++++--- .../StateMachineTransitionBuilder.java | 8 +-- .../builders/StateMachineTransitions.java | 7 ++- .../DefaultExternalTransitionConfigurer.java | 18 ++++-- .../DefaultInternalTransitionConfigurer.java | 18 ++++-- .../DefaultLocalTransitionConfigurer.java | 18 ++++-- .../configurers/TransitionConfigurer.java | 12 ++-- .../statemachine/region/Region.java | 6 +- .../statemachine/state/AbstractState.java | 3 +- .../statemachine/state/RegionState.java | 4 +- .../statemachine/state/State.java | 3 +- .../statemachine/state/StateMachineState.java | 10 +++ .../support/AbstractStateMachine.java | 46 +++++++++++--- .../transition/AbstractTransition.java | 5 +- .../AbstractStateMachineTests.java | 2 +- .../event/StateMachineEventTests.java | 60 ++++++++++++++++++ 16 files changed, 228 insertions(+), 53 deletions(-) diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineFactory.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineFactory.java index 137093e7..a359bb19 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineFactory.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineFactory.java @@ -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, E extends Enum> exten // use two stack, first for states and second for machines Stack> machineStack = new Stack>(); Stack> stateStack = new Stack>(); - for (Node> node : traverser.postOrderTraversal(tree.getRoot())) { + + Iterable>> postOrderTraversal = traverser.postOrderTraversal(tree.getRoot()); + Iterator>> iterator = postOrderTraversal.iterator(); + + + while (iterator.hasNext()) { + Node> node = iterator.next(); StateData stateData = node.getData(); if (stateStack.isEmpty()) { stateStack.push(stateData); @@ -114,12 +121,20 @@ public class EnumStateMachineFactory, E extends Enum> exten stateDatas.add(next); } stateStack.clear(); + + Collection> 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 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, E extends Enum> exten return machine; } - private static , E extends Enum> StateMachine buildSimpleMachinexxx( + private Collection> resolveTransitionData(Collection> in, Collection> stateDatas) { + ArrayList> out = new ArrayList>(); + + Collection states = new ArrayList(); + for (StateData stateData : stateDatas) { + states.add(stateData.getParent()); + } + + for (TransitionData transitionData : in) { + S state = transitionData.getState(); + if (state != null && states.contains(state)) { + out.add(transitionData); + } + } + + return out; + } + + private Collection> resolveTransitionData2(Collection> in) { + ArrayList> out = new ArrayList>(); + for (TransitionData transitionData : in) { + if (transitionData.getState() == null) { + out.add(transitionData); + } + } + return out; + } + + private static , E extends Enum> StateMachine buildSimpleMachine( Map> stateMap, Collection> stateDatas, - StateMachineTransitions transitionsData, BeanFactory beanFactory) { + Collection> transitionsData, BeanFactory beanFactory) { State initialState = null; State endState = null; for (StateData stateData : stateDatas) { @@ -153,7 +196,7 @@ public class EnumStateMachineFactory, E extends Enum> exten } Collection> transitions = new ArrayList>(); - for (TransitionData transitionData : transitionsData.getTransitions()) { + for (TransitionData transitionData : transitionsData) { S source = transitionData.getSource(); S target = transitionData.getTarget(); E event = transitionData.getEvent(); @@ -178,9 +221,9 @@ public class EnumStateMachineFactory, E extends Enum> exten return machine; } - private static , E extends Enum> StateMachine buildSubMachinexxx( + private static , E extends Enum> StateMachine buildSubMachine( Map> stateMap, StateMachine submachine, Collection> stateDatas, - StateMachineTransitions transitionsData, BeanFactory beanFactory) { + Collection> transitionsData, BeanFactory beanFactory) { Collection> states = new ArrayList>(); State state = null; for (StateData stateData : stateDatas) { @@ -192,7 +235,7 @@ public class EnumStateMachineFactory, E extends Enum> exten } Collection> transitions = new ArrayList>(); - for (TransitionData transitionData : transitionsData.getTransitions()) { + for (TransitionData transitionData : transitionsData) { S source = transitionData.getSource(); S target = transitionData.getTarget(); E event = transitionData.getEvent(); diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionBuilder.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionBuilder.java index 971ad6c2..ece046ca 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionBuilder.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionBuilder.java @@ -61,19 +61,19 @@ public class StateMachineTransitionBuilder public ExternalTransitionConfigurer withExternal() throws Exception { return apply(new DefaultExternalTransitionConfigurer()); } - + @Override public InternalTransitionConfigurer withInternal() throws Exception { return apply(new DefaultInternalTransitionConfigurer()); } - + @Override public LocalTransitionConfigurer withLocal() throws Exception { return apply(new DefaultLocalTransitionConfigurer()); } - public void add(S source, S target, E event, Collection> actions, Guard guard, TransitionKind kind) { - transitionData.add(new TransitionData(source, target, event, actions, guard, kind)); + public void add(S source, S target, S state, E event, Collection> actions, Guard guard, TransitionKind kind) { + transitionData.add(new TransitionData(source, target, state, event, actions, guard, kind)); } } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitions.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitions.java index eb08cfd1..1d3200a0 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitions.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitions.java @@ -36,13 +36,15 @@ public class StateMachineTransitions { public static class TransitionData { S source; S target; + S state; E event; Collection> actions; Guard guard; TransitionKind kind; - public TransitionData(S source, S target, E event, Collection> actions, Guard guard, TransitionKind kind) { + public TransitionData(S source, S target, S state, E event, Collection> actions, Guard 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 { public S getTarget() { return target; } + public S getState() { + return state; + } public E getEvent() { return event; } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultExternalTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultExternalTransitionConfigurer.java index a47cd53d..56ca070c 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultExternalTransitionConfigurer.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultExternalTransitionConfigurer.java @@ -32,7 +32,7 @@ import org.springframework.statemachine.transition.TransitionKind; /** * Default implementation of a {@link ExternalTransitionConfigurer}. - * + * * @author Janne Valkealahti * * @param the type of state @@ -46,15 +46,17 @@ public class DefaultExternalTransitionConfigurer private S target; + private S state; + private E event; private Collection> actions = new ArrayList>(); - + private Guard guard; @Override public void configure(StateMachineTransitionBuilder 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 return this; } + @Override + public ExternalTransitionConfigurer state(S state) { + this.state = state; + return this; + } + @Override public ExternalTransitionConfigurer event(E event) { this.event = event; @@ -80,13 +88,13 @@ public class DefaultExternalTransitionConfigurer actions.add(action); return this; } - + @Override public ExternalTransitionConfigurer guard(Guard guard) { this.guard = guard; return this; } - + @Override public ExternalTransitionConfigurer guardExpression(String expression) { SpelExpressionParser parser = new SpelExpressionParser( diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultInternalTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultInternalTransitionConfigurer.java index a00a38a8..c1d00650 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultInternalTransitionConfigurer.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultInternalTransitionConfigurer.java @@ -32,7 +32,7 @@ import org.springframework.statemachine.transition.TransitionKind; /** * Default implementation of a {@link InternalTransitionConfigurer}. - * + * * @author Janne Valkealahti * * @param the type of state @@ -46,15 +46,17 @@ public class DefaultInternalTransitionConfigurer private S target; + private S state; + private E event; private Collection> actions = new ArrayList>(); - + private Guard guard; @Override public void configure(StateMachineTransitionBuilder 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 return this; } + @Override + public InternalTransitionConfigurer state(S state) { + this.state = state; + return this; + } + @Override public InternalTransitionConfigurer event(E event) { this.event = event; @@ -74,13 +82,13 @@ public class DefaultInternalTransitionConfigurer actions.add(action); return this; } - + @Override public InternalTransitionConfigurer guard(Guard guard) { this.guard = guard; return this; } - + @Override public InternalTransitionConfigurer guardExpression(String expression) { SpelExpressionParser parser = new SpelExpressionParser( diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultLocalTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultLocalTransitionConfigurer.java index ebbcd239..0b872757 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultLocalTransitionConfigurer.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultLocalTransitionConfigurer.java @@ -32,7 +32,7 @@ import org.springframework.statemachine.transition.TransitionKind; /** * Default implementation of a {@link LocalTransitionConfigurer}. - * + * * @author Janne Valkealahti * * @param the type of state @@ -46,15 +46,17 @@ public class DefaultLocalTransitionConfigurer private S target; + private S state; + private E event; private Collection> actions = new ArrayList>(); - + private Guard guard; @Override public void configure(StateMachineTransitionBuilder 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 return this; } + @Override + public LocalTransitionConfigurer state(S state) { + this.state = state; + return this; + } + @Override public LocalTransitionConfigurer event(E event) { this.event = event; @@ -80,13 +88,13 @@ public class DefaultLocalTransitionConfigurer actions.add(action); return this; } - + @Override public LocalTransitionConfigurer guard(Guard guard) { this.guard = guard; return this; } - + @Override public LocalTransitionConfigurer guardExpression(String expression) { SpelExpressionParser parser = new SpelExpressionParser( diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/TransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/TransitionConfigurer.java index fead6e7d..a1815121 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/TransitionConfigurer.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/TransitionConfigurer.java @@ -23,7 +23,7 @@ import org.springframework.statemachine.transition.Transition; /** * Base {@code TransitionConfigurer} interface for configuring {@link Transition}s. - * + * * @author Janne Valkealahti * * @param the type of a transition configurer @@ -41,9 +41,11 @@ public interface TransitionConfigurer 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 extends /** * Specify {@link Action} for this {@link Transition}. - * + * * @param action the action * @return configurer for chaining */ @@ -59,7 +61,7 @@ public interface TransitionConfigurer extends /** * Specify a {@link Guard} for this {@link Transition}. - * + * * @param guard the guard * @return configurer for chaining */ @@ -67,7 +69,7 @@ public interface TransitionConfigurer extends /** * Specify a {@link Guard} backed by a SpEL expression for this {@link Transition}. - * + * * @param expression the SpEL expression * @return configurer for chaining */ diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/region/Region.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/region/Region.java index 93eef870..7919580b 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/region/Region.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/region/Region.java @@ -46,15 +46,17 @@ public interface Region { * 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 event); + boolean sendEvent(Message 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}. diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/AbstractState.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/AbstractState.java index 17cf98c3..b12c9f96 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/AbstractState.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/AbstractState.java @@ -146,7 +146,8 @@ public abstract class AbstractState implements State { } @Override - public void sendEvent(Message event) { + public boolean sendEvent(Message event) { + return false; } @Override diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/RegionState.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/RegionState.java index dfbe56fb..804ac7c5 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/RegionState.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/RegionState.java @@ -94,12 +94,14 @@ public class RegionState extends AbstractState { } @Override - public void sendEvent(Message event) { + public boolean sendEvent(Message event) { if (getRegions() != null) { for (Region r : getRegions()) { r.sendEvent(event); } + return true; } + return false; } @Override diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/State.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/State.java index 6b03fa1d..f495a15e 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/State.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/State.java @@ -35,8 +35,9 @@ public interface State { * 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 event); + boolean sendEvent(Message event); /** * Initiate an exit sequence for the state. diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/StateMachineState.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/StateMachineState.java index a9416d50..70ad8340 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/StateMachineState.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/StateMachineState.java @@ -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 extends AbstractState { } } + @Override + public boolean sendEvent(Message event) { + StateMachine machine = getSubmachine(); + if (machine != null) { + return machine.sendEvent(event); + } + return super.sendEvent(event); + } + private boolean isLocal(StateContext context) { Transition transition = context.getTransition(); if (transition != null && TransitionKind.LOCAL == transition.getKind() && this == transition.getTarget()) { diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java index c4f2c622..5cafcdb4 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java @@ -157,9 +157,9 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport } @Override - public void sendEvent(Message event) { + public boolean sendEvent(Message 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 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 extends LifecycleObjectSupport return transitions; } + protected boolean acceptEvent(Message event) { + + boolean accepted = currentState.sendEvent(event); + if (accepted) { + return true; + } + + Message defer = null; + for (Transition transition : transitions) { + State source = transition.getSource(); + Trigger 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 state, Message event, Transition transition) { exitFromState(currentState, event, transition); notifyStateChanged(currentState, state); diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractTransition.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractTransition.java index aa55cf2d..f2e4b4ab 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractTransition.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractTransition.java @@ -27,7 +27,7 @@ import org.springframework.util.Assert; /** * Base implementation of a {@link Transition}. - * + * * @author Janne Valkealahti * * @param the type of state @@ -42,7 +42,7 @@ public abstract class AbstractTransition implements Transition { private final Collection> actions; private final TransitionKind kind; - + private final Guard guard; private Trigger trigger; @@ -50,7 +50,6 @@ public abstract class AbstractTransition implements Transition { public AbstractTransition(State source, State target, Collection> actions, E event, TransitionKind kind, Guard 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; diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/AbstractStateMachineTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/AbstractStateMachineTests.java index 7f6e5714..116accb6 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/AbstractStateMachineTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/AbstractStateMachineTests.java @@ -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 } diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/event/StateMachineEventTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/event/StateMachineEventTests.java index 420b9756..fec8b8d3 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/event/StateMachineEventTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/event/StateMachineEventTests.java @@ -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 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 { @@ -123,6 +140,49 @@ public class StateMachineEventTests extends AbstractStateMachineTests { } + @Configuration + @EnableStateMachine + static class Config2 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer 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 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 { CountDownLatch onEventLatch = new CountDownLatch(6);