Support fork/join with regions
- Adding first changes for supporting fork and join concepts with regions. - Change of some pseudostate concepts to have better model for this and future work. - Fixes #39
This commit is contained in:
@@ -21,6 +21,7 @@ import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Stack;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
@@ -33,15 +34,17 @@ import org.springframework.statemachine.config.builders.StateMachineTransitions.
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitions.TransitionData;
|
||||
import org.springframework.statemachine.region.Region;
|
||||
import org.springframework.statemachine.state.ChoicePseudoState;
|
||||
import org.springframework.statemachine.state.ChoicePseudoState.ChoiceStateData;
|
||||
import org.springframework.statemachine.state.DefaultPseudoState;
|
||||
import org.springframework.statemachine.state.EnumState;
|
||||
import org.springframework.statemachine.state.ForkPseudoState;
|
||||
import org.springframework.statemachine.state.HistoryPseudoState;
|
||||
import org.springframework.statemachine.state.JoinPseudoState;
|
||||
import org.springframework.statemachine.state.PseudoState;
|
||||
import org.springframework.statemachine.state.PseudoStateKind;
|
||||
import org.springframework.statemachine.state.RegionState;
|
||||
import org.springframework.statemachine.state.State;
|
||||
import org.springframework.statemachine.state.StateMachineState;
|
||||
import org.springframework.statemachine.state.ChoicePseudoState.ChoiceStateData;
|
||||
import org.springframework.statemachine.support.DefaultExtendedState;
|
||||
import org.springframework.statemachine.support.LifecycleObjectSupport;
|
||||
import org.springframework.statemachine.support.tree.Tree;
|
||||
@@ -152,6 +155,20 @@ public class EnumStateMachineFactory<S extends Enum<S>, E extends Enum<E>> exten
|
||||
new DefaultPseudoState<S, E>(PseudoStateKind.INITIAL));
|
||||
if (stateData != null) {
|
||||
stateMap.put(stateData.getState(), rstate);
|
||||
} else {
|
||||
// TODO: don't like that we create a last machine here
|
||||
Collection<State<S, E>> states = new ArrayList<State<S, E>>();
|
||||
states.add(rstate);
|
||||
EnumStateMachine<S, E> m = new EnumStateMachine<S, E>(states, new ArrayList<Transition<S, E>>(), rstate,
|
||||
null, null, defaultExtendedState);
|
||||
if (contextEvents != null) {
|
||||
m.setContextEventsEnabled(contextEvents);
|
||||
}
|
||||
if (getBeanFactory() != null) {
|
||||
m.setBeanFactory(getBeanFactory());
|
||||
}
|
||||
m.afterPropertiesSet();
|
||||
machine = m;
|
||||
}
|
||||
} else {
|
||||
machine = buildMachine(machineMap, stateMap, stateDatas, transitionsData, getBeanFactory(),
|
||||
@@ -306,6 +323,10 @@ public class EnumStateMachineFactory<S extends Enum<S>, E extends Enum<E>> exten
|
||||
} else if (stateData.getPseudoStateKind() == PseudoStateKind.HISTORY_DEEP) {
|
||||
pseudoState = new HistoryPseudoState<S, E>(PseudoStateKind.HISTORY_DEEP);
|
||||
historyState = pseudoState;
|
||||
} else if (stateData.getPseudoStateKind() == PseudoStateKind.JOIN) {
|
||||
continue;
|
||||
} else if (stateData.getPseudoStateKind() == PseudoStateKind.FORK) {
|
||||
continue;
|
||||
} else if (stateData.getPseudoStateKind() == PseudoStateKind.CHOICE) {
|
||||
continue;
|
||||
}
|
||||
@@ -334,6 +355,41 @@ public class EnumStateMachineFactory<S extends Enum<S>, E extends Enum<E>> exten
|
||||
stateData.getEntryActions(), stateData.getExitActions(), pseudoState);
|
||||
states.add(state);
|
||||
stateMap.put(stateData.getState(), state);
|
||||
} else if (stateData.getPseudoStateKind() == PseudoStateKind.FORK) {
|
||||
S s = stateData.getState();
|
||||
List<S> list = stateMachineTransitions.getForks().get(s);
|
||||
List<State<S, E>> forks = new ArrayList<State<S,E>>();
|
||||
for (S fs : list) {
|
||||
forks.add(stateMap.get(fs));
|
||||
}
|
||||
PseudoState<S, E> pseudoState = new ForkPseudoState<S, E>(forks);
|
||||
state = new EnumState<S, E>(stateData.getState(), stateData.getDeferred(),
|
||||
stateData.getEntryActions(), stateData.getExitActions(), pseudoState);
|
||||
states.add(state);
|
||||
stateMap.put(stateData.getState(), state);
|
||||
} else if (stateData.getPseudoStateKind() == PseudoStateKind.JOIN) {
|
||||
S s = stateData.getState();
|
||||
List<S> list = stateMachineTransitions.getJoins().get(s);
|
||||
List<State<S, E>> joins = new ArrayList<State<S,E>>();
|
||||
for (S fs : list) {
|
||||
joins.add(stateMap.get(fs));
|
||||
}
|
||||
JoinPseudoState<S, E> pseudoState = new JoinPseudoState<S, E>(joins);
|
||||
state = new EnumState<S, E>(stateData.getState(), stateData.getDeferred(),
|
||||
stateData.getEntryActions(), stateData.getExitActions(), pseudoState);
|
||||
states.add(state);
|
||||
stateMap.put(stateData.getState(), state);
|
||||
|
||||
// find joins sources and associate
|
||||
for (Entry<S, State<S, E>> e : stateMap.entrySet()) {
|
||||
State<S, E> value = e.getValue();
|
||||
if (value.isOrthogonal()) {
|
||||
Collection<State<S, E>> states2 = value.getStates();
|
||||
if (states2.containsAll(joins)) {
|
||||
((RegionState<S, E>)value).setJoin(pseudoState);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,10 +30,14 @@ import org.springframework.statemachine.config.common.annotation.ObjectPostProce
|
||||
import org.springframework.statemachine.config.configurers.ChoiceTransitionConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.DefaultChoiceTransitionConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.DefaultExternalTransitionConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.DefaultForkTransitionConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.DefaultInternalTransitionConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.DefaultJoinTransitionConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.DefaultLocalTransitionConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.ExternalTransitionConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.ForkTransitionConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.InternalTransitionConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.JoinTransitionConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.LocalTransitionConfigurer;
|
||||
import org.springframework.statemachine.guard.Guard;
|
||||
import org.springframework.statemachine.transition.TransitionKind;
|
||||
@@ -53,6 +57,8 @@ public class StateMachineTransitionBuilder<S, E>
|
||||
|
||||
private final Collection<TransitionData<S, E>> transitionData = new ArrayList<TransitionData<S, E>>();
|
||||
private final Map<S, List<ChoiceData<S, E>>> choices = new HashMap<S, List<ChoiceData<S, E>>>();
|
||||
private final Map<S, List<S>> forks = new HashMap<S, List<S>>();
|
||||
private final Map<S, List<S>> joins = new HashMap<S, List<S>>();
|
||||
|
||||
public StateMachineTransitionBuilder() {
|
||||
super();
|
||||
@@ -69,7 +75,7 @@ public class StateMachineTransitionBuilder<S, E>
|
||||
|
||||
@Override
|
||||
protected StateMachineTransitions<S, E> performBuild() throws Exception {
|
||||
return new StateMachineTransitions<S, E>(transitionData, choices);
|
||||
return new StateMachineTransitions<S, E>(transitionData, choices, forks, joins);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -92,6 +98,16 @@ public class StateMachineTransitionBuilder<S, E>
|
||||
return apply(new DefaultChoiceTransitionConfigurer<S, E>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ForkTransitionConfigurer<S, E> withFork() throws Exception {
|
||||
return apply(new DefaultForkTransitionConfigurer<S, E>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public JoinTransitionConfigurer<S, E> withJoin() throws Exception {
|
||||
return apply(new DefaultJoinTransitionConfigurer<S, E>());
|
||||
}
|
||||
|
||||
public void add(S source, S target, S state, E event, Long period, Collection<Action<S, E>> actions,
|
||||
Guard<S, E> guard, TransitionKind kind) {
|
||||
transitionData.add(new TransitionData<S, E>(source, target, state, event, period, actions, guard, kind));
|
||||
@@ -101,4 +117,12 @@ public class StateMachineTransitionBuilder<S, E>
|
||||
this.choices.put(source, choices);
|
||||
}
|
||||
|
||||
public void addFork(S source, List<S> targets) {
|
||||
this.forks.put(source, targets);
|
||||
}
|
||||
|
||||
public void addJoin(S target, List<S> sources) {
|
||||
this.joins.put(target, sources);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,7 +17,9 @@ package org.springframework.statemachine.config.builders;
|
||||
|
||||
import org.springframework.statemachine.config.configurers.ChoiceTransitionConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.ExternalTransitionConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.ForkTransitionConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.InternalTransitionConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.JoinTransitionConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.LocalTransitionConfigurer;
|
||||
|
||||
/**
|
||||
@@ -65,9 +67,25 @@ public interface StateMachineTransitionConfigurer<S, E> {
|
||||
/**
|
||||
* Gets a configurer for transition from a choice pseudostate.
|
||||
*
|
||||
* @return {@link LocalTransitionConfigurer} for chaining
|
||||
* @return {@link ChoiceTransitionConfigurer} for chaining
|
||||
* @throws Exception if configuration error happens
|
||||
*/
|
||||
ChoiceTransitionConfigurer<S, E> withChoice() throws Exception;
|
||||
|
||||
/**
|
||||
* Gets a configurer for transition from a fork pseudostate.
|
||||
*
|
||||
* @return {@link ForkTransitionConfigurer} for chaining
|
||||
* @throws Exception if configuration error happens
|
||||
*/
|
||||
ForkTransitionConfigurer<S, E> withFork() throws Exception;
|
||||
|
||||
/**
|
||||
* Gets a configurer for transition from a join pseudostate.
|
||||
*
|
||||
* @return {@link JoinTransitionConfigurer} for chaining
|
||||
* @throws Exception if configuration error happens
|
||||
*/
|
||||
JoinTransitionConfigurer<S, E> withJoin() throws Exception;
|
||||
|
||||
}
|
||||
|
||||
@@ -35,10 +35,15 @@ public class StateMachineTransitions<S, E> {
|
||||
|
||||
private Collection<TransitionData<S, E>> transitions;
|
||||
private Map<S, List<ChoiceData<S, E>>> choices;
|
||||
private Map<S, List<S>> forks;
|
||||
private Map<S, List<S>> joins;
|
||||
|
||||
public StateMachineTransitions(Collection<TransitionData<S, E>> transitions, Map<S, List<ChoiceData<S, E>>> choices) {
|
||||
public StateMachineTransitions(Collection<TransitionData<S, E>> transitions,
|
||||
Map<S, List<ChoiceData<S, E>>> choices, Map<S, List<S>> forks, Map<S, List<S>> joins) {
|
||||
this.transitions = transitions;
|
||||
this.choices = choices;
|
||||
this.forks = forks;
|
||||
this.joins = joins;
|
||||
}
|
||||
|
||||
public Collection<TransitionData<S, E>> getTransitions() {
|
||||
@@ -49,6 +54,14 @@ public class StateMachineTransitions<S, E> {
|
||||
return choices;
|
||||
}
|
||||
|
||||
public Map<S, List<S>> getForks() {
|
||||
return forks;
|
||||
}
|
||||
|
||||
public Map<S, List<S>> getJoins() {
|
||||
return joins;
|
||||
}
|
||||
|
||||
public static class TransitionData<S, E> {
|
||||
S source;
|
||||
S target;
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.config.configurers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitionBuilder;
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitions;
|
||||
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter;
|
||||
|
||||
/**
|
||||
* Default implementation of a {@link ForkTransitionConfigurer}.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public class DefaultForkTransitionConfigurer<S, E>
|
||||
extends AnnotationConfigurerAdapter<StateMachineTransitions<S, E>, StateMachineTransitionConfigurer<S, E>, StateMachineTransitionBuilder<S, E>>
|
||||
implements ForkTransitionConfigurer<S, E> {
|
||||
|
||||
private S source;
|
||||
|
||||
private final List<S> targets = new ArrayList<S>();
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionBuilder<S, E> builder) throws Exception {
|
||||
builder.addFork(source, targets);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ForkTransitionConfigurer<S, E> source(S source) {
|
||||
this.source = source;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ForkTransitionConfigurer<S, E> target(S target) {
|
||||
targets.add(target);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.config.configurers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitionBuilder;
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitions;
|
||||
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter;
|
||||
|
||||
/**
|
||||
* Default implementation of a {@link JoinTransitionConfigurer}.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public class DefaultJoinTransitionConfigurer<S, E>
|
||||
extends AnnotationConfigurerAdapter<StateMachineTransitions<S, E>, StateMachineTransitionConfigurer<S, E>, StateMachineTransitionBuilder<S, E>>
|
||||
implements JoinTransitionConfigurer<S, E> {
|
||||
|
||||
private S target;
|
||||
|
||||
private final List<S> sources = new ArrayList<S>();
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionBuilder<S, E> builder) throws Exception {
|
||||
builder.addJoin(target, sources);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JoinTransitionConfigurer<S, E> source(S source) {
|
||||
this.sources.add(source);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JoinTransitionConfigurer<S, E> target(S target) {
|
||||
this.target = target;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -59,6 +59,10 @@ public class DefaultStateConfigurer<S, E>
|
||||
|
||||
private final Collection<S> choices = new ArrayList<S>();
|
||||
|
||||
private final Collection<S> forks = new ArrayList<S>();
|
||||
|
||||
private final Collection<S> joins = new ArrayList<S>();
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateBuilder<S, E> builder) throws Exception {
|
||||
// before passing state datas to builder, update structure
|
||||
@@ -76,6 +80,10 @@ public class DefaultStateConfigurer<S, E>
|
||||
}
|
||||
if (choices.contains(s.getState())) {
|
||||
s.setPseudoStateKind(PseudoStateKind.CHOICE);
|
||||
} else if (forks.contains(s.getState())) {
|
||||
s.setPseudoStateKind(PseudoStateKind.FORK);
|
||||
} else if (joins.contains(s.getState())) {
|
||||
s.setPseudoStateKind(PseudoStateKind.JOIN);
|
||||
}
|
||||
if (s.getState() == history) {
|
||||
if (History.SHALLOW == historyType) {
|
||||
@@ -163,6 +171,20 @@ public class DefaultStateConfigurer<S, E>
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateConfigurer<S, E> fork(S fork) {
|
||||
state(fork);
|
||||
forks.add(fork);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateConfigurer<S, E> join(S join) {
|
||||
state(join);
|
||||
joins.add(join);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateConfigurer<S, E> history(S history, History type) {
|
||||
this.history = history;
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.config.configurers;
|
||||
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerBuilder;
|
||||
import org.springframework.statemachine.transition.Transition;
|
||||
|
||||
/**
|
||||
* {@code TransitionConfigurer} interface for configuring {@link Transition}
|
||||
* from a fork pseudo state.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public interface ForkTransitionConfigurer<S, E>
|
||||
extends AnnotationConfigurerBuilder<StateMachineTransitionConfigurer<S, E>> {
|
||||
|
||||
/**
|
||||
* Specify a source state {@code S} for this {@link Transition}.
|
||||
*
|
||||
* @param source the source state {@code S}
|
||||
* @return configurer for chaining
|
||||
*/
|
||||
ForkTransitionConfigurer<S, E> source(S source);
|
||||
|
||||
/**
|
||||
* Specify a target state {@code S} for this {@link Transition}.
|
||||
*
|
||||
* @param target the target state {@code S}
|
||||
* @return configurer for chaining
|
||||
*/
|
||||
ForkTransitionConfigurer<S, E> target(S target);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.config.configurers;
|
||||
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerBuilder;
|
||||
import org.springframework.statemachine.transition.Transition;
|
||||
|
||||
/**
|
||||
* {@code TransitionConfigurer} interface for configuring {@link Transition}
|
||||
* from a join pseudo state.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public interface JoinTransitionConfigurer<S, E>
|
||||
extends AnnotationConfigurerBuilder<StateMachineTransitionConfigurer<S, E>> {
|
||||
|
||||
/**
|
||||
* Specify a source state {@code S} for this {@link Transition}.
|
||||
*
|
||||
* @param source the source state {@code S}
|
||||
* @return configurer for chaining
|
||||
*/
|
||||
JoinTransitionConfigurer<S, E> source(S source);
|
||||
|
||||
/**
|
||||
* Specify a target state {@code S} for this {@link Transition}.
|
||||
*
|
||||
* @param target the target state {@code S}
|
||||
* @return configurer for chaining
|
||||
*/
|
||||
JoinTransitionConfigurer<S, E> target(S target);
|
||||
|
||||
}
|
||||
@@ -123,6 +123,22 @@ public interface StateConfigurer<S, E> extends
|
||||
*/
|
||||
StateConfigurer<S, E> choice(S choice);
|
||||
|
||||
/**
|
||||
* Specify a state {@code S} to be fork pseudo state.
|
||||
*
|
||||
* @param fork the fork pseudo state
|
||||
* @return configurer for chaining
|
||||
*/
|
||||
StateConfigurer<S, E> fork(S fork);
|
||||
|
||||
/**
|
||||
* Specify a state {@code S} to be join pseudo state.
|
||||
*
|
||||
* @param join the join pseudo state
|
||||
* @return configurer for chaining
|
||||
*/
|
||||
StateConfigurer<S, E> join(S join);
|
||||
|
||||
/**
|
||||
* Specify a state {@code S} to be history pseudo state.
|
||||
*
|
||||
|
||||
@@ -29,6 +29,8 @@ public abstract class AbstractPseudoState<S, E> implements PseudoState<S, E> {
|
||||
|
||||
private final PseudoStateKind kind;
|
||||
|
||||
private final CompositePseudoStateListener<S, E> pseudoStateListener = new CompositePseudoStateListener<S, E>();
|
||||
|
||||
/**
|
||||
* Instantiates a new abstract pseudo state.
|
||||
*
|
||||
@@ -44,8 +46,26 @@ public abstract class AbstractPseudoState<S, E> implements PseudoState<S, E> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public State<S, E> entry(E event, StateContext<S, E> context) {
|
||||
public State<S, E> entry(StateContext<S, E> context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exit(StateContext<S, E> context) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPseudoStateListener(PseudoStateListener<S, E> listener) {
|
||||
pseudoStateListener.register(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all {@link PseudoStateListener}s of a new context.
|
||||
*
|
||||
* @param context the new context
|
||||
*/
|
||||
protected void notifyContext(PseudoStateContext<S, E> context) {
|
||||
pseudoStateListener.onContext(context);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public class ChoicePseudoState<S, E> implements PseudoState<S, E> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public State<S, E> entry(E event, StateContext<S, E> context) {
|
||||
public State<S, E> entry(StateContext<S, E> context) {
|
||||
State<S, E> s = null;
|
||||
for (ChoiceStateData<S, E> c : choices) {
|
||||
s = c.getState();
|
||||
@@ -52,6 +52,14 @@ public class ChoicePseudoState<S, E> implements PseudoState<S, E> {
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exit(StateContext<S, E> context) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPseudoStateListener(PseudoStateListener<S, E> listener) {
|
||||
}
|
||||
|
||||
public static class ChoiceStateData<S, E> {
|
||||
private final State<S, E> state;
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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 java.util.Iterator;
|
||||
|
||||
import org.springframework.statemachine.listener.AbstractCompositeListener;
|
||||
|
||||
public class CompositePseudoStateListener<S, E> extends AbstractCompositeListener<PseudoStateListener<S, E>> implements
|
||||
PseudoStateListener<S, E> {
|
||||
|
||||
@Override
|
||||
public void onContext(PseudoStateContext<S, E> context) {
|
||||
for (Iterator<PseudoStateListener<S, E>> iterator = getListeners().reverse(); iterator.hasNext();) {
|
||||
PseudoStateListener<S, E> listener = iterator.next();
|
||||
listener.onContext(context);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Default implementation of a {@link PseudoStateContext}.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public class DefaultPseudoStateContext<S, E> implements PseudoStateContext<S, E> {
|
||||
|
||||
private final PseudoState<S, E> pseudoState;
|
||||
|
||||
private final PseudoAction pseudoAction;
|
||||
|
||||
/**
|
||||
* Instantiates a new default pseudo state context.
|
||||
*
|
||||
* @param pseudoState the pseudo state
|
||||
* @param pseudoAction the pseudo action
|
||||
*/
|
||||
public DefaultPseudoStateContext(PseudoState<S, E> pseudoState, PseudoAction pseudoAction) {
|
||||
this.pseudoState = pseudoState;
|
||||
this.pseudoAction = pseudoAction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PseudoState<S, E> getPseudoState() {
|
||||
return pseudoState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PseudoAction getPseudoAction() {
|
||||
return pseudoAction;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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 java.util.List;
|
||||
|
||||
import org.springframework.statemachine.StateContext;
|
||||
|
||||
/**
|
||||
* Fork implementation of a {@link PseudoState}.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public class ForkPseudoState<S, E> extends AbstractPseudoState<S, E> {
|
||||
|
||||
private final List<State<S, E>> forks;
|
||||
|
||||
public ForkPseudoState(List<State<S, E>> forks) {
|
||||
super(PseudoStateKind.FORK);
|
||||
this.forks = forks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public State<S, E> entry(StateContext<S, E> context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<State<S, E>> getForks() {
|
||||
return forks;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -41,6 +41,11 @@ public class HistoryPseudoState<S, E> extends AbstractPseudoState<S, E> {
|
||||
"Pseudo state must be either shallow or deep");
|
||||
}
|
||||
|
||||
@Override
|
||||
public State<S, E> entry(StateContext<S, E> context) {
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current recorded state.
|
||||
*
|
||||
@@ -59,9 +64,4 @@ public class HistoryPseudoState<S, E> extends AbstractPseudoState<S, E> {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public State<S, E> entry(E event, StateContext<S, E> context) {
|
||||
return state;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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 java.util.List;
|
||||
|
||||
import org.springframework.statemachine.StateContext;
|
||||
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
|
||||
import org.springframework.statemachine.state.PseudoStateContext.PseudoAction;
|
||||
|
||||
/**
|
||||
* Join implementation of a {@link PseudoState}.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public class JoinPseudoState<S, E> extends AbstractPseudoState<S, E> {
|
||||
|
||||
private final List<State<S, E>> joins;
|
||||
|
||||
private volatile JoinTracker tracker;
|
||||
|
||||
public JoinPseudoState(List<State<S, E>> joins) {
|
||||
super(PseudoStateKind.JOIN);
|
||||
this.joins = joins;
|
||||
}
|
||||
|
||||
@Override
|
||||
public State<S, E> entry(StateContext<S, E> context) {
|
||||
tracker = new JoinTracker(this, joins);
|
||||
context.getStateMachine().addStateListener(tracker);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exit(StateContext<S, E> context) {
|
||||
tracker = null;
|
||||
}
|
||||
|
||||
public List<State<S, E>> getJoins() {
|
||||
return joins;
|
||||
}
|
||||
|
||||
private class JoinTracker extends StateMachineListenerAdapter<S, E> {
|
||||
|
||||
private final PseudoState<S, E> pseudoState;
|
||||
private final List<State<S, E>> track;
|
||||
// TOOO use flat till we can unregister listener
|
||||
private boolean done = false;
|
||||
|
||||
public JoinTracker(PseudoState<S, E> pseudoState, List<State<S, E>> track) {
|
||||
this.pseudoState = pseudoState;
|
||||
this.track = track;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateChanged(State<S, E> from, State<S, E> to) {
|
||||
if (done) {
|
||||
return;
|
||||
}
|
||||
track.remove(to);
|
||||
if (track.size() == 0) {
|
||||
done = true;
|
||||
notifyContext(new DefaultPseudoStateContext<S, E>(pseudoState, PseudoAction.JOIN_COMPLETED));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -46,10 +46,23 @@ public interface PseudoState<S, E> {
|
||||
* Initiate an entry sequence for the state and return a next
|
||||
* state where state machine should go.
|
||||
*
|
||||
* @param event the event
|
||||
* @param context the context
|
||||
* @return the next state or null
|
||||
*/
|
||||
State<S, E> entry(E event, StateContext<S, E> context);
|
||||
State<S, E> entry(StateContext<S, E> context);
|
||||
|
||||
/**
|
||||
* Initiate an exit sequence for the state.
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
void exit(StateContext<S, E> context);
|
||||
|
||||
/**
|
||||
* Registers a new {@link PseudoStateListener}.
|
||||
*
|
||||
* @param listener the listener
|
||||
*/
|
||||
void addPseudoStateListener(PseudoStateListener<S, E> listener);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Context object using in {@link PseudoStateListener}.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public interface PseudoStateContext<S, E> {
|
||||
|
||||
/**
|
||||
* Gets the pseudo state.
|
||||
*
|
||||
* @return the pseudo state
|
||||
*/
|
||||
PseudoState<S, E> getPseudoState();
|
||||
|
||||
/**
|
||||
* Gets the pseudo action.
|
||||
*
|
||||
* @return the pseudo action
|
||||
*/
|
||||
PseudoAction getPseudoAction();
|
||||
|
||||
/**
|
||||
* The PseudoAction enumeration.
|
||||
*/
|
||||
public enum PseudoAction {
|
||||
|
||||
/**
|
||||
* Indication that states has been joined.
|
||||
*/
|
||||
JOIN_COMPLETED;
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,12 @@ public enum PseudoStateKind {
|
||||
HISTORY_DEEP,
|
||||
|
||||
/** History shallow kind */
|
||||
HISTORY_SHALLOW
|
||||
HISTORY_SHALLOW,
|
||||
|
||||
/** Fork kind */
|
||||
FORK,
|
||||
|
||||
/** Join kind */
|
||||
JOIN
|
||||
|
||||
}
|
||||
|
||||
@@ -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.state;
|
||||
|
||||
/**
|
||||
* {@code PseudoStateListener} for various pseudo state events.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public interface PseudoStateListener<S, E> {
|
||||
|
||||
/**
|
||||
* Called when {@link PseudoState} want to notify of
|
||||
* a new {@link PseudoStateContext}.
|
||||
*
|
||||
* @param context the context
|
||||
*/
|
||||
void onContext(PseudoStateContext<S, E> context);
|
||||
|
||||
}
|
||||
@@ -33,6 +33,8 @@ import org.springframework.statemachine.region.Region;
|
||||
*/
|
||||
public class RegionState<S, E> extends AbstractState<S, E> {
|
||||
|
||||
private JoinPseudoState<S, E> join;
|
||||
|
||||
/**
|
||||
* Instantiates a new region state.
|
||||
*
|
||||
@@ -96,13 +98,13 @@ public class RegionState<S, E> extends AbstractState<S, E> {
|
||||
|
||||
@Override
|
||||
public boolean sendEvent(Message<E> event) {
|
||||
boolean accept = false;
|
||||
if (getRegions() != null) {
|
||||
for (Region<S, E> r : getRegions()) {
|
||||
r.sendEvent(event);
|
||||
accept |= r.sendEvent(event);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return accept;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -121,6 +123,9 @@ public class RegionState<S, E> extends AbstractState<S, E> {
|
||||
|
||||
@Override
|
||||
public void entry(StateContext<S, E> context) {
|
||||
if (join != null) {
|
||||
join.entry(context);
|
||||
}
|
||||
Collection<? extends Action<S, E>> actions = getEntryActions();
|
||||
if (actions != null) {
|
||||
for (Action<S, E> action : actions) {
|
||||
@@ -144,7 +149,9 @@ public class RegionState<S, E> extends AbstractState<S, E> {
|
||||
@Override
|
||||
public Collection<S> getIds() {
|
||||
ArrayList<S> ids = new ArrayList<S>();
|
||||
ids.add(getId());
|
||||
if (getId() != null) {
|
||||
ids.add(getId());
|
||||
}
|
||||
for (Region<S, E> r : getRegions()) {
|
||||
State<S, E> s = r.getState();
|
||||
if (s != null) {
|
||||
@@ -164,4 +171,14 @@ public class RegionState<S, E> extends AbstractState<S, E> {
|
||||
return states;
|
||||
}
|
||||
|
||||
public void setJoin(JoinPseudoState<S, E> join) {
|
||||
this.join = join;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RegionState [getIds()=" + getIds() + ", getClass()=" + getClass() + ", hashCode()=" + hashCode()
|
||||
+ ", toString()=" + super.toString() + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -49,9 +49,12 @@ import org.springframework.statemachine.processor.StateMachineOnTransitionHandle
|
||||
import org.springframework.statemachine.processor.StateMachineRuntime;
|
||||
import org.springframework.statemachine.region.Region;
|
||||
import org.springframework.statemachine.state.AbstractState;
|
||||
import org.springframework.statemachine.state.ForkPseudoState;
|
||||
import org.springframework.statemachine.state.HistoryPseudoState;
|
||||
import org.springframework.statemachine.state.PseudoState;
|
||||
import org.springframework.statemachine.state.PseudoStateContext;
|
||||
import org.springframework.statemachine.state.PseudoStateKind;
|
||||
import org.springframework.statemachine.state.PseudoStateListener;
|
||||
import org.springframework.statemachine.state.State;
|
||||
import org.springframework.statemachine.transition.Transition;
|
||||
import org.springframework.statemachine.transition.TransitionKind;
|
||||
@@ -60,6 +63,7 @@ import org.springframework.statemachine.trigger.TimerTrigger;
|
||||
import org.springframework.statemachine.trigger.Trigger;
|
||||
import org.springframework.statemachine.trigger.TriggerListener;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Base implementation of a {@link StateMachine} loosely modelled from UML state
|
||||
@@ -222,6 +226,7 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
|
||||
return;
|
||||
}
|
||||
registerTriggerListener();
|
||||
registerPseudoStateListener();
|
||||
switchToState(initialState, initialEvent, null, this);
|
||||
// TODO: for now execute outside of switchToState
|
||||
if (initialTransition != null) {
|
||||
@@ -278,6 +283,10 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
|
||||
for (State<S, E> s : all) {
|
||||
buf.append(s.getId() + " ");
|
||||
}
|
||||
buf.append(" / ");
|
||||
if (currentState != null) {
|
||||
buf.append(StringUtils.collectionToCommaDelimitedString(currentState.getIds()));
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
@@ -322,8 +331,13 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
|
||||
if (kind == PseudoStateKind.CHOICE || kind == PseudoStateKind.HISTORY_SHALLOW
|
||||
|| kind == PseudoStateKind.HISTORY_DEEP) {
|
||||
StateContext<S, E> stateContext = buildStateContext(message, transition, stateMachine);
|
||||
State<S, E> toState = state.getPseudoState().entry(message.getPayload(), stateContext);
|
||||
State<S, E> toState = state.getPseudoState().entry(stateContext);
|
||||
setCurrentState(toState, message, transition, true, stateMachine);
|
||||
} else if (kind == PseudoStateKind.FORK) {
|
||||
ForkPseudoState<S, E> fps = (ForkPseudoState<S, E>) state.getPseudoState();
|
||||
for (State<S, E> ss : fps.getForks()) {
|
||||
setCurrentState(ss, message, transition, false, stateMachine);
|
||||
}
|
||||
} else {
|
||||
setCurrentState(state, message, transition, true, stateMachine);
|
||||
}
|
||||
@@ -341,6 +355,32 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
|
||||
}
|
||||
}
|
||||
|
||||
private void registerPseudoStateListener() {
|
||||
for (State<S, E> state : states) {
|
||||
PseudoState<S, E> p = state.getPseudoState();
|
||||
if (p != null) {
|
||||
p.addPseudoStateListener(new PseudoStateListener<S, E>() {
|
||||
@Override
|
||||
public void onContext(PseudoStateContext<S, E> context) {
|
||||
PseudoState<S, E> pseudoState = context.getPseudoState();
|
||||
State<S, E> toState = findStateWithPseudoState(pseudoState);
|
||||
pseudoState.exit(null);
|
||||
switchToState(toState, null, null, AbstractStateMachine.this);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private State<S, E> findStateWithPseudoState(PseudoState<S, E> pseudoState) {
|
||||
for (State<S, E> s : states) {
|
||||
if (s.getPseudoState() == pseudoState) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private StateContext<S, E> buildStateContext(Message<E> message, Transition<S,E> transition, StateMachine<S, E> stateMachine) {
|
||||
E event = message != null ? message.getPayload() : null;
|
||||
MessageHeaders messageHeaders = message != null ? message.getHeaders() : new MessageHeaders(
|
||||
@@ -384,22 +424,48 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
|
||||
exitCurrentState(state, message, transition, stateMachine);
|
||||
}
|
||||
if (currentState == findDeep) {
|
||||
StateMachine<S, E> submachine = ((AbstractState<S, E>)currentState).getSubmachine();
|
||||
if (submachine.getState() == state) {
|
||||
if (currentState == findDeep) {
|
||||
if (isTargetSubOf) {
|
||||
entryToState(currentState, message, transition, stateMachine);
|
||||
|
||||
if (currentState.isSubmachineState()) {
|
||||
StateMachine<S, E> submachine = ((AbstractState<S, E>)currentState).getSubmachine();
|
||||
if (submachine.getState() == state) {
|
||||
if (currentState == findDeep) {
|
||||
if (isTargetSubOf) {
|
||||
entryToState(currentState, message, transition, stateMachine);
|
||||
}
|
||||
currentState = findDeep;
|
||||
((AbstractStateMachine<S, E>)submachine).setCurrentState(state, message, transition, false, stateMachine);
|
||||
return;
|
||||
}
|
||||
currentState = findDeep;
|
||||
((AbstractStateMachine<S, E>)submachine).setCurrentState(state, message, transition, false, stateMachine);
|
||||
return;
|
||||
}
|
||||
} else if (currentState.isOrthogonal()) {
|
||||
Collection<Region<S, E>> regions = ((AbstractState<S, E>)currentState).getRegions();
|
||||
for (Region<S, E> region : regions) {
|
||||
if (region.getState() == state) {
|
||||
if (currentState == findDeep) {
|
||||
if (isTargetSubOf) {
|
||||
entryToState(currentState, message, transition, stateMachine);
|
||||
}
|
||||
currentState = findDeep;
|
||||
((AbstractStateMachine<S, E>)region).setCurrentState(state, message, transition, false, stateMachine);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
currentState = findDeep;
|
||||
entryToState(currentState, message, transition, stateMachine);
|
||||
StateMachine<S, E> submachine = ((AbstractState<S, E>)currentState).getSubmachine();
|
||||
((AbstractStateMachine<S, E>)submachine).setCurrentState(state, message, transition, false, stateMachine);
|
||||
|
||||
if (currentState.isSubmachineState()) {
|
||||
StateMachine<S, E> submachine = ((AbstractState<S, E>)currentState).getSubmachine();
|
||||
((AbstractStateMachine<S, E>)submachine).setCurrentState(state, message, transition, false, stateMachine);
|
||||
} else if (currentState.isOrthogonal()) {
|
||||
Collection<Region<S, E>> regions = ((AbstractState<S, E>)currentState).getRegions();
|
||||
for (Region<S, E> region : regions) {
|
||||
((AbstractStateMachine<S, E>)region).setCurrentState(state, message, transition, false, stateMachine);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (history != null) {
|
||||
@@ -424,33 +490,38 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
|
||||
}
|
||||
}
|
||||
|
||||
private void exitFromState(State<S, E> state, Message<E> message, Transition<S, E> transition, StateMachine<S, E> stateMachine) {
|
||||
private void exitFromState(State<S, E> state, Message<E> message, Transition<S, E> transition,
|
||||
StateMachine<S, E> stateMachine) {
|
||||
if (state == null) {
|
||||
return;
|
||||
}
|
||||
log.trace("Trying Exit state=[" + state + "]");
|
||||
StateContext<S, E> stateContext = buildStateContext(message, transition, stateMachine);
|
||||
|
||||
State<S, E> findDeep = findDeepParent(transition.getTarget());
|
||||
boolean isTargetSubOfOtherState = findDeep != null && findDeep != currentState;
|
||||
boolean isTargetSubOfSource = StateMachineUtils.isSubstate(transition.getSource(), transition.getTarget());
|
||||
boolean isSubOfSource = StateMachineUtils.isSubstate(transition.getSource(), currentState);
|
||||
boolean isSubOfTarget = StateMachineUtils.isSubstate(transition.getTarget(), currentState);
|
||||
if (transition != null) {
|
||||
|
||||
// TODO: this and entry below should be done via a separate
|
||||
// voter of some sort which would reveal transition path
|
||||
// we could make a choice on.
|
||||
if (currentState == transition.getSource() && currentState == transition.getTarget()) {
|
||||
} else if (!isSubOfSource && !isSubOfTarget && currentState == transition.getSource()) {
|
||||
} else if (!isSubOfSource && !isSubOfTarget && currentState == transition.getTarget()) {
|
||||
} else if (isTargetSubOfOtherState) {
|
||||
} else if (!isSubOfSource && !isSubOfTarget && findDeep == null) {
|
||||
} else if (!isSubOfSource && !isSubOfTarget) {
|
||||
return;
|
||||
}
|
||||
State<S, E> findDeep = findDeepParent(transition.getTarget());
|
||||
boolean isTargetSubOfOtherState = findDeep != null && findDeep != currentState;
|
||||
boolean isTargetSubOfSource = StateMachineUtils.isSubstate(transition.getSource(), transition.getTarget());
|
||||
boolean isSubOfSource = StateMachineUtils.isSubstate(transition.getSource(), currentState);
|
||||
boolean isSubOfTarget = StateMachineUtils.isSubstate(transition.getTarget(), currentState);
|
||||
|
||||
// TODO: this and entry below should be done via a separate
|
||||
// voter of some sort which would reveal transition path
|
||||
// we could make a choice on.
|
||||
if (currentState == transition.getSource() && currentState == transition.getTarget()) {
|
||||
} else if (!isSubOfSource && !isSubOfTarget && currentState == transition.getSource()) {
|
||||
} else if (!isSubOfSource && !isSubOfTarget && currentState == transition.getTarget()) {
|
||||
} else if (isTargetSubOfOtherState) {
|
||||
} else if (!isSubOfSource && !isSubOfTarget && findDeep == null) {
|
||||
} else if (!isSubOfSource && !isSubOfTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (transition.getSource() == currentState && isTargetSubOfSource) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (transition.getSource() == currentState && isTargetSubOfSource) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug("Exit state=[" + state + "]");
|
||||
|
||||
@@ -52,8 +52,8 @@ public abstract class StateMachineObjectSupport<S, E> extends LifecycleObjectSup
|
||||
*/
|
||||
protected StateMachineEventPublisher getStateMachineEventPublisher() {
|
||||
if(stateMachineEventPublisher == null && getBeanFactory() != null) {
|
||||
if(log.isDebugEnabled()) {
|
||||
log.debug("getting stateMachineEventPublisher service from bean factory " + getBeanFactory());
|
||||
if(log.isTraceEnabled()) {
|
||||
log.trace("getting stateMachineEventPublisher service from bean factory " + getBeanFactory());
|
||||
}
|
||||
stateMachineEventPublisher = StateMachineContextUtils.getEventPublisher(getBeanFactory());
|
||||
}
|
||||
|
||||
@@ -219,9 +219,7 @@ public class RegionMachineTests extends AbstractStateMachineTests {
|
||||
assertThat(exitActionS112.stateContexts.size(), is(0));
|
||||
}
|
||||
|
||||
// effectively broken now until we get more fixes
|
||||
// due to work with region fork/join
|
||||
//@Test
|
||||
@Test
|
||||
public void testMultiRegion() throws Exception {
|
||||
context.register(BaseConfig.class, StateMachineEventPublisherConfiguration.class, Config1.class);
|
||||
context.refresh();
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.statemachine;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
@@ -100,6 +101,24 @@ public class StateMachineTests extends AbstractStateMachineTests {
|
||||
assertThat(testAction2.stateContexts.size(), is(timedTriggered));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testForkJoin() {
|
||||
context.register(BaseConfig.class, Config3.class);
|
||||
context.refresh();
|
||||
EnumStateMachine<TestStates,TestEvents> machine =
|
||||
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class);
|
||||
assertThat(machine, notNullValue());
|
||||
machine.start();
|
||||
assertThat(machine.getState().getIds(), containsInAnyOrder(TestStates.SI));
|
||||
machine.sendEvent(TestEvents.E1);
|
||||
assertThat(machine.getState().getIds(), containsInAnyOrder(TestStates.S2, TestStates.S20, TestStates.S30));
|
||||
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.S4));
|
||||
}
|
||||
|
||||
private static class LoggingAction implements Action<TestStates, TestEvents> {
|
||||
|
||||
private static final Log log = LogFactory.getLog(StateMachineTests.LoggingAction.class);
|
||||
@@ -237,4 +256,68 @@ public class StateMachineTests extends AbstractStateMachineTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
static class Config3 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(TestStates.SI)
|
||||
.state(TestStates.SI)
|
||||
.fork(TestStates.S1)
|
||||
.state(TestStates.S2)
|
||||
.end(TestStates.SF)
|
||||
.join(TestStates.S3)
|
||||
.state(TestStates.S4)
|
||||
.and()
|
||||
.withStates()
|
||||
.parent(TestStates.S2)
|
||||
.initial(TestStates.S20)
|
||||
.state(TestStates.S20)
|
||||
.state(TestStates.S21)
|
||||
.and()
|
||||
.withStates()
|
||||
.parent(TestStates.S2)
|
||||
.initial(TestStates.S30)
|
||||
.state(TestStates.S30)
|
||||
.state(TestStates.S31);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source(TestStates.SI)
|
||||
.target(TestStates.S2)
|
||||
.event(TestEvents.E1)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(TestStates.S20)
|
||||
.target(TestStates.S21)
|
||||
.event(TestEvents.E2)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(TestStates.S30)
|
||||
.target(TestStates.S31)
|
||||
.event(TestEvents.E3)
|
||||
.and()
|
||||
.withFork()
|
||||
.source(TestStates.S1)
|
||||
.target(TestStates.S20)
|
||||
.target(TestStates.S30)
|
||||
.and()
|
||||
.withJoin()
|
||||
.source(TestStates.S21)
|
||||
.source(TestStates.S31)
|
||||
.target(TestStates.S3)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(TestStates.S3)
|
||||
.target(TestStates.S4);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* 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.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
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.messaging.support.MessageBuilder;
|
||||
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 ForkStateTests extends AbstractStateMachineTests {
|
||||
|
||||
@Override
|
||||
protected AnnotationConfigApplicationContext buildContext() {
|
||||
return new AnnotationConfigApplicationContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testForkEventPassed() {
|
||||
context.register(BaseConfig.class, Config1.class);
|
||||
context.refresh();
|
||||
EnumStateMachine<TestStates,TestEvents> machine =
|
||||
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class);
|
||||
TestEntryAction s20EntryAction = context.getBean("s20EntryAction", TestEntryAction.class);
|
||||
TestEntryAction s21EntryAction = context.getBean("s21EntryAction", TestEntryAction.class);
|
||||
TestEntryAction s30EntryAction = context.getBean("s30EntryAction", TestEntryAction.class);
|
||||
TestEntryAction s31EntryAction = context.getBean("s31EntryAction", TestEntryAction.class);
|
||||
assertThat(machine, notNullValue());
|
||||
machine.start();
|
||||
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).setHeader("foo", "bar").build());
|
||||
|
||||
assertThat(machine.getState().getIds(), containsInAnyOrder(TestStates.S2, TestStates.S21, TestStates.S31));
|
||||
assertThat(s20EntryAction.stateContexts.size(), is(1));
|
||||
assertThat(s21EntryAction.stateContexts.size(), is(1));
|
||||
assertThat(s30EntryAction.stateContexts.size(), is(1));
|
||||
assertThat(s31EntryAction.stateContexts.size(), is(1));
|
||||
assertThat((String)s20EntryAction.stateContexts.get(0).getMessageHeader("foo"), nullValue());
|
||||
assertThat((String)s21EntryAction.stateContexts.get(0).getMessageHeader("foo"), is("bar"));
|
||||
assertThat((String)s30EntryAction.stateContexts.get(0).getMessageHeader("foo"), nullValue());
|
||||
assertThat((String)s31EntryAction.stateContexts.get(0).getMessageHeader("foo"), is("bar"));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
static class Config1 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(TestStates.SI)
|
||||
.fork(TestStates.S1)
|
||||
.state(TestStates.SI)
|
||||
.state(TestStates.S2)
|
||||
.end(TestStates.SF)
|
||||
.and()
|
||||
.withStates()
|
||||
.parent(TestStates.S2)
|
||||
.initial(TestStates.S20)
|
||||
.state(TestStates.S20, s20EntryAction(), null)
|
||||
.state(TestStates.S21, s21EntryAction(), null)
|
||||
.and()
|
||||
.withStates()
|
||||
.parent(TestStates.S2)
|
||||
.initial(TestStates.S30)
|
||||
.state(TestStates.S30, s30EntryAction(), null)
|
||||
.state(TestStates.S31, s31EntryAction(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source(TestStates.SI)
|
||||
.target(TestStates.S1)
|
||||
.event(TestEvents.E1)
|
||||
.and()
|
||||
.withFork()
|
||||
.source(TestStates.S1)
|
||||
.target(TestStates.S21)
|
||||
.target(TestStates.S31);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TestEntryAction s20EntryAction() {
|
||||
return new TestEntryAction();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TestEntryAction s21EntryAction() {
|
||||
return new TestEntryAction();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TestEntryAction s30EntryAction() {
|
||||
return new TestEntryAction();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TestEntryAction s31EntryAction() {
|
||||
return new TestEntryAction();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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.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.Configuration;
|
||||
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 JoinStateTests extends AbstractStateMachineTests {
|
||||
|
||||
@Override
|
||||
protected AnnotationConfigApplicationContext buildContext() {
|
||||
return new AnnotationConfigApplicationContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testJoin() {
|
||||
context.register(BaseConfig.class, 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);
|
||||
machine.sendEvent(TestEvents.E2);
|
||||
machine.sendEvent(TestEvents.E3);
|
||||
|
||||
assertThat(machine.getState().getIds(), contains(TestStates.S4));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
static class Config1 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(TestStates.SI)
|
||||
.state(TestStates.SI)
|
||||
.state(TestStates.S2)
|
||||
.end(TestStates.SF)
|
||||
.join(TestStates.S3)
|
||||
.state(TestStates.S4)
|
||||
.and()
|
||||
.withStates()
|
||||
.parent(TestStates.S2)
|
||||
.initial(TestStates.S20)
|
||||
.state(TestStates.S20)
|
||||
.state(TestStates.S21)
|
||||
.and()
|
||||
.withStates()
|
||||
.parent(TestStates.S2)
|
||||
.initial(TestStates.S30)
|
||||
.state(TestStates.S30)
|
||||
.state(TestStates.S31);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source(TestStates.SI)
|
||||
.target(TestStates.S2)
|
||||
.event(TestEvents.E1)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(TestStates.S20)
|
||||
.target(TestStates.S21)
|
||||
.event(TestEvents.E2)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(TestStates.S30)
|
||||
.target(TestStates.S31)
|
||||
.event(TestEvents.E3)
|
||||
.and()
|
||||
.withJoin()
|
||||
.source(TestStates.S21)
|
||||
.source(TestStates.S31)
|
||||
.target(TestStates.S3)
|
||||
.and()
|
||||
.withExternal()
|
||||
.source(TestStates.S3)
|
||||
.target(TestStates.S4);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,5 +4,5 @@ log4j.appender.stdout=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %t %c{2} [%t] - %m%n
|
||||
|
||||
log4j.category.org.springframework.statemachine=TRACE
|
||||
log4j.category.org.springframework.statemachine=DEBUG
|
||||
|
||||
|
||||
Reference in New Issue
Block a user