Add proper substate handling for transitions

- Now you can define transition in all states
  and if substate guard prevents transtition to
  happen it will be checked from super state.
This commit is contained in:
Janne Valkealahti
2015-03-28 08:43:24 +00:00
parent 4bf49654b9
commit d108410005
4 changed files with 81 additions and 20 deletions

View File

@@ -27,6 +27,7 @@ import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.apache.commons.logging.Log;
@@ -55,6 +56,7 @@ import org.springframework.statemachine.state.State;
import org.springframework.statemachine.transition.Transition;
import org.springframework.statemachine.transition.TransitionKind;
import org.springframework.statemachine.trigger.DefaultTriggerContext;
import org.springframework.statemachine.trigger.EventTrigger;
import org.springframework.statemachine.trigger.TimerTrigger;
import org.springframework.statemachine.trigger.Trigger;
import org.springframework.statemachine.trigger.TriggerListener;
@@ -200,6 +202,7 @@ public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport
for (Transition<S, E> transition : transitions) {
Trigger<S, E> trigger = transition.getTrigger();
if (trigger != null) {
// we have same triggers with different transitions
triggerToTransitionMap.put(trigger, transition);
}
}
@@ -435,31 +438,71 @@ public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport
log.debug("Process trigger queue");
TriggerQueueItem queueItem = null;
while ((queueItem = triggerQueue.poll()) != null) {
if (currentState == null) {
continue;
}
Message<E> queuedEvent = queueItem.message;
Transition<S, E> transition = triggerToTransitionMap.get(queueItem.trigger);
StateContext<S, E> stateContext = new DefaultStateContext<S, E>(queuedEvent != null ? queuedEvent.getHeaders() : null, extendedState, transition, this);
if (transition == null) {
continue;
}
E event = queuedEvent != null ? queuedEvent.getPayload() : null;
State<S,E> source = transition.getSource();
if (source == null || currentState == null) {
continue;
}
if (!StateMachineUtils.containsAtleastOne(source.getIds(), currentState.getIds())) {
continue;
}
// need all transitions trigger could match, event trigger may match multiple
// need to go up from substates and ask if trigger transit, if not check super
ArrayList<Transition<S, E>> trans = new ArrayList<Transition<S,E>>();
notifyTransitionStart(transition);
callHandlers(transition.getSource(), transition.getTarget(), queuedEvent);
boolean transit = transition.transit(stateContext);
if (transit) {
if (transition.getKind() != TransitionKind.INTERNAL) {
switchToState(transition.getTarget(), queuedEvent, transition);
if (event != null) {
ArrayList<S> ids = new ArrayList<S>(currentState.getIds());
Collections.reverse(ids);
for (S id : ids) {
for (Entry<Trigger<S, E>, Transition<S, E>> e : triggerToTransitionMap.entrySet()) {
Trigger<S, E> tri = e.getKey();
E ee = tri.getEvent();
Transition<S, E> tra = e.getValue();
if (event == ee) {
if (tra.getSource().getId() == id && !trans.contains(tra)) {
trans.add(tra);
continue;
}
}
}
}
notifyTransition(transition);
}
notifyTransitionEnd(transition);
// most likely timer
if (trans.isEmpty()) {
trans.add(triggerToTransitionMap.get(queueItem.trigger));
}
// go through candidates and transit max one
for (Transition<S, E> t : trans) {
StateContext<S, E> stateContext = new DefaultStateContext<S, E>(
queuedEvent != null ? queuedEvent.getHeaders() : null, extendedState, t, this);
if (t == null) {
continue;
}
State<S,E> source = t.getSource();
if (source == null) {
continue;
}
if (!StateMachineUtils.containsAtleastOne(source.getIds(), currentState.getIds())) {
continue;
}
boolean transit = t.transit(stateContext);
if (transit) {
// TODO: should change trasition api so that we can ask
// if transition will transit so that we can post
// accurate notifyTransitionStart
notifyTransitionStart(t);
callHandlers(t.getSource(), t.getTarget(), queuedEvent);
if (t.getKind() != TransitionKind.INTERNAL) {
switchToState(t.getTarget(), queuedEvent, t);
}
notifyTransition(t);
notifyTransitionEnd(t);
break;
}
}
}
}

View File

@@ -35,4 +35,9 @@ public class EventTrigger<S, E> implements Trigger<S, E> {
// no-opt
}
@Override
public E getEvent() {
return event;
}
}

View File

@@ -41,6 +41,11 @@ public class TimerTrigger<S, E> extends LifecycleObjectSupport implements Trigge
triggerListener.register(listener);
}
@Override
public E getEvent() {
return null;
}
@Override
protected void doStart() {
scheduled = getTaskScheduler().scheduleAtFixedRate(new Runnable() {

View File

@@ -43,4 +43,12 @@ public interface Trigger<S,E> {
*/
void addTriggerListener(TriggerListener listener);
/**
* Gets the event associated with this trigger. It is possible that there
* are no event association.
*
* @return the event
*/
E getEvent();
}