package org.squirrelframework.foundation.fsm.impl; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import org.squirrelframework.foundation.component.SquirrelInstanceProvider; import org.squirrelframework.foundation.fsm.Action; import org.squirrelframework.foundation.fsm.AnonymousAction; import org.squirrelframework.foundation.fsm.ImmutableLinkedState; import org.squirrelframework.foundation.fsm.MutableLinkedState; import org.squirrelframework.foundation.fsm.StateContext; import org.squirrelframework.foundation.fsm.StateMachine; import org.squirrelframework.foundation.fsm.StateMachineContext; import org.squirrelframework.foundation.fsm.StateMachine.TransitionDeclinedEvent; import org.squirrelframework.foundation.fsm.StateMachineStatus; import com.google.common.collect.Maps; class LinkedStateImpl<T extends StateMachine<T, S, E, C>, S, E, C> extends StateImpl<T, S, E, C> implements ImmutableLinkedState<T, S, E, C>, MutableLinkedState<T, S, E, C> { class DeclineEventHandler<M> implements StateMachine.TransitionDeclinedListener<T, S, E, C> { private StateContext<T, S, E, C> orgStateContext; DeclineEventHandler(StateContext<T, S, E, C> orgStateContext) { this.orgStateContext = orgStateContext; } @Override public void transitionDeclined(TransitionDeclinedEvent<T, S, E, C> event) { LinkedStateImpl.super.internalFire(orgStateContext); } } private SquirrelInstanceProvider<? extends StateMachine<?, S, E, C>> provider; private Map<String, StateMachine<? extends StateMachine<?, S, E, C>, S, E, C>> linkedStateMachineInstances = Maps.newConcurrentMap(); private Action<T, S, E, C> lastEntryAction = new AnonymousAction<T, S, E, C>() { @Override public void execute(S from, S to, E event, C context, T stateMachine) { StateMachine<? extends StateMachine<?, S, E, C>, S, E, C> linkedStateMachine = getLinkedStateMachine(stateMachine); linkedStateMachine.start(context); } @Override public String name() { return "__LINK_STATE_ENTRY_ACTION"; } }; private Action<T, S, E, C> firstExitAction = new AnonymousAction<T, S, E, C>() { @Override public void execute(S from, S to, E event, C context, T stateMachine) { StateMachine<? extends StateMachine<?, S, E, C>, S, E, C> linkedStateMachine = linkedStateMachineInstances.remove(getKey(stateMachine)); if(linkedStateMachine!=null) { linkedStateMachine.terminate(context); } } @Override public String name() { return "__LINK_STATE_EXIT_ACTION"; } }; LinkedStateImpl(S stateId) { super(stateId); } @Override public void setLinkedStateMachineProvider( SquirrelInstanceProvider<? extends StateMachine<?, S, E, C>> provider) { this.provider = provider; } @SuppressWarnings({ "unchecked", "rawtypes" }) // TODO-hhe: check type safety @Override public void internalFire(StateContext<T, S, E, C> stateContext) { StateMachine<? extends StateMachine<?, S, E, C>, S, E, C> stateMachine = linkedStateMachineInstances.get(getKey(stateContext.getStateMachine().getThis())); if(stateMachine.getStatus()==StateMachineStatus.TERMINATED) { // if linked state machine entered its final state, then outside state will process event, super.internalFire(stateContext); } else { // otherwise the linked state machine will try to process event first and only handle event // to outside state when event was declined by linked state machine. DeclineEventHandler declinedEventHandler = new DeclineEventHandler(stateContext); try { // add declined event listener stateMachine.addTransitionDeclinedListener(declinedEventHandler); // set child(linked) state machine context StateMachineContext.set(stateMachine.getThis(), StateMachineContext.isTestEvent()); // delegate the event to linked state machine process stateMachine.fire(stateContext.getEvent(), stateContext.getContext()); } finally { StateMachineContext.set(null); // remove declined event listener stateMachine.removeTransitionDecleindListener(declinedEventHandler); } } } @Override public StateMachine<? extends StateMachine<?, S, E, C>, S, E, C> getLinkedStateMachine(T stateMachine) { String key = getKey(stateMachine); StateMachine<? extends StateMachine<?, S, E, C>, S, E, C> linkedStateMachine = linkedStateMachineInstances.get(key); if(linkedStateMachine==null) { linkedStateMachine = provider.get(); linkedStateMachineInstances.put(key, linkedStateMachine); } return linkedStateMachine; } @Override public List<Action<T, S, E, C>> getEntryActions() { List<Action<T, S, E, C>> actions = new ArrayList<Action<T, S, E, C>>(); actions.addAll(entryActions.getAll()); actions.add(lastEntryAction); return Collections.unmodifiableList(actions); } @Override public List<Action<T, S, E, C>> getExitActions() { List<Action<T, S, E, C>> actions = new ArrayList<Action<T, S, E, C>>(); actions.add(firstExitAction); actions.addAll(exitActions.getAll()); return Collections.unmodifiableList(actions); } @Override public void verify() { if(provider==null) { throw new IllegalStateException("Linked state machine provider cannot be null."); } if(isParallelState() || hasChildStates()) { throw new IllegalStateException("Linked state cannot be parallel state or has any child states."); } super.verify(); } }