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