Add base support for exit/entry points

- New concepts of using exit and entry points
  to transition between sub-states.
- Add hooks to annotation config model.
- Relates to #192
This commit is contained in:
Janne Valkealahti
2016-04-09 16:38:41 +01:00
parent 51145f1368
commit ba1729c928
23 changed files with 863 additions and 32 deletions

View File

@@ -38,6 +38,8 @@ import org.springframework.statemachine.access.StateMachineAccess;
import org.springframework.statemachine.access.StateMachineFunction;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.config.model.ChoiceData;
import org.springframework.statemachine.config.model.EntryData;
import org.springframework.statemachine.config.model.ExitData;
import org.springframework.statemachine.config.model.StateData;
import org.springframework.statemachine.config.model.StateMachineModel;
import org.springframework.statemachine.config.model.TransitionData;
@@ -52,6 +54,8 @@ import org.springframework.statemachine.state.AbstractState;
import org.springframework.statemachine.state.ChoicePseudoState;
import org.springframework.statemachine.state.ChoicePseudoState.ChoiceStateData;
import org.springframework.statemachine.state.DefaultPseudoState;
import org.springframework.statemachine.state.EntryPseudoState;
import org.springframework.statemachine.state.ExitPseudoState;
import org.springframework.statemachine.state.ForkPseudoState;
import org.springframework.statemachine.state.HistoryPseudoState;
import org.springframework.statemachine.state.JoinPseudoState;
@@ -59,6 +63,7 @@ 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.StateHolder;
import org.springframework.statemachine.state.StateMachineState;
import org.springframework.statemachine.support.DefaultExtendedState;
import org.springframework.statemachine.support.LifecycleObjectSupport;
@@ -141,6 +146,7 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
Stack<MachineStackItem<S, E>> regionStack = new Stack<MachineStackItem<S, E>>();
Stack<StateData<S, E>> stateStack = new Stack<StateData<S, E>>();
Map<Object, StateMachine<S, E>> machineMap = new HashMap<Object, StateMachine<S,E>>();
Map<S, StateHolder<S, E>> holderMap = new HashMap<S, StateHolder<S, E>>();
Iterator<Node<StateData<S, E>>> iterator = buildStateDataIterator();
while (iterator.hasNext()) {
@@ -176,7 +182,7 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
if (initialCount > 1) {
for (Collection<StateData<S, E>> regionStateDatas : regionsStateDatas) {
machine = buildMachine(machineMap, stateMap, regionStateDatas, transitionsData, resolveBeanFactory(),
machine = buildMachine(machineMap, stateMap, holderMap, regionStateDatas, transitionsData, resolveBeanFactory(),
contextEvents, defaultExtendedState, stateMachineModel.getTransitionsData(), resolveTaskExecutor(),
resolveTaskScheduler(), machineId);
regionStack.push(new MachineStackItem<S, E>(machine));
@@ -204,7 +210,7 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
machine = m;
}
} else {
machine = buildMachine(machineMap, stateMap, stateDatas, transitionsData, resolveBeanFactory(), contextEvents,
machine = buildMachine(machineMap, stateMap, holderMap, stateDatas, transitionsData, resolveBeanFactory(), contextEvents,
defaultExtendedState, stateMachineModel.getTransitionsData(), resolveTaskExecutor(), resolveTaskScheduler(),
machineId);
if (peek.isInitial() || (!peek.isInitial() && !machineMap.containsKey(peek.getParent()))) {
@@ -261,6 +267,12 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
machine.addStateListener(listener);
}
// go through holders and fix state references which
// were not known at a time holder was created
for (Entry<S, StateHolder<S, E>> holder : holderMap.entrySet()) {
holder.getValue().setState(stateMap.get(holder.getKey()));
}
return delegateAutoStartup(machine);
}
@@ -397,12 +409,10 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
}
private StateMachine<S, E> buildMachine(
Map<Object, StateMachine<S, E>> machineMap, Map<S, State<S, E>> stateMap,
Collection<StateData<S, E>> stateDatas, Collection<TransitionData<S, E>> transitionsData,
private StateMachine<S, E> buildMachine(Map<Object, StateMachine<S, E>> machineMap, Map<S, State<S, E>> stateMap,
Map<S, StateHolder<S, E>> holderMap, Collection<StateData<S, E>> stateDatas, Collection<TransitionData<S, E>> transitionsData,
BeanFactory beanFactory, Boolean contextEvents, DefaultExtendedState defaultExtendedState,
TransitionsData<S, E> stateMachineTransitions, TaskExecutor taskExecutor,
TaskScheduler taskScheduler, String machineId) {
TransitionsData<S, E> stateMachineTransitions, TaskExecutor taskExecutor, TaskScheduler taskScheduler, String machineId) {
State<S, E> state = null;
State<S, E> initialState = null;
PseudoState<S, E> historyState = null;
@@ -456,6 +466,10 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
continue;
} else if (stateData.getPseudoStateKind() == PseudoStateKind.CHOICE) {
continue;
} else if (stateData.getPseudoStateKind() == PseudoStateKind.ENTRY) {
continue;
} else if (stateData.getPseudoStateKind() == PseudoStateKind.EXIT) {
continue;
}
state = buildStateInternal(stateData.getState(), stateData.getDeferred(), stateData.getEntryActions(),
stateData.getExitActions(), pseudoState);
@@ -482,6 +496,36 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
stateData.getExitActions(), pseudoState);
states.add(state);
stateMap.put(stateData.getState(), state);
} else if (stateData.getPseudoStateKind() == PseudoStateKind.ENTRY) {
S s = stateData.getState();
Collection<EntryData<S, E>> entrys = stateMachineTransitions.getEntrys();
for (EntryData<S, E> entry : entrys) {
if (s.equals(entry.getSource())) {
PseudoState<S, E> pseudoState = new EntryPseudoState<S, E>(stateMap.get(entry.getTarget()));
state = buildStateInternal(stateData.getState(), stateData.getDeferred(), stateData.getEntryActions(),
stateData.getExitActions(), pseudoState);
states.add(state);
stateMap.put(stateData.getState(), state);
break;
}
}
} else if (stateData.getPseudoStateKind() == PseudoStateKind.EXIT) {
S s = stateData.getState();
Collection<ExitData<S, E>> exits = stateMachineTransitions.getExits();
for (ExitData<S, E> entry : exits) {
if (s.equals(entry.getSource())) {
StateHolder<S, E> holder = new StateHolder<S, E>(stateMap.get(entry.getTarget()));
if (holder.getState() == null) {
holderMap.put(entry.getTarget(), holder);
}
PseudoState<S, E> pseudoState = new ExitPseudoState<S, E>(holder);
state = buildStateInternal(stateData.getState(), stateData.getDeferred(), stateData.getEntryActions(),
stateData.getExitActions(), pseudoState);
states.add(state);
stateMap.put(stateData.getState(), state);
break;
}
}
} else if (stateData.getPseudoStateKind() == PseudoStateKind.FORK) {
S s = stateData.getState();
List<S> list = stateMachineTransitions.getForks().get(s);

View File

@@ -27,11 +27,15 @@ import org.springframework.statemachine.config.common.annotation.AnnotationBuild
import org.springframework.statemachine.config.common.annotation.ObjectPostProcessor;
import org.springframework.statemachine.config.configurers.ChoiceTransitionConfigurer;
import org.springframework.statemachine.config.configurers.DefaultChoiceTransitionConfigurer;
import org.springframework.statemachine.config.configurers.DefaultEntryTransitionConfigurer;
import org.springframework.statemachine.config.configurers.DefaultExitTransitionConfigurer;
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.EntryTransitionConfigurer;
import org.springframework.statemachine.config.configurers.ExitTransitionConfigurer;
import org.springframework.statemachine.config.configurers.ExternalTransitionConfigurer;
import org.springframework.statemachine.config.configurers.ForkTransitionConfigurer;
import org.springframework.statemachine.config.configurers.InternalTransitionConfigurer;
@@ -39,6 +43,8 @@ import org.springframework.statemachine.config.configurers.JoinTransitionConfigu
import org.springframework.statemachine.config.configurers.LocalTransitionConfigurer;
import org.springframework.statemachine.config.model.ChoiceData;
import org.springframework.statemachine.config.model.ConfigurationData;
import org.springframework.statemachine.config.model.EntryData;
import org.springframework.statemachine.config.model.ExitData;
import org.springframework.statemachine.config.model.TransitionsData;
import org.springframework.statemachine.config.model.TransitionData;
import org.springframework.statemachine.guard.Guard;
@@ -62,23 +68,39 @@ public class StateMachineTransitionBuilder<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>>();
private final Collection<EntryData<S, E>> entryData = new ArrayList<EntryData<S, E>>();
private final Collection<ExitData<S, E>> exitData = new ArrayList<ExitData<S, E>>();
/**
* Instantiates a new state machine transition builder.
*/
public StateMachineTransitionBuilder() {
super();
}
/**
* Instantiates a new state machine transition builder.
*
* @param objectPostProcessor the object post processor
* @param allowConfigurersOfSameType the allow configurers of same type
*/
public StateMachineTransitionBuilder(ObjectPostProcessor<Object> objectPostProcessor,
boolean allowConfigurersOfSameType) {
super(objectPostProcessor, allowConfigurersOfSameType);
}
/**
* Instantiates a new state machine transition builder.
*
* @param objectPostProcessor the object post processor
*/
public StateMachineTransitionBuilder(ObjectPostProcessor<Object> objectPostProcessor) {
super(objectPostProcessor);
}
@Override
protected TransitionsData<S, E> performBuild() throws Exception {
return new TransitionsData<S, E>(transitionData, choices, forks, joins);
return new TransitionsData<S, E>(transitionData, choices, forks, joins, entryData, exitData);
}
@Override
@@ -111,7 +133,31 @@ public class StateMachineTransitionBuilder<S, E>
return apply(new DefaultJoinTransitionConfigurer<S, E>());
}
public void add(S source, S target, S state, E event, Long period, Integer count, Collection<Action<S, E>> actions,
@Override
public EntryTransitionConfigurer<S, E> withEntry() throws Exception {
return apply(new DefaultEntryTransitionConfigurer<S, E>());
}
@Override
public ExitTransitionConfigurer<S, E> withExit() throws Exception {
return apply(new DefaultExitTransitionConfigurer<S, E>());
}
/**
* Adds the transition.
*
* @param source the source
* @param target the target
* @param state the state
* @param event the event
* @param period the period
* @param count the count
* @param actions the actions
* @param guard the guard
* @param kind the kind
* @param securityRule the security rule
*/
public void addTransition(S source, S target, S state, E event, Long period, Integer count, Collection<Action<S, E>> actions,
Guard<S, E> guard, TransitionKind kind, SecurityRule securityRule) {
// if rule not given, get it from global
if (securityRule == null) {
@@ -122,16 +168,53 @@ public class StateMachineTransitionBuilder<S, E>
transitionData.add(new TransitionData<S, E>(source, target, state, event, period, count, actions, guard, kind, securityRule));
}
public void add(S source, List<ChoiceData<S, E>> choices) {
/**
* Adds the choice.
*
* @param source the source
* @param choices the choices
*/
public void addChoice(S source, List<ChoiceData<S, E>> choices) {
this.choices.put(source, choices);
}
/**
* Adds the entry.
*
* @param source the source
* @param target the target
*/
public void addEntry(S source, S target) {
this.entryData.add(new EntryData<S, E>(source, target));
}
/**
* Adds the exit.
*
* @param source the source
* @param target the target
*/
public void addExit(S source, S target) {
this.exitData.add(new ExitData<S, E>(source, target));
}
/**
* Adds the fork.
*
* @param source the source
* @param targets the targets
*/
public void addFork(S source, List<S> targets) {
this.forks.put(source, targets);
}
/**
* Adds the join.
*
* @param target the target
* @param sources the sources
*/
public void addJoin(S target, List<S> sources) {
this.joins.put(target, sources);
}
}

View File

@@ -16,6 +16,8 @@
package org.springframework.statemachine.config.builders;
import org.springframework.statemachine.config.configurers.ChoiceTransitionConfigurer;
import org.springframework.statemachine.config.configurers.EntryTransitionConfigurer;
import org.springframework.statemachine.config.configurers.ExitTransitionConfigurer;
import org.springframework.statemachine.config.configurers.ExternalTransitionConfigurer;
import org.springframework.statemachine.config.configurers.ForkTransitionConfigurer;
import org.springframework.statemachine.config.configurers.InternalTransitionConfigurer;
@@ -88,4 +90,19 @@ public interface StateMachineTransitionConfigurer<S, E> {
*/
JoinTransitionConfigurer<S, E> withJoin() throws Exception;
/**
* Gets a configurer for transition from an entrypoint pseudostate.
*
* @return {@link EntryTransitionConfigurer} for chaining
* @throws Exception if configuration error happens
*/
EntryTransitionConfigurer<S, E> withEntry() throws Exception;
/**
* Gets a configurer for transition from an exitpoint pseudostate.
*
* @return {@link ExitTransitionConfigurer} for chaining
* @throws Exception if configuration error happens
*/
ExitTransitionConfigurer<S, E> withExit() throws Exception;
}

View File

@@ -52,7 +52,7 @@ public class DefaultChoiceTransitionConfigurer<S, E>
if (last != null) {
choices.add(last);
}
builder.add(source, choices);
builder.addChoice(source, choices);
}
@Override

View File

@@ -0,0 +1,54 @@
/*
* 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.config.configurers;
import org.springframework.statemachine.config.builders.StateMachineTransitionBuilder;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter;
import org.springframework.statemachine.config.model.TransitionsData;
/**
* Default implementation of a {@link EntryTransitionConfigurer}.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
public class DefaultEntryTransitionConfigurer<S, E>
extends AnnotationConfigurerAdapter<TransitionsData<S, E>, StateMachineTransitionConfigurer<S, E>, StateMachineTransitionBuilder<S, E>>
implements EntryTransitionConfigurer<S, E> {
private S source;
private S target;
@Override
public void configure(StateMachineTransitionBuilder<S, E> builder) throws Exception {
builder.addEntry(source, target);
}
@Override
public EntryTransitionConfigurer<S, E> source(S source) {
this.source = source;
return this;
}
@Override
public EntryTransitionConfigurer<S, E> target(S target) {
this.target = target;
return this;
}
}

View File

@@ -0,0 +1,54 @@
/*
* 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.config.configurers;
import org.springframework.statemachine.config.builders.StateMachineTransitionBuilder;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter;
import org.springframework.statemachine.config.model.TransitionsData;
/**
* Default implementation of a {@link ExitTransitionConfigurer}.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
public class DefaultExitTransitionConfigurer<S, E>
extends AnnotationConfigurerAdapter<TransitionsData<S, E>, StateMachineTransitionConfigurer<S, E>, StateMachineTransitionBuilder<S, E>>
implements ExitTransitionConfigurer<S, E> {
private S source;
private S target;
@Override
public void configure(StateMachineTransitionBuilder<S, E> builder) throws Exception {
builder.addExit(source, target);
}
@Override
public ExitTransitionConfigurer<S, E> source(S source) {
this.source = source;
return this;
}
@Override
public ExitTransitionConfigurer<S, E> target(S target) {
this.target = target;
return this;
}
}

View File

@@ -38,7 +38,7 @@ public class DefaultExternalTransitionConfigurer<S, E> extends AbstractTransitio
@Override
public void configure(StateMachineTransitionBuilder<S, E> builder) throws Exception {
builder.add(getSource(), getTarget(), getState(), getEvent(), getPeriod(), getCount(), getActions(), getGuard(), TransitionKind.EXTERNAL,
builder.addTransition(getSource(), getTarget(), getState(), getEvent(), getPeriod(), getCount(), getActions(), getGuard(), TransitionKind.EXTERNAL,
getSecurityRule());
}

View File

@@ -38,7 +38,7 @@ public class DefaultInternalTransitionConfigurer<S, E> extends AbstractTransitio
@Override
public void configure(StateMachineTransitionBuilder<S, E> builder) throws Exception {
builder.add(getSource(), getTarget(), getState(), getEvent(), getPeriod(), getCount(), getActions(), getGuard(), TransitionKind.INTERNAL,
builder.addTransition(getSource(), getTarget(), getState(), getEvent(), getPeriod(), getCount(), getActions(), getGuard(), TransitionKind.INTERNAL,
getSecurityRule());
}

View File

@@ -37,7 +37,7 @@ public class DefaultLocalTransitionConfigurer<S, E> extends AbstractTransitionCo
@Override
public void configure(StateMachineTransitionBuilder<S, E> builder) throws Exception {
builder.add(getSource(), getTarget(), getState(), getEvent(), getPeriod(), getCount(), getActions(), getGuard(), TransitionKind.LOCAL,
builder.addTransition(getSource(), getTarget(), getState(), getEvent(), getPeriod(), getCount(), getActions(), getGuard(), TransitionKind.LOCAL,
getSecurityRule());
}

View File

@@ -44,26 +44,18 @@ public class DefaultStateConfigurer<S, E>
implements StateConfigurer<S, E> {
private Object parent;
private final Object region = UUID.randomUUID().toString();
private final Map<S, StateData<S, E>> incomplete = new HashMap<S, StateData<S, E>>();
private S initialState;
private Action<S, E> initialAction;
private S end;
private S history;
private History historyType;
private final Collection<S> choices = new ArrayList<S>();
private final Collection<S> forks = new ArrayList<S>();
private final Collection<S> joins = new ArrayList<S>();
private final Collection<S> exits = new ArrayList<S>();
private final Collection<S> entrys = new ArrayList<S>();
@Override
public void configure(StateMachineStateBuilder<S, E> builder) throws Exception {
@@ -86,6 +78,10 @@ public class DefaultStateConfigurer<S, E>
s.setPseudoStateKind(PseudoStateKind.FORK);
} else if (joins.contains(s.getState())) {
s.setPseudoStateKind(PseudoStateKind.JOIN);
} else if (entrys.contains(s.getState())) {
s.setPseudoStateKind(PseudoStateKind.ENTRY);
} else if (exits.contains(s.getState())) {
s.setPseudoStateKind(PseudoStateKind.EXIT);
}
if (s.getState() == history) {
if (History.SHALLOW == historyType) {
@@ -199,6 +195,20 @@ public class DefaultStateConfigurer<S, E>
return this;
}
@Override
public StateConfigurer<S, E> entry(S entry) {
state(entry);
entrys.add(entry);
return this;
}
@Override
public StateConfigurer<S, E> exit(S exit) {
state(exit);
exits.add(exit);
return this;
}
private void addIncomplete(Object parent, S state, Collection<E> deferred,
Collection<? extends Action<S, E>> entryActions, Collection<? extends Action<S, E>> exitActions) {
StateData<S, E> stateData = incomplete.get(state);

View File

@@ -0,0 +1,49 @@
/*
* 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.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}
* with an entrypoint pseudo state.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
public interface EntryTransitionConfigurer<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
*/
EntryTransitionConfigurer<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
*/
EntryTransitionConfigurer<S, E> target(S target);
}

View File

@@ -0,0 +1,49 @@
/*
* 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.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}
* with an exitpoint pseudo state.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
public interface ExitTransitionConfigurer<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
*/
ExitTransitionConfigurer<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
*/
ExitTransitionConfigurer<S, E> target(S target);
}

