View Javadoc

1   /*
2    *
3    *   Copyright 2005-2006 The Apache Software Foundation.
4    *
5    *  Licensed under the Apache License, Version 2.0 (the "License");
6    *  you may not use this file except in compliance with the License.
7    *  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   */
18  package org.apache.commons.scxml.semantics;
19  
20  import java.util.Arrays;
21  import java.util.Collection;
22  import java.util.Collections;
23  import java.util.Comparator;
24  import java.util.HashMap;
25  import java.util.HashSet;
26  import java.util.Iterator;
27  import java.util.LinkedList;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Set;
31  
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  import org.apache.commons.scxml.Context;
35  import org.apache.commons.scxml.ErrorReporter;
36  import org.apache.commons.scxml.Evaluator;
37  import org.apache.commons.scxml.EventDispatcher;
38  import org.apache.commons.scxml.NotificationRegistry;
39  import org.apache.commons.scxml.PathResolver;
40  import org.apache.commons.scxml.SCInstance;
41  import org.apache.commons.scxml.SCXMLExpressionException;
42  import org.apache.commons.scxml.SCXMLHelper;
43  import org.apache.commons.scxml.SCXMLSemantics;
44  import org.apache.commons.scxml.Step;
45  import org.apache.commons.scxml.TriggerEvent;
46  import org.apache.commons.scxml.invoke.Invoker;
47  import org.apache.commons.scxml.invoke.InvokerException;
48  import org.apache.commons.scxml.model.Action;
49  import org.apache.commons.scxml.model.Finalize;
50  import org.apache.commons.scxml.model.History;
51  import org.apache.commons.scxml.model.Initial;
52  import org.apache.commons.scxml.model.Invoke;
53  import org.apache.commons.scxml.model.ModelException;
54  import org.apache.commons.scxml.model.OnEntry;
55  import org.apache.commons.scxml.model.OnExit;
56  import org.apache.commons.scxml.model.Parallel;
57  import org.apache.commons.scxml.model.Path;
58  import org.apache.commons.scxml.model.SCXML;
59  import org.apache.commons.scxml.model.State;
60  import org.apache.commons.scxml.model.Transition;
61  import org.apache.commons.scxml.model.TransitionTarget;
62  
63  /***
64   * <p>This class encapsulates a particular SCXML semantics, that is, a
65   * particular semantic interpretation of Harel Statecharts, which aligns
66   * mostly with W3C SCXML July 5 public draft (that is, UML 1.5). However,
67   * certain aspects are taken from STATEMATE.</p>
68   *
69   * <p>Specific semantics can be created by subclassing this class.</p>
70   */
71  public class SCXMLSemanticsImpl implements SCXMLSemantics {
72  
73      /***
74       * SCXML Logger for the application.
75       */
76      private Log appLog = LogFactory.getLog("scxml.app.log");
77  
78      /***
79       * The TransitionTarget comparator.
80       */
81      private TransitionTargetComparator targetComparator =
82          new TransitionTargetComparator();
83  
84      /***
85       * @param input
86       *            SCXML state machine
87       * @return normalized SCXML state machine, pseudo states are removed, etc.
88       * @param errRep
89       *            ErrorReporter callback
90       */
91      public SCXML normalizeStateMachine(final SCXML input,
92              final ErrorReporter errRep) {
93          //it is a no-op for now
94          return input;
95      }
96  
97      /***
98       * @param input
99       *            SCXML state machine [in]
100      * @param states
101      *            a set of States to populate [out]
102      * @param entryList
103      *            a list of States and Parallels to enter [out]
104      * @param errRep
105      *            ErrorReporter callback [inout]
106      * @param scInstance
107      *            The state chart instance [in]
108      * @throws ModelException
109      *             in case there is a fatal SCXML object model problem.
110      */
111     public void determineInitialStates(final SCXML input, final Set states,
112             final List entryList, final ErrorReporter errRep,
113             final SCInstance scInstance)
114             throws ModelException {
115         State tmp = input.getInitialState();
116         if (tmp == null) {
117             errRep.onError(ErrorReporter.NO_INITIAL,
118                     "SCXML initialstate is missing!", input);
119         } else {
120             states.add(tmp);
121             determineTargetStates(states, errRep, scInstance);
122             //set of ALL entered states (even if initialState is a jump-over)
123             Set onEntry = SCXMLHelper.getAncestorClosure(states, null);
124             // sort onEntry according state hierarchy
125             Object[] oen = onEntry.toArray();
126             onEntry.clear();
127             Arrays.sort(oen, getTTComparator());
128             // we need to impose reverse order for the onEntry list
129             List entering = Arrays.asList(oen);
130             Collections.reverse(entering);
131             entryList.addAll(entering);
132 
133         }
134     }
135 
136     /***
137      * Executes all OnExit/Transition/OnEntry transitional actions.
138      *
139      * @param step
140      *            provides EntryList, TransitList, ExitList gets
141      *            updated its AfterStatus/Events
142      * @param stateMachine
143      *            state machine - SCXML instance
144      * @param evtDispatcher
145      *            the event dispatcher - EventDispatcher instance
146      * @param errRep
147      *            error reporter
148      * @param scInstance
149      *            The state chart instance
150      * @throws ModelException
151      *             in case there is a fatal SCXML object model problem.
152      */
153     public void executeActions(final Step step, final SCXML stateMachine,
154             final EventDispatcher evtDispatcher,
155             final ErrorReporter errRep, final SCInstance scInstance)
156     throws ModelException {
157         NotificationRegistry nr = scInstance.getNotificationRegistry();
158         Collection internalEvents = step.getAfterStatus().getEvents();
159         Map invokers = scInstance.getInvokers();
160         // ExecutePhaseActions / OnExit
161         for (Iterator i = step.getExitList().iterator(); i.hasNext();) {
162             TransitionTarget tt = (TransitionTarget) i.next();
163             OnExit oe = tt.getOnExit();
164             try {
165                 for (Iterator onExitIter = oe.getActions().iterator();
166                         onExitIter.hasNext();) {
167                     ((Action) onExitIter.next()).execute(evtDispatcher,
168                         errRep, scInstance, appLog, internalEvents);
169                 }
170             } catch (SCXMLExpressionException e) {
171                 errRep.onError(ErrorReporter.EXPRESSION_ERROR, e.getMessage(),
172                         oe);
173             }
174             // check if invoke is active in this state
175             if (invokers.containsKey(tt)) {
176                 Invoker toCancel = (Invoker) invokers.get(tt);
177                 try {
178                     toCancel.cancel();
179                 } catch (InvokerException ie) {
180                     TriggerEvent te = new TriggerEvent(tt.getId()
181                         + ".invoke.cancel.failed", TriggerEvent.ERROR_EVENT);
182                     internalEvents.add(te);
183                 }
184                 // done here, don't wait for cancel response
185                 invokers.remove(tt);
186             }
187             nr.fireOnExit(tt, tt);
188             nr.fireOnExit(stateMachine, tt);
189             TriggerEvent te = new TriggerEvent(tt.getId() + ".exit",
190                     TriggerEvent.CHANGE_EVENT);
191             internalEvents.add(te);
192         }
193         // ExecutePhaseActions / Transitions
194         for (Iterator i = step.getTransitList().iterator(); i.hasNext();) {
195             Transition t = (Transition) i.next();
196             try {
197                 for (Iterator transitIter = t.getActions().iterator();
198                         transitIter.hasNext();) {
199                     ((Action) transitIter.next()).execute(evtDispatcher,
200                         errRep, scInstance, appLog, internalEvents);
201                 }
202             } catch (SCXMLExpressionException e) {
203                 errRep.onError(ErrorReporter.EXPRESSION_ERROR,
204                     e.getMessage(), t);
205             }
206             nr.fireOnTransition(t, t.getParent(), t.getRuntimeTarget(), t);
207             nr.fireOnTransition(stateMachine, t.getParent(),
208                 t.getRuntimeTarget(), t);
209         }
210         // ExecutePhaseActions / OnEntry
211         for (Iterator i = step.getEntryList().iterator(); i.hasNext();) {
212             TransitionTarget tt = (TransitionTarget) i.next();
213             OnEntry oe = tt.getOnEntry();
214             try {
215                 for (Iterator onEntryIter = oe.getActions().iterator();
216                         onEntryIter.hasNext();) {
217                     ((Action) onEntryIter.next()).execute(evtDispatcher,
218                         errRep, scInstance, appLog, internalEvents);
219                 }
220             } catch (SCXMLExpressionException e) {
221                 errRep.onError(ErrorReporter.EXPRESSION_ERROR, e.getMessage(),
222                         oe);
223             }
224             nr.fireOnEntry(tt, tt);
225             nr.fireOnEntry(stateMachine, tt);
226             TriggerEvent te = new TriggerEvent(tt.getId() + ".entry",
227                     TriggerEvent.CHANGE_EVENT);
228             internalEvents.add(te);
229             //3.2.1 and 3.4 (.done events)
230             if (tt instanceof State) {
231                 State ts = (State) tt;
232                 if (ts.getIsFinal()) {
233                     State parent = (State) ts.getParent();
234                     String prefix = "";
235                     if (parent != null) {
236                         prefix = parent.getId();
237                     }
238                     te = new TriggerEvent(prefix + ".done",
239                             TriggerEvent.CHANGE_EVENT);
240                     internalEvents.add(te);
241                     if (parent != null) {
242                         parent.setDone(true);
243                     }
244                     if (parent != null && parent.isRegion()) {
245                         //3.4 we got a region, which is finalized
246                         //let's check its siblings too
247                         Parallel p = (Parallel) parent.getParent();
248                         int finCount = 0;
249                         int pCount = p.getStates().size();
250                         for (Iterator regions = p.getStates().iterator();
251                                 regions.hasNext();) {
252                             State reg = (State) regions.next();
253                             if (reg.isDone()) {
254                                 finCount++;
255                             }
256                         }
257                         if (finCount == pCount) {
258                             te = new TriggerEvent(p.getId() + ".done",
259                                         TriggerEvent.CHANGE_EVENT);
260                             internalEvents.add(te);
261                             te = new TriggerEvent(p.getParent().getId()
262                                 + ".done", TriggerEvent.CHANGE_EVENT);
263                             internalEvents.add(te);
264                             //this is not in the specs, but is makes sense
265                             p.getParentState().setDone(true);
266                         }
267                     }
268                 }
269             }
270         }
271     }
272 
273     /***
274      * @param stateMachine
275      *            a SM to traverse [in]
276      * @param step
277      *            with current status and list of transitions to populate
278      *            [inout]
279      * @param errRep
280      *            ErrorReporter callback [inout]
281      */
282     public void enumerateReachableTransitions(final SCXML stateMachine,
283             final Step step, final ErrorReporter errRep) {
284         // prevents adding the same transition multiple times
285         Set transSet = new HashSet();
286         // prevents visiting the same state multiple times
287         Set stateSet = new HashSet(step.getBeforeStatus().getStates());
288         // breath-first search to-do list
289         LinkedList todoList = new LinkedList(stateSet);
290         while (!todoList.isEmpty()) {
291             State st = (State) todoList.removeFirst();
292             for (Iterator i = st.getTransitionsList().iterator();
293                     i.hasNext();) {
294                 Transition t = (Transition) i.next();
295                 if (!transSet.contains(t)) {
296                     transSet.add(t);
297                     step.getTransitList().add(t);
298                 }
299             }
300             State parent = st.getParentState();
301             if (parent != null && !stateSet.contains(parent)) {
302                 stateSet.add(parent);
303                 todoList.addLast(parent);
304             }
305         }
306         transSet.clear();
307         stateSet.clear();
308         todoList.clear();
309     }
310 
311     /***
312      * @param step
313      *            [inout]
314      * @param evtDispatcher
315      *            The {@link EventDispatcher} [in]
316      * @param errRep
317      *            ErrorReporter callback [inout]
318      * @param scInstance
319      *            The state chart instance [in]
320      * @throws ModelException
321      *             in case there is a fatal SCXML object model problem.
322      */
323     public void filterTransitionsSet(final Step step,
324             final EventDispatcher evtDispatcher,
325             final ErrorReporter errRep, final SCInstance scInstance)
326     throws ModelException {
327         /*
328          * - filter transition set by applying events
329          * (step/beforeStatus/events + step/externalEvents) (local check)
330          * - evaluating guard conditions for
331          * each transition (local check) - transition precedence (bottom-up)
332          * as defined by SCXML specs
333          */
334         Set allEvents = new HashSet(step.getBeforeStatus().getEvents().size()
335             + step.getExternalEvents().size());
336         //for now, we only match against event names
337         for (Iterator ei = step.getBeforeStatus().getEvents().iterator();
338                 ei.hasNext();) {
339             TriggerEvent te = (TriggerEvent) ei.next();
340             allEvents.add(te.getName());
341         }
342         for (Iterator ei = step.getExternalEvents().iterator();
343                 ei.hasNext();) {
344             TriggerEvent te = (TriggerEvent) ei.next();
345             allEvents.add(te.getName());
346         }
347         // Finalize invokes, if applicable
348         for (Iterator iter = scInstance.getInvokers().keySet().iterator();
349                 iter.hasNext();) {
350             State s = (State) iter.next();
351             if (finalizeMatch(s.getId(), allEvents)) {
352                 Finalize fn = s.getInvoke().getFinalize();
353                 if (fn != null) {
354                     try {
355                         for (Iterator fnIter = fn.getActions().iterator();
356                                 fnIter.hasNext();) {
357                             ((Action) fnIter.next()).execute(evtDispatcher,
358                                 errRep, scInstance, appLog,
359                                 step.getAfterStatus().getEvents());
360                         }
361                     } catch (SCXMLExpressionException e) {
362                         errRep.onError(ErrorReporter.EXPRESSION_ERROR,
363                             e.getMessage(), fn);
364                     }
365                 }
366             }
367         }
368         //remove list (filtered-out list)
369         List removeList = new LinkedList();
370         //iterate over non-filtered transition set
371         for (Iterator iter = step.getTransitList().iterator();
372                 iter.hasNext();) {
373             Transition t = (Transition) iter.next();
374             // event check
375             String event = t.getEvent();
376             if (!eventMatch(event, allEvents)) {
377                 // t has a non-empty event which is not triggered
378                 removeList.add(t);
379                 continue; //makes no sense to eval guard cond.
380             }
381             // guard condition check
382             Boolean rslt;
383             String expr = t.getCond();
384             if (SCXMLHelper.isStringEmpty(expr)) {
385                 rslt = Boolean.TRUE;
386             } else {
387                 try {
388                     rslt = scInstance.getEvaluator().evalCond(scInstance.
389                             getContext(t.getParent()), t.getCond());
390                 } catch (SCXMLExpressionException e) {
391                     rslt = Boolean.FALSE;
392                     errRep.onError(ErrorReporter.EXPRESSION_ERROR, e
393                             .getMessage(), t);
394                 }
395             }
396             if (!rslt.booleanValue()) {
397                 // guard condition has not passed
398                 removeList.add(t);
399             }
400         }
401         // apply event + guard condition filter
402         step.getTransitList().removeAll(removeList);
403         // cleanup temporary structures
404         allEvents.clear();
405         removeList.clear();
406         // optimization - global precedence potentially applies
407         // only if there are multiple enabled transitions
408         if (step.getTransitList().size() > 1) {
409             // global transition precedence check
410             Object[] trans = step.getTransitList().toArray();
411             Set currentStates = step.getBeforeStatus().getStates();
412             // non-determinism candidates
413             Set nonDeterm = new HashSet();
414             for (int i = 0; i < trans.length; i++) {
415                 Transition t = (Transition) trans[i];
416                 TransitionTarget tsrc = t.getParent();
417                 for (int j = i + 1; j < trans.length; j++) {
418                     Transition t2 = (Transition) trans[j];
419                     boolean conflict = SCXMLHelper.inConflict(t, t2,
420                             currentStates);
421                     if (conflict) {
422                         //potentially conflicting transitions
423                         TransitionTarget t2src = t2.getParent();
424                         if (SCXMLHelper.isDescendant(t2src, tsrc)) {
425                             //t2 takes precedence over t
426                             removeList.add(t);
427                             break; //it makes no sense to waste cycles with t
428                         } else if (SCXMLHelper.isDescendant(tsrc, t2src)) {
429                             //t takes precendence over t2
430                             removeList.add(t2);
431                         } else {
432                             //add both to the non-determinism candidates
433                             nonDeterm.add(t);
434                             nonDeterm.add(t2);
435                         }
436                     }
437                 }
438             }
439             // check if all non-deterministic situations have been resolved
440             nonDeterm.removeAll(removeList);
441             if (nonDeterm.size() > 0) {
442                 errRep.onError(ErrorReporter.NON_DETERMINISTIC,
443                     "Multiple conflicting transitions enabled.", nonDeterm);
444             }
445             // apply global transition filter
446             step.getTransitList().removeAll(removeList);
447             removeList.clear();
448             nonDeterm.clear();
449         }
450     }
451 
452     /***
453      * Populate the target set.
454      * <ul>
455      * <li>take targets of selected transitions</li>
456      * <li>take exited regions into account and make sure every active
457      * parallel region has all siblings active
458      * [that is, explicitly visit or sibling regions in case of newly visited
459      * (revisited) orthogonal states]</li>
460      * </ul>
461      * @param residual [in]
462      * @param transitList [in]
463      * @param errRep
464      *            ErrorReporter callback [inout]
465      * @return Set The target set
466      */
467     public Set seedTargetSet(final Set residual, final List transitList,
468             final ErrorReporter errRep) {
469         Set seedSet = new HashSet();
470         Set regions = new HashSet();
471         for (Iterator i = transitList.iterator(); i.hasNext();) {
472             Transition t = (Transition) i.next();
473             //iterate over transitions and add target states
474             if (t.getTarget() != null) {
475                 seedSet.add(t.getTarget());
476             }
477             //build a set of all entered regions
478             Path p = t.getPath();
479             if (p.isCrossRegion()) {
480                 List regs = p.getRegionsEntered();
481                 for (Iterator j = regs.iterator(); j.hasNext();) {
482                     State region = (State) j.next();
483                     regions.addAll(((Parallel) region.getParent()).
484                         getStates());
485                 }
486             }
487         }
488         //check whether all active regions have their siblings active too
489         Set allStates = new HashSet(residual);
490         allStates.addAll(seedSet);
491         allStates = SCXMLHelper.getAncestorClosure(allStates, null);
492         regions.removeAll(allStates);
493         //iterate over inactive regions and visit them implicitly using initial
494         for (Iterator i = regions.iterator(); i.hasNext();) {
495             State reg = (State) i.next();
496             seedSet.add(reg);
497         }
498         return seedSet;
499     }
500 
501     /***
502      * @param states
503      *            a set seeded in previous step [inout]
504      * @param errRep
505      *            ErrorReporter callback [inout]
506      * @param scInstance
507      *            The state chart instance [in]
508      * @throws ModelException On illegal configuration
509      * @see #seedTargetSet(Set, List, ErrorReporter)
510      */
511     public void determineTargetStates(final Set states,
512             final ErrorReporter errRep, final SCInstance scInstance)
513     throws ModelException {
514         LinkedList wrkSet = new LinkedList(states);
515         // clear the seed-set - will be populated by leaf states
516         states.clear();
517         while (!wrkSet.isEmpty()) {
518             TransitionTarget tt = (TransitionTarget) wrkSet.removeFirst();
519             if (tt instanceof State) {
520                 State st = (State) tt;
521                 //state can either have parallel or substates w. initial
522                 //or it is a leaf state
523                 // NOTE: Digester has to verify this precondition!
524                 if (st.isSimple()) {
525                     states.add(st); //leaf
526                 } else if (st.isOrthogonal()) {
527                     wrkSet.addLast(st.getParallel()); //parallel
528                 } else {
529                     // composite state
530                     Initial ini = st.getInitial();
531                     if (ini == null) {
532                         errRep.onError(ErrorReporter.NO_INITIAL,
533                             "Initial pseudostate is missing!", st);
534                     } else {
535                         // If we are here, transition target must be a State
536                         // or History
537                         Transition initialTransition = ini.getTransition();
538                         if (initialTransition == null) {
539                             errRep.onError(ErrorReporter.ILLEGAL_INITIAL,
540                                 "Initial transition is null!", st);
541                         } else {
542                             TransitionTarget init = initialTransition.
543                                 getTarget();
544                             if (init == null
545                                 ||
546                                 !(init instanceof State
547                                   || init instanceof History)) {
548                                 errRep.onError(ErrorReporter.ILLEGAL_INITIAL,
549                                 "Initial not pointing to a State or History!",
550                                 st);
551                             } else {
552                                 wrkSet.addLast(init);
553                             }
554                         }
555                     }
556                 }
557             } else if (tt instanceof Parallel) {
558                 Parallel prl = (Parallel) tt;
559                 for (Iterator i = prl.getStates().iterator(); i.hasNext();) {
560                     //fork
561                     wrkSet.addLast(i.next());
562                 }
563             } else if (tt instanceof History) {
564                 History h = (History) tt;
565                 if (scInstance.isEmpty(h)) {
566                     wrkSet.addLast(h.getTransition().getRuntimeTarget());
567                 } else {
568                     wrkSet.addAll(scInstance.getLastConfiguration(h));
569                 }
570             } else {
571                 throw new ModelException("Unknown TransitionTarget subclass:"
572                         + tt.getClass().getName());
573             }
574         }
575     }
576 
577     /***
578      * Go over the exit list and update history information for
579      * relevant states.
580      *
581      * @param step
582      *            [inout]
583      * @param errRep
584      *            ErrorReporter callback [inout]
585      * @param scInstance
586      *            The state chart instance [inout]
587      */
588     public void updateHistoryStates(final Step step,
589             final ErrorReporter errRep, final SCInstance scInstance) {
590         Set oldState = step.getBeforeStatus().getStates();
591         for (Iterator i = step.getExitList().iterator(); i.hasNext();) {
592             Object o = i.next();
593             if (o instanceof State) {
594                 State s = (State) o;
595                 if (s.hasHistory()) {
596                     Set shallow = null;
597                     Set deep = null;
598                     for (Iterator j = s.getHistory().iterator();
599                             j.hasNext();) {
600                         History h = (History) j.next();
601                         if (h.isDeep()) {
602                             if (deep == null) {
603                                 //calculate deep history for a given state once
604                                 deep = new HashSet();
605                                 Iterator k = oldState.iterator();
606                                 while (k.hasNext()) {
607                                     State os = (State) k.next();
608                                     if (SCXMLHelper.isDescendant(os, s)) {
609                                         deep.add(os);
610                                     }
611                                 }
612                             }
613                             scInstance.setLastConfiguration(h, deep);
614                         } else {
615                             if (shallow == null) {
616                                 //calculate shallow history for a given state
617                                 // once
618                                 shallow = new HashSet();
619                                 shallow.addAll(s.getChildren().values());
620                                 shallow.retainAll(SCXMLHelper
621                                         .getAncestorClosure(oldState, null));
622                             }
623                             scInstance.setLastConfiguration(h, shallow);
624                         }
625                     }
626                     shallow = null;
627                     deep = null;
628                 }
629             }
630         }
631     }
632 
633     /***
634      * Follow the candidate transitions for this execution Step, and update the
635      * lists of entered and exited states accordingly.
636      *
637      * @param step The current Step
638      * @param errorReporter The ErrorReporter for the current environment
639      * @param scInstance The state chart instance
640      *
641      * @throws ModelException
642      *             in case there is a fatal SCXML object model problem.
643      */
644     public void followTransitions(final Step step,
645             final ErrorReporter errorReporter, final SCInstance scInstance)
646     throws ModelException {
647         Set currentStates = step.getBeforeStatus().getStates();
648         List transitions = step.getTransitList();
649         // DetermineExitedStates (currentStates, transitList) -> exitedStates
650         Set exitedStates = new HashSet();
651         for (Iterator i = transitions.iterator(); i.hasNext();) {
652             Transition t = (Transition) i.next();
653             Set ext = SCXMLHelper.getStatesExited(t, currentStates);
654             exitedStates.addAll(ext);
655         }
656         // compute residual states - these are preserved from the previous step
657         Set residual = new HashSet(currentStates);
658         residual.removeAll(exitedStates);
659         // SeedTargetSet (residual, transitList) -> seedSet
660         Set seedSet = seedTargetSet(residual, transitions, errorReporter);
661         // DetermineTargetStates (initialTargetSet) -> targetSet
662         Set targetSet = step.getAfterStatus().getStates();
663         targetSet.addAll(seedSet); //copy to preserve seedSet
664         determineTargetStates(targetSet, errorReporter, scInstance);
665         // BuildOnEntryList (targetSet, seedSet) -> entryList
666         Set entered = SCXMLHelper.getAncestorClosure(targetSet, seedSet);
667         seedSet.clear();
668         for (Iterator i = transitions.iterator(); i.hasNext();) {
669             Transition t = (Transition) i.next();
670             entered.addAll(t.getPath().getDownwardSegment());
671             // If target is a History pseudo state, remove from entered list
672             if (t.getRuntimeTarget() instanceof History) {
673                 entered.remove(t.getRuntimeTarget());
674             }
675         }
676         // Check whether the computed state config is legal
677         targetSet.addAll(residual);
678         residual.clear();
679         if (!SCXMLHelper.isLegalConfig(targetSet, errorReporter)) {
680             throw new ModelException("Illegal state machine configuration!");
681         }
682         // sort onEntry and onExit according state hierarchy
683         Object[] oex = exitedStates.toArray();
684         exitedStates.clear();
685         Object[] oen = entered.toArray();
686         entered.clear();
687         Arrays.sort(oex, getTTComparator());
688         Arrays.sort(oen, getTTComparator());
689         step.getExitList().addAll(Arrays.asList(oex));
690         // we need to impose reverse order for the onEntry list
691         List entering = Arrays.asList(oen);
692         Collections.reverse(entering);
693         step.getEntryList().addAll(entering);
694         // reset 'done' flag
695         for (Iterator reset = entering.iterator(); reset.hasNext();) {
696             Object o = reset.next();
697             if (o instanceof State) {
698                 ((State) o).setDone(false);
699             }
700         }
701     }
702     /***
703      * Process any existing invokes, includes forwarding external events,
704      * and executing any finalize handlers.
705      *
706      * @param events
707      *            The events to be forwarded
708      * @param errRep
709      *            ErrorReporter callback
710      * @param scInstance
711      *            The state chart instance
712      * @throws ModelException
713      *             in case there is a fatal SCXML object model problem.
714      */
715     public void processInvokes(final TriggerEvent[] events,
716             final ErrorReporter errRep, final SCInstance scInstance)
717     throws ModelException {
718         Set eventNames = new HashSet();
719         //for now, we only match against event names
720         for (int i = 0; i < events.length; i++) {
721             eventNames.add(events[i].getName());
722         }
723         for (Iterator invokeIter = scInstance.getInvokers().entrySet().
724                 iterator(); invokeIter.hasNext();) {
725             Map.Entry iEntry = (Map.Entry) invokeIter.next();
726             String parentId = ((TransitionTarget) iEntry.getKey()).getId();
727             if (!finalizeMatch(parentId, eventNames)) { // prevent cycles
728                 Invoker inv = (Invoker) iEntry.getValue();
729                 try {
730                     inv.parentEvents(events);
731                 } catch (InvokerException ie) {
732                     appLog.error(ie.getMessage(), ie);
733                     throw new ModelException(ie.getMessage(), ie.getCause());
734                 }
735             }
736         }
737     }
738 
739     /***
740      * Initiate any new invokes.
741      *
742      * @param step
743      *            The current Step
744      * @param errRep
745      *            ErrorReporter callback
746      * @param scInstance
747      *            The state chart instance
748      */
749     public void initiateInvokes(final Step step, final ErrorReporter errRep,
750             final SCInstance scInstance) {
751         Evaluator eval = scInstance.getEvaluator();
752         Collection internalEvents = step.getAfterStatus().getEvents();
753         for (Iterator iter = step.getAfterStatus().getStates().iterator();
754                 iter.hasNext();) {
755             State s = (State) iter.next();
756             Context ctx = scInstance.getContext(s);
757             Invoke i = s.getInvoke();
758             if (i != null && scInstance.getInvoker(s) == null) {
759                 String src = i.getSrc();
760                 if (src == null) {
761                     String srcexpr = i.getSrcexpr();
762                     Object srcObj = null;
763                     try {
764                         srcObj = eval.eval(ctx, srcexpr);
765                         src = String.valueOf(srcObj);
766                     } catch (SCXMLExpressionException see) {
767                         errRep.onError(ErrorReporter.EXPRESSION_ERROR,
768                             see.getMessage(), i);
769                     }
770                 }
771                 String source = src;
772                 PathResolver pr = i.getPathResolver();
773                 if (pr != null) {
774                     source = i.getPathResolver().resolvePath(src);
775                 }
776                 String ttype = i.getTargettype();
777                 Invoker inv = null;
778                 try {
779                     inv = scInstance.newInvoker(ttype);
780                 } catch (InvokerException ie) {
781                     TriggerEvent te = new TriggerEvent(s.getId()
782                         + ".invoke.failed", TriggerEvent.ERROR_EVENT);
783                     internalEvents.add(te);
784                     continue;
785                 }
786                 inv.setParentStateId(s.getId());
787                 inv.setSCInstance(scInstance);
788                 Map params = i.getParams();
789                 Map args = new HashMap();
790                 for (Iterator pIter = params.entrySet().iterator();
791                         pIter.hasNext();) {
792                     Map.Entry entry = (Map.Entry) pIter.next();
793                     String argName = (String) entry.getKey();
794                     String argExpr = (String) entry.getValue();
795                     Object argValue = null;
796                     if (argExpr != null && argExpr.trim().length() > 0) {
797                         try {
798                             argValue = eval.eval(ctx, argExpr);
799                         } catch (SCXMLExpressionException see) {
800                             errRep.onError(ErrorReporter.EXPRESSION_ERROR,
801                                 see.getMessage(), i);
802                         }
803                     }
804                     args.put(argName, argValue);
805                 }
806                 try {
807                     inv.invoke(source, args);
808                 } catch (InvokerException ie) {
809                     TriggerEvent te = new TriggerEvent(s.getId()
810                         + ".invoke.failed", TriggerEvent.ERROR_EVENT);
811                     internalEvents.add(te);
812                     continue;
813                 }
814                 scInstance.setInvoker(s, inv);
815             }
816         }
817     }
818 
819     /***
820      * Implements prefix match, that is, if, for example,
821      * &quot;mouse.click&quot; is a member of eventOccurrences and a
822      * transition is triggered by &quot;mouse&quot;, the method returns true.
823      *
824      * @param transEvent
825      *            a trigger event of a transition
826      * @param eventOccurrences
827      *            current events
828      * @return true/false
829      */
830     protected boolean eventMatch(final String transEvent,
831             final Set eventOccurrences) {
832         if (SCXMLHelper.isStringEmpty(transEvent)) {
833             return true;
834         } else {
835             String transEventDot = transEvent + "."; // prefix event support
836             Iterator i = eventOccurrences.iterator();
837             while (i.hasNext()) {
838                 String evt = (String) i.next();
839                 if (evt == null) {
840                     continue; // Unnamed events
841                 } else if (evt.equals("*")) {
842                     return true; // Wildcard
843                 } else if (evt.equals(transEvent)
844                             || evt.startsWith(transEventDot)) {
845                     return true;
846                 }
847             }
848             return false;
849         }
850     }
851 
852     /***
853      * Implements event prefix match to ascertain &lt;finalize&gt; execution.
854      *
855      * @param parentStateId
856      *            the ID of the parent state of the &lt;invoke&gt; holding
857      *            the &lt;finalize&gt;
858      * @param eventOccurrences
859      *            current events
860      * @return true/false
861      */
862     protected boolean finalizeMatch(final String parentStateId,
863             final Set eventOccurrences) {
864         String prefix = parentStateId + ".invoke."; // invoke prefix
865         Iterator i = eventOccurrences.iterator();
866         while (i.hasNext()) {
867             String evt = (String) i.next();
868             if (evt == null) {
869                 continue; // Unnamed events
870             } else if (evt.startsWith(prefix)) {
871                 return true;
872             }
873         }
874         return false;
875     }
876 
877     /***
878      * TransitionTargetComparator factory method.
879      * @return Comparator The TransitionTarget comparator
880      */
881     protected Comparator getTTComparator() {
882         return targetComparator;
883     }
884 
885     /***
886      * Set the log used by this <code>SCXMLSemantics</code> instance.
887      *
888      * @param log The new log.
889      */
890     protected void setLog(final Log log) {
891         this.appLog = log;
892     }
893 
894     /***
895      * Get the log used by this <code>SCXMLSemantics</code> instance.
896      *
897      * @return Log The log being used.
898      */
899     protected Log getLog() {
900         return appLog;
901     }
902 
903 }
904