Region fixes

This commit is contained in:
Janne Valkealahti
2015-02-28 17:02:53 +00:00
parent 714b8131ff
commit 2ceca88e6a
3 changed files with 200 additions and 112 deletions

View File

@@ -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;
}
}

View File

@@ -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 + "]";
}
}

View File

@@ -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);
}
}
}