View File

@@ -149,6 +149,22 @@ public interface StateConfigurer<S, E> extends
*/
StateConfigurer<S, E> history(S history, History type);
/**
* Specify a state {@code S} to be entrypoint pseudo state.
*
* @param entry the entrypoint pseudo state
* @return configurer for chaining
*/
StateConfigurer<S, E> entry(S entry);
/**
* Specify a state {@code S} to be exitpoint pseudo state.
*
* @param exit the exitpoint pseudo state
* @return configurer for chaining
*/
StateConfigurer<S, E> exit(S exit);
/**
* Enumeration of a possible history pseudostate type.
*/
@@ -166,5 +182,4 @@ public interface StateConfigurer<S, E> extends
*/
DEEP
}
}

View File

@@ -0,0 +1,56 @@
/*
* 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.config.model;
/**
* A simple data object keeping entrypoint related configs in a same place.
*
* @param <S> the type of state
* @param <E> the type of event
*/
public class EntryData<S, E> {
private final S source;
private final S target;
/**
* Instantiates a new entry data.
*
* @param source the source
* @param target the target
*/
public EntryData(S source, S target) {
this.source = source;
this.target = target;
}
/**
* Gets the source.
*
* @return the source
*/
public S getSource() {
return source;
}
/**
* Gets the target.
*
* @return the target
*/
public S getTarget() {
return target;
}
}

