From 8e42ca0688e5fe517c390ebf7021b9a6460f1737 Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Wed, 6 May 2015 10:59:06 +0100 Subject: [PATCH] Should not define initial state as state - Tweak DefaultStateConfigurer so that we don't need to define initial state as additional state. Also fixed some other issue which caused a bit of a mess when i.e multiple states were introduces twice. - Fixes #29 --- .../statemachine/config/StateData.java | 12 ++ .../configurers/DefaultStateConfigurer.java | 28 +++- .../DefaultStateConfigurerTests.java | 138 ++++++++++++++++++ .../statemachine/state/InitialStateTests.java | 37 +++++ 4 files changed, 212 insertions(+), 3 deletions(-) create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/config/configurers/DefaultStateConfigurerTests.java diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateData.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateData.java index 26a069f7..74162987 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateData.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/StateData.java @@ -61,14 +61,26 @@ public class StateData { public Collection getDeferred() { return deferred; } + + public void setDeferred(Collection deferred) { + this.deferred = deferred; + } public Collection> getEntryActions() { return entryActions; } + + public void setEntryActions(Collection> entryActions) { + this.entryActions = entryActions; + } public Collection> getExitActions() { return exitActions; } + + public void setExitActions(Collection> exitActions) { + this.exitActions = exitActions; + } public Object getParent() { return parent; diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultStateConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultStateConfigurer.java index cccd992f..033e4d9a 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultStateConfigurer.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultStateConfigurer.java @@ -18,6 +18,8 @@ package org.springframework.statemachine.config.configurers; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; +import java.util.Map; import java.util.Set; import java.util.UUID; @@ -45,7 +47,7 @@ public class DefaultStateConfigurer private final Object region = UUID.randomUUID().toString(); - private final Collection> incomplete = new ArrayList>(); + private final Map> incomplete = new HashMap>(); private S initialState; @@ -68,7 +70,7 @@ public class DefaultStateConfigurer // before passing state datas to builder, update structure // for missing parent, initial and end state infos. Collection> stateDatas = new ArrayList>(); - for (StateData s : incomplete) { + for (StateData s : incomplete.values()) { s.setParent(parent); stateDatas.add(s); if (s.getState() == initialState) { @@ -99,6 +101,7 @@ public class DefaultStateConfigurer @Override public StateConfigurer initial(S initial) { this.initialState = initial; + state(initial); return this; } @@ -195,7 +198,26 @@ public class DefaultStateConfigurer private void addIncomplete(Object parent, S state, Collection deferred, Collection> entryActions, Collection> exitActions) { - incomplete.add(new StateData(parent, region, state, deferred, entryActions, exitActions)); + StateData stateData = incomplete.get(state); + if (stateData == null) { + stateData = new StateData(parent, region, state, deferred, entryActions, exitActions); + incomplete.put(state, stateData); + } + if (stateData.getParent() == null) { + stateData.setParent(parent); + } + if (stateData.getRegion() == null) { + stateData.setRegion(region); + } + if (stateData.getDeferred() == null) { + stateData.setDeferred(deferred); + } + if (stateData.getEntryActions() == null) { + stateData.setEntryActions(entryActions); + } + if (stateData.getExitActions() == null) { + stateData.setExitActions(exitActions); + } } } diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/configurers/DefaultStateConfigurerTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/configurers/DefaultStateConfigurerTests.java new file mode 100644 index 00000000..f0677783 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/configurers/DefaultStateConfigurerTests.java @@ -0,0 +1,138 @@ +/* + * Copyright 2015 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.config.configurers; + +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + +import java.util.Arrays; +import java.util.Collection; + +import org.junit.Test; +import org.springframework.statemachine.AbstractStateMachineTests.TestEntryAction; +import org.springframework.statemachine.AbstractStateMachineTests.TestEvents; +import org.springframework.statemachine.AbstractStateMachineTests.TestExitAction; +import org.springframework.statemachine.AbstractStateMachineTests.TestStates; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.config.StateData; +import org.springframework.statemachine.config.builders.StateMachineStateBuilder; + +public class DefaultStateConfigurerTests { + + @Test + public void testInitialWithoutState() throws Exception { + DefaultStateConfigurer configurer = new DefaultStateConfigurer(); + TestStateMachineStateBuilder builder = new TestStateMachineStateBuilder(); + configurer.initial(TestStates.SI); + configurer.configure(builder); + assertThat(builder.data, notNullValue()); + assertThat(builder.data.size(), is(1)); + assertThat(builder.data.iterator().next().getState(), is(TestStates.SI)); + } + + @Test + public void testInitialWithState() throws Exception { + DefaultStateConfigurer configurer = new DefaultStateConfigurer(); + TestStateMachineStateBuilder builder = new TestStateMachineStateBuilder(); + configurer.initial(TestStates.SI); + configurer.state(TestStates.SI); + configurer.configure(builder); + assertThat(builder.data, notNullValue()); + assertThat(builder.data.size(), is(1)); + assertThat(builder.data.iterator().next().getState(), is(TestStates.SI)); + } + + @Test + public void testSameStateShouldResultOneState() throws Exception { + DefaultStateConfigurer configurer = new DefaultStateConfigurer(); + TestStateMachineStateBuilder builder = new TestStateMachineStateBuilder(); + configurer.state(TestStates.SI); + configurer.state(TestStates.SI); + configurer.configure(builder); + assertThat(builder.data, notNullValue()); + assertThat(builder.data.size(), is(1)); + assertThat(builder.data.iterator().next().getState(), is(TestStates.SI)); + } + + @Test + public void testParentSet() throws Exception { + DefaultStateConfigurer configurer = new DefaultStateConfigurer(); + TestStateMachineStateBuilder builder = new TestStateMachineStateBuilder(); + configurer.parent(TestStates.SI); + configurer.state(TestStates.S1); + configurer.configure(builder); + assertThat(builder.data, notNullValue()); + assertThat(builder.data.size(), is(1)); + assertThat(builder.data.iterator().next().getState(), is(TestStates.S1)); + assertThat((TestStates)builder.data.iterator().next().getParent(), is(TestStates.SI)); + } + + @Test + public void testActionsInitialFirst() throws Exception { + @SuppressWarnings("unchecked") + Collection> exitActions = Arrays.asList(testExitAction()); + + DefaultStateConfigurer configurer = new DefaultStateConfigurer(); + TestStateMachineStateBuilder builder = new TestStateMachineStateBuilder(); + configurer.initial(TestStates.S1); + configurer.state(TestStates.S1, null, exitActions); + configurer.configure(builder); + assertThat(builder.data, notNullValue()); + assertThat(builder.data.size(), is(1)); + + assertThat(builder.data.iterator().next().getState(), is(TestStates.S1)); + assertThat(builder.data.iterator().next().getEntryActions(), nullValue()); + assertThat(builder.data.iterator().next().getExitActions(), notNullValue()); + } + + @Test + public void testActionsJustState() throws Exception { + @SuppressWarnings("unchecked") + Collection> entryActions = Arrays.asList(testEntryAction()); + + DefaultStateConfigurer configurer = new DefaultStateConfigurer(); + TestStateMachineStateBuilder builder = new TestStateMachineStateBuilder(); + configurer.state(TestStates.S2, entryActions, null); + configurer.configure(builder); + assertThat(builder.data, notNullValue()); + assertThat(builder.data.size(), is(1)); + + assertThat(builder.data.iterator().next().getState(), is(TestStates.S2)); + assertThat(builder.data.iterator().next().getExitActions(), nullValue()); + assertThat(builder.data.iterator().next().getEntryActions(), notNullValue()); + } + + private static class TestStateMachineStateBuilder extends StateMachineStateBuilder { + + Collection> data; + + @Override + public void addStateData(Collection> stateDatas) { + this.data = stateDatas; + } + } + + private Action testEntryAction() { + return new TestEntryAction(); + } + + private Action testExitAction() { + return new TestExitAction(); + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/InitialStateTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/InitialStateTests.java index 4ca751f0..694e782a 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/InitialStateTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/InitialStateTests.java @@ -58,6 +58,20 @@ public class InitialStateTests extends AbstractStateMachineTests { context.refresh(); } + @SuppressWarnings({ "unchecked" }) + @Test + public void testInitialNoNeedAsState() throws Exception { + context.register(BaseConfig.class, Config3.class); + context.refresh(); + assertTrue(context.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE)); + EnumStateMachine machine = + context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); + machine.start(); + assertThat(machine.getState().getIds(), contains(TestStates.SI)); + machine.sendEvent(TestEvents.E1); + assertThat(machine.getState().getIds(), contains(TestStates.S1)); + } + @Configuration @EnableStateMachine public static class Config1 extends EnumStateMachineConfigurerAdapter { @@ -111,6 +125,29 @@ public class InitialStateTests extends AbstractStateMachineTests { } + @Configuration + @EnableStateMachine + public static class Config3 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.SI) + .state(TestStates.S1); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.SI) + .target(TestStates.S1) + .event(TestEvents.E1); + } + + } + @Override protected AnnotationConfigApplicationContext buildContext() { return new AnnotationConfigApplicationContext();