package org.squirrelframework.foundation.fsm.impl;
import org.squirrelframework.foundation.fsm.*;
import java.util.List;
class TransitionImpl<T extends StateMachine<T, S, E, C>, S, E, C> implements MutableTransition<T, S, E, C> {
private ImmutableState<T, S, E, C> sourceState;
private ImmutableState<T, S, E, C> targetState;
private E event;
private final Actions<T, S, E, C> actions = FSM.newActions();
private Condition<C> condition = Conditions.always();
private TransitionType type = TransitionType.EXTERNAL;
private int priority;
@Override
public ImmutableState<T, S, E, C> getSourceState() {
return sourceState;
}
@Override
public ImmutableState<T, S, E, C> getTargetState() {
return targetState;
}
@Override
public List<Action<T, S, E, C>> getActions() {
return actions.getAll();
}
@Override
public ImmutableState<T, S, E, C> transit(final StateContext<T, S, E, C> stateContext) {
stateContext.getExecutor().begin("TRANSITION__"+this.toString());
for(final Action<T, S, E, C> action : getActions()) {
stateContext.getExecutor().defer(action,
sourceState.getStateId(), targetState.getStateId(), stateContext.getEvent(),
stateContext.getContext(), stateContext.getStateMachine().getThis());
}
return targetState;
}
@Override
public void setSourceState(ImmutableState<T, S, E, C> state) {
this.sourceState = state;
}
@Override
public void setTargetState(ImmutableState<T, S, E, C> state) {
this.targetState = state;
}
@Override
public void addAction(Action<T, S, E, C> newAction) {
actions.add(newAction);
}
@Override
public void addActions(List<? extends Action<T, S, E, C>> newActions) {
actions.addAll(newActions);
}
@Override
public Condition<C> getCondition() {
return condition;
}
@Override
public void setCondition(Condition<C> condition) {
this.condition = condition;
}
@Override
public E getEvent() {
return event;
}
@Override
public void setEvent(E event) {
this.event = event;
}
@Override
public TransitionType getType() {
return type;
}
@Override
public void setType(TransitionType type) {
this.type = type;
}
@Override
public int getPriority() {
return priority;
}
@Override
public void setPriority(int priority) {
this.priority = priority;
}
private void doTransit(ImmutableState<T, S, E, C> source, ImmutableState<T, S, E, C> target, StateContext<T, S, E, C> stateContext) {
if (target.isChildStateOf(source) && type == TransitionType.EXTERNAL) {
// exit and re-enter current state for external transition to child state
source.exit(stateContext);
source.entry(stateContext);
}
doTransitInternal(source, target, stateContext);
}
/**
* Recursively traverses the state hierarchy, exiting states along the way, performing the action, and entering states to the target.
* <hr>
* There exist the following transition scenarios:
* <ul>
* <li>0. there is no target state (internal transition) --> handled outside this method.</li>
* <li>1. The source and target state are the same (self transition) --> perform the transition directly: Exit source state, perform
* transition actions and enter target state</li>
* <li>2. The target state is a direct or indirect sub-state of the source state --> perform the transition actions, then traverse the
* hierarchy from the source state down to the target state, entering each state along the way. No state is exited.
* <li>3. The source state is a sub-state of the target state --> traverse the hierarchy from the source up to the target, exiting each
* state along the way. Then perform transition actions. Finally enter the target state.</li>
* <li>4. The source and target state share the same super-state</li>
* <li>5. All other scenarios:
* <ul>
* <li>a. The source and target states reside at the same level in the hierarchy but do not share the same direct super-state</li>
* <li>b. The source state is lower in the hierarchy than the target state</li>
* <li>c. The target state is lower in the hierarchy than the source state</li>
* </ul>
* </ul>
*
* @param source the source state
* @param target the target state
* @param stateContext the state context
*/
private void doTransitInternal(ImmutableState<T, S, E, C> source, ImmutableState<T, S, E, C> target, StateContext<T, S, E, C> stateContext) {
if (source == this.getTargetState()) {
// Handles 1.
// Handles 3. after traversing from the source to the target.
if(type==TransitionType.LOCAL) {
// not exit and re-enter the composite (source) state for
// local transition
transit(stateContext);
} else {
source.exit(stateContext);
transit(stateContext);
getTargetState().entry(stateContext);
}
} else if (source == target) {
// Handles 2. after traversing from the target to the source.
transit(stateContext);
} else if (source.getParentState() == target.getParentState()) {
// Handles 4.
// Handles 5a. after traversing the hierarchy until a common ancestor if found.
source.exit(stateContext);
transit(stateContext);
target.entry(stateContext);
} else {
// traverses the hierarchy until one of the above scenarios is met.
if (source.getLevel() > target.getLevel()) {
// Handles 3.
// Handles 5b.
source.exit(stateContext);
doTransitInternal(source.getParentState(), target, stateContext);
} else if (source.getLevel() < target.getLevel()) {
// Handles 2.
// Handles 5c.
doTransitInternal(source, target.getParentState(), stateContext);
target.entry(stateContext);
} else {
// Handles 5a.
source.exit(stateContext);
doTransitInternal(source.getParentState(), target.getParentState(), stateContext);
target.entry(stateContext);
}
}
}
@Override
public void internalFire(StateContext<T, S, E, C> stateContext) {
// Fix issue17
if(type==TransitionType.INTERNAL && stateContext.
getSourceState().getStateId()!=targetState.getStateId()) {
return;
}
if(condition.isSatisfied(stateContext.getContext())) {
ImmutableState<T, S, E, C> newState = stateContext.getSourceState();
if(type==TransitionType.INTERNAL) {
newState = transit(stateContext);
} else {
// exit origin states
unwindSubStates(stateContext.getSourceState(), stateContext);
// perform transition actions
doTransit(getSourceState(), getTargetState(), stateContext);
// enter new states
newState = getTargetState().enterByHistory(stateContext);
}
stateContext.getResult().setAccepted(true).setTargetState(newState);
}
}
private void unwindSubStates(ImmutableState<T, S, E, C> orgState, StateContext<T, S, E, C> stateContext) {
for (ImmutableState<T, S, E, C> state=orgState; state!=getSourceState(); state=state.getParentState()) {
if(state!=null) { state.exit(stateContext); }
}
}
@Override
public void accept(Visitor visitor) {
visitor.visitOnEntry(this);
visitor.visitOnExit(this);
}
@Override
public boolean isMatch(S fromState, S toState, E event, int priority) {
if(toState==null && !getTargetState().isFinalState())
return false;
if(toState!=null && !getTargetState().isFinalState() &&
!getTargetState().getStateId().equals(toState))
return false;
if(!getEvent().equals(event))
return false;
if(getPriority()!=priority)
return false;
return true;
}
@Override
public boolean isMatch(S fromState, S toState, E event, int priority, Class<?> condClazz, TransitionType type) {
if(!isMatch(fromState, toState, event, priority))
return false;
if(getCondition().getClass()!=condClazz)
return false;
if(!getType().equals(type))
return false;
return true;
}
@Override
public final String toString() {
return sourceState + "-[" + event.toString() +", "+
condition.name()+", "+priority+", "+type+"]->" + targetState;
}
@Override
public void verify() {
if(type==TransitionType.INTERNAL && sourceState!=targetState) {
throw new RuntimeException(String.format("Internal transition source state '%s' " +
"and target state '%s' must be same.", sourceState, targetState));
}
}
}