From 49b0d1b8c1c80877f8623744d183b5e408c61b71 Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Sun, 8 Feb 2015 08:32:31 +0000 Subject: [PATCH] Add base support for end/final state - resolves #9 --- .../statemachine/EnumStateMachine.java | 4 +- .../config/EnumStateMachineFactory.java | 2 +- .../builders/StateMachineStateBuilder.java | 7 +- .../config/builders/StateMachineStates.java | 9 +- .../configurers/DefaultStateConfigurer.java | 9 ++ .../config/configurers/StateConfigurer.java | 2 + .../statemachine/region/Region.java | 7 ++ .../support/AbstractStateMachine.java | 29 ++++- .../AbstractStateMachineTests.java | 4 +- .../statemachine/EnumStateMachineTests.java | 6 +- .../statemachine/StateMachineTests.java | 2 +- .../config/ConfigurationTests.java | 30 +++++ .../statemachine/state/EndStateTests.java | 117 ++++++++++++++++++ .../statemachine/state/RegionStateTests.java | 2 +- .../state/SubmachineStateTests.java | 2 +- 15 files changed, 217 insertions(+), 15 deletions(-) create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/state/EndStateTests.java diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/EnumStateMachine.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/EnumStateMachine.java index df1b1aa6..11aa8b19 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/EnumStateMachine.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/EnumStateMachine.java @@ -40,8 +40,8 @@ public class EnumStateMachine, E extends Enum> extends Abst * @param initialState the initial state */ public EnumStateMachine(Collection> states, Collection> transitions, - State initialState) { - super(states, transitions, initialState); + State initialState, State endState) { + super(states, transitions, initialState, endState); } } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineFactory.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineFactory.java index 456f5d81..001b9f8a 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineFactory.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineFactory.java @@ -99,7 +99,7 @@ public class EnumStateMachineFactory, E extends Enum> exten } EnumStateMachine machine = new EnumStateMachine(stateMap.values(), transitions, - stateMap.get(stateMachineStates.getInitialState())); + stateMap.get(stateMachineStates.getInitialState()), stateMap.get(stateMachineStates.getEndState())); machine.afterPropertiesSet(); if (getBeanFactory() != null) { machine.setBeanFactory(getBeanFactory()); diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineStateBuilder.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineStateBuilder.java index fcb7c48b..9d9786e5 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineStateBuilder.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineStateBuilder.java @@ -30,6 +30,7 @@ public class StateMachineStateBuilder private Collection> states = new ArrayList>(); private S initialState; + private S endState; public StateMachineStateBuilder() { super(); @@ -46,7 +47,7 @@ public class StateMachineStateBuilder @Override protected StateMachineStates performBuild() throws Exception { - StateMachineStates bean = new StateMachineStates(initialState, states); + StateMachineStates bean = new StateMachineStates(initialState, endState, states); return bean; } @@ -62,5 +63,9 @@ public class StateMachineStateBuilder public void setInitialState(S state) { this.initialState = state; } + + public void setEndState(S endState) { + this.endState = endState; + } } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineStates.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineStates.java index aff6a033..39633e80 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineStates.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineStates.java @@ -27,9 +27,12 @@ public class StateMachineStates { private final S initialState; - public StateMachineStates(S initialState, Collection> states) { + private final S endState; + + public StateMachineStates(S initialState, S endState, Collection> states) { this.states = states; this.initialState = initialState; + this.endState = endState; } public Collection> getStates() { @@ -40,6 +43,10 @@ public class StateMachineStates { return initialState; } + public S getEndState() { + return endState; + } + public static class StateData { private S state; private Collection deferred; 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 a8179e97..aaf97fac 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 @@ -34,10 +34,13 @@ public class DefaultStateConfigurer private S initial; + private S end; + @Override public void configure(StateMachineStateBuilder builder) throws Exception { builder.add(states); builder.setInitialState(initial); + builder.setEndState(end); } @Override @@ -45,6 +48,12 @@ public class DefaultStateConfigurer this.initial = initial; return this; } + + @Override + public StateConfigurer end(S end) { + this.end = end; + return this; + } @Override public StateConfigurer state(S state) { diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/StateConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/StateConfigurer.java index 366ed70f..c2aa3856 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/StateConfigurer.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/StateConfigurer.java @@ -35,4 +35,6 @@ public interface StateConfigurer extends StateConfigurer states(Set states); + StateConfigurer end(S end); + } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/region/Region.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/region/Region.java index c39fa47d..2ac62179 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/region/Region.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/region/Region.java @@ -53,4 +53,11 @@ public interface Region { */ Collection> getTransitions(); + /** + * Checks if region complete. Region is considered to be completed if it has + * reached its end state and no further event processing is happening. + * + * @return true, if complete + */ + boolean isComplete(); } 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 0e11eef8..3192a50a 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 @@ -71,6 +71,8 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport private final State initialState; + private final State endState; + private final Message initialEvent; private final ExtendedState extendedState; @@ -97,6 +99,19 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport this(states, transitions, initialState, new DefaultExtendedState()); } + /** + * Instantiates a new abstract state machine. + * + * @param states the states + * @param transitions the transitions + * @param initialState the initial state + * @param endState the end state + */ + public AbstractStateMachine(Collection> states, Collection> transitions, + State initialState, State endState) { + this(states, transitions, initialState, endState, null, null); + } + /** * Instantiates a new abstract state machine. * @@ -107,7 +122,7 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport */ public AbstractStateMachine(Collection> states, Collection> transitions, State initialState, ExtendedState extendedState) { - this(states, transitions, initialState, null, extendedState); + this(states, transitions, initialState, null, null, extendedState); } /** @@ -116,15 +131,17 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport * @param states the states of this machine * @param transitions the transitions of this machine * @param initialState the initial state of this machine + * @param endState the final state of this machine * @param initialEvent the initial event of this machine * @param extendedState the extended state of this machine */ public AbstractStateMachine(Collection> states, Collection> transitions, - State initialState, Message initialEvent, ExtendedState extendedState) { + State initialState, State endState, Message initialEvent, ExtendedState extendedState) { super(); this.states = states; this.transitions = transitions; this.initialState = initialState; + this.endState = endState; this.initialEvent = initialEvent; this.extendedState = extendedState; } @@ -141,6 +158,9 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport @Override public void sendEvent(Message event) { + if (isComplete()) { + return; + } // TODO: machine header looks weird! event = MessageBuilder.fromMessage(event).setHeader("machine", this).build(); if (log.isDebugEnabled()) { @@ -174,6 +194,11 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport public void addStateListener(StateMachineListener, E> listener) { stateListener.register(listener); } + + @Override + public boolean isComplete() { + return (endState != null && endState.equals(currentState)); + } /** * Gets the {@link State}s defined in this machine. Returned collection is diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/AbstractStateMachineTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/AbstractStateMachineTests.java index c44ca1bb..cbf6183d 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/AbstractStateMachineTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/AbstractStateMachineTests.java @@ -55,7 +55,7 @@ public abstract class AbstractStateMachineTests { } public enum TestStates { - SI,S1,S2,S3,S4 + SI,S1,S2,S3,S4,SF } public enum TestSubStates { @@ -63,7 +63,7 @@ public abstract class AbstractStateMachineTests { } public enum TestEvents { - E1,E2,E3,E4 + E1,E2,E3,E4,EF } public enum TestSubEvents { diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/EnumStateMachineTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/EnumStateMachineTests.java index ea352a91..1b00d57b 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/EnumStateMachineTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/EnumStateMachineTests.java @@ -73,7 +73,7 @@ public class EnumStateMachineTests extends AbstractStateMachineTests { transitions.add(transitionFromS2ToS3); SyncTaskExecutor taskExecutor = new SyncTaskExecutor(); - EnumStateMachine machine = new EnumStateMachine(states, transitions, stateSI); + EnumStateMachine machine = new EnumStateMachine(states, transitions, stateSI, null); machine.setTaskExecutor(taskExecutor); machine.start(); @@ -144,7 +144,7 @@ public class EnumStateMachineTests extends AbstractStateMachineTests { // create machine SyncTaskExecutor taskExecutor = new SyncTaskExecutor(); - EnumStateMachine machine = new EnumStateMachine(states, transitions, stateSI); + EnumStateMachine machine = new EnumStateMachine(states, transitions, stateSI, null); // StateMachine, TestEvents> machine2 = new EnumStateMachine(states, transitions, stateSI); machine.setTaskExecutor(taskExecutor); machine.start(); @@ -184,7 +184,7 @@ public class EnumStateMachineTests extends AbstractStateMachineTests { transitions.add(transitionInternalSI); SyncTaskExecutor taskExecutor = new SyncTaskExecutor(); - EnumStateMachine machine = new EnumStateMachine(states, transitions, stateSI); + EnumStateMachine machine = new EnumStateMachine(states, transitions, stateSI, null); machine.setTaskExecutor(taskExecutor); machine.start(); 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 775ebace..f111fa79 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 @@ -40,7 +40,7 @@ import org.springframework.statemachine.config.builders.StateMachineTransitionCo public class StateMachineTests extends AbstractStateMachineTests { @Test - public void test1() { + public void testLoggingEvents() { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class); assertTrue(ctx.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE)); @SuppressWarnings("unchecked") diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/ConfigurationTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/ConfigurationTests.java index 071811f8..6936a5ad 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/ConfigurationTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/ConfigurationTests.java @@ -30,6 +30,7 @@ import org.springframework.core.task.TaskExecutor; import org.springframework.statemachine.AbstractStateMachineTests; import org.springframework.statemachine.EnumStateMachine; import org.springframework.statemachine.StateMachineSystemConstants; +import org.springframework.statemachine.TestUtils; import org.springframework.statemachine.config.EnableStateMachine; import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; @@ -58,6 +59,20 @@ public class ConfigurationTests extends AbstractStateMachineTests { ctx.close(); } + + @SuppressWarnings({ "unchecked" }) + @Test + public void testEndState() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config3.class); + assertTrue(ctx.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE)); + EnumStateMachine machine = + ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); + assertThat(machine, notNullValue()); + Object endState = TestUtils.readField("endState", machine); + assertThat(endState, notNullValue()); + ctx.close(); + } + @Configuration @EnableStateMachine public static class Config1 extends EnumStateMachineConfigurerAdapter { @@ -114,5 +129,20 @@ public class ConfigurationTests extends AbstractStateMachineTests { } } + + @Configuration + @EnableStateMachine + public static class Config3 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.S1) + .end(TestStates.SF) + .states(EnumSet.allOf(TestStates.class)); + } + + } } diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/EndStateTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/EndStateTests.java new file mode 100644 index 00000000..687d0c49 --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/EndStateTests.java @@ -0,0 +1,117 @@ +/* + * 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.state; + +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.util.EnumSet; + +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.core.task.SyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; +import org.springframework.statemachine.AbstractStateMachineTests; +import org.springframework.statemachine.EnumStateMachine; +import org.springframework.statemachine.StateMachineSystemConstants; +import org.springframework.statemachine.config.EnableStateMachine; +import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; + +public class EndStateTests extends AbstractStateMachineTests { + + @Override + protected AnnotationConfigApplicationContext buildContext() { + return new AnnotationConfigApplicationContext(); + } + + @Test + public void testEndStateCompletes() { + context.register(Config1.class); + context.refresh(); + assertTrue(context.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE)); + @SuppressWarnings("unchecked") + EnumStateMachine machine = + context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); + assertThat(machine, notNullValue()); + assertThat(machine.isComplete(), is(false)); + machine.sendEvent(TestEvents.E1); + assertThat(machine.isComplete(), is(false)); + machine.sendEvent(TestEvents.E2); + assertThat(machine.isComplete(), is(false)); + machine.sendEvent(TestEvents.E3); + assertThat(machine.isComplete(), is(false)); + machine.sendEvent(TestEvents.E4); + assertThat(machine.isComplete(), is(false)); + machine.sendEvent(TestEvents.EF); + assertThat(machine.isComplete(), is(true)); + } + + @Configuration + @EnableStateMachine + static class Config1 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.SI) + .states(EnumSet.allOf(TestStates.class)) + .end(TestStates.SF); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.SI) + .target(TestStates.S1) + .event(TestEvents.E1) + .and() + .withExternal() + .source(TestStates.S1) + .target(TestStates.S2) + .event(TestEvents.E2) + .and() + .withExternal() + .source(TestStates.S2) + .target(TestStates.S3) + .event(TestEvents.E3) + .and() + .withExternal() + .source(TestStates.S3) + .target(TestStates.S4) + .event(TestEvents.E4) + .and() + .withExternal() + .source(TestStates.S4) + .target(TestStates.SF) + .event(TestEvents.EF); + } + + @Bean + public TaskExecutor taskExecutor() { + return new SyncTaskExecutor(); + } + + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/RegionStateTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/RegionStateTests.java index 46d259ee..86ba05ae 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/RegionStateTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/RegionStateTests.java @@ -68,7 +68,7 @@ public class RegionStateTests extends AbstractStateMachineTests { transitions.add(transitionFromS2ToS3); SyncTaskExecutor taskExecutor = new SyncTaskExecutor(); - EnumStateMachine machine = new EnumStateMachine(states, transitions, stateSI); + EnumStateMachine machine = new EnumStateMachine(states, transitions, stateSI, null); machine.setTaskExecutor(taskExecutor); machine.start(); diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/SubmachineStateTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/SubmachineStateTests.java index db2064be..13e97c98 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/SubmachineStateTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/SubmachineStateTests.java @@ -67,7 +67,7 @@ public class SubmachineStateTests extends AbstractStateMachineTests { transitions.add(transitionFromS2ToS3); SyncTaskExecutor taskExecutor = new SyncTaskExecutor(); - EnumStateMachine machine = new EnumStateMachine(states, transitions, stateSI); + EnumStateMachine machine = new EnumStateMachine(states, transitions, stateSI, null); machine.setTaskExecutor(taskExecutor); machine.start();