diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateMachine.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateMachine.java index 1982fb6a..98386856 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateMachine.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateMachine.java @@ -15,7 +15,6 @@ */ package org.springframework.statemachine; -import org.springframework.messaging.Message; import org.springframework.statemachine.listener.StateMachineListener; import org.springframework.statemachine.region.Region; import org.springframework.statemachine.state.State; @@ -38,31 +37,6 @@ public interface StateMachine extends Region { */ State getInitialState(); - /** - * Start the state machine. - */ - void start(); - - /** - * Stop the state machine. - */ - void stop(); - - /** - * Send an event {@code E} wrapped with a {@link Message} to the state - * machine. - * - * @param event the wrapped event to send - */ - void sendEvent(Message event); - - /** - * Send an event {@code E} to the state machine. - * - * @param event the event to send - */ - void sendEvent(E event); - /** * Adds the state listener. * 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 2ac62179..93eef870 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 @@ -17,13 +17,14 @@ package org.springframework.statemachine.region; import java.util.Collection; +import org.springframework.messaging.Message; import org.springframework.statemachine.state.State; import org.springframework.statemachine.transition.Transition; /** * A region is an orthogonal part of either a composite state or a state * machine. It contains states and transitions. - * + * * @author Janne Valkealahti * * @param the type of state @@ -31,28 +32,52 @@ import org.springframework.statemachine.transition.Transition; */ public interface Region { + /** + * Start the region. + */ + void start(); + + /** + * Stop the region. + */ + void stop(); + + /** + * Send an event {@code E} wrapped with a {@link Message} to the region. + * + * @param event the wrapped event to send + */ + void sendEvent(Message event); + + /** + * Send an event {@code E} to the region. + * + * @param event the event to send + */ + void sendEvent(E event); + /** * Gets the current {@link State}. * * @return current state */ State getState(); - + /** * Gets the {@link State}s defined in this region. Returned collection is * an unmodifiable copy because states in a state machine are immutable. * * @return immutable copy of states - */ + */ Collection> getStates(); - + /** * Gets a {@link Transition}s for this region. - * + * * @return immutable copy of transitions */ Collection> getTransitions(); - + /** * Checks if region complete. Region is considered to be completed if it has * reached its end state and no further event processing is happening. 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 9cd0e30d..3cce2515 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 @@ -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; @@ -136,6 +137,10 @@ public abstract class AbstractState implements State { this.submachine = submachine; } + @Override + public void sendEvent(Message event) { + } + @Override public abstract void exit(E event, StateContext context); 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 d88384a3..a8681f81 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 @@ -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.action.Action; import org.springframework.statemachine.region.Region; @@ -87,8 +88,21 @@ public class RegionState extends AbstractState { super(deferred, entryActions, exitActions, null, regions); } + @Override + public void sendEvent(Message event) { + if (getRegions() != null) { + for (Region r : getRegions()) { + r.sendEvent(event); + } + } + } + @Override public void exit(E event, StateContext context) { + for (Region region : getRegions()) { + region.getState().exit(event, context); + region.stop(); + } Collection> actions = getExitActions(); if (actions != null) { for (Action action : actions) { @@ -105,13 +119,26 @@ public class RegionState extends AbstractState { action.execute(context); } } + + if (getPseudoState() != null && getPseudoState().getKind() == PseudoStateKind.INITIAL) { + for (Region region : getRegions()) { + region.start(); + } + } else { + for (Region region : getRegions()) { + region.getState().entry(event, context); + } + } } @Override public Collection getIds() { ArrayList ids = new ArrayList(); for (Region r : getRegions()) { - ids.addAll(r.getState().getIds()); + State s = r.getState(); + if (s != null) { + ids.addAll(s.getIds()); + } } return ids; } 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 dc7dd349..6d3bb818 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 @@ -17,6 +17,7 @@ package org.springframework.statemachine.state; import java.util.Collection; +import org.springframework.messaging.Message; import org.springframework.statemachine.StateContext; import org.springframework.statemachine.action.Action; @@ -30,6 +31,13 @@ import org.springframework.statemachine.action.Action; */ public interface State { + /** + * Send an event {@code E} wrapped with a {@link Message} to the state. + * + * @param event the wrapped event to send + */ + void sendEvent(Message event); + /** * Initiate an exit sequence for the state. * 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 fa82a3ca..c4f2c622 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 @@ -166,6 +166,12 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport if (log.isDebugEnabled()) { log.debug("Queue event " + event); } + + // TODO: should not do here + if (currentState != null) { + currentState.sendEvent(event); + } + eventQueue.add(event); scheduleEventQueueProcessing(); } 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 c04e8de9..e06faef4 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,8 @@ public abstract class AbstractStateMachineTests { public enum TestStates { SI,S1,S2,S3,S4,SF, - S11,S111,S21,S211 + S11,S111,S112,S12,S121,S122, + S21,S211 } public enum TestEvents { diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/RegionMachineTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/RegionMachineTests.java index 78a35bdb..3e985de2 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/RegionMachineTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/RegionMachineTests.java @@ -15,8 +15,8 @@ */ package org.springframework.statemachine; -import static org.hamcrest.Matchers.contains; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.contains; import static org.junit.Assert.assertThat; import java.util.ArrayList; @@ -31,7 +31,10 @@ import org.springframework.statemachine.AbstractStateMachineTests.TestExitAction import org.springframework.statemachine.AbstractStateMachineTests.TestStates; import org.springframework.statemachine.action.Action; import org.springframework.statemachine.region.Region; +import org.springframework.statemachine.state.DefaultPseudoState; import org.springframework.statemachine.state.EnumState; +import org.springframework.statemachine.state.PseudoState; +import org.springframework.statemachine.state.PseudoStateKind; import org.springframework.statemachine.state.RegionState; import org.springframework.statemachine.state.State; import org.springframework.statemachine.transition.DefaultExternalTransition; @@ -106,4 +109,97 @@ public class RegionMachineTests { assertThat(exitActionS1.stateContexts.size(), is(1)); } + @Test + public void testMultiRegion() throws Exception { + SyncTaskExecutor taskExecutor = new SyncTaskExecutor(); + PseudoState pseudoState = new DefaultPseudoState(PseudoStateKind.INITIAL); + State stateSI = new EnumState(TestStates.SI); + + TestEntryAction entryActionS111 = new TestEntryAction("S111"); + TestExitAction exitActionS111 = new TestExitAction("S111"); + Collection> entryActionsS111 = new ArrayList>(); + entryActionsS111.add(entryActionS111); + Collection> exitActionsS111 = new ArrayList>(); + exitActionsS111.add(exitActionS111); + State stateS111 = new EnumState(TestStates.S111, null, entryActionsS111, exitActionsS111, pseudoState); + + TestEntryAction entryActionS112 = new TestEntryAction("S112"); + TestExitAction exitActionS112 = new TestExitAction("S112"); + Collection> entryActionsS112 = new ArrayList>(); + entryActionsS112.add(entryActionS112); + Collection> exitActionsS112 = new ArrayList>(); + exitActionsS112.add(exitActionS112); + State stateS112 = new EnumState(TestStates.S112, null, entryActionsS112, exitActionsS112); + + TestEntryAction entryActionS121 = new TestEntryAction("S121"); + TestExitAction exitActionS121 = new TestExitAction("S121"); + Collection> entryActionsS121 = new ArrayList>(); + entryActionsS111.add(entryActionS121); + Collection> exitActionsS121 = new ArrayList>(); + exitActionsS111.add(exitActionS121); + State stateS121 = new EnumState(TestStates.S121, null, entryActionsS121, exitActionsS121, pseudoState); + + Collection> states11 = new ArrayList>(); + states11.add(stateSI); + states11.add(stateS111); + states11.add(stateS112); + Collection> transitions11 = new ArrayList>(); + DefaultExternalTransition transitionFromS111ToS112 = + new DefaultExternalTransition(stateS111, stateS112, null, TestEvents.E2, null); + transitions11.add(transitionFromS111ToS112); + EnumStateMachine machine11 = new EnumStateMachine(states11, transitions11, stateS111, null); + machine11.setTaskExecutor(taskExecutor); + machine11.afterPropertiesSet(); + + Collection> states12 = new ArrayList>(); + states12.add(stateSI); + states12.add(stateS121); + Collection> transitions12 = new ArrayList>(); + DefaultExternalTransition transitionFromSIToS121 = + new DefaultExternalTransition(stateSI, stateS111, null, TestEvents.E3, null); + transitions12.add(transitionFromSIToS121); + EnumStateMachine machine12 = new EnumStateMachine(states12, transitions12, stateS121, null); + machine12.setTaskExecutor(taskExecutor); + machine12.afterPropertiesSet(); + + Collection> regions = new ArrayList>(); + regions.add(machine11); + regions.add(machine12); + RegionState stateR = new RegionState(regions, null, null, null, pseudoState); + + Collection> states = new ArrayList>(); + states.add(stateR); + Collection> transitions = new ArrayList>(); + DefaultExternalTransition transitionFromSIToRegionstate = + new DefaultExternalTransition(stateSI, stateR, null, TestEvents.E1, null); + transitions.add(transitionFromSIToRegionstate); + EnumStateMachine machine = new EnumStateMachine(states, transitions, stateR, null); + + machine.setTaskExecutor(taskExecutor); + machine.afterPropertiesSet(); + machine.start(); + + assertThat(entryActionS111.onExecuteLatch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(exitActionS111.onExecuteLatch.await(1, TimeUnit.SECONDS), is(false)); + assertThat(entryActionS121.onExecuteLatch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(exitActionS121.onExecuteLatch.await(1, TimeUnit.SECONDS), is(false)); + + assertThat(entryActionS111.stateContexts.size(), is(1)); + assertThat(exitActionS111.stateContexts.size(), is(0)); + assertThat(entryActionS121.stateContexts.size(), is(1)); + assertThat(exitActionS121.stateContexts.size(), is(0)); + + machine.sendEvent(TestEvents.E2); + + assertThat(entryActionS111.onExecuteLatch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(exitActionS111.onExecuteLatch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(entryActionS112.onExecuteLatch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(exitActionS112.onExecuteLatch.await(1, TimeUnit.SECONDS), is(false)); + + assertThat(entryActionS111.stateContexts.size(), is(1)); + assertThat(exitActionS111.stateContexts.size(), is(1)); + assertThat(entryActionS112.stateContexts.size(), is(1)); + assertThat(exitActionS112.stateContexts.size(), is(0)); + } + }