From 7602cf68ce9862def85732710a62b1388833da22 Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Thu, 12 May 2016 07:45:49 +0100 Subject: [PATCH] Pseudostates may cause exit from parent - Fix better handling of when parent state is trying to enter if transition happens with pseudostate in substates. - Fixes #223 --- .../buildtests/ChoiceExitTests.java | 17 +++++ .../buildtests/choice-exit.notation | 75 ++++++++++++++++--- .../statemachine/buildtests/choice-exit.uml | 10 ++- .../support/AbstractStateMachine.java | 43 +++++++++-- 4 files changed, 126 insertions(+), 19 deletions(-) diff --git a/spring-statemachine-build-tests/src/test/java/org/springframework/statemachine/buildtests/ChoiceExitTests.java b/spring-statemachine-build-tests/src/test/java/org/springframework/statemachine/buildtests/ChoiceExitTests.java index 8d301d81..7332f784 100644 --- a/spring-statemachine-build-tests/src/test/java/org/springframework/statemachine/buildtests/ChoiceExitTests.java +++ b/spring-statemachine-build-tests/src/test/java/org/springframework/statemachine/buildtests/ChoiceExitTests.java @@ -65,6 +65,23 @@ public class ChoiceExitTests extends AbstractBuildTests { plan.test(); } + @Test + @SuppressWarnings("unchecked") + public void testChoiceIntoSubstateDoesntExitParent() throws Exception { + context.register(Config1.class); + context.refresh(); + StateMachine stateMachine = context.getBean(StateMachine.class); + + StateMachineTestPlan plan = + StateMachineTestPlanBuilder.builder() + .stateMachine(stateMachine) + .step().expectState("READY").and() + .step().sendEvent("E1").expectStateChanged(2).expectStates("DOSTUFF", "START").and() + .step().sendEvent("E3").expectStateChanged(1).expectStateExited(1).expectStates("DOSTUFF", "S1").and() + .build(); + plan.test(); + } + @Configuration @EnableStateMachine public static class Config1 extends StateMachineConfigurerAdapter { diff --git a/spring-statemachine-build-tests/src/test/resources/org/springframework/statemachine/buildtests/choice-exit.notation b/spring-statemachine-build-tests/src/test/resources/org/springframework/statemachine/buildtests/choice-exit.notation index ad15bd67..ea77be8e 100644 --- a/spring-statemachine-build-tests/src/test/resources/org/springframework/statemachine/buildtests/choice-exit.notation +++ b/spring-statemachine-build-tests/src/test/resources/org/springframework/statemachine/buildtests/choice-exit.notation @@ -2,7 +2,7 @@ - + @@ -38,7 +38,7 @@ - + @@ -67,7 +67,7 @@ - + @@ -85,15 +85,38 @@ + + + + + + + + + + + + + + + + + + + + + + + - + - + - + @@ -108,12 +131,12 @@ - + - + - + @@ -167,7 +190,7 @@ - + @@ -233,4 +256,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-statemachine-build-tests/src/test/resources/org/springframework/statemachine/buildtests/choice-exit.uml b/spring-statemachine-build-tests/src/test/resources/org/springframework/statemachine/buildtests/choice-exit.uml index ca831a09..a2801cef 100644 --- a/spring-statemachine-build-tests/src/test/resources/org/springframework/statemachine/buildtests/choice-exit.uml +++ b/spring-statemachine-build-tests/src/test/resources/org/springframework/statemachine/buildtests/choice-exit.uml @@ -24,9 +24,15 @@ + + + + - + + + @@ -36,4 +42,6 @@ + + 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 afa9a7d4..1db52fc6 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 @@ -740,7 +740,9 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo setCurrentState(ss, message, transition, false, stateMachine, null, fps.getForks()); } } else { - setCurrentState(toState, message, transition, true, stateMachine); + Collection> targets = new ArrayList<>(); + targets.add(toState); + setCurrentState(toState, message, transition, true, stateMachine, null, targets); } callPostStateChangeInterceptors(state, message, transition, stateMachine); @@ -856,7 +858,7 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo if (states.contains(state)) { if (exit) { - exitCurrentState(state, message, transition, stateMachine); + exitCurrentState(state, message, transition, stateMachine, sources, targets); } State notifyFrom = currentState; currentState = state; @@ -868,7 +870,7 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo nonDeepStatePresent = true; } else if (currentState == null && StateMachineUtils.isSubstate(findDeep, state)) { if (exit) { - exitCurrentState(findDeep, message, transition, stateMachine); + exitCurrentState(findDeep, message, transition, stateMachine, sources, targets); } State notifyFrom = currentState; currentState = findDeep; @@ -882,7 +884,7 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo if (currentState != null && !nonDeepStatePresent) { if (findDeep != null) { if (exit) { - exitCurrentState(state, message, transition, stateMachine); + exitCurrentState(state, message, transition, stateMachine, sources, targets); } if (currentState == findDeep) { @@ -956,30 +958,40 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo } void exitCurrentState(State state, Message message, Transition transition, StateMachine stateMachine) { + exitCurrentState(state, message, transition, stateMachine, null, null); + } + + void exitCurrentState(State state, Message message, Transition transition, StateMachine stateMachine, + Collection> sources, Collection> targets) { if (currentState == null) { return; } if (currentState.isSubmachineState()) { StateMachine submachine = ((AbstractState)currentState).getSubmachine(); ((AbstractStateMachine)submachine).exitCurrentState(state, message, transition, stateMachine); - exitFromState(currentState, message, transition, stateMachine); + exitFromState(currentState, message, transition, stateMachine, sources, targets); } else if (currentState.isOrthogonal()) { Collection> regions = ((AbstractState)currentState).getRegions(); for (Region r : regions) { if (r.getStates().contains(state)) { - exitFromState(r.getState(), message, transition, stateMachine); + exitFromState(r.getState(), message, transition, stateMachine, sources, targets); } } if (transition == null) { - exitFromState(currentState, message, transition, stateMachine); + exitFromState(currentState, message, transition, stateMachine, sources, targets); } } else { - exitFromState(currentState, message, transition, stateMachine); + exitFromState(currentState, message, transition, stateMachine, sources, targets); } } private void exitFromState(State state, Message message, Transition transition, StateMachine stateMachine) { + exitFromState(state, message, transition, stateMachine, null, null); + } + + private void exitFromState(State state, Message message, Transition transition, + StateMachine stateMachine, Collection> sources, Collection> targets) { if (state == null) { return; } @@ -1003,6 +1015,9 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo } else if (!isSubOfSource && !isSubOfTarget && findDeep == null) { } else if (!isSubOfSource && !isSubOfTarget && (transition.getSource() == currentState && StateMachineUtils.isSubstate(currentState, transition.getTarget()))) { } else if (StateMachineUtils.isNormalPseudoState(transition.getTarget())) { + if (isPseudoStateSubstate(findDeep, targets)) { + return; + } } else if (findDeep != null && findDeep != state && findDeep.getStates().contains(state)) { } else if (!isSubOfSource && !isSubOfTarget) { return; @@ -1016,6 +1031,18 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo notifyStateExited(buildStateContext(Stage.STATE_EXIT, message, null, getRelayStateMachine(), state, null)); } + private boolean isPseudoStateSubstate(State left, Collection> rights) { + if (rights == null || left == null) { + return false; + } + for (State s : rights) { + if (StateMachineUtils.isSubstate(left, s)) { + return true; + } + } + return false; + } + private void entryToState(State state, Message message, Transition transition, StateMachine stateMachine) { entryToState(state, message, transition, stateMachine, null, null); }