All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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> newActions) {
        entryActions.addAll(newActions);
    }

    @Override
    public void addExitAction(Action newAction) {
        exitActions.add(newAction);
    }

    @Override
    public void addExitActions(List> 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