Add base support for end/final state

- resolves #9
This commit is contained in:
Janne Valkealahti
2015-02-08 08:32:31 +00:00
parent 0bcb69d7fe
commit 49b0d1b8c1
15 changed files with 217 additions and 15 deletions

View File

@@ -40,8 +40,8 @@ public class EnumStateMachine<S extends Enum<S>, E extends Enum<E>> extends Abst
* @param initialState the initial state
*/
public EnumStateMachine(Collection<State<S, E>> states, Collection<Transition<S, E>> transitions,
State<S, E> initialState) {
super(states, transitions, initialState);
State<S, E> initialState, State<S, E> endState) {
super(states, transitions, initialState, endState);
}
}

View File

@@ -99,7 +99,7 @@ public class EnumStateMachineFactory<S extends Enum<S>, E extends Enum<E>> exten
}
EnumStateMachine<S, E> machine = new EnumStateMachine<S, E>(stateMap.values(), transitions,
stateMap.get(stateMachineStates.getInitialState()));
stateMap.get(stateMachineStates.getInitialState()), stateMap.get(stateMachineStates.getEndState()));
machine.afterPropertiesSet();
if (getBeanFactory() != null) {
machine.setBeanFactory(getBeanFactory());

View File

@@ -30,6 +30,7 @@ public class StateMachineStateBuilder<S, E>
private Collection<StateData<S, E>> states = new ArrayList<StateData<S, E>>();
private S initialState;
private S endState;
public StateMachineStateBuilder() {
super();
@@ -46,7 +47,7 @@ public class StateMachineStateBuilder<S, E>
@Override
protected StateMachineStates<S, E> performBuild() throws Exception {
StateMachineStates<S, E> bean = new StateMachineStates<S, E>(initialState, states);
StateMachineStates<S, E> bean = new StateMachineStates<S, E>(initialState, endState, states);
return bean;
}
@@ -62,5 +63,9 @@ public class StateMachineStateBuilder<S, E>
public void setInitialState(S state) {
this.initialState = state;
}
public void setEndState(S endState) {
this.endState = endState;
}
}

View File

@@ -27,9 +27,12 @@ public class StateMachineStates<S, E> {
private final S initialState;
public StateMachineStates(S initialState, Collection<StateData<S, E>> states) {
private final S endState;
public StateMachineStates(S initialState, S endState, Collection<StateData<S, E>> states) {
this.states = states;
this.initialState = initialState;
this.endState = endState;
}
public Collection<StateData<S, E>> getStates() {
@@ -40,6 +43,10 @@ public class StateMachineStates<S, E> {
return initialState;
}
public S getEndState() {
return endState;
}
public static class StateData<S, E> {
private S state;
private Collection<E> deferred;

View File

@@ -34,10 +34,13 @@ public class DefaultStateConfigurer<S, E>
private S initial;
private S end;
@Override
public void configure(StateMachineStateBuilder<S, E> builder) throws Exception {
builder.add(states);
builder.setInitialState(initial);
builder.setEndState(end);
}
@Override
@@ -45,6 +48,12 @@ public class DefaultStateConfigurer<S, E>
this.initial = initial;
return this;
}
@Override
public StateConfigurer<S, E> end(S end) {
this.end = end;
return this;
}
@Override
public StateConfigurer<S, E> state(S state) {

View File

@@ -35,4 +35,6 @@ public interface StateConfigurer<S, E> extends
StateConfigurer<S, E> states(Set<S> states);
StateConfigurer<S, E> end(S end);
}

View File

@@ -53,4 +53,11 @@ public interface Region<S, E> {
*/
Collection<Transition<S,E>> 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();
}

View File

@@ -71,6 +71,8 @@ public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport
private final State<S,E> initialState;
private final State<S,E> endState;
private final Message<E> initialEvent;
private final ExtendedState extendedState;
@@ -97,6 +99,19 @@ public abstract class AbstractStateMachine<S, E> 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<State<S, E>> states, Collection<Transition<S, E>> transitions,
State<S, E> initialState, State<S, E> endState) {
this(states, transitions, initialState, endState, null, null);
}
/**
* Instantiates a new abstract state machine.
*
@@ -107,7 +122,7 @@ public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport
*/
public AbstractStateMachine(Collection<State<S, E>> states, Collection<Transition<S, E>> transitions,
State<S, E> initialState, ExtendedState extendedState) {
this(states, transitions, initialState, null, extendedState);
this(states, transitions, initialState, null, null, extendedState);
}
/**
@@ -116,15 +131,17 @@ public abstract class AbstractStateMachine<S, E> 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<State<S, E>> states, Collection<Transition<S, E>> transitions,
State<S, E> initialState, Message<E> initialEvent, ExtendedState extendedState) {
State<S, E> initialState, State<S, E> endState, Message<E> 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<S, E> extends LifecycleObjectSupport
@Override
public void sendEvent(Message<E> 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<S, E> extends LifecycleObjectSupport
public void addStateListener(StateMachineListener<State<S, E>, 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

View File

@@ -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 {

View File

@@ -73,7 +73,7 @@ public class EnumStateMachineTests extends AbstractStateMachineTests {
transitions.add(transitionFromS2ToS3);
SyncTaskExecutor taskExecutor = new SyncTaskExecutor();
EnumStateMachine<TestStates, TestEvents> machine = new EnumStateMachine<TestStates, TestEvents>(states, transitions, stateSI);
EnumStateMachine<TestStates, TestEvents> machine = new EnumStateMachine<TestStates, TestEvents>(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<TestStates, TestEvents> machine = new EnumStateMachine<TestStates, TestEvents>(states, transitions, stateSI);
EnumStateMachine<TestStates, TestEvents> machine = new EnumStateMachine<TestStates, TestEvents>(states, transitions, stateSI, null);
// StateMachine<State<TestStates, TestEvents>, TestEvents> machine2 = new EnumStateMachine<TestStates, TestEvents>(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<TestStates, TestEvents> machine = new EnumStateMachine<TestStates, TestEvents>(states, transitions, stateSI);
EnumStateMachine<TestStates, TestEvents> machine = new EnumStateMachine<TestStates, TestEvents>(states, transitions, stateSI, null);
machine.setTaskExecutor(taskExecutor);
machine.start();

View File

@@ -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")

View File

@@ -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<TestStates,TestEvents> 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<TestStates, TestEvents> {
@@ -114,5 +129,20 @@ public class ConfigurationTests extends AbstractStateMachineTests {
}
}
@Configuration
@EnableStateMachine
public static class Config3 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
@Override
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
states
.withStates()
.initial(TestStates.S1)
.end(TestStates.SF)
.states(EnumSet.allOf(TestStates.class));
}
}
}

View File

@@ -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<TestStates,TestEvents> 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<TestStates, TestEvents> {
@Override
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
states
.withStates()
.initial(TestStates.SI)
.states(EnumSet.allOf(TestStates.class))
.end(TestStates.SF);
}
@Override
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> 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();
}
}
}

View File

@@ -68,7 +68,7 @@ public class RegionStateTests extends AbstractStateMachineTests {
transitions.add(transitionFromS2ToS3);
SyncTaskExecutor taskExecutor = new SyncTaskExecutor();
EnumStateMachine<TestStates, TestEvents> machine = new EnumStateMachine<TestStates, TestEvents>(states, transitions, stateSI);
EnumStateMachine<TestStates, TestEvents> machine = new EnumStateMachine<TestStates, TestEvents>(states, transitions, stateSI, null);
machine.setTaskExecutor(taskExecutor);
machine.start();

View File

@@ -67,7 +67,7 @@ public class SubmachineStateTests extends AbstractStateMachineTests {
transitions.add(transitionFromS2ToS3);
SyncTaskExecutor taskExecutor = new SyncTaskExecutor();
EnumStateMachine<TestStates, TestEvents> machine = new EnumStateMachine<TestStates, TestEvents>(states, transitions, stateSI);
EnumStateMachine<TestStates, TestEvents> machine = new EnumStateMachine<TestStates, TestEvents>(states, transitions, stateSI, null);
machine.setTaskExecutor(taskExecutor);
machine.start();