Support for UML Sub State Machines
- Enhance config model so that a state can be configured as a submachine or a factory. - With uml it's possible to put multiple machines into one xml(one needs to be a root machine) and in a same way, define state as a separate machine. - JavaConfig vs. uml config differs in a bit where latter is passing raw model data into pojo config and JavaConfig either full machine or machine factory. - Fixes #121
This commit is contained in:
@@ -430,6 +430,14 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
|
||||
|
||||
for (StateData<S, E> stateData : stateDatas) {
|
||||
StateMachine<S, E> stateMachine = machineMap.get(stateData.getState());
|
||||
if (stateMachine == null) {
|
||||
// get a submachine from state data if we didn't have
|
||||
// it already. stays null if we don't have one.
|
||||
stateMachine = stateData.getSubmachine();
|
||||
if (stateMachine == null && stateData.getSubmachineFactory() != null) {
|
||||
stateMachine = stateData.getSubmachineFactory().getStateMachine(machineId);
|
||||
}
|
||||
}
|
||||
state = stateMap.get(stateData.getState());
|
||||
if (state != null) {
|
||||
states.add(state);
|
||||
@@ -732,23 +740,24 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
|
||||
|
||||
private Iterator<Node<StateData<S, E>>> buildStateDataIterator() {
|
||||
Tree<StateData<S, E>> tree = new Tree<StateData<S, E>>();
|
||||
|
||||
for (StateData<S, E> stateData : stateMachineModel.getStatesData().getStateData()) {
|
||||
Object id = stateData.getState();
|
||||
Object parent = stateData.getParent();
|
||||
tree.add(stateData, id, parent);
|
||||
}
|
||||
|
||||
TreeTraverser<Node<StateData<S, E>>> traverser = new TreeTraverser<Node<StateData<S, E>>>() {
|
||||
treeAdd(tree, stateMachineModel.getStatesData().getStateData());
|
||||
return new TreeTraverser<Node<StateData<S, E>>>() {
|
||||
@Override
|
||||
public Iterable<Node<StateData<S, E>>> children(Node<StateData<S, E>> root) {
|
||||
return root.getChildren();
|
||||
}
|
||||
};
|
||||
}.postOrderTraversal(tree.getRoot()).iterator();
|
||||
}
|
||||
|
||||
Iterable<Node<StateData<S, E>>> postOrderTraversal = traverser.postOrderTraversal(tree.getRoot());
|
||||
Iterator<Node<StateData<S, E>>> iterator = postOrderTraversal.iterator();
|
||||
return iterator;
|
||||
private void treeAdd(Tree<StateData<S, E>> tree, Collection<StateData<S, E>> stateDatas) {
|
||||
// recursive call due to possible submachine data ref
|
||||
if (stateDatas == null) {
|
||||
return;
|
||||
}
|
||||
for (StateData<S, E> stateData : stateDatas) {
|
||||
tree.add(stateData, stateData.getState(), stateData.getParent());
|
||||
treeAdd(tree, stateData.getSubmachineStateData());
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract RegionState<S, E> buildRegionStateInternal(S id, Collection<Region<S, E>> regions, Collection<E> deferred,
|
||||
|
||||
@@ -23,7 +23,9 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.action.Action;
|
||||
import org.springframework.statemachine.config.StateMachineFactory;
|
||||
import org.springframework.statemachine.config.builders.StateMachineStateBuilder;
|
||||
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
|
||||
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter;
|
||||
@@ -57,6 +59,8 @@ public class DefaultStateConfigurer<S, E>
|
||||
private final Collection<S> joins = new ArrayList<S>();
|
||||
private final Collection<S> exits = new ArrayList<S>();
|
||||
private final Collection<S> entrys = new ArrayList<S>();
|
||||
private final Map<S, StateMachine<S, E>> submachines = new HashMap<>();
|
||||
private final Map<S, StateMachineFactory<S, E>> submachinefactories = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateBuilder<S, E> builder) throws Exception {
|
||||
@@ -93,6 +97,8 @@ public class DefaultStateConfigurer<S, E>
|
||||
s.setPseudoStateKind(PseudoStateKind.HISTORY_DEEP);
|
||||
}
|
||||
}
|
||||
s.setSubmachine(submachines.get(s.getState()));
|
||||
s.setSubmachineFactory(submachinefactories.get(s.getState()));
|
||||
}
|
||||
builder.addStateData(stateDatas);
|
||||
}
|
||||
@@ -128,6 +134,20 @@ public class DefaultStateConfigurer<S, E>
|
||||
return state(state, (E[])null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateConfigurer<S, E> state(S state, StateMachine<S, E> stateMachine) {
|
||||
state(state);
|
||||
submachines.put(state, stateMachine);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateConfigurer<S, E> state(S state, StateMachineFactory<S, E> stateMachineFactory) {
|
||||
state(state);
|
||||
submachinefactories.put(state, stateMachineFactory);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateConfigurer<S, E> state(S state, Collection<? extends Action<S, E>> entryActions,
|
||||
Collection<? extends Action<S, E>> exitActions) {
|
||||
|
||||
@@ -18,7 +18,9 @@ package org.springframework.statemachine.config.configurers;
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.action.Action;
|
||||
import org.springframework.statemachine.config.StateMachineFactory;
|
||||
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
|
||||
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerBuilder;
|
||||
import org.springframework.statemachine.state.State;
|
||||
@@ -69,6 +71,26 @@ public interface StateConfigurer<S, E> extends
|
||||
*/
|
||||
StateConfigurer<S, E> state(S state);
|
||||
|
||||
/**
|
||||
* Specify a state {@code S} and its relation with a given
|
||||
* machine as substate machine.
|
||||
*
|
||||
* @param state the state
|
||||
* @param stateMachine the submachine
|
||||
* @return configurer for chaining
|
||||
*/
|
||||
StateConfigurer<S, E> state(S state, StateMachine<S, E> stateMachine);
|
||||
|
||||
/**
|
||||
* Specify a state {@code S} and its relation with a given
|
||||
* machine as substate machine factory.
|
||||
*
|
||||
* @param state the state
|
||||
* @param stateMachineFactory the submachine factory
|
||||
* @return configurer for chaining
|
||||
*/
|
||||
StateConfigurer<S, E> state(S state, StateMachineFactory<S, E> stateMachineFactory);
|
||||
|
||||
/**
|
||||
* Specify a state {@code S} with entry and exit {@link Action}s.
|
||||
*
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.statemachine.config.model;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.action.Action;
|
||||
import org.springframework.statemachine.config.StateMachineFactory;
|
||||
import org.springframework.statemachine.state.PseudoStateKind;
|
||||
@@ -37,6 +38,9 @@ public class StateData<S, E> {
|
||||
private Object parent;
|
||||
private Object region;
|
||||
private S state;
|
||||
private Collection<StateData<S, E>> submachineStateData;
|
||||
private StateMachine<S, E> submachine;
|
||||
private StateMachineFactory<S, E> submachineFactory;
|
||||
private Collection<E> deferred;
|
||||
private Collection<? extends Action<S, E>> entryActions;
|
||||
private Collection<? extends Action<S, E>> exitActions;
|
||||
@@ -122,6 +126,69 @@ public class StateData<S, E> {
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the submachine state data.
|
||||
*
|
||||
* @return the submachine state data
|
||||
*/
|
||||
public Collection<StateData<S, E>> getSubmachineStateData() {
|
||||
return submachineStateData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the submachine state data.
|
||||
*
|
||||
* @param submachineStateData the submachine state data
|
||||
*/
|
||||
public void setSubmachineStateData(Collection<StateData<S, E>> submachineStateData) {
|
||||
this.submachineStateData = submachineStateData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the submachine.
|
||||
*
|
||||
* @return the submachine
|
||||
*/
|
||||
public StateMachine<S, E> getSubmachine() {
|
||||
return submachine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the submachine.
|
||||
*
|
||||
* @param submachine the submachine
|
||||
*/
|
||||
public void setSubmachine(StateMachine<S, E> submachine) {
|
||||
this.submachine = submachine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the submachine factory.
|
||||
*
|
||||
* @return the submachine factory
|
||||
*/
|
||||
public StateMachineFactory<S, E> getSubmachineFactory() {
|
||||
return submachineFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the submachine factory.
|
||||
*
|
||||
* @param submachineFactory the submachine factory
|
||||
*/
|
||||
public void setSubmachineFactory(StateMachineFactory<S, E> submachineFactory) {
|
||||
this.submachineFactory = submachineFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the submachine factory.
|
||||
*
|
||||
* @param submachineFactory the submachine factory
|
||||
*/
|
||||
public void setSubmachine(StateMachineFactory<S, E> submachineFactory) {
|
||||
this.submachineFactory = submachineFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the deferred.
|
||||
*
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.statemachine.config.model;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -111,4 +112,48 @@ public class StateMachineModelTests {
|
||||
stateMachine.sendEvent("E1");
|
||||
assertThat(stateMachine.getState().getIds(), contains("S2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubmachineRefConfig() {
|
||||
// *S1 S2
|
||||
// / \
|
||||
// *S20 S21
|
||||
// / \
|
||||
// *S30 S31
|
||||
ConfigurationData<String, String> configurationData = new ConfigurationData<>();
|
||||
|
||||
Collection<StateData<String, String>> stateData2 = new ArrayList<>();
|
||||
stateData2.add(new StateData<String, String>("S2", null, "S20", true));
|
||||
stateData2.add(new StateData<String, String>("S2", null, "S21", false));
|
||||
stateData2.add(new StateData<String, String>("S21", null, "S30", true));
|
||||
stateData2.add(new StateData<String, String>("S21", null, "S31", false));
|
||||
|
||||
Collection<StateData<String, String>> stateData1 = new ArrayList<>();
|
||||
stateData1.add(new StateData<String, String>("S1", true));
|
||||
StateData<String, String> stateDataS2 = new StateData<String, String>("S2");
|
||||
stateDataS2.setSubmachineStateData(stateData2);
|
||||
stateData1.add(stateDataS2);
|
||||
StatesData<String, String> statesData = new StatesData<>(stateData1);
|
||||
|
||||
Collection<TransitionData<String, String>> transitionData = new ArrayList<>();
|
||||
transitionData.add(new TransitionData<String, String>("S1", "S2", "E1"));
|
||||
transitionData.add(new TransitionData<String, String>("S20", "S21", "E2"));
|
||||
transitionData.add(new TransitionData<String, String>("S30", "S31", "E3"));
|
||||
TransitionsData<String, String> transitionsData = new TransitionsData<>(transitionData);
|
||||
|
||||
StateMachineModel<String, String> stateMachineModel = new DefaultStateMachineModel<>(configurationData, statesData, transitionsData);
|
||||
|
||||
ObjectStateMachineFactory<String, String> factory = new ObjectStateMachineFactory<>(stateMachineModel);
|
||||
|
||||
StateMachine<String,String> stateMachine = factory.getStateMachine();
|
||||
stateMachine.start();
|
||||
|
||||
assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S1"));
|
||||
stateMachine.sendEvent("E1");
|
||||
assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S2", "S20"));
|
||||
stateMachine.sendEvent("E2");
|
||||
assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S2", "S21", "S30"));
|
||||
stateMachine.sendEvent("E3");
|
||||
assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S2", "S21", "S31"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* 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.state;
|
||||
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.statemachine.AbstractStateMachineTests;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.StateMachineSystemConstants;
|
||||
import org.springframework.statemachine.config.EnableStateMachine;
|
||||
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
|
||||
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
|
||||
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||
|
||||
/**
|
||||
* Tests for submachine references.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
*/
|
||||
public class SubmachineRefEnumTests extends AbstractStateMachineTests {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testSubmachineRef() throws Exception {
|
||||
context.register(Config2.class, Config1.class);
|
||||
context.refresh();
|
||||
StateMachine<TestStates, TestEvents> machine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
|
||||
assertThat(machine, notNullValue());
|
||||
machine.start();
|
||||
assertThat(machine.getState().getIds(), containsInAnyOrder(TestStates.S1));
|
||||
machine.sendEvent(TestEvents.E1);
|
||||
assertThat(machine.getState().getIds(), containsInAnyOrder(TestStates.S2, TestStates.S20));
|
||||
machine.sendEvent(TestEvents.E2);
|
||||
assertThat(machine.getState().getIds(), containsInAnyOrder(TestStates.S2, TestStates.S21, TestStates.S30));
|
||||
machine.sendEvent(TestEvents.E3);
|
||||
assertThat(machine.getState().getIds(), containsInAnyOrder(TestStates.S2, TestStates.S21, TestStates.S31));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testSubmachineRefDifferentTypes() throws Exception {
|
||||
context.register(Config4.class, Config3.class);
|
||||
context.refresh();
|
||||
StateMachine<Object, Object> machine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
|
||||
assertThat(machine, notNullValue());
|
||||
machine.start();
|
||||
assertThat(machine.getState().getIds(), containsInAnyOrder(States1.S1));
|
||||
machine.sendEvent(Events1.E1);
|
||||
assertThat(machine.getState().getIds(), containsInAnyOrder(States1.S2, States2.S20));
|
||||
machine.sendEvent(Events2.E2);
|
||||
assertThat(machine.getState().getIds(), containsInAnyOrder(States1.S2, States2.S21, States2.S30));
|
||||
machine.sendEvent(Events2.E3);
|
||||
assertThat(machine.getState().getIds(), containsInAnyOrder(States1.S2, States2.S21, States2.S31));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
static class Config1 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("subStateMachine")
|
||||
private StateMachine<TestStates, TestEvents> subStateMachine;
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(TestStates.S1)
|
||||
.state(TestStates.S2, subStateMachine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source(TestStates.S1).target(TestStates.S2).event(TestEvents.E1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine(name = "subStateMachine")
|
||||
static class Config2 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(TestStates.S20)
|
||||
.state(TestStates.S21)
|
||||
.and()
|
||||
.withStates()
|
||||
.parent(TestStates.S21)
|
||||
.initial(TestStates.S30)
|
||||
.state(TestStates.S31);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source(TestStates.S20).target(TestStates.S21).event(TestEvents.E2).and()
|
||||
.withExternal()
|
||||
.source(TestStates.S30).target(TestStates.S31).event(TestEvents.E3);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
static class Config3 extends StateMachineConfigurerAdapter<Object, Object> {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("subStateMachine")
|
||||
private StateMachine<Object, Object> subStateMachine;
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<Object, Object> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(States1.S1)
|
||||
.state(States1.S2, subStateMachine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<Object, Object> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source(States1.S1).target(States1.S2).event(Events1.E1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine(name = "subStateMachine")
|
||||
static class Config4 extends StateMachineConfigurerAdapter<Object, Object> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<Object, Object> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(States2.S20)
|
||||
.state(States2.S21)
|
||||
.and()
|
||||
.withStates()
|
||||
.parent(States2.S21)
|
||||
.initial(States2.S30)
|
||||
.state(States2.S31);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<Object, Object> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source(States2.S20).target(States2.S21).event(Events2.E2).and()
|
||||
.withExternal()
|
||||
.source(States2.S30).target(States2.S31).event(Events2.E3);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AnnotationConfigApplicationContext buildContext() {
|
||||
return new AnnotationConfigApplicationContext();
|
||||
}
|
||||
|
||||
enum States1 {
|
||||
S1, S2;
|
||||
}
|
||||
|
||||
enum Events1 {
|
||||
E1
|
||||
}
|
||||
|
||||
enum States2 {
|
||||
S20, S21, S30, S31;
|
||||
}
|
||||
|
||||
enum Events2 {
|
||||
E2, E3;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* 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.state;
|
||||
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.statemachine.AbstractStateMachineTests;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.StateMachineSystemConstants;
|
||||
import org.springframework.statemachine.config.EnableStateMachine;
|
||||
import org.springframework.statemachine.config.EnableStateMachineFactory;
|
||||
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
|
||||
import org.springframework.statemachine.config.StateMachineFactory;
|
||||
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||
|
||||
/**
|
||||
* Tests for submachine references.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
*/
|
||||
public class SubmachineRefTests extends AbstractStateMachineTests {
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testSubmachineRef() throws Exception {
|
||||
context.register(Config2.class, Config1.class);
|
||||
context.refresh();
|
||||
StateMachine<String, String> machine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, StateMachine.class);
|
||||
assertThat(machine, notNullValue());
|
||||
machine.start();
|
||||
assertThat(machine.getState().getIds(), containsInAnyOrder("S1"));
|
||||
machine.sendEvent("E1");
|
||||
assertThat(machine.getState().getIds(), containsInAnyOrder("S2", "S20"));
|
||||
machine.sendEvent("E2");
|
||||
assertThat(machine.getState().getIds(), containsInAnyOrder("S2", "S21", "S30"));
|
||||
machine.sendEvent("E3");
|
||||
assertThat(machine.getState().getIds(), containsInAnyOrder("S2", "S21", "S31"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testSubmachineRefWithFactory() throws Exception {
|
||||
context.register(Config4.class, Config3.class);
|
||||
context.refresh();
|
||||
StateMachineFactory<String, String> factory = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINEFACTORY, StateMachineFactory.class);
|
||||
StateMachine<String, String> machine = factory.getStateMachine();
|
||||
assertThat(machine, notNullValue());
|
||||
machine.start();
|
||||
assertThat(machine.getState().getIds(), containsInAnyOrder("S1"));
|
||||
machine.sendEvent("E1");
|
||||
assertThat(machine.getState().getIds(), containsInAnyOrder("S2", "S20"));
|
||||
machine.sendEvent("E2");
|
||||
assertThat(machine.getState().getIds(), containsInAnyOrder("S2", "S21", "S30"));
|
||||
machine.sendEvent("E3");
|
||||
assertThat(machine.getState().getIds(), containsInAnyOrder("S2", "S21", "S31"));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
static class Config1 extends StateMachineConfigurerAdapter<String, String> {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("subStateMachine")
|
||||
private StateMachine<String, String> subStateMachine;
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial("S1")
|
||||
.state("S2", subStateMachine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source("S1").target("S2").event("E1");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine(name = "subStateMachine")
|
||||
static class Config2 extends StateMachineConfigurerAdapter<String, String> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial("S20")
|
||||
.state("S21")
|
||||
.and()
|
||||
.withStates()
|
||||
.parent("S21")
|
||||
.initial("S30")
|
||||
.state("S31");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source("S20").target("S21").event("E2").and()
|
||||
.withExternal()
|
||||
.source("S30").target("S31").event("E3");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachineFactory
|
||||
static class Config3 extends StateMachineConfigurerAdapter<String, String> {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("subStateMachineFactory")
|
||||
private StateMachineFactory<String, String> subStateMachineFactory;
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial("S1")
|
||||
.state("S2", subStateMachineFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source("S1").target("S2").event("E1");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachineFactory(name = "subStateMachineFactory")
|
||||
static class Config4 extends StateMachineConfigurerAdapter<String, String> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial("S20")
|
||||
.state("S21")
|
||||
.and()
|
||||
.withStates()
|
||||
.parent("S21")
|
||||
.initial("S30")
|
||||
.state("S31");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source("S20").target("S21").event("E2").and()
|
||||
.withExternal()
|
||||
.source("S30").target("S31").event("E3");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AnnotationConfigApplicationContext buildContext() {
|
||||
return new AnnotationConfigApplicationContext();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user