View File

@@ -0,0 +1,56 @@
/*
* 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.config.model;
/**
* A simple data object keeping exitpoint related configs in a same place.
*
* @param <S> the type of state
* @param <E> the type of event
*/
public class ExitData<S, E> {
private final S source;
private final S target;
/**
* Instantiates a new exit data.
*
* @param source the source
* @param target the target
*/
public ExitData(S source, S target) {
this.source = source;
this.target = target;
}
/**
* Gets the source.
*
* @return the source
*/
public S getSource() {
return source;
}
/**
* Gets the target.
*
* @return the target
*/
public S getTarget() {
return target;
}
}

View File

@@ -33,6 +33,8 @@ public class TransitionsData<S, E> {
private final Map<S, List<ChoiceData<S, E>>> choices;
private final Map<S, List<S>> forks;
private final Map<S, List<S>> joins;
private final Collection<EntryData<S, E>> entrys;
private final Collection<ExitData<S, E>> exits;
/**
* Instantiates a new transitions data.
@@ -40,7 +42,7 @@ public class TransitionsData<S, E> {
* @param transitionsData the transitions data
*/
public TransitionsData(Collection<TransitionData<S, E>> transitionsData) {
this(transitionsData, null, null, null);
this(transitionsData, null, null, null, null, null);
}
/**
@@ -50,13 +52,17 @@ public class TransitionsData<S, E> {
* @param choices the choices
* @param forks the forks
* @param joins the joins
* @param entrys the entrys
* @param exits the exits
*/
public TransitionsData(Collection<TransitionData<S, E>> transitionsData,
Map<S, List<ChoiceData<S, E>>> choices, Map<S, List<S>> forks, Map<S, List<S>> joins) {
public TransitionsData(Collection<TransitionData<S, E>> transitionsData, Map<S, List<ChoiceData<S, E>>> choices, Map<S, List<S>> forks,
Map<S, List<S>> joins, Collection<EntryData<S, E>> entrys, Collection<ExitData<S, E>> exits) {
this.transitions = transitionsData;
this.choices = choices;
this.forks = forks;
this.joins = joins;
this.entrys = entrys;
this.exits = exits;
}
/**
@@ -95,4 +101,21 @@ public class TransitionsData<S, E> {
return joins;
}
/**
* Gets the entrys.
*
* @return the entrys
*/
public Collection<EntryData<S, E>> getEntrys() {
return entrys;
}
/**
* Gets the exits.
*
* @return the exits
*/
public Collection<ExitData<S, E>> getExits() {
return exits;
}
}

View File

@@ -0,0 +1,59 @@
/*
* 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 org.springframework.statemachine.StateContext;
/**
* Entrypoint implementation of a {@link PseudoState}.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
public class EntryPseudoState<S, E> implements PseudoState<S, E> {
private final State<S, E> state;
/**
* Instantiates a new entry pseudo state.
*
* @param state the state
*/
public EntryPseudoState(State<S, E> state) {
this.state = state;
}
@Override
final public PseudoStateKind getKind() {
return PseudoStateKind.ENTRY;
}
@Override
public State<S, E> entry(StateContext<S, E> context) {
return state;
}
@Override
public void exit(StateContext<S, E> context) {
}
@Override
public void addPseudoStateListener(PseudoStateListener<S, E> listener) {
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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 org.springframework.statemachine.StateContext;
import org.springframework.util.Assert;
/**
* Exitpoint implementation of a {@link PseudoState}.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
public class ExitPseudoState<S, E> implements PseudoState<S, E> {
private StateHolder<S, E> state;
/**
* Instantiates a new exit pseudo state.
*
* @param state the state
*/
public ExitPseudoState(StateHolder<S, E> state) {
Assert.notNull(state, "Holder must be set");
this.state = state;
}
@Override
final public PseudoStateKind getKind() {
return PseudoStateKind.EXIT;
}
@Override
public State<S, E> entry(StateContext<S, E> context) {
return state.getState();
}
@Override
public void exit(StateContext<S, E> context) {
}
@Override
public void addPseudoStateListener(PseudoStateListener<S, E> listener) {
}
/**
* Gets the state holder.
*
* @return the state holder
*/
public StateHolder<S, E> getStateHolder() {
return state;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* Copyright 2015-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.
@@ -43,6 +43,11 @@ public enum PseudoStateKind {
FORK,
/** Join kind */
JOIN
JOIN,
/** Entrypoint kind */
ENTRY,
/** Exitpoint kind */
EXIT
}

View File

@@ -0,0 +1,56 @@
/*
* 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;
/**
* Utility class using holder pattern to keep a {@link State} reference.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
public class StateHolder<S, E> {
private State<S, E> state;
/**
* Instantiates a new state holder.
*
* @param state the state
*/
public StateHolder(State<S, E> state) {
this.state = state;
}
/**
* Gets the state.
*
* @return the state
*/
public State<S, E> getState() {
return state;
}
/**
* Sets the state.
*
* @param state the state
*/
public void setState(State<S, E> state) {
this.state = state;
}
}

View File

@@ -733,6 +733,14 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
callPreStateChangeInterceptors(toState, message, transition, stateMachine);
}
setCurrentState(toState, message, transition, true, stateMachine);
} else if (kind == PseudoStateKind.ENTRY) {
StateContext<S, E> stateContext = buildStateContext(Stage.STATE_CHANGED, message, transition, stateMachine);
State<S, E> toState = state.getPseudoState().entry(stateContext);
setCurrentState(toState, message, transition, true, stateMachine);
} else if (kind == PseudoStateKind.EXIT) {
StateContext<S, E> stateContext = buildStateContext(Stage.STATE_CHANGED, message, transition, stateMachine);
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();

View File

@@ -76,7 +76,7 @@ public class StateMachineModelTests {
Map<String, List<ChoiceData<String, String>>> choices = new HashMap<>();
Map<String, List<String>> forks = new HashMap<>();
Map<String, List<String>> joins = new HashMap<>();
TransitionsData<String, String> transitionsData = new TransitionsData<>(transitions, choices, forks, joins);
TransitionsData<String, String> transitionsData = new TransitionsData<>(transitions, choices, forks, joins, null, null);
StateMachineModel<String, String> stateMachineModel = new DefaultStateMachineModel<>(configurationData, statesData, transitionsData);
ObjectStateMachineFactory<String, String> factory = new ObjectStateMachineFactory<>(stateMachineModel);

View File

@@ -0,0 +1,124 @@
/*
* 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.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.StateMachine;
import org.springframework.statemachine.StateMachineSystemConstants;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
public class ExitEntryStateTests extends AbstractStateMachineTests {
@SuppressWarnings("unchecked")
@Test
public void testSimpleEntryExit() {
context.register(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(), contains("S1"));
machine.sendEvent("ENTRY1");
assertThat(machine.getState().getIds(), contains("S2", "S22"));
machine.sendEvent("EXIT1");
assertThat(machine.getState().getIds(), contains("S4"));
}
@Configuration
@EnableStateMachine
static class Config1 extends StateMachineConfigurerAdapter<String, String> {
@Override
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
// entry point takes to S22 instead of initial S21
// exit point takes to S4 instead from S2 to S3
states
.withStates()
.initial("S1")
.state("S2")
.state("S3")
.state("S4")
.state("S5")
.and()
.withStates()
.parent("S2")
.initial("S21")
.entry("S2ENTRY1")
.entry("S2ENTRY2")
.exit("S2EXIT1")
.exit("S2EXIT2")
.state("S22")
.state("S23");
}
@Override
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
transitions
.withExternal()
.source("S1").target("S2")
.event("E1")
.and()
.withExternal()
.source("S2").target("S3")
.event("E2")
.and()
.withExternal()
.source("S1").target("S2ENTRY1")
.event("ENTRY1")
.and()
.withExternal()
.source("S1").target("S2ENTRY2")
.event("ENTRY2")
.and()
.withExternal()
.source("S22").target("S2EXIT1")
.event("EXIT1")
.and()
.withExternal()
.source("S22").target("S2EXIT2")
.event("EXIT2")
.and()
.withEntry()
.source("S2ENTRY1").target("S22")
.and()
.withEntry()
.source("S2ENTRY2").target("S23")
.and()
.withExit()
.source("S2EXIT1").target("S4")
.and()
.withExit()
.source("S2EXIT2").target("S5");
}
}
@Override
protected AnnotationConfigApplicationContext buildContext() {
return new AnnotationConfigApplicationContext();
}
}