
org.squirrelframework.foundation.fsm.impl.StateImpl Maven / Gradle / Ivy
package org.squirrelframework.foundation.fsm.impl;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.squirrelframework.foundation.fsm.*;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
/**
* The state model of the state machine implementation.
*
* @author Henry.He
*
* @param The type of implemented state machine
* @param The type of implemented state
* @param The type of implemented event
* @param The type of implemented context
*/
class StateImpl, S, E, C> implements MutableState {
private static final Logger logger = LoggerFactory.getLogger(StateImpl.class);
protected final S stateId;
protected final Actions entryActions = FSM.newActions();
protected final Actions exitActions = FSM.newActions();
private LinkedListMultimap> transitions;
private Set acceptableEvents;
/**
* The super-state of this state. Null for states with level
equal to 1.
*/
private MutableState parentState;
private List> childStates;
/**
* The initial child state of this state.
*/
private MutableState childInitialState;
/**
* The HistoryType of this state.
*/
private HistoryType historyType = HistoryType.NONE;
/**
* The level of this state within the state hierarchy [1..maxLevel].
*/
private int level = 0;
/**
* Whether the state is a final state
*/
private boolean isFinalState = false;
/**
* Composite type of child states
*/
private StateCompositeType compositeType = StateCompositeType.SEQUENTIAL;
StateImpl(S stateId) {
this.stateId = stateId;
}
@Override
public S getStateId() {
return stateId;
}
@Override
public List> getEntryActions() {
return entryActions.getAll();
}
@Override
public List> getExitActions() {
return exitActions.getAll();
}
@Override
public List> getAllTransitions() {
if(transitions==null) return Collections.emptyList();
return Lists.newArrayList(getTransitions().values());
}
@Override
public List> getTransitions(E event) {
if(transitions==null) return Collections.emptyList();
return Lists.newArrayList(getTransitions().get(event));
}
@Override
public Set getAcceptableEvents() {
if(acceptableEvents==null) {
Set events = Sets.newHashSet();
events.addAll(getTransitions().keySet());
acceptableEvents = Collections.unmodifiableSet(events);
}
return acceptableEvents;
}
@Override
public void prioritizeTransitions() {
for(E key : getTransitions().keySet()) {
List> trans = transitions.get(key);
Collections.sort(trans, new Comparator>() {
@Override
public int compare(ImmutableTransition o1, ImmutableTransition o2) {
return o2.getPriority()-o1.getPriority();
}
});
}
}
@Override
public void entry(final StateContext stateContext) {
stateContext.getExecutor().begin("STATE_ENTRY__"+getStateId());
for(final Action entryAction : getEntryActions()) {
stateContext.getExecutor().defer(entryAction,
null, getStateId(), stateContext.getEvent(),
stateContext.getContext(), stateContext.getStateMachine().getThis());
}
if(isParallelState()) {
// When a parallel state group is entered, all its child states will be simultaneously entered.
for(ImmutableState parallelState : getChildStates()) {
parallelState.entry(stateContext);
ImmutableState subState = parallelState.enterByHistory(stateContext);
stateContext.getStateMachineData().write().subStateFor(getStateId(), subState.getStateId());
}
}
logger.debug("State \""+getStateId()+"\" entry.");
}
@Override
public void exit(final StateContext stateContext) {
if(isParallelState()) {
List> subStates = getSubStatesOn(this, stateContext.getStateMachineData().read());
for(ImmutableState subState : subStates) {
if(!subState.isFinalState()) {
subState.exit(stateContext);
}
if(subState.getParentState()!=this)
subState.getParentState().exit(stateContext);
}
stateContext.getStateMachineData().write().removeSubStatesOn(getStateId());
}
if(isFinalState()) return;
stateContext.getExecutor().begin("STATE_EXIT__"+getStateId());
for(final Action exitAction : getExitActions()) {
stateContext.getExecutor().defer(exitAction,
getStateId(), null, stateContext.getEvent(),
stateContext.getContext(), stateContext.getStateMachine().getThis());
}
if (getParentState() != null) {
// update historical state
ImmutableState parent = getParentState();
boolean shouldUpdateHistoricalState = parent.getHistoryType()!=HistoryType.NONE;
if(!shouldUpdateHistoricalState) {
ImmutableState iter = parent.getParentState();
while (iter != null) {
if(iter.getHistoryType()==HistoryType.DEEP) {
shouldUpdateHistoricalState = true;
break;
}
iter = iter.getParentState();
}
}
if(shouldUpdateHistoricalState) {
stateContext.getStateMachineData().write().lastActiveChildStateFor(getParentState().getStateId(), getStateId());
}
if(getParentState().isRegion()) {
S grandParentId = getParentState().getParentState().getStateId();
stateContext.getStateMachineData().write().removeSubState(grandParentId, getStateId());
}
}
logger.debug("State \""+getStateId()+"\" exit.");
}
@Override
public ImmutableState getParentState() {
return parentState;
}
@Override
public List> getChildStates() {
return Lists.>newArrayList(childStates);
}
@Override
public boolean hasChildStates() {
return childStates!=null && childStates.size()>0;
}
@Override
public void setParentState(MutableState parent) {
if(this==parent) {
throw new IllegalArgumentException("parent state cannot be state itself.");
}
if(this.parentState==null) {
this.parentState = parent;
setLevel(this.parentState!=null ? this.parentState.getLevel()+1 : 1);
} else {
throw new UnsupportedOperationException("Cannot change state parent.");
}
}
@Override
public ImmutableState getInitialState() {
return childInitialState;
}
@Override
public void setInitialState(MutableState childInitialState) {
if(isParallelState()) {
logger.warn("Ignoring attempt to set initial state of parallel state group.");
return;
}
if(this.childInitialState==null) {
this.childInitialState = childInitialState;
} else {
throw new UnsupportedOperationException("Cannot change child initial state.");
}
}
@Override
public ImmutableState enterByHistory(StateContext stateContext) {
if(isFinalState() || isParallelState()) // no historical info
return this;
ImmutableState result = null;
switch (this.historyType) {
case NONE:
result = enterHistoryNone(stateContext);
break;
case SHALLOW:
result = enterHistoryShallow(stateContext);
break;
case DEEP:
result = enterHistoryDeep(stateContext);
break;
default:
throw new IllegalArgumentException("Unknown HistoryType : " + historyType);
}
return result;
}
@Override
public ImmutableState enterDeep(StateContext stateContext) {
this.entry(stateContext);
final ImmutableState lastActiveState = getLastActiveChildStateOf(this, stateContext.getStateMachineData().read());
return lastActiveState == null ? this : lastActiveState.enterDeep(stateContext);
}
@Override
public ImmutableState enterShallow(StateContext stateContext) {
entry(stateContext);
return childInitialState!=null ? childInitialState.enterShallow(stateContext) : this;
}
/**
* Enters this instance with history type = shallow.
*
* @param stateContext
* state context
* @return the entered state
*/
private ImmutableState enterHistoryShallow(StateContext stateContext) {
final ImmutableState lastActiveState = getLastActiveChildStateOf(this, stateContext.getStateMachineData().read());
return lastActiveState != null ? lastActiveState.enterShallow(stateContext) : this;
}
/**
* Enters with history type = none.
*
* @param stateContext
* state context
* @return the entered state.
*/
private ImmutableState enterHistoryNone(StateContext stateContext) {
return childInitialState != null ? childInitialState.enterShallow(stateContext) : this;
}
/**
* Enters this instance with history type = deep.
*
* @param stateContext
* the state context.
* @return the state
*/
private ImmutableState enterHistoryDeep(StateContext stateContext) {
final ImmutableState lastActiveState = getLastActiveChildStateOf(
this, stateContext.getStateMachineData().read() );
return lastActiveState != null ? lastActiveState.enterDeep(stateContext) : this;
}
private LinkedListMultimap> getTransitions() {
if(transitions==null) {
transitions = LinkedListMultimap.create();
}
return transitions;
}
@Override
public MutableTransition addTransitionOn(E event) {
MutableTransition newTransition = FSM.newTransition();
newTransition.setSourceState(this);
newTransition.setEvent(event);
getTransitions().put(event, newTransition);
return newTransition;
}
@Override
public void addEntryAction(Action newAction) {
entryActions.add(newAction);
}
@Override
public void addEntryActions(List extends Action> newActions) {
entryActions.addAll(newActions);
}
@Override
public void addExitAction(Action newAction) {
exitActions.add(newAction);
}
@Override
public void addExitActions(List extends Action> newActions) {
exitActions.addAll(newActions);
}
private boolean isParentOf(ImmutableState state) {
ImmutableState parent = state.getParentState();
while(parent!=null) {
if(parent==this)
return true;
parent=parent.getParentState();
}
return false;
}
@Override
public void internalFire(StateContext stateContext) {
TransitionResult currentTransitionResult = stateContext.getResult();
if(isParallelState()) {
/**
* The parallelism in the State Machine framework follows an interleaved semantics.
* All parallel operations will be executed in a single, atomic step of the event
* processing, so no event can interrupt the parallel operations. However, events
* will still be processed sequentially, since the machine itself is single threaded.
*
* The child states execute in parallel in the sense that any event that is processed
* is processed in each child state independently, and each child state may take a different
* transition in response to the event. (Similarly, one child state may take a transition
* in response to an event, while another child ignores it.)
*/
List> parallelStates = getSubStatesOn(this, stateContext.getStateMachineData().read());
for(ImmutableState parallelState : parallelStates) {
if(parallelState.isFinalState()) continue;
// context isolation as entering a new region
TransitionResult subTransitionResult =
FSM.newResult(false, parallelState, currentTransitionResult);
StateContext subStateContext = FSM.newStateContext(
stateContext.getStateMachine(), stateContext.getStateMachineData(),
parallelState, stateContext.getEvent(), stateContext.getContext(),
subTransitionResult, stateContext.getExecutor());
parallelState.internalFire(subStateContext);
if(subTransitionResult.isDeclined()) continue;
if(!isParentOf(subTransitionResult.getTargetState())) {
// child state transition exit the parallel state
currentTransitionResult.setTargetState(subTransitionResult.getTargetState());
return;
}
stateContext.getStateMachineData().write().subStateFor(getStateId(),
subTransitionResult.getTargetState().getStateId());
if(subTransitionResult.getTargetState().isFinalState()) {
ImmutableState parentState = subTransitionResult.getTargetState().getParentState();
ImmutableState grandParentState = parentState.getParentState();
// When all of the children reach final states, the parallel state itself is considered
// to be in a final state, and a completion event is generated.
if(grandParentState!=null && grandParentState.isParallelState()) {
boolean allReachedFinal = true;
for( ImmutableState subState : getSubStatesOn(
grandParentState, stateContext.getStateMachineData().read()) ) {
if(!subState.isFinalState()) {
allReachedFinal = false;
break;
}
}
if(allReachedFinal) {
StateMachine stateMachine = stateContext.getStateMachine();
AbstractStateMachine stateMachineImpl = (AbstractStateMachine)stateMachine;
stateMachine.fireImmediate(stateMachineImpl.getFinishEvent(), stateContext.getContext());
return;
}
}
}
}
}
List> transitions = getTransitions(stateContext.getEvent());
for(final ImmutableTransition transition : transitions) {
transition.internalFire(stateContext);
if(currentTransitionResult.isAccepted()) {
ImmutableState targetState = currentTransitionResult.getTargetState();
if(targetState.isFinalState() && !targetState.isRootState()) {
// TODO-hhe: fire event to notify listeners???
ImmutableState parentState = targetState.getParentState();
AbstractStateMachine abstractStateMachine = (AbstractStateMachine)
stateContext.getStateMachine();
StateContext finishContext = FSM.newStateContext(
stateContext.getStateMachine(), stateContext.getStateMachineData(),parentState,
abstractStateMachine.getFinishEvent(), stateContext.getContext(),
currentTransitionResult, stateContext.getExecutor());
parentState.internalFire(finishContext);
// if(!parentState.isRegion()) {
// currentTransitionResult.setTargetState(parentState);
// StateMachine stateMachine = stateContext.getStateMachine();
// AbstractStateMachine stateMachineImpl = (AbstractStateMachine)
// stateContext.getStateMachine();
// stateMachine.fireImmediate(stateMachineImpl.getFinishEvent(), stateContext.getContext());
// }
}
return;
}
}
// fire to super state
if(currentTransitionResult.isDeclined() && getParentState()!=null &&
!getParentState().isRegion() && !getParentState().isParallelState()) {
logger.debug("Internal notify the same event to parent state");
getParentState().internalFire(stateContext);
}
}
@Override
public boolean isRootState() {
return parentState==null;
}
@Override
public boolean isFinalState() {
return isFinalState;
}
@Override
public void setFinal(boolean isFinal) {
this.isFinalState = isFinal;
}
@Override
public void accept(Visitor visitor) {
visitor.visitOnEntry(this);
for(ImmutableTransition transition : getAllTransitions()) {
transition.accept(visitor);
}
if(childStates!=null) {
for (ImmutableState childState : childStates) {
childState.accept(visitor);
}
}
visitor.visitOnExit(this);
}
@Override
public int getLevel() {
return level;
}
@Override
public void setLevel(int level) {
this.level = level;
if(childStates!=null) {
for (MutableState state : childStates) {
state.setLevel(this.level+1);
}
}
}
@Override
public void addChildState(MutableState childState) {
if(childState!=null) {
if(childStates==null) {
childStates = Lists.newArrayList();
}
if(!childStates.contains(childState))
childStates.add(childState);
}
}
@Override
public HistoryType getHistoryType() {
return historyType;
}
@Override
public void setHistoryType(HistoryType historyType) {
this.historyType = historyType;
}
@Override
public StateCompositeType getCompositeType() {
return compositeType;
}
@Override
public void setCompositeType(StateCompositeType compositeType) {
this.compositeType = compositeType;
}
@Override
public boolean isParallelState() {
return compositeType==StateCompositeType.PARALLEL;
}
@Override
public String toString() {
return getStateId().toString();
}
@Override
public boolean isRegion() {
return parentState!=null && parentState.isParallelState();
}
@Override
public void verify() {
if(isFinalState()) {
if(isParallelState()) {
throw new IllegalStateException("Final state cannot be parallel state.");
} else if(hasChildStates()) {
throw new IllegalStateException("Final state cannot have child states.");
}
}
// make sure that every event can only trigger one transition happen at one time
if(transitions!=null) {
List> allTransitions=transitions.values();
for(ImmutableTransition t : allTransitions) {
t.verify();
ImmutableTransition conflictTransition = checkConflictTransitions(t, allTransitions);
if(conflictTransition!=null) {
throw new RuntimeException(String.format("Transition '%s' is conflicted with '%s'.", t, conflictTransition));
}
}
}
}
public ImmutableTransition checkConflictTransitions(ImmutableTransition target,
List> allTransitions) {
for(ImmutableTransition t : allTransitions) {
if(target==t || t.getCondition().getClass()==Conditions.Never.class) continue;
if(t.isMatch(target.getSourceState().getStateId(), target.getTargetState().getStateId(),
target.getEvent(), target.getPriority())) {
if(t.getCondition().getClass()==Conditions.Always.class)
return target;
if(target.getCondition().getClass()==Conditions.Always.class)
return target;
if( t.getCondition().name().equals(target.getCondition().name()) )
return target;
}
}
return null;
}
private List> getSubStatesOn(ImmutableState parentState,
StateMachineData.Reader read) {
List> subStates = Lists.newArrayList();
for(S stateId : read.subStatesOn(parentState.getStateId())) {
subStates.add(read.rawStateFrom(stateId));
}
return subStates;
}
private ImmutableState getLastActiveChildStateOf(ImmutableState parentState,
StateMachineData.Reader read) {
S childStateId = read.lastActiveChildStateOf(parentState.getStateId());
if(childStateId!=null) {
return read.rawStateFrom(childStateId);
} else {
return parentState.getInitialState();
}
}
@Override
public ImmutableState getThis() {
return this;
}
protected String getKey(T stateMachine) {
return stateMachine.getIdentifier()+'@'+getPath();
}
@Override
public String getPath() {
String currentId = stateId.toString();
if(parentState==null) {
return currentId;
} else {
return parentState.getPath() + "/" + currentId;
}
}
@Override
public boolean isChildStateOf(ImmutableState input) {
ImmutableState curr = this;
while(curr.getLevel()>input.getLevel()) {
curr = curr.getParentState();
}
return curr==input;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy