Add first concept of better internal machine comm

- Add concept of functional interface to work with
  recursive machines for function access.
- This also fixes a machine relay so that every used
  state context gets a correct top level machine
  for correctly dispatching events into a top level handling.
This commit is contained in:
Janne Valkealahti
2015-05-17 15:28:42 +01:00
parent a4baf0135b
commit 2a31254b91
5 changed files with 227 additions and 8 deletions

View File

@@ -46,7 +46,9 @@ import org.springframework.statemachine.state.RegionState;
import org.springframework.statemachine.state.State;
import org.springframework.statemachine.state.StateMachineState;
import org.springframework.statemachine.support.DefaultExtendedState;
import org.springframework.statemachine.support.StateMachineFunction;
import org.springframework.statemachine.support.LifecycleObjectSupport;
import org.springframework.statemachine.support.StateMachineAccess;
import org.springframework.statemachine.support.tree.Tree;
import org.springframework.statemachine.support.tree.Tree.Node;
import org.springframework.statemachine.support.tree.TreeTraverser;
@@ -181,6 +183,16 @@ public class EnumStateMachineFactory<S extends Enum<S>, E extends Enum<E>> exten
stateStack.push(stateData);
}
// set top-level machine as relay
final StateMachine<S, E> mm = machine;
((StateMachineAccess<S, E>)machine).doWithAllRegions(new StateMachineFunction<StateMachineAccess<S, E>>() {
@Override
public void apply(StateMachineAccess<S, E> stateMachineAccess) {
stateMachineAccess.setRelay(mm);
}
});
return machine;
}

View File

