001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.scxml2.semantics;
018
019import java.util.ArrayList;
020import java.util.Collections;
021import java.util.HashMap;
022import java.util.HashSet;
023import java.util.LinkedHashSet;
024import java.util.List;
025import java.util.Map;
026import java.util.Set;
027
028import org.apache.commons.scxml2.ActionExecutionContext;
029import org.apache.commons.scxml2.Context;
030import org.apache.commons.scxml2.ErrorReporter;
031import org.apache.commons.scxml2.SCInstance;
032import org.apache.commons.scxml2.SCXMLExecutionContext;
033import org.apache.commons.scxml2.SCXMLExpressionException;
034import org.apache.commons.scxml2.SCXMLSemantics;
035import org.apache.commons.scxml2.SCXMLSystemContext;
036import org.apache.commons.scxml2.StateConfiguration;
037import org.apache.commons.scxml2.TriggerEvent;
038import org.apache.commons.scxml2.invoke.Invoker;
039import org.apache.commons.scxml2.invoke.InvokerException;
040import org.apache.commons.scxml2.model.Action;
041import org.apache.commons.scxml2.model.DocumentOrder;
042import org.apache.commons.scxml2.model.EnterableState;
043import org.apache.commons.scxml2.model.Executable;
044import org.apache.commons.scxml2.model.Final;
045import org.apache.commons.scxml2.model.History;
046import org.apache.commons.scxml2.model.Invoke;
047import org.apache.commons.scxml2.model.OnEntry;
048import org.apache.commons.scxml2.model.OnExit;
049import org.apache.commons.scxml2.model.Script;
050import org.apache.commons.scxml2.model.SimpleTransition;
051import org.apache.commons.scxml2.model.TransitionalState;
052import org.apache.commons.scxml2.model.ModelException;
053import org.apache.commons.scxml2.model.Parallel;
054import org.apache.commons.scxml2.model.SCXML;
055import org.apache.commons.scxml2.model.State;
056import org.apache.commons.scxml2.model.Transition;
057import org.apache.commons.scxml2.model.TransitionTarget;
058import org.apache.commons.scxml2.system.EventVariable;
059
060/**
061 * This class encapsulate and implements the
062 * <a href="http://www.w3.org/TR/2014/CR-scxml-20140313/#AlgorithmforSCXMLInterpretation">
063 *     W3C SCXML Algorithm for SCXML Interpretation</a>
064 *
065 * <p>Custom semantics can be created by sub-classing this implementation.</p>
066 * <p>This implementation is full stateless and all methods are public accessible to make
067 * it easier to extend, reuse and test its behavior.</p>
068 */
069public class SCXMLSemanticsImpl implements SCXMLSemantics {
070
071    /**
072     * Suffix for error event that are triggered in reaction to invalid data
073     * model locations.
074     */
075    public static final String ERR_ILLEGAL_ALLOC = ".error.illegalalloc";
076
077    /**
078     * Optional post processing immediately following SCXMLReader. May be used
079     * for removing pseudo-states etc.
080     *
081     * @param input  SCXML state machine
082     * @param errRep ErrorReporter callback
083     * @return normalized SCXML state machine, pseudo states are removed, etc.
084     */
085    public SCXML normalizeStateMachine(final SCXML input, final ErrorReporter errRep) {
086        //it is a no-op for now
087        return input;
088    }
089
090    /**
091     * First step in the execution of an SCXML state machine.
092     * <p>
093     * This will first (re)initialize the state machine instance, destroying any existing state!
094     * </p>
095     * <p>
096     * The first step is corresponding to the Algorithm for SCXML processing from the interpret() procedure to the
097     * mainLoop() procedure up to the blocking wait for an external event.
098     * </p>
099     * <p>
100     * This step will thus complete the SCXML initial execution and a subsequent macroStep to stabilize the state
101     * machine again before returning.
102     * </p>
103     * <p>
104     * If the state machine no longer is running after all this, first the {@link #finalStep(SCXMLExecutionContext)}
105     * will be called for cleanup before returning.
106     * </p>
107     * @param exctx The execution context for this step
108     * @throws ModelException if the state machine instance failed to initialize or a SCXML model error occurred during
109     * the execution.
110     */
111    public void firstStep(final SCXMLExecutionContext exctx) throws ModelException {
112        // (re)initialize the execution context and state machine instance
113        exctx.initialize();
114        // execute global script if defined
115        executeGlobalScript(exctx);
116        // enter initial states
117        HashSet<TransitionalState> statesToInvoke = new HashSet<TransitionalState>();
118        Step step = new Step(null);
119        step.getTransitList().add(exctx.getStateMachine().getInitialTransition());
120        microStep(exctx, step, statesToInvoke);
121        // Execute Immediate Transitions
122
123        if (exctx.isRunning()) {
124            macroStep(exctx, statesToInvoke);
125        }
126
127        if (!exctx.isRunning()) {
128            finalStep(exctx);
129        }
130    }
131
132    /**
133     * Next step in the execution of an SCXML state machine.
134     * <p>
135     * The next step is corresponding to the Algorithm for SCXML processing mainEventLoop() procedure after receiving an
136     * external event, up to the blocking wait for another external event.
137     * </p>
138     * <p>
139     * If the state machine isn't {@link SCXMLExecutionContext#isRunning()} (any more), invoking this method will simply
140     * do nothing.
141     * </p>
142     * <p>
143     * If the provided event is a {@link TriggerEvent#CANCEL_EVENT}, the state machine will stop running.
144     * </p>
145     * <p>
146     * Otherwise, the event is set in the {@link SCXMLSystemContext} and processing of the event then is started, and
147     * if the event leads to any transitions a microStep for this event will be performed, followed up by a macroStep
148     * to stabilize the state machine again before returning.
149     * </p>
150     * <p>
151     * If the state machine no longer is running after all this, first the {@link #finalStep(SCXMLExecutionContext)}
152     * will be called for cleanup before returning.
153     * </p>
154     * @param exctx The execution context for this step
155     * @param event The event to process
156     * @throws ModelException if a SCXML model error occurred during the execution.
157     */
158    public void nextStep(final SCXMLExecutionContext exctx, final TriggerEvent event) throws ModelException {
159        if (!exctx.isRunning()) {
160            return;
161        }
162        if (isCancelEvent(event)) {
163            exctx.stopRunning();
164        }
165        else {
166            setSystemEventVariable(exctx.getScInstance(), event, false);
167            processInvokes(exctx, event);
168            Step step = new Step(event);
169            selectTransitions(exctx, step);
170            if (!step.getTransitList().isEmpty()) {
171                HashSet<TransitionalState> statesToInvoke = new HashSet<TransitionalState>();
172                microStep(exctx, step, statesToInvoke);
173                if (exctx.isRunning()) {
174                    macroStep(exctx, statesToInvoke);
175                }
176            }
177        }
178        if (!exctx.isRunning()) {
179            finalStep(exctx);
180        }
181    }
182
183    /**
184     * The final step in the execution of an SCXML state machine.
185     * <p>
186     * This final step is corresponding to the Algorithm for SCXML processing exitInterpreter() procedure, after the
187     * state machine stopped running.
188     * </p>
189     * <p>
190     * If the state machine still is {@link SCXMLExecutionContext#isRunning()} invoking this method will simply
191     * do nothing.
192     * </p>
193     * <p>
194     * This final step will exit all remaining active states and cancel any active invokers.
195     * </p>
196     * <p>
197     *  <em>TODO: the current implementation does not yet provide final donedata handling.</em>
198     * </p>
199     * @param exctx The execution context for this step
200     * @throws ModelException if a SCXML model error occurred during the execution.
201     */
202    public void finalStep(SCXMLExecutionContext exctx) throws ModelException {
203        if (exctx.isRunning()) {
204            return;
205        }
206        ArrayList<EnterableState> configuration = new ArrayList<EnterableState>(exctx.getScInstance().getStateConfiguration().getActiveStates());
207        Collections.sort(configuration, DocumentOrder.reverseDocumentOrderComparator);
208        for (EnterableState es : configuration) {
209            for (OnExit onexit : es.getOnExits()) {
210                executeContent(exctx, onexit);
211            }
212            if (es instanceof TransitionalState) {
213                // check if invokers are active in this state
214                for (Invoke inv : ((TransitionalState)es).getInvokes()) {
215                    exctx.cancelInvoker(inv);
216                }
217            }
218            exctx.getNotificationRegistry().fireOnExit(es, es);
219            exctx.getNotificationRegistry().fireOnExit(exctx.getStateMachine(), es);
220            if (!(es instanceof Final && es.getParent() == null)) {
221                exctx.getScInstance().getStateConfiguration().exitState(es);
222            }
223            // else: keep final Final
224            // TODO: returnDoneEvent(s.donedata)?
225        }
226    }
227
228    /**
229     * Perform a micro step in the execution of a state machine.
230     * <p>
231     * This micro step is corresponding to the Algorithm for SCXML processing microstep() procedure.
232     * <p>
233     * @param exctx The execution context for this step
234     * @param step The current micro step
235     * @param statesToInvoke the set of activated states which invokes need to be invoked at the end of the current
236     *                       macro step
237     * @throws ModelException if a SCXML model error occurred during the execution.
238     */
239    public void microStep(final SCXMLExecutionContext exctx, final Step step,
240                          final Set<TransitionalState> statesToInvoke)
241            throws ModelException {
242        buildStep(exctx, step);
243        exitStates(exctx, step, statesToInvoke);
244        executeTransitionContent(exctx, step);
245        enterStates(exctx, step, statesToInvoke);
246        step.clearIntermediateState();
247    }
248
249    /**
250     * buildStep builds the exitSet and entrySet for the current configuration given the transitionList on the step.
251     *
252     * @param exctx The SCXML execution context
253     * @param step The step containing the list of transitions to be taken
254     * @throws ModelException if the result of taking the transitions would lead to an illegal configuration
255     */
256    public void buildStep(final SCXMLExecutionContext exctx, final Step step) throws ModelException {
257        step.clearIntermediateState();
258
259        // compute exitSet, if there is something to exit and record their History configurations if applicable
260        if (!exctx.getScInstance().getStateConfiguration().getActiveStates().isEmpty()) {
261            computeExitSet(step, exctx.getScInstance().getStateConfiguration());
262        }
263        // compute entrySet
264        computeEntrySet(exctx, step);
265
266        // default result states to entrySet
267        Set<EnterableState> states = step.getEntrySet();
268        if (!step.getExitSet().isEmpty()) {
269            // calculate result states by taking current states, subtracting exitSet and adding entrySet
270            states = new HashSet<EnterableState>(exctx.getScInstance().getStateConfiguration().getStates());
271            states.removeAll(step.getExitSet());
272            states.addAll(step.getEntrySet());
273        }
274        // validate the result states represent a legal configuration
275        if (exctx.isCheckLegalConfiguration() && !isLegalConfiguration(states, exctx.getErrorReporter())) {
276            throw new ModelException("Illegal state machine configuration!");
277        }
278    }
279
280    /**
281     * Perform a macro step in the execution of a state machine.
282     * <p>
283     * This macro step is corresponding to the Algorithm for SCXML processing mainEventLoop() procedure macro step
284     * sub-flow, which are the first <em>3</em> steps of the described <em>4</em>, so everything up to the blocking
285     * wait for an external event.
286     * <p>
287     * @param exctx The execution context for this step
288     * @param statesToInvoke the set of activated states which invokes need to be invoked at the end of the current
289     *                       macro step
290     * @throws ModelException if a SCXML model error occurred during the execution.
291     */
292    public void macroStep(final SCXMLExecutionContext exctx, final Set<TransitionalState> statesToInvoke)
293            throws ModelException {
294        do {
295            boolean macroStepDone = false;
296            do {
297                Step step = new Step(null);
298                selectTransitions(exctx, step);
299                if (step.getTransitList().isEmpty()) {
300                    TriggerEvent event = exctx.nextInternalEvent();
301                    if (event != null) {
302                        if (isCancelEvent(event)) {
303                            exctx.stopRunning();
304                        }
305                        else {
306                            setSystemEventVariable(exctx.getScInstance(), event, true);
307                            step = new Step(event);
308                            selectTransitions(exctx, step);
309                        }
310                    }
311                }
312                if (step.getTransitList().isEmpty()) {
313                    macroStepDone = true;
314                }
315                else {
316                    microStep(exctx, step, statesToInvoke);
317                }
318
319            } while (exctx.isRunning() && !macroStepDone);
320
321            if (exctx.isRunning() && !statesToInvoke.isEmpty()) {
322                initiateInvokes(exctx, statesToInvoke);
323                statesToInvoke.clear();
324            }
325        } while (exctx.isRunning() && exctx.hasPendingInternalEvent());
326    }
327
328    /**
329     * Compute and store the set of states to exit for the current list of transitions in the provided step.
330     * <p>
331     * This method corresponds to the Algorithm for SCXML processing computeExitSet() procedure.
332     * <p>
333     * @param step The step containing the list of transitions to be taken
334     * @param stateConfiguration The current configuration of the state machine ({@link SCInstance#getStateConfiguration()}).
335     */
336    public void computeExitSet(final Step step, final StateConfiguration stateConfiguration) {
337        if (!stateConfiguration.getActiveStates().isEmpty()) {
338            for (SimpleTransition st : step.getTransitList()) {
339                computeExitSet(st, step.getExitSet(), stateConfiguration.getActiveStates());
340            }
341            recordHistory(step, stateConfiguration.getStates(), stateConfiguration.getActiveStates());
342        }
343    }
344
345    /**
346     * Compute and store the set of states to exit for one specific transition in the provided step.
347     * <p>
348     * This method corresponds to the Algorithm for SCXML processing computeExitSet() procedure.
349     * <p>
350     * @param transition The transition to compute the states to exit from
351     * @param exitSet The set for adding the states to exit to
352     * @param activeStates The current active states of the state machine ({@link StateConfiguration#getActiveStates()}).
353     */
354    public void computeExitSet(SimpleTransition transition, Set<EnterableState> exitSet, Set<EnterableState> activeStates) {
355        if (!transition.getTargets().isEmpty()) {
356            TransitionalState transitionDomain = transition.getTransitionDomain();
357            if (transitionDomain == null) {
358                // root transition: every active state will be exited
359                exitSet.addAll(activeStates);
360            }
361            else {
362                for (EnterableState state : activeStates) {
363                    if (state.isDescendantOf(transitionDomain)) {
364                        exitSet.add(state);
365                    }
366                }
367            }
368        }
369    }
370
371    /**
372     * Record the history configurations for states to exit if applicable and temporarily store this in the step.
373     * <p>
374     * These history configurations must be pre-recorded as they might impact (re)entrance calculation during
375     * {@link #computeEntrySet(SCXMLExecutionContext, Step)}.
376     * </p>
377     * <p>
378     * Only after the new configuration has been validated (see: {@link #isLegalConfiguration(Set, ErrorReporter)}), the
379     * history configurations will be persisted during the actual {@link #exitStates(SCXMLExecutionContext, Step, Set)}
380     * processing.
381     * </p>
382     * @param step The step containing the list of states to exit, and the map to record the new history configurations
383     * @param atomicStates The current set of active atomic states in the state machine
384     * @param activeStates The current set of all active states in the state machine
385     */
386    public void recordHistory(final Step step, final Set<EnterableState> atomicStates, final Set<EnterableState> activeStates) {
387        for (EnterableState es : step.getExitSet()) {
388            if (es instanceof TransitionalState && ((TransitionalState)es).hasHistory()) {
389                TransitionalState ts = (TransitionalState)es;
390                Set<EnterableState> shallow = null;
391                Set<EnterableState> deep = null;
392                for (History h : ts.getHistory()) {
393                    if (h.isDeep()) {
394                        if (deep == null) {
395                            //calculate deep history for a given state once
396                            deep = new HashSet<EnterableState>();
397                            for (EnterableState ott : atomicStates) {
398                                if (ott.isDescendantOf(es)) {
399                                    deep.add(ott);
400                                }
401                            }
402                        }
403                        step.getNewHistoryConfigurations().put(h, deep);
404                    } else {
405                        if (shallow == null) {
406                            //calculate shallow history for a given state once
407                            shallow = new HashSet<EnterableState>(ts.getChildren());
408                            shallow.retainAll(activeStates);
409                        }
410                        step.getNewHistoryConfigurations().put(h, shallow);
411                    }
412                }
413            }
414        }
415    }
416
417    /**
418     * Compute and store the set of states to enter for the current list of transitions in the provided step.
419     * <p>
420     * This method corresponds to the Algorithm for SCXML processing computeEntrySet() procedure.
421     * <p>
422     * @param exctx The execution context for this step
423     * @param step The step containing the list of transitions to be taken
424     */
425    public void computeEntrySet(final SCXMLExecutionContext exctx, final Step step) {
426        Set<History> historyTargets = new HashSet<History>();
427        Set<EnterableState> entrySet = new HashSet<EnterableState>();
428        for (SimpleTransition st : step.getTransitList()) {
429            for (TransitionTarget tt : st.getTargets()) {
430                if (tt instanceof EnterableState) {
431                    entrySet.add((EnterableState) tt);
432                }
433                else {
434                    // History
435                    historyTargets.add((History)tt);
436                }
437            }
438        }
439        for (EnterableState es : entrySet) {
440            addDescendantStatesToEnter(exctx, step, es);
441        }
442        for (History h : historyTargets) {
443            addDescendantStatesToEnter(exctx, step, h);
444        }
445        for (SimpleTransition st : step.getTransitList()) {
446            TransitionalState ancestor = st.getTransitionDomain();
447            for (TransitionTarget tt : st.getTargets()) {
448                addAncestorStatesToEnter(exctx, step, tt, ancestor);
449            }
450        }
451    }
452
453    /**
454     * This method corresponds to the Algorithm for SCXML processing addDescendantStatesToEnter() procedure.
455     *
456     * @param exctx The execution context for this step
457     * @param step The step
458     * @param tt The TransitionTarget
459     */
460    public void addDescendantStatesToEnter(final SCXMLExecutionContext exctx, final Step step,
461                                              final TransitionTarget tt) {
462        if (tt instanceof History) {
463            History h = (History) tt;
464            Set<EnterableState> lastConfiguration = step.getNewHistoryConfigurations().get(h);
465            if (lastConfiguration == null) {
466                lastConfiguration = exctx.getScInstance().getLastConfiguration(h);
467            }
468            if (lastConfiguration.isEmpty()) {
469                step.getDefaultHistoryTransitions().put(h.getParent(), h.getTransition());
470                for (TransitionTarget dtt : h.getTransition().getTargets()) {
471                    addDescendantStatesToEnter(exctx, step, dtt);
472                }
473                for (TransitionTarget dtt : h.getTransition().getTargets()) {
474                    addAncestorStatesToEnter(exctx, step, dtt, tt.getParent());
475                }
476            } else {
477                for (TransitionTarget dtt : lastConfiguration) {
478                    addDescendantStatesToEnter(exctx, step, dtt);
479                }
480                for (TransitionTarget dtt : lastConfiguration) {
481                    addAncestorStatesToEnter(exctx, step, dtt, tt.getParent());
482                }
483            }
484        }
485        else { // tt instanceof EnterableState
486            EnterableState es = (EnterableState)tt;
487            step.getEntrySet().add(es);
488            if (es instanceof Parallel) {
489                for (EnterableState child : ((Parallel)es).getChildren()) {
490                    if (!containsDescendant(step.getEntrySet(), child)) {
491                        addDescendantStatesToEnter(exctx, step, child);
492                    }
493                }
494            }
495            else if (es instanceof State && ((State) es).isComposite()) {
496                step.getDefaultEntrySet().add(es);
497                for (TransitionTarget dtt : ((State)es).getInitial().getTransition().getTargets()) {
498                    addDescendantStatesToEnter(exctx, step, dtt);
499                }
500                for (TransitionTarget dtt : ((State)es).getInitial().getTransition().getTargets()) {
501                    addAncestorStatesToEnter(exctx, step, dtt, tt);
502                }
503            }
504        }
505    }
506
507    /**
508     * This method corresponds to the Algorithm for SCXML processing addAncestorStatesToEnter() procedure.
509     *
510     * @param exctx The execution context for this step
511     * @param step The step
512     * @param tt The TransitionTarget
513     * @param ancestor The ancestor TransitionTarget
514     */
515    public void addAncestorStatesToEnter(final SCXMLExecutionContext exctx, final Step step,
516                                            final TransitionTarget tt, TransitionTarget ancestor) {
517        // for for anc in getProperAncestors(tt,ancestor)
518        for (int i = tt.getNumberOfAncestors()-1; i > -1; i--) {
519            EnterableState anc = tt.getAncestor(i);
520            if (anc == ancestor) {
521                break;
522            }
523            step.getEntrySet().add(anc);
524            if (anc instanceof Parallel) {
525                for (EnterableState child : ((Parallel)anc).getChildren()) {
526                    if (!containsDescendant(step.getEntrySet(), child)) {
527                        addDescendantStatesToEnter(exctx, step, child);
528                    }
529                }
530
531            }
532        }
533    }
534
535    /**
536     * @return Returns true if a member of the provided states set is a descendant of the provided state.
537     * @param states the set of states to check for descendants
538     * @param state the state to check with
539     */
540    public boolean containsDescendant(Set<EnterableState> states, EnterableState state) {
541        for (EnterableState es : states) {
542            if (es.isDescendantOf(state)) {
543                return true;
544            }
545        }
546        return false;
547    }
548
549    /**
550     * This method corresponds to the Algorithm for SCXML processing selectTransitions() as well as the
551     * selectEventlessTransitions() procedure, depending on the event (or null) in the provided step
552     * <p>
553     * @param exctx The execution context for this step
554     * @param step The step
555     */
556    public void selectTransitions(final SCXMLExecutionContext exctx, final Step step) throws ModelException {
557        step.getTransitList().clear();
558        ArrayList<Transition> enabledTransitions = new ArrayList<Transition>();
559
560        ArrayList<EnterableState> configuration = new ArrayList<EnterableState>(exctx.getScInstance().getStateConfiguration().getActiveStates());
561        Collections.sort(configuration,DocumentOrder.documentOrderComparator);
562
563        HashSet<EnterableState> visited = new HashSet<EnterableState>();
564
565        String eventName = step.getEvent() != null ? step.getEvent().getName() : null;
566        for (EnterableState es : configuration) {
567            if (es.isAtomicState()) {
568                if (es instanceof Final) {
569                    // Final states don't have transitions, skip to parent
570                    if (es.getParent() == null) {
571                        // should not happen: a top level active Final state should have stopped the state machine
572                        throw new ModelException("Illegal state machine configuration: encountered top level <final> "
573                                + "state while processing an event");
574                    }
575                    else {
576                        es = es.getParent();
577                    }
578                }
579                TransitionalState state = (TransitionalState)es;
580                TransitionalState current = state;
581                int ancestorIndex = state.getNumberOfAncestors()-1;
582                boolean transitionMatched = false;
583                do {
584                    for (Transition transition : current.getTransitionsList()) {
585                        if (transitionMatched = matchTransition(exctx, transition, eventName)) {
586                            enabledTransitions.add(transition);
587                            break;
588                        }
589                    }
590                    current = (!transitionMatched && ancestorIndex > -1) ? state.getAncestor(ancestorIndex--) : null;
591                } while (!transitionMatched && current != null && visited.add(current));
592            }
593        }
594        removeConflictingTransitions(exctx, step, enabledTransitions);
595    }
596
597    /**
598     * This method corresponds to the Algorithm for SCXML processing removeConflictingTransitions() procedure.
599     *
600     * @param exctx The execution context for this step
601     * @param step The step
602     * @param enabledTransitions The list of enabled transitions
603     */
604    public void removeConflictingTransitions(final SCXMLExecutionContext exctx, final Step step,
605                                             final List<Transition> enabledTransitions) {
606        LinkedHashSet<Transition> filteredTransitions = new LinkedHashSet<Transition>();
607        LinkedHashSet<Transition> preemptedTransitions = new LinkedHashSet<Transition>();
608        Map<Transition, Set<EnterableState>> exitSets = new HashMap<Transition, Set<EnterableState>>();
609
610        Set<EnterableState> configuration = exctx.getScInstance().getStateConfiguration().getActiveStates();
611        Collections.sort(enabledTransitions, DocumentOrder.documentOrderComparator);
612
613        for (Transition t1 : enabledTransitions) {
614            boolean t1Preempted = false;
615            Set<EnterableState> t1ExitSet = exitSets.get(t1);
616            for (Transition t2 : filteredTransitions) {
617                if (t1ExitSet == null) {
618                    t1ExitSet = new HashSet<EnterableState>();
619                    computeExitSet(t1, t1ExitSet, configuration);
620                    exitSets.put(t1, t1ExitSet);
621                }
622                Set<EnterableState> t2ExitSet = exitSets.get(t2);
623                if (t2ExitSet == null) {
624                    t2ExitSet = new HashSet<EnterableState>();
625                    computeExitSet(t2, t2ExitSet, configuration);
626                    exitSets.put(t2, t2ExitSet);
627                }
628                Set<EnterableState> smaller = t1ExitSet.size() < t2ExitSet.size() ? t1ExitSet : t2ExitSet;
629                Set<EnterableState> larger = smaller == t1ExitSet ? t2ExitSet : t1ExitSet;
630                boolean hasIntersection = false;
631                for (EnterableState s1 : smaller) {
632                    hasIntersection = larger.contains(s1);
633                    if (hasIntersection) {
634                        break;
635                    }
636                }
637                if (hasIntersection) {
638                    if (t1.getParent().isDescendantOf(t2.getParent())) {
639                        preemptedTransitions.add(t2);
640                    }
641                    else {
642                        t1Preempted = true;
643                        break;
644                    }
645                }
646            }
647            if (t1Preempted) {
648                exitSets.remove(t1);
649            }
650            else {
651                for (Transition preempted : preemptedTransitions) {
652                    filteredTransitions.remove(preempted);
653                    exitSets.remove(preempted);
654                }
655                filteredTransitions.add(t1);
656            }
657        }
658        step.getTransitList().addAll(filteredTransitions);
659    }
660
661    /**
662     * @param exctx The execution context for this step
663     * @param transition The transition
664     * @param eventName The (optional) event name to match against
665     * @return Returns true if the transition matches against the provided eventName, or is event-less when no eventName
666     *         is provided, <em>AND</em> its (optional) condition guard evaluates to true.
667     */
668    public boolean matchTransition(final SCXMLExecutionContext exctx, final Transition transition, final String eventName) {
669        if (eventName != null) {
670            if (!(transition.isNoEventsTransition() || transition.isAllEventsTransition())) {
671                boolean eventMatch = false;
672                for (String event : transition.getEvents()) {
673                    if (eventName.startsWith(event)) {
674                        if (eventName.length() == event.length() || eventName.charAt(event.length())=='.')
675                            eventMatch = true;
676                        break;
677                    }
678                }
679                if (!eventMatch) {
680                    return false;
681                }
682            }
683            else if (!transition.isAllEventsTransition()) {
684                return false;
685            }
686        }
687        else if (!transition.isNoEventsTransition()) {
688            return false;
689        }
690        if (transition.getCond() != null) {
691            Boolean result = Boolean.FALSE;
692            Context context = exctx.getScInstance().getContext(transition.getParent());
693            context.setLocal(Context.NAMESPACES_KEY, transition.getNamespaces());
694            try {
695                if ((result = exctx.getEvaluator().evalCond(context, transition.getCond())) == null) {
696                    result = Boolean.FALSE;
697                    if (exctx.getAppLog().isDebugEnabled()) {
698                        exctx.getAppLog().debug("Treating as false because the cond expression was evaluated as null: '"
699                                + transition.getCond() + "'");
700                    }
701                }
702            }
703            catch (SCXMLExpressionException e) {
704                exctx.getInternalIOProcessor().addEvent(new TriggerEvent(TriggerEvent.ERROR_EXECUTION, TriggerEvent.ERROR_EVENT));
705                exctx.getErrorReporter().onError(ErrorConstants.EXPRESSION_ERROR, "Treating as false due to error: "
706                        + e.getMessage(), transition);
707            }
708            finally {
709                context.setLocal(Context.NAMESPACES_KEY, null);
710            }
711            return result;
712        }
713        return true;
714    }
715
716    /**
717     * This method corresponds to the Algorithm for SCXML processing isFinalState() function.
718     *
719     * @param es the enterable state to check
720     * @param configuration the current state machine configuration
721     * @return Return true if s is a compound state and one of its children is an active final state (i.e. is a member
722     *         of the current configuration), or if s is a parallel state and isInFinalState is true of all its children.
723     */
724    public boolean isInFinalState(final EnterableState es, final Set<EnterableState> configuration) {
725        if (es instanceof State) {
726            for (EnterableState child : ((State)es).getChildren()) {
727                if (child instanceof Final && configuration.contains(child)) {
728                    return true;
729                }
730            }
731        }
732        else if (es instanceof Parallel) {
733            for (EnterableState child : ((Parallel)es).getChildren()) {
734                if (!isInFinalState(child, configuration)) {
735                    return false;
736                }
737            }
738            return true;
739        }
740        return false;
741    }
742
743    /**
744     * Checks if an external event was send (back) by an specific Invoker
745     *
746     * @param invokerId the invokerId
747     *
748     * @param event received external event
749     * @return true if this event was send by the specific Invoker
750     */
751    public boolean isInvokerEvent(final String invokerId, final TriggerEvent event) {
752        return event.getName().equals("done.invoke."+invokerId) ||
753                event.getName().startsWith("done.invoke."+invokerId+".");
754    }
755
756    /**
757     * Check if an external event indicates the state machine execution must be cancelled.
758     *
759     * @param event received external event
760     * @return true if this event is of type {@link TriggerEvent#CANCEL_EVENT}.
761     */
762    public boolean isCancelEvent(TriggerEvent event) {
763        return (event.getType() == TriggerEvent.CANCEL_EVENT);
764    }
765
766    /**
767     * Checks whether a given set of states is a legal Harel State Table
768     * configuration (with the respect to the definition of the OR and AND
769     * states).
770     *
771     * @param states a set of states
772     * @param errRep ErrorReporter to report detailed error info if needed
773     * @return true if a given state configuration is legal, false otherwise
774     */
775    public boolean isLegalConfiguration(final Set<EnterableState> states, final ErrorReporter errRep) {
776        /*
777         * For every active state we add 1 to the count of its parent. Each
778         * Parallel should reach count equal to the number of its children and
779         * contribute by 1 to its parent. Each State should reach count exactly
780         * 1. SCXML elemnt (top) should reach count exactly 1. We essentially
781         * summarize up the hierarchy tree starting with a given set of
782         * states = active configuration.
783         */
784        boolean legalConfig = true; // let's be optimists
785        Map<EnterableState, Set<EnterableState>> counts = new HashMap<EnterableState, Set<EnterableState>>();
786        Set<EnterableState> scxmlCount = new HashSet<EnterableState>();
787        for (EnterableState es : states) {
788            EnterableState parent;
789            while ((parent = es.getParent()) != null) {
790                Set<EnterableState> cnt = counts.get(parent);
791                if (cnt == null) {
792                    cnt = new HashSet<EnterableState>();
793                    counts.put(parent, cnt);
794                }
795                cnt.add(es);
796                es = parent;
797            }
798            //top-level contribution
799            scxmlCount.add(es);
800        }
801        if (scxmlCount.size() > 1) {
802            errRep.onError(ErrorConstants.ILLEGAL_CONFIG, "Multiple top-level OR states active!", scxmlCount);
803            legalConfig = false;
804        }
805        else {
806            //Validate child counts:
807            for (Map.Entry<EnterableState, Set<EnterableState>> entry : counts.entrySet()) {
808                EnterableState es = entry.getKey();
809                Set<EnterableState> count = entry.getValue();
810                if (es instanceof Parallel) {
811                    Parallel p = (Parallel) es;
812                    if (count.size() < p.getChildren().size()) {
813                        errRep.onError(ErrorConstants.ILLEGAL_CONFIG, "Not all AND states active for parallel " + p.getId(), entry);
814                        legalConfig = false;
815                    }
816                } else {
817                    if (count.size() > 1) {
818                        errRep.onError(ErrorConstants.ILLEGAL_CONFIG, "Multiple OR states active for state " + es.getId(), entry);
819                        legalConfig = false;
820                    }
821                }
822                count.clear(); //cleanup
823            }
824        }
825        //cleanup
826        scxmlCount.clear();
827        counts.clear();
828        return legalConfig;
829    }
830
831    /**
832     * Stores the provided event in the system context
833     * <p>
834     * For the event a EventVariable is instantiated and the provided event its type is mapped to the one of the
835     * SCXML specification predefined types.
836     * </p>
837     * @param scInstance the state machine instance holding the system context
838     * @param event The event being stored
839     * @param internal Flag indicating the event was received internally or externally
840     */
841    public void setSystemEventVariable(final SCInstance scInstance, final TriggerEvent event, boolean internal) {
842        Context systemContext = scInstance.getSystemContext();
843        EventVariable eventVar = null;
844        if (event != null) {
845            String eventType = internal ? EventVariable.TYPE_INTERNAL : EventVariable.TYPE_EXTERNAL;
846
847            final int triggerEventType = event.getType();
848            if (triggerEventType == TriggerEvent.ERROR_EVENT || triggerEventType == TriggerEvent.CHANGE_EVENT) {
849                eventType = EventVariable.TYPE_PLATFORM;
850            }
851
852            // TODO: determine sendid, origin, originType and invokeid based on context later.
853            eventVar = new EventVariable(event.getName(), eventType, null, null, null, null, event.getPayload());
854        }
855        systemContext.setLocal(SCXMLSystemContext.EVENT_KEY, eventVar);
856    }
857
858    /**
859     * Executes the global SCXML script element
860     *
861     * @param exctx The execution context
862     * @throws ModelException if a SCXML model error occurred during the execution.
863     */
864    public void executeGlobalScript(final SCXMLExecutionContext exctx) throws ModelException {
865        Script globalScript = exctx.getStateMachine().getGlobalScript();
866        if ( globalScript != null) {
867            try {
868                globalScript.execute(exctx.getActionExecutionContext());
869            } catch (SCXMLExpressionException e) {
870                exctx.getInternalIOProcessor().addEvent(new TriggerEvent(TriggerEvent.ERROR_EXECUTION, TriggerEvent.ERROR_EVENT));
871                exctx.getErrorReporter().onError(ErrorConstants.EXPRESSION_ERROR, e.getMessage(), exctx.getStateMachine());
872            }
873        }
874    }
875
876    /**
877     * This method corresponds to the Algorithm for SCXML processing exitStates() procedure, where the states to exit
878     * already have been pre-computed in {@link #microStep(SCXMLExecutionContext, Step, java.util.Set)}.
879     *
880     * @param exctx The execution context for this micro step
881     * @param step the step
882     * @param statesToInvoke the set of activated states which invokes need to be invoked at the end of the current
883     *                       macro step
884     * @throws ModelException if a SCXML model error occurred during the execution.
885     */
886    public void exitStates(final SCXMLExecutionContext exctx, final Step step,
887                           final Set<TransitionalState> statesToInvoke)
888            throws ModelException {
889        if (step.getExitSet().isEmpty()) {
890            return;
891        }
892        ArrayList<EnterableState> exitList = new ArrayList<EnterableState>(step.getExitSet());
893        Collections.sort(exitList, DocumentOrder.reverseDocumentOrderComparator);
894
895        for (EnterableState es : exitList) {
896
897            if (es instanceof TransitionalState && ((TransitionalState)es).hasHistory()) {
898                // persist the new history configurations for this state to exit
899                for (History h : ((TransitionalState)es).getHistory()) {
900                    exctx.getScInstance().setLastConfiguration(h, step.getNewHistoryConfigurations().get(h));
901                }
902            }
903
904            boolean onexitEventRaised = false;
905            for (OnExit onexit : es.getOnExits()) {
906                executeContent(exctx, onexit);
907                if (!onexitEventRaised && onexit.isRaiseEvent()) {
908                    onexitEventRaised = true;
909                    exctx.getInternalIOProcessor().addEvent(new TriggerEvent("exit.state."+es.getId(), TriggerEvent.CHANGE_EVENT));
910                }
911            }
912            exctx.getNotificationRegistry().fireOnExit(es, es);
913            exctx.getNotificationRegistry().fireOnExit(exctx.getStateMachine(), es);
914
915            if (es instanceof TransitionalState && !statesToInvoke.remove(es)) {
916                // check if invokers are active in this state
917                for (Invoke inv : ((TransitionalState)es).getInvokes()) {
918                    exctx.cancelInvoker(inv);
919                }
920            }
921            exctx.getScInstance().getStateConfiguration().exitState(es);
922        }
923    }
924
925    /**
926     * Executes the executable content for all transitions in the micro step
927     *
928     * @param exctx The execution context for this micro step
929     * @param step the step
930     * @throws ModelException if a SCXML model error occurred during the execution.
931     */
932    public void executeTransitionContent(final SCXMLExecutionContext exctx, final Step step) throws ModelException {
933        for (SimpleTransition transition : step.getTransitList()) {
934            executeContent(exctx, transition);
935        }
936    }
937
938    /**
939     * Executes the executable content for a specific executable in the micro step
940     *
941     * @param exctx The execution context for this micro step
942     * @param exec the executable providing the execution content
943     * @throws ModelException if a SCXML model error occurred during the execution.
944     */
945    public void executeContent(SCXMLExecutionContext exctx, Executable exec) throws ModelException {
946        try {
947            for (Action action : exec.getActions()) {
948                action.execute(exctx.getActionExecutionContext());
949            }
950        } catch (SCXMLExpressionException e) {
951            exctx.getInternalIOProcessor().addEvent(new TriggerEvent(TriggerEvent.ERROR_EXECUTION, TriggerEvent.ERROR_EVENT));
952            exctx.getErrorReporter().onError(ErrorConstants.EXPRESSION_ERROR, e.getMessage(), exec);
953        }
954        if (exec instanceof Transition) {
955            Transition t = (Transition)exec;
956            if (t.getTargets().isEmpty()) {
957                notifyOnTransition(exctx, t, t.getParent());
958            }
959            else {
960                for (TransitionTarget tt : t.getTargets()) {
961                    notifyOnTransition(exctx, t, tt);
962                }
963            }
964        }
965    }
966
967    /**
968     * Notifies SCXMLListeners on the transition taken
969     *
970     * @param exctx The execution context for this micro step
971     * @param t The Transition taken
972     * @param target The target of the Transition
973     */
974    public void notifyOnTransition(final SCXMLExecutionContext exctx, final Transition t,
975                                      final TransitionTarget target) {
976        EventVariable event = (EventVariable)exctx.getScInstance().getSystemContext().getVars().get(SCXMLSystemContext.EVENT_KEY);
977        String eventName = event != null ? event.getName() : null;
978        exctx.getNotificationRegistry().fireOnTransition(t, t.getParent(), target, t, eventName);
979        exctx.getNotificationRegistry().fireOnTransition(exctx.getStateMachine(), t.getParent(), target, t, eventName);
980    }
981
982    /**
983     * This method corresponds to the Algorithm for SCXML processing enterStates() procedure, where the states to enter
984     * already have been pre-computed in {@link #microStep(SCXMLExecutionContext, Step, java.util.Set)}.
985     *
986     * @param exctx The execution context for this micro step
987     * @param step the step
988     * @param statesToInvoke the set of activated states which invokes need to be invoked at the end of the current
989     *                       macro step
990     * @throws ModelException if a SCXML model error occurred during the execution.
991     */
992    public void enterStates(final SCXMLExecutionContext exctx, final Step step,
993                            final Set<TransitionalState> statesToInvoke)
994            throws ModelException {
995        if (step.getEntrySet().isEmpty()) {
996            return;
997        }
998        ArrayList<EnterableState> entryList = new ArrayList<EnterableState>(step.getEntrySet());
999        Collections.sort(entryList, DocumentOrder.documentOrderComparator);
1000        for (EnterableState es : entryList) {
1001            exctx.getScInstance().getStateConfiguration().enterState(es);
1002            if (es instanceof TransitionalState && !((TransitionalState)es).getInvokes().isEmpty()) {
1003                statesToInvoke.add((TransitionalState) es);
1004            }
1005
1006            boolean onentryEventRaised = false;
1007            for (OnEntry onentry : es.getOnEntries()) {
1008                executeContent(exctx, onentry);
1009                if (!onentryEventRaised && onentry.isRaiseEvent()) {
1010                    onentryEventRaised = true;
1011                    exctx.getInternalIOProcessor().addEvent(new TriggerEvent("entry.state."+es.getId(), TriggerEvent.CHANGE_EVENT));
1012                }
1013            }
1014            exctx.getNotificationRegistry().fireOnEntry(es, es);
1015            exctx.getNotificationRegistry().fireOnEntry(exctx.getStateMachine(), es);
1016
1017            if (es instanceof State && step.getDefaultEntrySet().contains(es) && ((State)es).getInitial() != null) {
1018                executeContent(exctx, ((State)es).getInitial().getTransition());
1019            }
1020            if (es instanceof TransitionalState) {
1021                SimpleTransition hTransition = step.getDefaultHistoryTransitions().get(es);
1022                if (hTransition != null) {
1023                    executeContent(exctx, hTransition);
1024                }
1025            }
1026
1027            if (es instanceof Final) {
1028                State parent = (State)es.getParent();
1029                if (parent == null) {
1030                    exctx.stopRunning();
1031                }
1032                else {
1033                    exctx.getInternalIOProcessor().addEvent(new TriggerEvent("done.state."+parent.getId(),TriggerEvent.CHANGE_EVENT));
1034                    if (parent.isRegion()) {
1035                        if (isInFinalState(parent.getParent(), exctx.getScInstance().getStateConfiguration().getActiveStates())) {
1036                            exctx.getInternalIOProcessor().addEvent(new TriggerEvent("done.state."+parent.getParent().getId()
1037                                    , TriggerEvent.CHANGE_EVENT));
1038                        }
1039                    }
1040                }
1041            }
1042        }
1043    }
1044
1045    /**
1046     * Initiate any new invoked activities.
1047     *
1048     * @param exctx provides the execution context
1049     * @param statesToInvoke the set of activated states which invokes need to be invoked
1050     */
1051    public void initiateInvokes(final SCXMLExecutionContext exctx,
1052                                final Set<TransitionalState> statesToInvoke) throws ModelException {
1053        ActionExecutionContext aexctx = exctx.getActionExecutionContext();
1054        for (TransitionalState ts : statesToInvoke) {
1055            for (Invoke invoke : ts.getInvokes()) {
1056                Context ctx = aexctx.getContext(invoke.getParentEnterableState());
1057                String exctxKey = invoke.getCurrentSCXMLExecutionContextKey();
1058                ctx.setLocal(exctxKey, exctx);
1059                invoke.execute(aexctx);
1060                ctx.setLocal(exctxKey, null);
1061            }
1062        }
1063    }
1064
1065    /**
1066     * Forward events to invoked activities, execute finalize handlers.
1067     *
1068     * @param exctx provides the execution context
1069     * @param event The events to be forwarded
1070     *
1071     * @throws ModelException in case there is a fatal SCXML object model problem.
1072     */
1073    public void processInvokes(final SCXMLExecutionContext exctx, final TriggerEvent event) throws ModelException {
1074        for (Map.Entry<Invoke, String> entry : exctx.getInvokeIds().entrySet()) {
1075            if (!isInvokerEvent(entry.getValue(), event)) {
1076                if (entry.getKey().isAutoForward()) {
1077                    Invoker inv = exctx.getInvoker(entry.getKey());
1078                    try {
1079                        inv.parentEvent(event);
1080                    } catch (InvokerException ie) {
1081                        exctx.getAppLog().error(ie.getMessage(), ie);
1082                        throw new ModelException(ie.getMessage(), ie.getCause());
1083                    }
1084                }
1085            }
1086            /*
1087            else {
1088                // TODO: applyFinalize
1089            }
1090            */
1091        }
1092    }
1093}
1094