View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.scxml2.semantics;
18  
19  import java.util.ArrayList;
20  import java.util.Collections;
21  import java.util.HashMap;
22  import java.util.HashSet;
23  import java.util.LinkedHashSet;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Set;
27  
28  import org.apache.commons.scxml2.ActionExecutionContext;
29  import org.apache.commons.scxml2.Context;
30  import org.apache.commons.scxml2.ErrorReporter;
31  import org.apache.commons.scxml2.SCInstance;
32  import org.apache.commons.scxml2.SCXMLExecutionContext;
33  import org.apache.commons.scxml2.SCXMLExpressionException;
34  import org.apache.commons.scxml2.SCXMLSemantics;
35  import org.apache.commons.scxml2.SCXMLSystemContext;
36  import org.apache.commons.scxml2.StateConfiguration;
37  import org.apache.commons.scxml2.TriggerEvent;
38  import org.apache.commons.scxml2.invoke.Invoker;
39  import org.apache.commons.scxml2.invoke.InvokerException;
40  import org.apache.commons.scxml2.model.Action;
41  import org.apache.commons.scxml2.model.DocumentOrder;
42  import org.apache.commons.scxml2.model.EnterableState;
43  import org.apache.commons.scxml2.model.Executable;
44  import org.apache.commons.scxml2.model.Final;
45  import org.apache.commons.scxml2.model.History;
46  import org.apache.commons.scxml2.model.Invoke;
47  import org.apache.commons.scxml2.model.OnEntry;
48  import org.apache.commons.scxml2.model.OnExit;
49  import org.apache.commons.scxml2.model.Script;
50  import org.apache.commons.scxml2.model.SimpleTransition;
51  import org.apache.commons.scxml2.model.TransitionalState;
52  import org.apache.commons.scxml2.model.ModelException;
53  import org.apache.commons.scxml2.model.Parallel;
54  import org.apache.commons.scxml2.model.SCXML;
55  import org.apache.commons.scxml2.model.State;
56  import org.apache.commons.scxml2.model.Transition;
57  import org.apache.commons.scxml2.model.TransitionTarget;
58  import org.apache.commons.scxml2.system.EventVariable;
59  
60  /**
61   * This class encapsulate and implements the
62   * <a href="http://www.w3.org/TR/2014/CR-scxml-20140313/#AlgorithmforSCXMLInterpretation">
63   *     W3C SCXML Algorithm for SCXML Interpretation</a>
64   *
65   * <p>Custom semantics can be created by sub-classing this implementation.</p>
66   * <p>This implementation is full stateless and all methods are public accessible to make
67   * it easier to extend, reuse and test its behavior.</p>
68   */
69  public class SCXMLSemanticsImpl implements SCXMLSemantics {
70  
71      /**
72       * Suffix for error event that are triggered in reaction to invalid data
73       * model locations.
74       */
75      public static final String ERR_ILLEGAL_ALLOC = ".error.illegalalloc";
76  
77      /**
78       * Optional post processing immediately following SCXMLReader. May be used
79       * for removing pseudo-states etc.
80       *
81       * @param input  SCXML state machine
82       * @param errRep ErrorReporter callback
83       * @return normalized SCXML state machine, pseudo states are removed, etc.
84       */
85      public SCXML normalizeStateMachine(final SCXML input, final ErrorReporter errRep) {
86          //it is a no-op for now
87          return input;
88      }
89  
90      /**
91       * First step in the execution of an SCXML state machine.
92       * <p>
93       * This will first (re)initialize the state machine instance, destroying any existing state!
94       * </p>
95       * <p>
96       * The first step is corresponding to the Algorithm for SCXML processing from the interpret() procedure to the
97       * mainLoop() procedure up to the blocking wait for an external event.
98       * </p>
99       * <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