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