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 13b6e91c..139c3a5c 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 @@ -17,9 +17,9 @@ package org.springframework.statemachine.config; import java.util.ArrayList; import java.util.Collection; -import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Stack; @@ -29,10 +29,12 @@ import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.config.builders.StateMachineStates; import org.springframework.statemachine.config.builders.StateMachineTransitions; import org.springframework.statemachine.config.builders.StateMachineTransitions.TransitionData; +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.state.StateMachineState; import org.springframework.statemachine.support.LifecycleObjectSupport; @@ -43,6 +45,7 @@ import org.springframework.statemachine.transition.DefaultExternalTransition; import org.springframework.statemachine.transition.DefaultInternalTransition; import org.springframework.statemachine.transition.Transition; import org.springframework.statemachine.transition.TransitionKind; +import org.springframework.util.ObjectUtils; /** * {@link StateMachineFactory} implementation using enums to build {@link StateMachine}s. @@ -96,54 +99,77 @@ public class EnumStateMachineFactory, E extends Enum> exten } }; - // use two stack, first for states and second for machines - Stack> machineStack = new Stack>(); + List> regionStack = new ArrayList>(); Stack> stateStack = new Stack>(); Iterable>> postOrderTraversal = traverser.postOrderTraversal(tree.getRoot()); Iterator>> iterator = postOrderTraversal.iterator(); + Map> machineMap = new HashMap>(); while (iterator.hasNext()) { Node> node = iterator.next(); StateData stateData = node.getData(); + StateData peek = stateStack.isEmpty() ? null : stateStack.peek(); + + // simply push and continue if (stateStack.isEmpty()) { stateStack.push(stateData); - } else { - StateData peek = stateStack.peek(); - if ((stateData != null) && ((peek.getParent() == null && stateData.getParent() == null) || peek.getParent().equals(stateData.getParent()))) { - stateStack.push(stateData); - } else { - Collection> stateDatas = new ArrayList>(); - Enumeration> elements = stateStack.elements(); - while (elements.hasMoreElements()) { - StateData next = elements.nextElement(); - stateDatas.add(next); - } - stateStack.clear(); + continue; + } - Collection> transitionsData = null; - if (iterator.hasNext()) { - transitionsData = resolveTransitionData(stateMachineTransitions.getTransitions(), stateDatas); - } else { - transitionsData = resolveTransitionData2(stateMachineTransitions.getTransitions()); - } + if (stateData != null && ObjectUtils.nullSafeEquals(peek.getParent(), stateData.getParent())) { + stateStack.push(stateData); + continue; + } - if (machineStack.isEmpty()) { - machine = buildSimpleMachine(stateMap, stateDatas, transitionsData, getBeanFactory()); - machineStack.push(new MachineStackItem(machine, peek.getParent())); - } else { - MachineStackItem pop = machineStack.pop(); - machine = buildSubMachine(stateMap, pop, stateDatas, transitionsData, getBeanFactory()); - machineStack.push(new MachineStackItem(machine, null)); - } - stateStack.push(stateData); - } + Collection> stateDatas = popSameParents(stateStack); + Collection> transitionsData = getTransitionData(iterator.hasNext(), stateDatas); + + machine = buildMachine(machineMap, stateMap, stateDatas, transitionsData, getBeanFactory()); + machineMap.put(peek.getParent(), machine); + regionStack.add(new MachineStackItem(machine, peek.getParent())); + stateStack.push(stateData); + } + + Collection> regions = new ArrayList>(); + for (MachineStackItem si : regionStack) { + if (si.parent == null) { + regions.add(si.machine); } } + + if (regions.size() > 1) { + RegionState rstate = new RegionState(null, regions); + Collection> states = new ArrayList>(); + states.add(rstate); + machine = new EnumStateMachine(states, null, rstate, null); + } + return machine; } + private Collection> getTransitionData(boolean roots, Collection> stateDatas) { + if (roots) { + return resolveTransitionData(stateMachineTransitions.getTransitions(), stateDatas); + } else { + return resolveTransitionData2(stateMachineTransitions.getTransitions()); + } + } + + private static Collection> popSameParents(Stack> stack) { + Collection> data = new ArrayList>(); + Object parent = null; + if (!stack.isEmpty()) { + parent = stack.peek().getParent(); + } + while (!stack.isEmpty() && ObjectUtils.nullSafeEquals(parent, stack.peek().getParent())) { + data.add(stack.pop()); + } + return data; + } + + private static class MachineStackItem { StateMachine machine; @@ -185,26 +211,41 @@ public class EnumStateMachineFactory, E extends Enum> exten return out; } - private static , E extends Enum> StateMachine buildSimpleMachine( - Map> stateMap, Collection> stateDatas, + + private static , E extends Enum> StateMachine buildMachine( + Map> machineMap, Map> stateMap, Collection> stateDatas, Collection> transitionsData, BeanFactory beanFactory) { + State state = null; State initialState = null; State endState = null; + Collection> states = new ArrayList>(); for (StateData stateData : stateDatas) { + StateMachine stateMachine = machineMap.get(stateData.getState()); + if (stateMachine != null) { + state = new StateMachineState(stateData.getState(), stateMachine, stateData.getDeferred(), + stateData.getEntryActions(), stateData.getExitActions(), new DefaultPseudoState( + PseudoStateKind.INITIAL)); + initialState = state; + states.add(state); + } else { + PseudoState pseudoState = null; + if (stateData.isInitial()) { + pseudoState = new DefaultPseudoState(PseudoStateKind.INITIAL); + } + state = new EnumState(stateData.getState(), stateData.getDeferred(), + stateData.getEntryActions(), stateData.getExitActions(), pseudoState); + if (stateData.isInitial()) { + initialState = state; + } + if (stateData.isEnd()) { + endState = state; + } + states.add(state); + + } + // TODO: doesn't feel right to tweak initial kind like this - PseudoState pseudoState = null; - if (stateData.isInitial()) { - pseudoState = new DefaultPseudoState(PseudoStateKind.INITIAL); - } - EnumState state = new EnumState(stateData.getState(), stateData.getDeferred(), - stateData.getEntryActions(), stateData.getExitActions(), pseudoState); - if (stateData.isInitial()) { - initialState = state; - } - if (stateData.isEnd()) { - endState = state; - } stateMap.put(stateData.getState(), state); } @@ -225,7 +266,7 @@ public class EnumStateMachineFactory, E extends Enum> exten } } - EnumStateMachine machine = new EnumStateMachine(stateMap.values(), transitions, + EnumStateMachine machine = new EnumStateMachine(/*stateMap.values()*/ states, transitions, initialState, endState); machine.afterPropertiesSet(); if (beanFactory != null) { @@ -234,50 +275,4 @@ public class EnumStateMachineFactory, E extends Enum> exten return machine; } - private static , E extends Enum> StateMachine buildSubMachine( - Map> stateMap, MachineStackItem stackItem, Collection> stateDatas, - Collection> transitionsData, BeanFactory beanFactory) { - Collection> states = new ArrayList>(); - State state = null; - State initialState = null; - for (StateData stateData : stateDatas) { - if (stackItem.parent == null || stateData.getState().equals(stackItem.parent)) { - state = new StateMachineState(stateData.getState(), stackItem.machine, stateData.getDeferred(), - stateData.getEntryActions(), stateData.getExitActions(), new DefaultPseudoState( - PseudoStateKind.INITIAL)); - initialState = state; - } else { - state = new EnumState(stateData.getState(), stateData.getDeferred(), - stateData.getEntryActions(), stateData.getExitActions(), null); - } - states.add(state); - stateMap.put(stateData.getState(), state); - } - - Collection> transitions = new ArrayList>(); - for (TransitionData transitionData : transitionsData) { - S source = transitionData.getSource(); - S target = transitionData.getTarget(); - E event = transitionData.getEvent(); - if (transitionData.getKind() == TransitionKind.EXTERNAL) { - DefaultExternalTransition transition = new DefaultExternalTransition(stateMap.get(source), - stateMap.get(target), transitionData.getActions(), event, transitionData.getGuard()); - transitions.add(transition); - - } else if (transitionData.getKind() == TransitionKind.INTERNAL) { - DefaultInternalTransition transition = new DefaultInternalTransition(stateMap.get(source), - transitionData.getActions(), event, transitionData.getGuard()); - transitions.add(transition); - } - } - - EnumStateMachine machine = new EnumStateMachine(states, transitions, initialState, null); - - machine.afterPropertiesSet(); - if (beanFactory != null) { - machine.setBeanFactory(beanFactory); - } - return machine; - } - } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateData.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateData.java index 21372db2..1da75b1e 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateData.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateData.java @@ -92,7 +92,7 @@ public class StateData { @Override public String toString() { return "StateData [parent=" + parent + ", state=" + state + ", deferred=" + deferred + ", entryActions=" - + entryActions + ", exitActions=" + exitActions + "]"; + + entryActions + ", exitActions=" + exitActions + ", initial=" + initial + ", end=" + end + "]"; } } diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/ConfigurationTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/ConfigurationTests.java index 9746b22d..5297fa2f 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/ConfigurationTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/ConfigurationTests.java @@ -91,6 +91,39 @@ public class ConfigurationTests extends AbstractStateMachineTests { assertThat(machine, notNullValue()); } + @SuppressWarnings({ "unchecked" }) + @Test + public void testRegions() throws Exception { + context.register(Config6.class); + context.refresh(); + assertTrue(context.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE)); + EnumStateMachine machine = + context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); + assertThat(machine, notNullValue()); + } + + @SuppressWarnings({ "unchecked" }) + @Test + public void testSubmachineWithState() throws Exception { + context.register(Config7.class); + context.refresh(); + assertTrue(context.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE)); + EnumStateMachine machine = + context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); + assertThat(machine, notNullValue()); + } + + @SuppressWarnings({ "unchecked" }) + @Test + public void testSubmachineWithRegion() throws Exception { + context.register(Config8.class); + context.refresh(); + assertTrue(context.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE)); + EnumStateMachine machine = + context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); + assertThat(machine, notNullValue()); + } + @Configuration @EnableStateMachine public static class Config1 extends EnumStateMachineConfigurerAdapter { @@ -305,6 +338,57 @@ public class ConfigurationTests extends AbstractStateMachineTests { @EnableStateMachine static class Config6 extends EnumStateMachineConfigurerAdapter { + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.S10) + .state(TestStates.S10) + .state(TestStates.S11) + .and() + .withStates() + .parent(TestStates.S10) + .initial(TestStates.S101) + .state(TestStates.S101) + .and() + .withStates() + .parent(TestStates.S101) + .initial(TestStates.S1011) + .state(TestStates.S1011) + .state(TestStates.S1012) + .and() + .withStates() + .initial(TestStates.S20) + .state(TestStates.S20) + .state(TestStates.S21) + .and() + .withStates() + .parent(TestStates.S20) + .initial(TestStates.S201) + .state(TestStates.S201) + .and() + .withStates() + .parent(TestStates.S201) + .initial(TestStates.S2011) + .state(TestStates.S2011) + .state(TestStates.S2012); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.S1011) + .target(TestStates.S11) + .event(TestEvents.E1); + } + + } + + @Configuration + @EnableStateMachine + static class Config7 extends EnumStateMachineConfigurerAdapter { + @Override public void configure(StateMachineStateConfigurer states) throws Exception { states @@ -322,34 +406,43 @@ public class ConfigurationTests extends AbstractStateMachineTests { .parent(TestStates.S101) .initial(TestStates.S1011) .state(TestStates.S1011) - .state(TestStates.S1012) - .and() - .withStates() - .initial(TestStates.S20) - .state(TestStates.S20) - .state(TestStates.S21) - .and() - .withStates() - .parent(TestStates.S20) - .initial(TestStates.S201) - .state(TestStates.S201) - .and() - .withStates() - .parent(TestStates.S201) - .initial(TestStates.S2011) - .state(TestStates.S2011) - .state(TestStates.S2012); + .state(TestStates.S1012); } @Override public void configure(StateMachineTransitionConfigurer transitions) throws Exception { transitions .withExternal() - .source(TestStates.S111) - .target(TestStates.S1) + .source(TestStates.S1011) + .target(TestStates.S11) .event(TestEvents.E1); } } + @Configuration + @EnableStateMachine + static class Config8 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.S10) + .state(TestStates.S10) + .state(TestStates.S11) + .and() + .withStates() + .parent(TestStates.S10) + .initial(TestStates.S101) + .state(TestStates.S101) + .and() + .withStates() + .parent(TestStates.S10) + .initial(TestStates.S111) + .state(TestStates.S111); + } + + } + }