From b3e6a22dce9a3a4adfc474e3b9d9bc71b1e749fb Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Fri, 11 Nov 2016 21:55:44 +0000 Subject: [PATCH] Fix choice back to source - Previously if choice took transition back to its original source, this source wasn't entered. Adding this special case to get handled. - Fixes #228 --- .../support/AbstractStateMachine.java | 4 +- .../statemachine/StateMachineTests.java | 66 ++++++++++++++- .../statemachine/state/ChoiceStateTests.java | 83 ++++++++++++++++++- 3 files changed, 150 insertions(+), 3 deletions(-) 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 158da8b8..5ed5c3ef 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 @@ -1138,7 +1138,9 @@ public abstract class AbstractStateMachine extends StateMachineObjectSuppo } else if (!isSubOfSource && !isSubOfTarget && (transition.getSource() == currentState && StateMachineUtils.isSubstate(currentState, transition.getTarget()))) { } else if (!isSubOfSource && !isSubOfTarget) { - return; + if (!StateMachineUtils.isTransientPseudoState(transition.getTarget())) { + return; + } } } diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineTests.java index b3454943..e3630f7a 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-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. @@ -22,6 +22,9 @@ import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -175,6 +178,25 @@ public class StateMachineTests extends AbstractStateMachineTests { assertThat(machine.getState().getIds(), containsInAnyOrder("S1")); } + @Test + public void testBackToItself() { + context.register(BaseConfig.class, Config5.class); + context.refresh(); + @SuppressWarnings("unchecked") + StateMachine machine = + context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class); + assertThat(machine, notNullValue()); + TestStateEntryExitListener listener = new TestStateEntryExitListener(); + machine.addStateListener(listener); + machine.start(); + assertThat(machine.getState().getIds(), contains(TestStates.SI)); + listener.reset(); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build()); + assertThat(machine.getState().getIds(), contains(TestStates.SI)); + assertThat(listener.exited.size(), is(1)); + assertThat(listener.entered.size(), is(1)); + } + private static class LoggingAction implements Action { private static final Log log = LogFactory.getLog(StateMachineTests.LoggingAction.class); @@ -400,6 +422,28 @@ public class StateMachineTests extends AbstractStateMachineTests { } + @Configuration + @EnableStateMachine + static class Config5 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.SI) + .states(EnumSet.allOf(TestStates.class)); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.SI) + .target(TestStates.SI) + .event(TestEvents.E1); + } + } + private static class TestListener extends StateMachineListenerAdapter { volatile CountDownLatch stateChangedLatch = new CountDownLatch(1); @@ -458,4 +502,24 @@ public class StateMachineTests extends AbstractStateMachineTests { } + private static class TestStateEntryExitListener extends StateMachineListenerAdapter { + + List> entered = new ArrayList<>(); + List> exited = new ArrayList<>(); + + @Override + public void stateEntered(State state) { + entered.add(state); + } + + @Override + public void stateExited(State state) { + exited.add(state); + } + + public void reset() { + entered.clear(); + exited.clear(); + } + } } diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/ChoiceStateTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/ChoiceStateTests.java index 41e5e62a..0d4ded6a 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/ChoiceStateTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/ChoiceStateTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2015-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. @@ -17,9 +17,12 @@ package org.springframework.statemachine.state; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; +import java.util.ArrayList; import java.util.EnumSet; +import java.util.List; import org.junit.Test; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -35,6 +38,7 @@ import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; import org.springframework.statemachine.guard.Guard; +import org.springframework.statemachine.listener.StateMachineListenerAdapter; import org.springframework.util.ObjectUtils; public class ChoiceStateTests extends AbstractStateMachineTests { @@ -128,6 +132,30 @@ public class ChoiceStateTests extends AbstractStateMachineTests { assertThat(machine.getState().getIds(), contains(TestStates.S21)); } + @Test + @SuppressWarnings("unchecked") + public void testBackToItself() { + context.register(BaseConfig.class, Config4.class); + context.refresh(); + ObjectStateMachine machine = + context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class); + assertThat(machine, notNullValue()); + TestStateEntryExitListener listener = new TestStateEntryExitListener(); + machine.addStateListener(listener); + machine.start(); + assertThat(machine.getState().getIds(), contains(TestStates.SI)); + listener.reset(); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build()); + assertThat(machine.getState().getIds(), contains(TestStates.SI)); + assertThat(listener.exited.size(), is(1)); + assertThat(listener.entered.size(), is(1)); + listener.reset(); + machine.sendEvent(MessageBuilder.withPayload(TestEvents.E2).build()); + assertThat(machine.getState().getIds(), contains(TestStates.S4)); + assertThat(listener.exited.size(), is(1)); + assertThat(listener.entered.size(), is(1)); + } + @Configuration @EnableStateMachine static class Config1 extends EnumStateMachineConfigurerAdapter { @@ -264,6 +292,59 @@ public class ChoiceStateTests extends AbstractStateMachineTests { } } + @Configuration + @EnableStateMachine + static class Config4 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.SI) + .states(EnumSet.allOf(TestStates.class)) + .choice(TestStates.S2); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.SI) + .target(TestStates.S2) + .event(TestEvents.E1) + .and() + .withExternal() + .source(TestStates.SI) + .target(TestStates.S4) + .event(TestEvents.E2) + .and() + .withChoice() + .source(TestStates.S2) + .last(TestStates.SI); + } + } + + private static class TestStateEntryExitListener extends StateMachineListenerAdapter { + + List> entered = new ArrayList<>(); + List> exited = new ArrayList<>(); + + @Override + public void stateEntered(State state) { + entered.add(state); + } + + @Override + public void stateExited(State state) { + exited.add(state); + } + + public void reset() { + entered.clear(); + exited.clear(); + } + } + private static class ChoiceGuard implements Guard { private final String match;