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
This commit is contained in:
Janne Valkealahti
2016-05-12 07:45:49 +01:00
parent 657c1d8e4b
commit 7602cf68ce
4 changed files with 126 additions and 19 deletions

View File

@@ -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<String, String> stateMachine = context.getBean(StateMachine.class);
StateMachineTestPlan<String, String> plan =
StateMachineTestPlanBuilder.<String, String>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<String, String> {

View File

@@ -2,7 +2,7 @@
<notation:Diagram xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" xmlns:notation="http://www.eclipse.org/gmf/runtime/1.0.2/notation" xmlns:style="http://www.eclipse.org/papyrus/infra/viewpoints/policy/style" xmlns:uml="http://www.eclipse.org/uml2/5.0.0/UML" xmi:id="_AtQnYAZPEeayEI1yTJhWhg" type="PapyrusUMLStateMachineDiagram" name="StateMachine Diagram" measurementUnit="Pixel">
<children xmi:type="notation:Shape" xmi:id="_AtQnYQZPEeayEI1yTJhWhg" type="2000">
<children xmi:type="notation:DecorationNode" xmi:id="_AtQnYgZPEeayEI1yTJhWhg" type="2001">
<layoutConstraint xmi:type="notation:Bounds" xmi:id="_AtQnYwZPEeayEI1yTJhWhg" width="791" height="23"/>
<layoutConstraint xmi:type="notation:Bounds" xmi:id="_AtQnYwZPEeayEI1yTJhWhg" width="931" height="23"/>
</children>
<children xmi:type="notation:DecorationNode" xmi:id="_AtQnZAZPEeayEI1yTJhWhg" type="2002">
<children xmi:type="notation:Shape" xmi:id="_AtQnZQZPEeayEI1yTJhWhg" type="3000">
@@ -38,7 +38,7 @@
</children>
<children xmi:type="notation:Shape" xmi:id="_Kqo2gAZPEeayEI1yTJhWhg" type="6000">
<children xmi:type="notation:DecorationNode" xmi:id="_KqpdkAZPEeayEI1yTJhWhg" type="6001">
<layoutConstraint xmi:type="notation:Bounds" xmi:id="_N1ELIAZPEeayEI1yTJhWhg" width="361" height="23"/>
<layoutConstraint xmi:type="notation:Bounds" xmi:id="_N1ELIAZPEeayEI1yTJhWhg" width="541" height="23"/>
</children>
<children xmi:type="notation:DecorationNode" xmi:id="_KqpdkQZPEeayEI1yTJhWhg" type="19003">
<layoutConstraint xmi:type="notation:Location" xmi:id="_KqpdkgZPEeayEI1yTJhWhg" x="40"/>
@@ -67,7 +67,7 @@
</children>
<children xmi:type="notation:Shape" xmi:id="_Ydjz0AZPEeayEI1yTJhWhg" type="11000">
<children xmi:type="notation:DecorationNode" xmi:id="_Ydjz0gZPEeayEI1yTJhWhg" type="11001">
<layoutConstraint xmi:type="notation:Location" xmi:id="_Ydjz0wZPEeayEI1yTJhWhg" x="50" y="3"/>
<layoutConstraint xmi:type="notation:Location" xmi:id="_Ydjz0wZPEeayEI1yTJhWhg" x="69" y="2"/>
</children>
<children xmi:type="notation:DecorationNode" xmi:id="_Ydjz1AZPEeayEI1yTJhWhg" type="11002">
<layoutConstraint xmi:type="notation:Location" xmi:id="_Ydjz1QZPEeayEI1yTJhWhg" x="25" y="-10"/>
@@ -85,15 +85,38 @@
<element xmi:type="uml:Pseudostate" href="choice-exit.uml#_l37HMAZPEeayEI1yTJhWhg"/>
<layoutConstraint xmi:type="notation:Bounds" xmi:id="_l4E4MQZPEeayEI1yTJhWhg" x="20" y="17"/>
</children>
<children xmi:type="notation:Shape" xmi:id="_u6nsYBeKEeapnMylBysFZA" type="11000">
<children xmi:type="notation:DecorationNode" xmi:id="_u6oTcBeKEeapnMylBysFZA" type="11001">
<layoutConstraint xmi:type="notation:Location" xmi:id="_u6oTcReKEeapnMylBysFZA" x="60"/>
</children>
<children xmi:type="notation:DecorationNode" xmi:id="_u6oTcheKEeapnMylBysFZA" type="11002">
<layoutConstraint xmi:type="notation:Location" xmi:id="_u6oTcxeKEeapnMylBysFZA" x="25" y="-10"/>
</children>
<element xmi:type="uml:Pseudostate" href="choice-exit.uml#_u6UxcBeKEeapnMylBysFZA"/>
<layoutConstraint xmi:type="notation:Bounds" xmi:id="_u6nsYReKEeapnMylBysFZA" x="280" y="77"/>
</children>
<children xmi:type="notation:Shape" xmi:id="_1c_0QBeKEeapnMylBysFZA" type="6000">
<children xmi:type="notation:DecorationNode" xmi:id="_1dAbUBeKEeapnMylBysFZA" type="6001">
<layoutConstraint xmi:type="notation:Bounds" xmi:id="_2kAfoBeKEeapnMylBysFZA" width="81"/>
</children>
<children xmi:type="notation:DecorationNode" xmi:id="_1dAbUReKEeapnMylBysFZA" type="19003">
<layoutConstraint xmi:type="notation:Location" xmi:id="_1dAbUheKEeapnMylBysFZA" x="40"/>
</children>
<children xmi:type="notation:DecorationNode" xmi:id="_1dAbUxeKEeapnMylBysFZA" type="6002">
<layoutConstraint xmi:type="notation:Bounds" xmi:id="_1dAbVBeKEeapnMylBysFZA" y="-1" width="81"/>
</children>
<element xmi:type="uml:State" href="choice-exit.uml#_1cuugBeKEeapnMylBysFZA"/>
<layoutConstraint xmi:type="notation:Bounds" xmi:id="_1c_0QReKEeapnMylBysFZA" x="280" y="157" width="81" height="47"/>
</children>
<layoutConstraint xmi:type="notation:Bounds" xmi:id="_Qa6MAQZPEeayEI1yTJhWhg"/>
</children>
<element xmi:type="uml:Region" href="choice-exit.uml#_Qa4W0QZPEeayEI1yTJhWhg"/>
<layoutConstraint xmi:type="notation:Bounds" xmi:id="_Qa5k8QZPEeayEI1yTJhWhg" width="361" height="198"/>
<layoutConstraint xmi:type="notation:Bounds" xmi:id="_Qa5k8QZPEeayEI1yTJhWhg" width="541" height="358"/>
</children>
<layoutConstraint xmi:type="notation:Bounds" xmi:id="_KqpdlAZPEeayEI1yTJhWhg" y="23" width="361" height="198"/>
<layoutConstraint xmi:type="notation:Bounds" xmi:id="_KqpdlAZPEeayEI1yTJhWhg" y="23" width="541" height="358"/>
</children>
<element xmi:type="uml:State" href="choice-exit.uml#_KqlMIAZPEeayEI1yTJhWhg"/>
<layoutConstraint xmi:type="notation:Bounds" xmi:id="_Kqo2gQZPEeayEI1yTJhWhg" x="310" y="27" width="361" height="221"/>
<layoutConstraint xmi:type="notation:Bounds" xmi:id="_Kqo2gQZPEeayEI1yTJhWhg" x="310" y="27" width="541" height="381"/>
</children>
<children xmi:type="notation:Shape" xmi:id="_ho2esAZPEeayEI1yTJhWhg" type="8000">
<children xmi:type="notation:DecorationNode" xmi:id="_ho2esgZPEeayEI1yTJhWhg" type="8001">
@@ -108,12 +131,12 @@
<layoutConstraint xmi:type="notation:Bounds" xmi:id="_AtQnaQZPEeayEI1yTJhWhg"/>
</children>
<element xmi:type="uml:Region" href="choice-exit.uml#_AtQAUAZPEeayEI1yTJhWhg"/>
<layoutConstraint xmi:type="notation:Bounds" xmi:id="_AtQnagZPEeayEI1yTJhWhg" width="791" height="348"/>
<layoutConstraint xmi:type="notation:Bounds" xmi:id="_AtQnagZPEeayEI1yTJhWhg" width="931" height="488"/>
</children>
<layoutConstraint xmi:type="notation:Bounds" xmi:id="_AtQnawZPEeayEI1yTJhWhg" y="23" width="791" height="348"/>
<layoutConstraint xmi:type="notation:Bounds" xmi:id="_AtQnawZPEeayEI1yTJhWhg" y="23" width="931" height="488"/>
</children>
<element xmi:type="uml:StateMachine" href="choice-exit.uml#_AtPZQAZPEeayEI1yTJhWhg"/>
<layoutConstraint xmi:type="notation:Bounds" xmi:id="_AtQnbAZPEeayEI1yTJhWhg" x="30" y="30" width="791" height="371"/>
<layoutConstraint xmi:type="notation:Bounds" xmi:id="_AtQnbAZPEeayEI1yTJhWhg" x="30" y="30" width="931" height="511"/>
</children>
<styles xmi:type="notation:StringValueStyle" xmi:id="_AtQnbQZPEeayEI1yTJhWhg" name="diagram_compatibility_version" stringValue="1.1.0"/>
<styles xmi:type="notation:DiagramStyle" xmi:id="_AtQnbgZPEeayEI1yTJhWhg"/>
@@ -167,7 +190,7 @@
<element xmi:type="uml:Transition" href="choice-exit.uml#_sVrNwAZPEeayEI1yTJhWhg"/>
<bendpoints xmi:type="notation:RelativeBendpoints" xmi:id="_sV3bAgZPEeayEI1yTJhWhg" points="[13, -1, -122, 0]$[132, -2, -3, -1]"/>
<sourceAnchor xmi:type="notation:IdentityAnchor" xmi:id="_sWa0oAZPEeayEI1yTJhWhg" id="(1.0,0.40425531914893614)"/>
<targetAnchor xmi:type="notation:IdentityAnchor" xmi:id="_sWa0oQZPEeayEI1yTJhWhg" id="(0.0,0.3574660633484163)"/>
<targetAnchor xmi:type="notation:IdentityAnchor" xmi:id="_sWa0oQZPEeayEI1yTJhWhg" id="(0.0,0.2099737532808399)"/>
</edges>
<edges xmi:type="notation:Connector" xmi:id="_vTnlkAZPEeayEI1yTJhWhg" type="7000" source="_Qa6zEAZPEeayEI1yTJhWhg" target="_Ydjz0AZPEeayEI1yTJhWhg">
<children xmi:type="notation:DecorationNode" xmi:id="_vToMoAZPEeayEI1yTJhWhg" type="7001">
@@ -233,4 +256,36 @@
<sourceAnchor xmi:type="notation:IdentityAnchor" xmi:id="_zObOQAZPEeayEI1yTJhWhg" id="(0.5061728395061729,0.0)"/>
<targetAnchor xmi:type="notation:IdentityAnchor" xmi:id="_zObOQQZPEeayEI1yTJhWhg" id="(0.5061728395061729,1.0)"/>
</edges>
<edges xmi:type="notation:Connector" xmi:id="_xdTrkBeKEeapnMylBysFZA" type="7000" source="_Qa6zEAZPEeayEI1yTJhWhg" target="_u6nsYBeKEeapnMylBysFZA">
<children xmi:type="notation:DecorationNode" xmi:id="_xdUSoBeKEeapnMylBysFZA" type="7001">
<layoutConstraint xmi:type="notation:Location" xmi:id="_xdUSoReKEeapnMylBysFZA"/>
</children>
<children xmi:type="notation:DecorationNode" xmi:id="_xdUSoheKEeapnMylBysFZA" type="7002">
<layoutConstraint xmi:type="notation:Location" xmi:id="_xdUSoxeKEeapnMylBysFZA" x="-2" y="-21"/>
</children>
<children xmi:type="notation:DecorationNode" xmi:id="_xdUSpBeKEeapnMylBysFZA" type="7003">
<layoutConstraint xmi:type="notation:Location" xmi:id="_xdUSpReKEeapnMylBysFZA" y="60"/>
</children>
<styles xmi:type="notation:FontStyle" xmi:id="_xdTrkReKEeapnMylBysFZA"/>
<element xmi:type="uml:Transition" href="choice-exit.uml#_xdFCEBeKEeapnMylBysFZA"/>
<bendpoints xmi:type="notation:RelativeBendpoints" xmi:id="_xdTrkheKEeapnMylBysFZA" points="[10, 4, -84, -40]$[92, 44, -2, 0]"/>
<sourceAnchor xmi:type="notation:IdentityAnchor" xmi:id="_xd8kwBeKEeapnMylBysFZA" id="(1.0,0.6595744680851063)"/>
<targetAnchor xmi:type="notation:IdentityAnchor" xmi:id="_xd8kwReKEeapnMylBysFZA" id="(0.0,0.4)"/>
</edges>
<edges xmi:type="notation:Connector" xmi:id="_5HX0QBeKEeapnMylBysFZA" type="7000" source="_u6nsYBeKEeapnMylBysFZA" target="_1c_0QBeKEeapnMylBysFZA">
<children xmi:type="notation:DecorationNode" xmi:id="_5HYbUBeKEeapnMylBysFZA" type="7001">
<layoutConstraint xmi:type="notation:Location" xmi:id="_5HYbUReKEeapnMylBysFZA"/>
</children>
<children xmi:type="notation:DecorationNode" xmi:id="_5HYbUheKEeapnMylBysFZA" type="7002">
<layoutConstraint xmi:type="notation:Location" xmi:id="_5HYbUxeKEeapnMylBysFZA"/>
</children>
<children xmi:type="notation:DecorationNode" xmi:id="_5HYbVBeKEeapnMylBysFZA" type="7003">
<layoutConstraint xmi:type="notation:Location" xmi:id="_5HYbVReKEeapnMylBysFZA" y="60"/>
</children>
<styles xmi:type="notation:FontStyle" xmi:id="_5HX0QReKEeapnMylBysFZA"/>
<element xmi:type="uml:Transition" href="choice-exit.uml#_5HE5UBeKEeapnMylBysFZA"/>
<bendpoints xmi:type="notation:RelativeBendpoints" xmi:id="_5HX0QheKEeapnMylBysFZA" points="[-4, 8, 26, -62]$[-27, 72, 3, 2]"/>
<sourceAnchor xmi:type="notation:IdentityAnchor" xmi:id="_5IHbIBeKEeapnMylBysFZA" id="(0.48333333333333334,1.0)"/>
<targetAnchor xmi:type="notation:IdentityAnchor" xmi:id="_5IICMBeKEeapnMylBysFZA" id="(0.32098765432098764,0.0)"/>
</edges>
</notation:Diagram>

View File

@@ -24,9 +24,15 @@
</specification>
</ownedRule>
</transition>
<transition xmi:type="uml:Transition" xmi:id="_xdFCEBeKEeapnMylBysFZA" source="_Qa6MBAZPEeayEI1yTJhWhg" target="_u6UxcBeKEeapnMylBysFZA">
<trigger xmi:type="uml:Trigger" xmi:id="_zb2b4BeKEeapnMylBysFZA" event="_rp2fMBeKEeapnMylBysFZA"/>
</transition>
<transition xmi:type="uml:Transition" xmi:id="_5HE5UBeKEeapnMylBysFZA" source="_u6UxcBeKEeapnMylBysFZA" target="_1cuugBeKEeapnMylBysFZA"/>
<subvertex xmi:type="uml:State" xmi:id="_Qa6MBAZPEeayEI1yTJhWhg" name="START"/>
<subvertex xmi:type="uml:Pseudostate" xmi:id="_YddGIAZPEeayEI1yTJhWhg" name="CHOICE" kind="choice"/>
<subvertex xmi:type="uml:Pseudostate" xmi:id="_YddGIAZPEeayEI1yTJhWhg" name="CHOICE1" kind="choice"/>
<subvertex xmi:type="uml:Pseudostate" xmi:id="_l37HMAZPEeayEI1yTJhWhg" name=""/>
<subvertex xmi:type="uml:Pseudostate" xmi:id="_u6UxcBeKEeapnMylBysFZA" name="CHOICE2" kind="choice"/>
<subvertex xmi:type="uml:State" xmi:id="_1cuugBeKEeapnMylBysFZA" name="S1"/>
</region>
</subvertex>
<subvertex xmi:type="uml:Pseudostate" xmi:id="_houi4AZPEeayEI1yTJhWhg"/>
@@ -36,4 +42,6 @@
<packagedElement xmi:type="uml:Signal" xmi:id="_6njX8AZPEeayEI1yTJhWhg" name="E2"/>
<packagedElement xmi:type="uml:SignalEvent" xmi:id="_8Ob2MAZPEeayEI1yTJhWhg" name="SignalEventE1" signal="_5WbHQAZPEeayEI1yTJhWhg"/>
<packagedElement xmi:type="uml:SignalEvent" xmi:id="_-aPmcAZPEeayEI1yTJhWhg" name="SignalEventE2" signal="_6njX8AZPEeayEI1yTJhWhg"/>
<packagedElement xmi:type="uml:Signal" xmi:id="_p7Bl4BeKEeapnMylBysFZA" name="E3"/>
<packagedElement xmi:type="uml:SignalEvent" xmi:id="_rp2fMBeKEeapnMylBysFZA" name="SignalEventE3" signal="_p7Bl4BeKEeapnMylBysFZA"/>
</uml:Model>

View File

@@ -740,7 +740,9 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
setCurrentState(ss, message, transition, false, stateMachine, null, fps.getForks());
}
} else {
setCurrentState(toState, message, transition, true, stateMachine);
Collection<State<S, E>> 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<S, E> extends StateMachineObjectSuppo
if (states.contains(state)) {
if (exit) {
exitCurrentState(state, message, transition, stateMachine);
exitCurrentState(state, message, transition, stateMachine, sources, targets);
}
State<S, E> notifyFrom = currentState;
currentState = state;
@@ -868,7 +870,7 @@ public abstract class AbstractStateMachine<S, E> 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<S, E> notifyFrom = currentState;
currentState = findDeep;
@@ -882,7 +884,7 @@ public abstract class AbstractStateMachine<S, E> 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<S, E> extends StateMachineObjectSuppo
}
void exitCurrentState(State<S, E> state, Message<E> message, Transition<S, E> transition, StateMachine<S, E> stateMachine) {
exitCurrentState(state, message, transition, stateMachine, null, null);
}
void exitCurrentState(State<S, E> state, Message<E> message, Transition<S, E> transition, StateMachine<S, E> stateMachine,
Collection<State<S, E>> sources, Collection<State<S, E>> targets) {
if (currentState == null) {
return;
}
if (currentState.isSubmachineState()) {
StateMachine<S, E> submachine = ((AbstractState<S, E>)currentState).getSubmachine();
((AbstractStateMachine<S, E>)submachine).exitCurrentState(state, message, transition, stateMachine);
exitFromState(currentState, message, transition, stateMachine);
exitFromState(currentState, message, transition, stateMachine, sources, targets);
} else if (currentState.isOrthogonal()) {
Collection<Region<S,E>> regions = ((AbstractState<S, E>)currentState).getRegions();
for (Region<S,E> 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<S, E> state, Message<E> message, Transition<S, E> transition,
StateMachine<S, E> stateMachine) {
exitFromState(state, message, transition, stateMachine, null, null);
}
private void exitFromState(State<S, E> state, Message<E> message, Transition<S, E> transition,
StateMachine<S, E> stateMachine, Collection<State<S, E>> sources, Collection<State<S, E>> targets) {
if (state == null) {
return;
}
@@ -1003,6 +1015,9 @@ public abstract class AbstractStateMachine<S, E> 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<S, E> extends StateMachineObjectSuppo
notifyStateExited(buildStateContext(Stage.STATE_EXIT, message, null, getRelayStateMachine(), state, null));
}
private boolean isPseudoStateSubstate(State<S, E> left, Collection<State<S, E>> rights) {
if (rights == null || left == null) {
return false;
}
for (State<S, E> s : rights) {
if (StateMachineUtils.isSubstate(left, s)) {
return true;
}
}
return false;
}
private void entryToState(State<S, E> state, Message<E> message, Transition<S, E> transition, StateMachine<S, E> stateMachine) {
entryToState(state, message, transition, stateMachine, null, null);
}