From 9c4524e1cb93f2735c0127bfd4f6068993afa713 Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Wed, 4 May 2016 07:33:25 +0100 Subject: [PATCH] Complex linked pseudostates cause trouble - Fix complex linked state handling. - Fixes #217 --- .../buildtests/LinkedPseudoStatesTests.java | 116 +++ .../buildtests/linked-pseudostates.di | 2 + .../buildtests/linked-pseudostates.notation | 688 ++++++++++++++++++ .../buildtests/linked-pseudostates.uml | 82 +++ .../support/AbstractStateMachine.java | 74 +- 5 files changed, 915 insertions(+), 47 deletions(-) create mode 100644 spring-statemachine-build-tests/src/test/java/org/springframework/statemachine/buildtests/LinkedPseudoStatesTests.java create mode 100644 spring-statemachine-build-tests/src/test/resources/org/springframework/statemachine/buildtests/linked-pseudostates.di create mode 100644 spring-statemachine-build-tests/src/test/resources/org/springframework/statemachine/buildtests/linked-pseudostates.notation create mode 100644 spring-statemachine-build-tests/src/test/resources/org/springframework/statemachine/buildtests/linked-pseudostates.uml diff --git a/spring-statemachine-build-tests/src/test/java/org/springframework/statemachine/buildtests/LinkedPseudoStatesTests.java b/spring-statemachine-build-tests/src/test/java/org/springframework/statemachine/buildtests/LinkedPseudoStatesTests.java new file mode 100644 index 00000000..42a9f280 --- /dev/null +++ b/spring-statemachine-build-tests/src/test/java/org/springframework/statemachine/buildtests/LinkedPseudoStatesTests.java @@ -0,0 +1,116 @@ +/* + * Copyright 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.statemachine.buildtests; + +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.StateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineModelConfigurer; +import org.springframework.statemachine.config.model.StateMachineModelFactory; +import org.springframework.statemachine.test.StateMachineTestPlan; +import org.springframework.statemachine.test.StateMachineTestPlanBuilder; +import org.springframework.statemachine.uml.UmlStateMachineModelFactory; + +@SuppressWarnings("unchecked") +public class LinkedPseudoStatesTests extends AbstractBuildTests { + + @Test + public void testTwoChoices() throws Exception { + context.register(Config1.class); + context.refresh(); + StateMachine stateMachine = context.getBean(StateMachine.class); + + StateMachineTestPlan plan = + StateMachineTestPlanBuilder.builder() + .stateMachine(stateMachine) + .step().expectState("S1").and() + .step().sendEvent("E1").expectStates("S2").and() + .build(); + plan.test(); + } + + @Test + public void testForkViaChoice() throws Exception { + context.register(Config1.class); + context.refresh(); + StateMachine stateMachine = context.getBean(StateMachine.class); + + StateMachineTestPlan plan = + StateMachineTestPlanBuilder.builder() + .stateMachine(stateMachine) + .step().expectState("S1").and() + .step().sendEvent("E2").expectStates("S3", "S31", "S32").and() + .build(); + plan.test(); + } + + @Test + public void testJunctionChoiceTwice() throws Exception { + context.register(Config1.class); + context.refresh(); + StateMachine stateMachine = context.getBean(StateMachine.class); + + StateMachineTestPlan plan = + StateMachineTestPlanBuilder.builder() + .stateMachine(stateMachine) + .step().expectState("S1").and() + .step().sendEvent("E3").expectStates("S4").and() + .build(); + plan.test(); + } + + @Test + public void testEntryJunctionExitJunction() throws Exception { + context.register(Config1.class); + context.refresh(); + StateMachine stateMachine = context.getBean(StateMachine.class); + + StateMachineTestPlan plan = + StateMachineTestPlanBuilder.builder() + .stateMachine(stateMachine) + .step().expectState("S1").and() + .step().sendEvent("E4").expectStates("S5", "S52").and() + .step().sendEvent("E5").expectStates("S4").and() + .build(); + plan.test(); + } + + @Configuration + @EnableStateMachine + public static class Config1 extends StateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineModelConfigurer model) throws Exception { + model + .withModel() + .factory(modelFactory()); + } + + @Bean + public StateMachineModelFactory modelFactory() { + return new UmlStateMachineModelFactory("classpath:org/springframework/statemachine/buildtests/linked-pseudostates.uml"); + } + } + + @Override + protected AnnotationConfigApplicationContext buildContext() { + return new AnnotationConfigApplicationContext(); + } +} diff --git a/spring-statemachine-build-tests/src/test/resources/org/springframework/statemachine/buildtests/linked-pseudostates.di b/spring-statemachine-build-tests/src/test/resources/org/springframework/statemachine/buildtests/linked-pseudostates.di new file mode 100644 index 00000000..bf9abab3 --- /dev/null +++ b/spring-statemachine-build-tests/src/test/resources/org/springframework/statemachine/buildtests/linked-pseudostates.di @@ -0,0 +1,2 @@ + + diff --git a/spring-statemachine-build-tests/src/test/resources/org/springframework/statemachine/buildtests/linked-pseudostates.notation b/spring-statemachine-build-tests/src/test/resources/org/springframework/statemachine/buildtests/linked-pseudostates.notation new file mode 100644 index 00000000..e212c6dc --- /dev/null +++ b/spring-statemachine-build-tests/src/test/resources/org/springframework/statemachine/buildtests/linked-pseudostates.notation @@ -0,0 +1,688 @@ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-statemachine-build-tests/src/test/resources/org/springframework/statemachine/buildtests/linked-pseudostates.uml b/spring-statemachine-build-tests/src/test/resources/org/springframework/statemachine/buildtests/linked-pseudostates.uml new file mode 100644 index 00000000..b908463c --- /dev/null +++ b/spring-statemachine-build-tests/src/test/resources/org/springframework/statemachine/buildtests/linked-pseudostates.uml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 ab995be0..5a4f1991 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 @@ -721,60 +721,24 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo if (!isInitialTransition(transition) && !callPreStateChangeInterceptors(state, message, transition, stateMachine)) { return; } - // TODO: need to make below more clear when - // we figure out rest of a pseudostates + + StateContext stateContext = buildStateContext(Stage.STATE_CHANGED, message, transition, stateMachine); + State toState = followLinkedPseudoStates(state, stateContext); PseudoStateKind kind = state.getPseudoState() != null ? state.getPseudoState().getKind() : null; - if (kind == PseudoStateKind.CHOICE || kind == PseudoStateKind.JUNCTION || kind == PseudoStateKind.HISTORY_SHALLOW - || kind == PseudoStateKind.HISTORY_DEEP) { - StateContext stateContext = buildStateContext(Stage.STATE_CHANGED, message, transition, stateMachine); - State toState = state.getPseudoState().entry(stateContext); + if (kind != null && (kind != PseudoStateKind.INITIAL && kind != PseudoStateKind.JOIN && kind != PseudoStateKind.FORK)) { + callPreStateChangeInterceptors(toState, message, transition, stateMachine); + } - // TODO: should do recursive linked pseudostates centrally - if (kind == PseudoStateKind.CHOICE) { - while (toState != null && toState.getPseudoState() != null - && toState.getPseudoState().getKind() != PseudoStateKind.INITIAL) { - toState = toState.getPseudoState().entry(stateContext); - } - } - if (kind == PseudoStateKind.CHOICE) { - callPreStateChangeInterceptors(toState, message, transition, stateMachine); - } - - if (kind == PseudoStateKind.JUNCTION) { - while (toState != null && toState.getPseudoState() != null - && toState.getPseudoState().getKind() != PseudoStateKind.INITIAL) { - toState = toState.getPseudoState().entry(stateContext); - } - } - if (kind == PseudoStateKind.JUNCTION) { - callPreStateChangeInterceptors(toState, message, transition, stateMachine); - } - - setCurrentState(toState, message, transition, true, stateMachine); - } else if (kind == PseudoStateKind.ENTRY) { - StateContext stateContext = buildStateContext(Stage.STATE_CHANGED, message, transition, stateMachine); - State toState = state.getPseudoState().entry(stateContext); - while (toState != null && toState.getPseudoState() != null - && toState.getPseudoState().getKind() != PseudoStateKind.INITIAL) { - toState = toState.getPseudoState().entry(stateContext); - } - setCurrentState(toState, message, transition, true, stateMachine); - } else if (kind == PseudoStateKind.EXIT) { - StateContext stateContext = buildStateContext(Stage.STATE_CHANGED, message, transition, stateMachine); - State toState = state.getPseudoState().entry(stateContext); - while (toState != null && toState.getPseudoState() != null - && toState.getPseudoState().getKind() != PseudoStateKind.INITIAL) { - toState = toState.getPseudoState().entry(stateContext); - } - setCurrentState(toState, message, transition, true, stateMachine); - } else if (kind == PseudoStateKind.FORK) { - ForkPseudoState fps = (ForkPseudoState) state.getPseudoState(); + // need to check for from original state passed in + kind = toState.getPseudoState() != null ? toState.getPseudoState().getKind() : null; + if (kind == PseudoStateKind.FORK) { + ForkPseudoState fps = (ForkPseudoState) toState.getPseudoState(); for (State ss : fps.getForks()) { callPreStateChangeInterceptors(ss, message, transition, stateMachine); setCurrentState(ss, message, transition, false, stateMachine); } } else { - setCurrentState(state, message, transition, true, stateMachine); + setCurrentState(toState, message, transition, true, stateMachine); } callPostStateChangeInterceptors(state, message, transition, stateMachine); @@ -785,6 +749,22 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo } } + private State followLinkedPseudoStates(State state, StateContext stateContext) { + PseudoStateKind kind = state.getPseudoState() != null ? state.getPseudoState().getKind() : null; + if (kind == PseudoStateKind.INITIAL || kind == PseudoStateKind.JOIN || kind == PseudoStateKind.FORK) { + return state; + } else if (kind != null) { + State toState = state.getPseudoState().entry(stateContext); + if (toState == null) { + return state; + } else { + return followLinkedPseudoStates(toState, stateContext); + } + } else { + return state; + } + } + private void registerPseudoStateListener() { for (State state : states) { PseudoState p = state.getPseudoState();