@@ -76,7 +76,7 @@ import org.springframework.util.StringUtils;
* @param <S> the type of state
* @param <E> the type of event
*/
public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSupport<S, E> implements StateMachine<S, E> {
public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSupport<S, E> implements StateMachine<S, E>, StateMachineAccess<S, E> {
private static final Log log = LogFactory.getLog(AbstractStateMachine.class);
@@ -114,6 +114,8 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
private final List<Transition<S, E>> triggerlessTransitions = new ArrayList<Transition<S,E>>();
private StateMachine<S, E> relay;
/**
* Instantiates a new abstract state machine.
*
@@ -235,12 +237,12 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
}
registerTriggerListener();
registerPseudoStateListener();
switchToState(initialState, initialEvent, null, this);
switchToState(initialState, initialEvent, null, getRelayStateMachine());
// TODO: it is a bit off to call handlers after switchToState for initial state
callHandlers(null, initialState, initialEvent);
// TODO: for now execute outside of switchToState
if (initialTransition != null) {
StateContext<S, E> stateContext = buildStateContext(initialEvent, initialTransition, this);
StateContext<S, E> stateContext = buildStateContext(initialEvent, initialTransition, getRelayStateMachine());
initialTransition.transit(stateContext);
}
notifyStateMachineStarted(this);
@@ -288,6 +290,33 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
return transitions;
}
@Override
public void doWithAllRegions(StateMachineFunction<StateMachineAccess<S, E>> stateMachineAccess) {
stateMachineAccess.apply(this);
for (State<S, E> state : states) {
if (state.isSubmachineState()) {
StateMachine<S, E> submachine = ((AbstractState<S, E>)state).getSubmachine();
if (submachine instanceof StateMachineAccess) {
((StateMachineAccess<S, E>)submachine).doWithAllRegions(stateMachineAccess);
}
} else if (state.isOrthogonal()) {
Collection<Region<S, E>> regions = ((AbstractState<S, E>)state).getRegions();
for (Region<S, E> region : regions) {
((StateMachineAccess<S, E>)region).doWithAllRegions(stateMachineAccess);
}
}
}
}
@Override
public void setRelay(StateMachine<S, E> stateMachine) {
this.relay = stateMachine;
}
private StateMachine<S, E> getRelayStateMachine() {
return relay != null ? relay : this;
}
@Override
public String toString() {
ArrayList<State<S, E>> all = new ArrayList<State<S,E>>();
@@ -372,9 +401,9 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
public void onContext(PseudoStateContext<S, E> context) {
PseudoState<S, E> pseudoState = context.getPseudoState();
State<S, E> toState = findStateWithPseudoState(pseudoState);
StateContext<S, E> stateContext = buildStateContext(null, null, AbstractStateMachine.this);
StateContext<S, E> stateContext = buildStateContext(null, null, getRelayStateMachine());
pseudoState.exit(stateContext);
switchToState(toState, null, null, AbstractStateMachine.this);
switchToState(toState, null, null, getRelayStateMachine());
}
});
}
@@ -689,7 +718,7 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
private void handleTriggerTrans(List<Transition<S, E>> trans, Message<E> queuedMessage) {
for (Transition<S, E> t : trans) {
StateContext<S, E> stateContext = buildStateContext(queuedMessage, t, this);
StateContext<S, E> stateContext = buildStateContext(queuedMessage, t, getRelayStateMachine());
if (t == null) {
continue;
}
@@ -708,7 +737,7 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
notifyTransitionStart(t);
callHandlers(t.getSource(), t.getTarget(), queuedMessage);
if (t.getKind() != TransitionKind.INTERNAL) {
switchToState(t.getTarget(), queuedMessage, t, this);
switchToState(t.getTarget(), queuedMessage, t, getRelayStateMachine());
}
notifyTransition(t);
notifyTransitionEnd(t);
@@ -720,7 +749,7 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
}
private void callHandlers(State<S,E> sourceState, State<S,E> targetState, Message<E> message) {
StateContext<S, E> stateContext = buildStateContext(message, null, this);
StateContext<S, E> stateContext = buildStateContext(message, null, getRelayStateMachine());
getStateMachineHandlerResults(getStateMachineHandlers(sourceState, targetState), stateContext);
}

View File

@@ -0,0 +1,45 @@
/*
* 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.support;
import org.springframework.statemachine.StateMachine;
/**
* Functional interface for {@link StateMachine} to allow more programmetic
* access to underlying functionality.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
public interface StateMachineAccess<S, E> {
/**
* Execute given {@link StateMachineFunction} with all recursive regions.
*
* @param stateMachineAccess the state machine access
*/
void doWithAllRegions(StateMachineFunction<StateMachineAccess<S, E>> stateMachineAccess);
/**
* Sets the relay state machine.
*
* @param stateMachine the state machine
*/
void setRelay(StateMachine<S, E> stateMachine);
}

View File

@@ -0,0 +1,36 @@
/*
* 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.support;
/**
* Strategic function interface for applying arbitrary function
* or feature.
*
* @author Janne Valkealahti
*
* @param <I> the function type
* @see StateMachineAccess
*/
public interface StateMachineFunction<I> {
/**
* Apply a function.
*
* @param function the function
*/
void apply(I function);
}

View File

@@ -0,0 +1,97 @@
/*
* 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;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
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.statemachine.action.Action;
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 RelayTests extends AbstractStateMachineTests {
@Override
protected AnnotationConfigApplicationContext buildContext() {
return new AnnotationConfigApplicationContext();
}
@Test
@SuppressWarnings("unchecked")
public void testRelayFromSubmachine() throws Exception {
context.register(Config1.class);
context.refresh();
EnumStateMachine<TestStates,TestEvents> machine =
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class);
assertThat(machine, notNullValue());
machine.start();
machine.sendEvent(TestEvents.E1);
assertThat(machine.getState().getIds(), contains(TestStates.S2, TestStates.S21));
}
@Configuration
@EnableStateMachine
static class Config1 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
@Override
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
states
.withStates()
.initial(TestStates.S1)
.state(TestStates.S2)
.and()
.withStates()
.parent(TestStates.S2)
.initial(TestStates.S20)
.state(TestStates.S20, action1(), null)
.state(TestStates.S21);
}
@Override
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
transitions
.withExternal()
.source(TestStates.S1)
.target(TestStates.S2)
.event(TestEvents.E1)
.and()
.withExternal()
.source(TestStates.S20)
.target(TestStates.S21)
.event(TestEvents.E2);
}
@Bean
public Action<TestStates, TestEvents> action1() {
return new Action<TestStates, TestEvents>() {
@Override
public void execute(StateContext<TestStates, TestEvents> context) {
context.getStateMachine().sendEvent(TestEvents.E2);
}
};
}
}
}