Fix state holder usage

- In AbstractStateMachineFactory change used holder map
  to list so that same states don't get lost because of
  used key.
- Backport #416
- Relates #307
This commit is contained in:
Janne Valkealahti
2017-10-28 15:07:45 +01:00
parent fb8d534e8e
commit 10ed7feeae
2 changed files with 104 additions and 13 deletions

View File

@@ -180,7 +180,7 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
Stack<MachineStackItem<S, E>> regionStack = new Stack<MachineStackItem<S, E>>();
Stack<StateData<S, E>> stateStack = new Stack<StateData<S, E>>();
Map<Object, StateMachine<S, E>> machineMap = new HashMap<Object, StateMachine<S,E>>();
Map<S, StateHolder<S, E>> holderMap = new HashMap<S, StateHolder<S, E>>();
List<HolderListItem<S, E>> holderList = new ArrayList<>();
Iterator<Node<StateData<S, E>>> iterator = buildStateDataIterator(stateMachineModel);
while (iterator.hasNext()) {
@@ -216,7 +216,7 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
if (initialCount > 1) {
for (Collection<StateData<S, E>> regionStateDatas : regionsStateDatas) {
machine = buildMachine(machineMap, stateMap, holderMap, regionStateDatas, transitionsData, resolveBeanFactory(stateMachineModel),
machine = buildMachine(machineMap, stateMap, holderList, regionStateDatas, transitionsData, resolveBeanFactory(stateMachineModel),
contextEvents, defaultExtendedState, stateMachineModel.getTransitionsData(), resolveTaskExecutor(stateMachineModel),
resolveTaskScheduler(stateMachineModel), machineId, null, stateMachineModel);
regionStack.push(new MachineStackItem<S, E>(machine));
@@ -245,7 +245,7 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
machine = m;
}
} else {
machine = buildMachine(machineMap, stateMap, holderMap, stateDatas, transitionsData, resolveBeanFactory(stateMachineModel), contextEvents,
machine = buildMachine(machineMap, stateMap, holderList, stateDatas, transitionsData, resolveBeanFactory(stateMachineModel), contextEvents,
defaultExtendedState, stateMachineModel.getTransitionsData(), resolveTaskExecutor(stateMachineModel), resolveTaskScheduler(stateMachineModel),
machineId, uuid, stateMachineModel);
if (peek.isInitial() || (!peek.isInitial() && !machineMap.containsKey(peek.getParent()))) {
@@ -321,8 +321,8 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
// go through holders and fix state references which
// were not known at a time holder was created
for (Entry<S, StateHolder<S, E>> holder : holderMap.entrySet()) {
holder.getValue().setState(stateMap.get(holder.getKey()));
for (HolderListItem<S, E> holderItem : holderList) {
holderItem.value.setState(stateMap.get(holderItem.key));
}
// set parent machines for each built machine
@@ -519,7 +519,7 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
@SuppressWarnings("unchecked")
private StateMachine<S, E> buildMachine(Map<Object, StateMachine<S, E>> machineMap, Map<S, State<S, E>> stateMap,
Map<S, StateHolder<S, E>> holderMap, Collection<StateData<S, E>> stateDatas, Collection<TransitionData<S, E>> transitionsData,
List<HolderListItem<S, E>> holderList, Collection<StateData<S, E>> stateDatas, Collection<TransitionData<S, E>> transitionsData,
BeanFactory beanFactory, Boolean contextEvents, DefaultExtendedState defaultExtendedState,
TransitionsData<S, E> stateMachineTransitions, TaskExecutor taskExecutor, TaskScheduler taskScheduler, String machineId,
UUID uuid, StateMachineModel<S, E> stateMachineModel) {
@@ -614,7 +614,7 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
StateHolder<S, E> defaultStateHolder = new StateHolder<S, E>(defaultState);
StateHolder<S, E> containingStateHolder = new StateHolder<S, E>(stateMap.get(stateData.getParent()));
if (containingStateHolder.getState() == null) {
holderMap.put((S)stateData.getParent(), containingStateHolder);
holderList.add(new HolderListItem<S, E>((S)stateData.getParent(), containingStateHolder));
}
PseudoState<S, E> pseudoState = new HistoryPseudoState<S, E>(PseudoStateKind.HISTORY_SHALLOW, defaultStateHolder, containingStateHolder);
state = buildStateInternal(stateData.getState(), stateData.getDeferred(), stateData.getEntryActions(),
@@ -634,7 +634,7 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
StateHolder<S, E> defaultStateHolder = new StateHolder<S, E>(defaultState);
StateHolder<S, E> containingStateHolder = new StateHolder<S, E>(stateMap.get(stateData.getParent()));
if (containingStateHolder.getState() == null) {
holderMap.put((S)stateData.getParent(), containingStateHolder);
holderList.add(new HolderListItem<S, E>((S)stateData.getParent(), containingStateHolder));
}
PseudoState<S, E> pseudoState = new HistoryPseudoState<S, E>(PseudoStateKind.HISTORY_DEEP, defaultStateHolder, containingStateHolder);
state = buildStateInternal(stateData.getState(), stateData.getDeferred(), stateData.getEntryActions(),
@@ -651,7 +651,7 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
for (ChoiceData<S, E> c : list) {
StateHolder<S, E> holder = new StateHolder<S, E>(stateMap.get(c.getTarget()));
if (holder.getState() == null) {
holderMap.put(c.getTarget(), holder);
holderList.add(new HolderListItem<S, E>(c.getTarget(), holder));
}
choices.add(new ChoiceStateData<S, E>(holder, c.getGuard(), c.getActions()));
}
@@ -667,7 +667,7 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
for (JunctionData<S, E> c : list) {
StateHolder<S, E> holder = new StateHolder<S, E>(stateMap.get(c.getTarget()));
if (holder.getState() == null) {
holderMap.put(c.getTarget(), holder);
holderList.add(new HolderListItem<S, E>(c.getTarget(), holder));
}
junctions.add(new JunctionStateData<S, E>(holder, c.getGuard(), c.getActions()));
}
@@ -696,7 +696,7 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
if (s.equals(entry.getSource())) {
StateHolder<S, E> holder = new StateHolder<S, E>(stateMap.get(entry.getTarget()));
if (holder.getState() == null) {
holderMap.put(entry.getTarget(), holder);
holderList.add(new HolderListItem<S, E>(entry.getTarget(), holder));
}
PseudoState<S, E> pseudoState = new ExitPseudoState<S, E>(holder);
state = buildStateInternal(stateData.getState(), stateData.getDeferred(), stateData.getEntryActions(),
@@ -751,7 +751,7 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
if (tt.getSource() == s) {
StateHolder<S, E> holder = new StateHolder<S, E>(stateMap.get(tt.getTarget()));
if (holder.getState() == null) {
holderMap.put(tt.getTarget(), holder);
holderList.add(new HolderListItem<S, E>(tt.getTarget(), holder));
}
joinTargets.add(new JoinStateData<S, E>(holder, tt.getGuard()));
}
@@ -891,4 +891,14 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
latch.countDown();
}
}
private static class HolderListItem<S, E> {
S key;
StateHolder<S, E> value;
public HolderListItem(S key, StateHolder<S, E> value) {
this.key = key;
this.value = value;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2016-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -48,6 +48,29 @@ public class ExitEntryStateTests extends AbstractStateMachineTests {
assertThat(machine.getState().getIds(), contains("S4"));
}
@SuppressWarnings("unchecked")
@Test
public void testMultipleExitsToSameState() {
context.register(Config2.class);
context.refresh();
StateMachine<String, String> machine =
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
assertThat(machine, notNullValue());
machine.start();
assertThat(machine.getState().getIds(), contains("S1"));
machine.sendEvent("E1");
assertThat(machine.getState().getIds(), contains("S2", "S22"));
machine.sendEvent("EXIT2");
assertThat(machine.getState().getIds(), contains("S1"));
machine.sendEvent("E2");
assertThat(machine.getState().getIds(), contains("S3", "S32"));
machine.sendEvent("EXIT3");
assertThat(machine.getState().getIds(), contains("S1"));
}
@Configuration
@EnableStateMachine
static class Config1 extends StateMachineConfigurerAdapter<String, String> {
@@ -116,6 +139,64 @@ public class ExitEntryStateTests extends AbstractStateMachineTests {
}
}
@Configuration
@EnableStateMachine
static class Config2 extends StateMachineConfigurerAdapter<String, String> {
@Override
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
states
.withStates()
.initial("S1")
.state("S2")
.state("S3")
.and()
.withStates()
.parent("S2")
.initial("S21")
.exit("S2EXIT")
.state("S22")
.and()
.withStates()
.parent("S3")
.initial("S31")
.exit("S3EXIT")
.state("S32");
}
@Override
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
transitions
.withExternal()
.source("S1").target("S2")
.event("E1")
.and()
.withExternal()
.source("S1").target("S3")
.event("E2")
.and()
.withExternal()
.source("S21").target("S22")
.and()
.withExternal()
.source("S31").target("S32")
.and()
.withExternal()
.source("S22").target("S2EXIT")
.event("EXIT2")
.and()
.withExternal()
.source("S32").target("S3EXIT")
.event("EXIT3")
.and()
.withExit()
.source("S2EXIT").target("S1")
.and()
.withExit()
.source("S3EXIT").target("S1");
}
}
@Override
protected AnnotationConfigApplicationContext buildContext() {