Region fixes
This commit is contained in:
@@ -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<S extends Enum<S>, E extends Enum<E>> exten
|
||||
}
|
||||
};
|
||||
|
||||
// use two stack, first for states and second for machines
|
||||
Stack<MachineStackItem<S, E>> machineStack = new Stack<MachineStackItem<S, E>>();
|
||||
List<MachineStackItem<S, E>> regionStack = new ArrayList<MachineStackItem<S, E>>();
|
||||
Stack<StateData<S, E>> stateStack = new Stack<StateData<S, E>>();
|
||||
|
||||
Iterable<Node<StateData<S, E>>> postOrderTraversal = traverser.postOrderTraversal(tree.getRoot());
|
||||
Iterator<Node<StateData<S, E>>> iterator = postOrderTraversal.iterator();
|
||||
|
||||
Map<Object, StateMachine<S, E>> machineMap = new HashMap<Object, StateMachine<S,E>>();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
Node<StateData<S, E>> node = iterator.next();
|
||||
StateData<S, E> stateData = node.getData();
|
||||
StateData<S, E> peek = stateStack.isEmpty() ? null : stateStack.peek();
|
||||
|
||||
// simply push and continue
|
||||
if (stateStack.isEmpty()) {
|
||||
stateStack.push(stateData);
|
||||
} else {
|
||||
StateData<S, E> peek = stateStack.peek();
|
||||
if ((stateData != null) && ((peek.getParent() == null && stateData.getParent() == null) || peek.getParent().equals(stateData.getParent()))) {
|
||||
stateStack.push(stateData);
|
||||
} else {
|
||||
Collection<StateData<S, E>> stateDatas = new ArrayList<StateData<S,E>>();
|
||||
Enumeration<StateData<S, E>> elements = stateStack.elements();
|
||||
while (elements.hasMoreElements()) {
|
||||
StateData<S, E> next = elements.nextElement();
|
||||
stateDatas.add(next);
|
||||
}
|
||||
stateStack.clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
Collection<TransitionData<S, E>> 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<S, E>(machine, peek.getParent()));
|
||||
} else {
|
||||
MachineStackItem<S, E> pop = machineStack.pop();
|
||||
machine = buildSubMachine(stateMap, pop, stateDatas, transitionsData, getBeanFactory());
|
||||
machineStack.push(new MachineStackItem<S, E>(machine, null));
|
||||
}
|
||||
stateStack.push(stateData);
|
||||
}
|
||||
Collection<StateData<S, E>> stateDatas = popSameParents(stateStack);
|
||||
Collection<TransitionData<S, E>> transitionsData = getTransitionData(iterator.hasNext(), stateDatas);
|
||||
|
||||
machine = buildMachine(machineMap, stateMap, stateDatas, transitionsData, getBeanFactory());
|
||||
machineMap.put(peek.getParent(), machine);
|
||||
regionStack.add(new MachineStackItem<S, E>(machine, peek.getParent()));
|
||||
stateStack.push(stateData);
|
||||
}
|
||||
|
||||
Collection<Region<S, E>> regions = new ArrayList<Region<S,E>>();
|
||||
for (MachineStackItem<S, E> si : regionStack) {
|
||||
if (si.parent == null) {
|
||||
regions.add(si.machine);
|
||||
}
|
||||
}
|
||||
|
||||
if (regions.size() > 1) {
|
||||
RegionState<S, E> rstate = new RegionState<S, E>(null, regions);
|
||||
Collection<State<S, E>> states = new ArrayList<State<S,E>>();
|
||||
states.add(rstate);
|
||||
machine = new EnumStateMachine<S, E>(states, null, rstate, null);
|
||||
}
|
||||
|
||||
return machine;
|
||||
}
|
||||
|
||||
private Collection<TransitionData<S, E>> getTransitionData(boolean roots, Collection<StateData<S, E>> stateDatas) {
|
||||
if (roots) {
|
||||
return resolveTransitionData(stateMachineTransitions.getTransitions(), stateDatas);
|
||||
} else {
|
||||
return resolveTransitionData2(stateMachineTransitions.getTransitions());
|
||||
}
|
||||
}
|
||||
|
||||
private static <S, E> Collection<StateData<S, E>> popSameParents(Stack<StateData<S, E>> stack) {
|
||||
Collection<StateData<S, E>> data = new ArrayList<StateData<S, E>>();
|
||||
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<S, E> {
|
||||
|
||||
StateMachine<S, E> machine;
|
||||
@@ -185,26 +211,41 @@ public class EnumStateMachineFactory<S extends Enum<S>, E extends Enum<E>> exten
|
||||
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,
|
||||
|
||||
private static <S extends Enum<S>, E extends Enum<E>> StateMachine<S, E> buildMachine(
|
||||
Map<Object, StateMachine<S, E>> machineMap, Map<S, State<S, E>> stateMap, Collection<StateData<S, E>> stateDatas,
|
||||
Collection<TransitionData<S, E>> transitionsData, BeanFactory beanFactory) {
|
||||
State<S, E> state = null;
|
||||
State<S, E> initialState = null;
|
||||
State<S, E> endState = null;
|
||||
Collection<State<S, E>> states = new ArrayList<State<S,E>>();
|
||||
for (StateData<S, E> stateData : stateDatas) {
|
||||
StateMachine<S, E> stateMachine = machineMap.get(stateData.getState());
|
||||
if (stateMachine != null) {
|
||||
state = new StateMachineState<S, E>(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<S, E>(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<S,E> state = new EnumState<S, E>(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<S extends Enum<S>, E extends Enum<E>> exten
|
||||
}
|
||||
}
|
||||
|
||||
EnumStateMachine<S, E> machine = new EnumStateMachine<S, E>(stateMap.values(), transitions,
|
||||
EnumStateMachine<S, E> machine = new EnumStateMachine<S, E>(/*stateMap.values()*/ states, transitions,
|
||||
initialState, endState);
|
||||
machine.afterPropertiesSet();
|
||||
if (beanFactory != null) {
|
||||
@@ -234,50 +275,4 @@ 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> buildSubMachine(
|
||||
Map<S, State<S, E>> stateMap, MachineStackItem<S, E> stackItem, Collection<StateData<S, E>> stateDatas,
|
||||
Collection<TransitionData<S, E>> transitionsData, BeanFactory beanFactory) {
|
||||
Collection<State<S, E>> states = new ArrayList<State<S,E>>();
|
||||
State<S, E> state = null;
|
||||
State<S, E> initialState = null;
|
||||
for (StateData<S, E> stateData : stateDatas) {
|
||||
if (stackItem.parent == null || stateData.getState().equals(stackItem.parent)) {
|
||||
state = new StateMachineState<S, E>(stateData.getState(), stackItem.machine, stateData.getDeferred(),
|
||||
stateData.getEntryActions(), stateData.getExitActions(), new DefaultPseudoState(
|
||||
PseudoStateKind.INITIAL));
|
||||
initialState = state;
|
||||
} else {
|
||||
state = new EnumState<S, E>(stateData.getState(), stateData.getDeferred(),
|
||||
stateData.getEntryActions(), stateData.getExitActions(), null);
|
||||
}
|
||||
states.add(state);
|
||||
stateMap.put(stateData.getState(), state);
|
||||
}
|
||||
|
||||
Collection<Transition<S, E>> transitions = new ArrayList<Transition<S, E>>();
|
||||
for (TransitionData<S, E> transitionData : transitionsData) {
|
||||
S source = transitionData.getSource();
|
||||
S target = transitionData.getTarget();
|
||||
E event = transitionData.getEvent();
|
||||
if (transitionData.getKind() == TransitionKind.EXTERNAL) {
|
||||
DefaultExternalTransition<S, E> transition = new DefaultExternalTransition<S, E>(stateMap.get(source),
|
||||
stateMap.get(target), transitionData.getActions(), event, transitionData.getGuard());
|
||||
transitions.add(transition);
|
||||
|
||||
} else if (transitionData.getKind() == TransitionKind.INTERNAL) {
|
||||
DefaultInternalTransition<S, E> transition = new DefaultInternalTransition<S, E>(stateMap.get(source),
|
||||
transitionData.getActions(), event, transitionData.getGuard());
|
||||
transitions.add(transition);
|
||||
}
|
||||
}
|
||||
|
||||
EnumStateMachine<S, E> machine = new EnumStateMachine<S, E>(states, transitions, initialState, null);
|
||||
|
||||
machine.afterPropertiesSet();
|
||||
if (beanFactory != null) {
|
||||
machine.setBeanFactory(beanFactory);
|
||||
}
|
||||
return machine;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ public class StateData<S, E> {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StateData [parent=" + parent + ", state=" + state + ", deferred=" + deferred + ", entryActions="
|
||||
+ entryActions + ", exitActions=" + exitActions + "]";
|
||||
+ entryActions + ", exitActions=" + exitActions + ", initial=" + initial + ", end=" + end + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<TestStates,TestEvents> 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<TestStates,TestEvents> 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<TestStates,TestEvents> machine =
|
||||
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class);
|
||||
assertThat(machine, notNullValue());
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
public static class Config1 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
|
||||
@@ -305,6 +338,57 @@ public class ConfigurationTests extends AbstractStateMachineTests {
|
||||
@EnableStateMachine
|
||||
static class Config6 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> 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<TestStates, TestEvents> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source(TestStates.S1011)
|
||||
.target(TestStates.S11)
|
||||
.event(TestEvents.E1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
static class Config7 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> 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<TestStates, TestEvents> 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<TestStates, TestEvents> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user