001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.scxml.semantics;
018    
019    import java.io.Serializable;
020    import java.util.Arrays;
021    import java.util.Collection;
022    import java.util.Collections;
023    import java.util.Comparator;
024    import java.util.HashMap;
025    import java.util.HashSet;
026    import java.util.Iterator;
027    import java.util.LinkedHashSet;
028    import java.util.LinkedList;
029    import java.util.List;
030    import java.util.Map;
031    import java.util.Set;
032    
033    import org.apache.commons.logging.Log;
034    import org.apache.commons.logging.LogFactory;
035    import org.apache.commons.scxml.Context;
036    import org.apache.commons.scxml.ErrorReporter;
037    import org.apache.commons.scxml.Evaluator;
038    import org.apache.commons.scxml.EventDispatcher;
039    import org.apache.commons.scxml.NotificationRegistry;
040    import org.apache.commons.scxml.PathResolver;
041    import org.apache.commons.scxml.SCInstance;
042    import org.apache.commons.scxml.SCXMLExpressionException;
043    import org.apache.commons.scxml.SCXMLHelper;
044    import org.apache.commons.scxml.SCXMLSemantics;
045    import org.apache.commons.scxml.Step;
046    import org.apache.commons.scxml.TriggerEvent;
047    import org.apache.commons.scxml.invoke.Invoker;
048    import org.apache.commons.scxml.invoke.InvokerException;
049    import org.apache.commons.scxml.model.Action;
050    import org.apache.commons.scxml.model.Finalize;
051    import org.apache.commons.scxml.model.History;
052    import org.apache.commons.scxml.model.Initial;
053    import org.apache.commons.scxml.model.Invoke;
054    import org.apache.commons.scxml.model.ModelException;
055    import org.apache.commons.scxml.model.OnEntry;
056    import org.apache.commons.scxml.model.OnExit;
057    import org.apache.commons.scxml.model.Parallel;
058    import org.apache.commons.scxml.model.Param;
059    import org.apache.commons.scxml.model.Path;
060    import org.apache.commons.scxml.model.SCXML;
061    import org.apache.commons.scxml.model.State;
062    import org.apache.commons.scxml.model.Transition;
063    import org.apache.commons.scxml.model.TransitionTarget;
064    
065    /**
066     * <p>This class encapsulates a particular SCXML semantics, that is, a
067     * particular semantic interpretation of Harel Statecharts, which aligns
068     * mostly with W3C SCXML July 5 public draft (that is, UML 1.5). However,
069     * certain aspects are taken from STATEMATE.</p>
070     *
071     * <p>Specific semantics can be created by subclassing this class.</p>
072     */
073    public class SCXMLSemanticsImpl implements SCXMLSemantics, Serializable {
074    
075        /**
076         * Serial version UID.
077         */
078        private static final long serialVersionUID = 1L;
079    
080        /**
081         * SCXML Logger for the application.
082         */
083        private Log appLog = LogFactory.getLog(SCXMLSemantics.class);
084    
085        /**
086         * The TransitionTarget comparator.
087         */
088        private TransitionTargetComparator targetComparator =
089            new TransitionTargetComparator();
090    
091        /**
092         * Current document namespaces are saved under this key in the parent
093         * state's context.
094         */
095        private static final String NAMESPACES_KEY = "_ALL_NAMESPACES";
096    
097        /**
098         * Suffix for error event that are triggered in reaction to invalid data
099         * model locations.
100         */
101        private static final String ERR_ILLEGAL_ALLOC = ".error.illegalalloc";
102    
103        /**
104         * @param input
105         *            SCXML state machine
106         * @return normalized SCXML state machine, pseudo states are removed, etc.
107         * @param errRep
108         *            ErrorReporter callback
109         */
110        public SCXML normalizeStateMachine(final SCXML input,
111                final ErrorReporter errRep) {
112            //it is a no-op for now
113            return input;
114        }
115    
116        /**
117         * @param input
118         *            SCXML state machine [in]
119         * @param targets
120         *            a set of initial targets to populate [out]
121         * @param entryList
122         *            a list of States and Parallels to enter [out]
123         * @param errRep
124         *            ErrorReporter callback [inout]
125         * @param scInstance
126         *            The state chart instance [in]
127         * @throws ModelException
128         *             in case there is a fatal SCXML object model problem.
129         */
130        public void determineInitialStates(final SCXML input, final Set targets,
131                final List entryList, final ErrorReporter errRep,
132                final SCInstance scInstance)
133                throws ModelException {
134            TransitionTarget tmp = input.getInitialTarget();
135            if (tmp == null) {
136                errRep.onError(ErrorConstants.NO_INITIAL,
137                        "SCXML initialstate is missing!", input);
138            } else {
139                targets.add(tmp);
140                determineTargetStates(targets, errRep, scInstance);
141                //set of ALL entered states (even if initialState is a jump-over)
142                Set onEntry = SCXMLHelper.getAncestorClosure(targets, null);
143                // sort onEntry according state hierarchy
144                Object[] oen = onEntry.toArray();
145                onEntry.clear();
146                Arrays.sort(oen, getTTComparator());
147                // we need to impose reverse order for the onEntry list
148                List entering = Arrays.asList(oen);
149                Collections.reverse(entering);
150                entryList.addAll(entering);
151    
152            }
153        }
154    
155        /**
156         * Executes all OnExit/Transition/OnEntry transitional actions.
157         *
158         * @param step
159         *            provides EntryList, TransitList, ExitList gets
160         *            updated its AfterStatus/Events
161         * @param stateMachine
162         *            state machine - SCXML instance
163         * @param evtDispatcher
164         *            the event dispatcher - EventDispatcher instance
165         * @param errRep
166         *            error reporter
167         * @param scInstance
168         *            The state chart instance
169         * @throws ModelException
170         *             in case there is a fatal SCXML object model problem.
171         */
172        public void executeActions(final Step step, final SCXML stateMachine,
173                final EventDispatcher evtDispatcher,
174                final ErrorReporter errRep, final SCInstance scInstance)
175        throws ModelException {
176            NotificationRegistry nr = scInstance.getNotificationRegistry();
177            Collection internalEvents = step.getAfterStatus().getEvents();
178            Map invokers = scInstance.getInvokers();
179            // ExecutePhaseActions / OnExit
180            for (Iterator i = step.getExitList().iterator(); i.hasNext();) {
181                TransitionTarget tt = (TransitionTarget) i.next();
182                OnExit oe = tt.getOnExit();
183                try {
184                    for (Iterator onExitIter = oe.getActions().iterator();
185                            onExitIter.hasNext();) {
186                        ((Action) onExitIter.next()).execute(evtDispatcher,
187                            errRep, scInstance, appLog, internalEvents);
188                    }
189                } catch (SCXMLExpressionException e) {
190                    errRep.onError(ErrorConstants.EXPRESSION_ERROR, e.getMessage(),
191                            oe);
192                }
193                // check if invoke is active in this state
194                if (invokers.containsKey(tt)) {
195                    Invoker toCancel = (Invoker) invokers.get(tt);
196                    try {
197                        toCancel.cancel();
198                    } catch (InvokerException ie) {
199                        TriggerEvent te = new TriggerEvent(tt.getId()
200                            + ".invoke.cancel.failed", TriggerEvent.ERROR_EVENT);
201                        internalEvents.add(te);
202                    }
203                    // done here, don't wait for cancel response
204                    invokers.remove(tt);
205                }
206                nr.fireOnExit(tt, tt);
207                nr.fireOnExit(stateMachine, tt);
208                TriggerEvent te = new TriggerEvent(tt.getId() + ".exit",
209                        TriggerEvent.CHANGE_EVENT);
210                internalEvents.add(te);
211            }
212            // ExecutePhaseActions / Transitions
213            for (Iterator i = step.getTransitList().iterator(); i.hasNext();) {
214                Transition t = (Transition) i.next();
215                try {
216                    for (Iterator transitIter = t.getActions().iterator();
217                            transitIter.hasNext();) {
218                        ((Action) transitIter.next()).execute(evtDispatcher,
219                            errRep, scInstance, appLog, internalEvents);
220                    }
221                } catch (SCXMLExpressionException e) {
222                    errRep.onError(ErrorConstants.EXPRESSION_ERROR,
223                        e.getMessage(), t);
224                }
225                List rtargets = t.getRuntimeTargets();
226                for (int j = 0; j < rtargets.size(); j++) {
227                    TransitionTarget tt = (TransitionTarget) rtargets.get(j);
228                    nr.fireOnTransition(t, t.getParent(), tt, t);
229                    nr.fireOnTransition(stateMachine, t.getParent(), tt, t);
230                }
231            }
232            // ExecutePhaseActions / OnEntry
233            for (Iterator i = step.getEntryList().iterator(); i.hasNext();) {
234                TransitionTarget tt = (TransitionTarget) i.next();
235                OnEntry oe = tt.getOnEntry();
236                try {
237                    for (Iterator onEntryIter = oe.getActions().iterator();
238                            onEntryIter.hasNext();) {
239                        ((Action) onEntryIter.next()).execute(evtDispatcher,
240                            errRep, scInstance, appLog, internalEvents);
241                    }
242                } catch (SCXMLExpressionException e) {
243                    errRep.onError(ErrorConstants.EXPRESSION_ERROR, e.getMessage(),
244                            oe);
245                }
246                nr.fireOnEntry(tt, tt);
247                nr.fireOnEntry(stateMachine, tt);
248                TriggerEvent te = new TriggerEvent(tt.getId() + ".entry",
249                        TriggerEvent.CHANGE_EVENT);
250                internalEvents.add(te);
251                // actions in initial transition (if any) and .done events
252                if (tt instanceof State) {
253                    State ts = (State) tt;
254                    Initial ini = ts.getInitial();
255                    if (ts.isComposite() && ini != null) {
256                        try {
257                            for (Iterator iniIter = ini.getTransition().
258                                    getActions().iterator(); iniIter.hasNext();) {
259                                ((Action) iniIter.next()).execute(evtDispatcher,
260                                    errRep, scInstance, appLog, internalEvents);
261                            }
262                        } catch (SCXMLExpressionException e) {
263                            errRep.onError(ErrorConstants.EXPRESSION_ERROR,
264                                e.getMessage(), ini);
265                        }
266                    }
267                    if (ts.isFinal()) {
268                        State parent = (State) ts.getParent();
269                        String prefix = "";
270                        if (parent != null) {
271                            prefix = parent.getId();
272                        }
273                        te = new TriggerEvent(prefix + ".done",
274                                TriggerEvent.CHANGE_EVENT);
275                        internalEvents.add(te);
276                        if (parent != null) {
277                            scInstance.setDone(parent, true);
278                        }
279                        if (parent != null && parent.isRegion()) {
280                            //3.4 we got a region, which is finalized
281                            //let's check its siblings too
282                            Parallel p = (Parallel) parent.getParent();
283                            int finCount = 0;
284                            int pCount = p.getChildren().size();
285                            for (Iterator regions = p.getChildren().iterator();
286                                    regions.hasNext();) {
287                                State reg = (State) regions.next();
288                                if (scInstance.isDone(reg)) {
289                                    finCount++;
290                                }
291                            }
292                            if (finCount == pCount) {
293                                te = new TriggerEvent(p.getId() + ".done",
294                                            TriggerEvent.CHANGE_EVENT);
295                                internalEvents.add(te);
296                                scInstance.setDone(p, true);
297                                if (stateMachine.isLegacy()) {
298                                    te = new TriggerEvent(p.getParent().getId()
299                                        + ".done", TriggerEvent.CHANGE_EVENT);
300                                    internalEvents.add(te);
301                                    //this is not in the specs, but is makes sense
302                                    scInstance.setDone(p.getParentState(), true);
303                                }
304                            }
305                        }
306                    }
307                }
308            }
309        }
310    
311        /**
312         * @param stateMachine
313         *            a SM to traverse [in]
314         * @param step
315         *            with current status and list of transitions to populate
316         *            [inout]
317         * @param errRep
318         *            ErrorReporter callback [inout]
319         */
320        public void enumerateReachableTransitions(final SCXML stateMachine,
321                final Step step, final ErrorReporter errRep) {
322            // prevents adding the same transition multiple times
323            Set transSet = new HashSet();
324            // prevents visiting the same state multiple times
325            Set stateSet = new HashSet(step.getBeforeStatus().getStates());
326            // breath-first search to-do list
327            LinkedList todoList = new LinkedList(stateSet);
328            while (!todoList.isEmpty()) {
329                TransitionTarget tt = (TransitionTarget) todoList.removeFirst();
330                for (Iterator i = tt.getTransitionsList().iterator();
331                        i.hasNext();) {
332                    Transition t = (Transition) i.next();
333                    if (!transSet.contains(t)) {
334                        transSet.add(t);
335                        step.getTransitList().add(t);
336                    }
337                }
338                TransitionTarget parent = tt.getParent();
339                if (parent != null && !stateSet.contains(parent)) {
340                    stateSet.add(parent);
341                    todoList.addLast(parent);
342                }
343            }
344            transSet.clear();
345            stateSet.clear();
346            todoList.clear();
347        }
348    
349        /**
350         * @param step
351         *            [inout]
352         * @param evtDispatcher
353         *            The {@link EventDispatcher} [in]
354         * @param errRep
355         *            ErrorReporter callback [inout]
356         * @param scInstance
357         *            The state chart instance [in]
358         * @throws ModelException
359         *             in case there is a fatal SCXML object model problem.
360         */
361        public void filterTransitionsSet(final Step step,
362                final EventDispatcher evtDispatcher,
363                final ErrorReporter errRep, final SCInstance scInstance)
364        throws ModelException {
365            /*
366             * - filter transition set by applying events
367             * (step/beforeStatus/events + step/externalEvents) (local check)
368             * - evaluating guard conditions for
369             * each transition (local check) - transition precedence (bottom-up)
370             * as defined by SCXML specs
371             */
372            Set allEvents = new HashSet(step.getBeforeStatus().getEvents().size()
373                + step.getExternalEvents().size());
374            allEvents.addAll(step.getBeforeStatus().getEvents());
375            allEvents.addAll(step.getExternalEvents());
376            // Finalize invokes, if applicable
377            for (Iterator iter = scInstance.getInvokers().keySet().iterator();
378                    iter.hasNext();) {
379                State s = (State) iter.next();
380                if (finalizeMatch(s.getId(), allEvents)) {
381                    Finalize fn = s.getInvoke().getFinalize();
382                    if (fn != null) {
383                        try {
384                            for (Iterator fnIter = fn.getActions().iterator();
385                                    fnIter.hasNext();) {
386                                ((Action) fnIter.next()).execute(evtDispatcher,
387                                    errRep, scInstance, appLog,
388                                    step.getAfterStatus().getEvents());
389                            }
390                        } catch (SCXMLExpressionException e) {
391                            errRep.onError(ErrorConstants.EXPRESSION_ERROR,
392                                e.getMessage(), fn);
393                        }
394                    }
395                }
396            }
397            //remove list (filtered-out list)
398            List removeList = new LinkedList();
399            //iterate over non-filtered transition set
400            for (Iterator iter = step.getTransitList().iterator();
401                    iter.hasNext();) {
402                Transition t = (Transition) iter.next();
403                // event check
404                String event = t.getEvent();
405                if (!eventMatch(event, allEvents)) {
406                    // t has a non-empty event which is not triggered
407                    removeList.add(t);
408                    continue; //makes no sense to eval guard cond.
409                }
410                // guard condition check
411                Boolean rslt;
412                String expr = t.getCond();
413                if (SCXMLHelper.isStringEmpty(expr)) {
414                    rslt = Boolean.TRUE;
415                } else {
416                    try {
417                        Context ctx = scInstance.getContext(t.getParent());
418                        ctx.setLocal(NAMESPACES_KEY, t.getNamespaces());
419                        rslt = scInstance.getEvaluator().evalCond(ctx,
420                            t.getCond());
421                        ctx.setLocal(NAMESPACES_KEY, null);
422                    } catch (SCXMLExpressionException e) {
423                        rslt = Boolean.FALSE;
424                        errRep.onError(ErrorConstants.EXPRESSION_ERROR, e
425                                .getMessage(), t);
426                    }
427                }
428                if (!rslt.booleanValue()) {
429                    // guard condition has not passed
430                    removeList.add(t);
431                }
432            }
433            // apply event + guard condition filter
434            step.getTransitList().removeAll(removeList);
435            // cleanup temporary structures
436            allEvents.clear();
437            removeList.clear();
438            // optimization - global precedence potentially applies
439            // only if there are multiple enabled transitions
440            if (step.getTransitList().size() > 1) {
441                // global transition precedence check
442                Object[] trans = step.getTransitList().toArray();
443                // non-determinism candidates
444                Set nonDeterm = new LinkedHashSet();
445                for (int i = 0; i < trans.length; i++) {
446                    Transition t = (Transition) trans[i];
447                    TransitionTarget tsrc = t.getParent();
448                    for (int j = i + 1; j < trans.length; j++) {
449                        Transition t2 = (Transition) trans[j];
450                        TransitionTarget t2src = t2.getParent();
451                        if (SCXMLHelper.isDescendant(t2src, tsrc)) {
452                            //t2 takes precedence over t
453                            removeList.add(t);
454                            break; //it makes no sense to waste cycles with t
455                        } else if (SCXMLHelper.isDescendant(tsrc, t2src)) {
456                            //t takes precendence over t2
457                            removeList.add(t2);
458                        } else {
459                            //add both to the non-determinism candidates
460                            nonDeterm.add(t);
461                            nonDeterm.add(t2);
462                        }
463                    }
464                }
465                // check if all non-deterministic situations have been resolved
466                nonDeterm.removeAll(removeList);
467                if (nonDeterm.size() > 0) {
468                    // if not, first one in each state / region (which is also
469                    // first in document order) wins
470                    Set regions = new HashSet();
471                    Iterator iter = nonDeterm.iterator();
472                    while (iter.hasNext()) {
473                        Transition t = (Transition) iter.next();
474                        TransitionTarget parent = t.getParent();
475                        if (regions.contains(parent)) {
476                            removeList.add(t);
477                        } else {
478                            regions.add(parent);
479                        }
480                    }
481                }
482                // apply global and document order transition filter
483                step.getTransitList().removeAll(removeList);
484            }
485        }
486    
487        /**
488         * Populate the target set.
489         * <ul>
490         * <li>take targets of selected transitions</li>
491         * <li>take exited regions into account and make sure every active
492         * parallel region has all siblings active
493         * [that is, explicitly visit or sibling regions in case of newly visited
494         * (revisited) orthogonal states]</li>
495         * </ul>
496         * @param residual [in]
497         * @param transitList [in]
498         * @param errRep
499         *            ErrorReporter callback [inout]
500         * @return Set The target set
501         */
502        public Set seedTargetSet(final Set residual, final List transitList,
503                final ErrorReporter errRep) {
504            Set seedSet = new HashSet();
505            Set regions = new HashSet();
506            for (Iterator i = transitList.iterator(); i.hasNext();) {
507                Transition t = (Transition) i.next();
508                //iterate over transitions and add target states
509                if (t.getTargets().size() > 0) {
510                    seedSet.addAll(t.getTargets());
511                }
512                //build a set of all entered regions
513                List paths = t.getPaths();
514                for (int j = 0; j < paths.size(); j++) {
515                    Path p = (Path) paths.get(j);
516                    if (p.isCrossRegion()) {
517                        List regs = p.getRegionsEntered();
518                        for (Iterator k = regs.iterator(); k.hasNext();) {
519                            State region = (State) k.next();
520                            regions.addAll(((Parallel) region.getParent()).
521                                getChildren());
522                        }
523                    }
524                }
525            }
526            //check whether all active regions have their siblings active too
527            Set allStates = new HashSet(residual);
528            allStates.addAll(seedSet);
529            allStates = SCXMLHelper.getAncestorClosure(allStates, null);
530            regions.removeAll(allStates);
531            //iterate over inactive regions and visit them implicitly using initial
532            for (Iterator i = regions.iterator(); i.hasNext();) {
533                State reg = (State) i.next();
534                seedSet.add(reg);
535            }
536            return seedSet;
537        }
538    
539        /**
540         * @param states
541         *            a set seeded in previous step [inout]
542         * @param errRep
543         *            ErrorReporter callback [inout]
544         * @param scInstance
545         *            The state chart instance [in]
546         * @throws ModelException On illegal configuration
547         * @see #seedTargetSet(Set, List, ErrorReporter)
548         */
549        public void determineTargetStates(final Set states,
550                final ErrorReporter errRep, final SCInstance scInstance)
551        throws ModelException {
552            LinkedList wrkSet = new LinkedList(states);
553            // clear the seed-set - will be populated by leaf states
554            states.clear();
555            while (!wrkSet.isEmpty()) {
556                TransitionTarget tt = (TransitionTarget) wrkSet.removeFirst();
557                if (tt instanceof State) {
558                    State st = (State) tt;
559                    //state can either have parallel or substates w. initial
560                    //or it is a leaf state
561                    // NOTE: Digester has to verify this precondition!
562                    if (st.isSimple()) {
563                        states.add(st); //leaf
564                    } else if (st.isOrthogonal()) { //TODO: Remove else if in v1.0
565                        wrkSet.addLast(st.getParallel()); //parallel
566                    } else {
567                        // composite state
568                        List initialStates = st.getInitial().getTransition().
569                            getTargets();
570                        wrkSet.addAll(initialStates);
571                    }
572                } else if (tt instanceof Parallel) {
573                    Parallel prl = (Parallel) tt;
574                    for (Iterator i = prl.getChildren().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.addAll(h.getTransition().getRuntimeTargets());
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                List paths = t.getPaths();
686                for (int j = 0; j < paths.size(); j++) {
687                    Path p = (Path) paths.get(j);
688                    entered.addAll(p.getDownwardSegment());
689                }
690                // If target is a History pseudo state, remove from entered list
691                List rtargets = t.getRuntimeTargets();
692                for (int j = 0; j < rtargets.size(); j++) {
693                    TransitionTarget tt = (TransitionTarget) rtargets.get(j);
694                    if (tt instanceof History) {
695                        entered.remove(tt);
696                    }
697                }
698            }
699            // Check whether the computed state config is legal
700            targetSet.addAll(residual);
701            residual.clear();
702            if (!SCXMLHelper.isLegalConfig(targetSet, errorReporter)) {
703                throw new ModelException("Illegal state machine configuration!");
704            }
705            // sort onEntry and onExit according state hierarchy
706            Object[] oex = exitedStates.toArray();
707            exitedStates.clear();
708            Object[] oen = entered.toArray();
709            entered.clear();
710            Arrays.sort(oex, getTTComparator());
711            Arrays.sort(oen, getTTComparator());
712            step.getExitList().addAll(Arrays.asList(oex));
713            // we need to impose reverse order for the onEntry list
714            List entering = Arrays.asList(oen);
715            Collections.reverse(entering);
716            step.getEntryList().addAll(entering);
717            // reset 'done' flag
718            for (Iterator reset = entering.iterator(); reset.hasNext();) {
719                Object o = reset.next();
720                if (o instanceof State) {
721                    scInstance.setDone((State) o, false);
722                }
723            }
724        }
725        /**
726         * Process any existing invokes, includes forwarding external events,
727         * and executing any finalize handlers.
728         *
729         * @param events
730         *            The events to be forwarded
731         * @param errRep
732         *            ErrorReporter callback
733         * @param scInstance
734         *            The state chart instance
735         * @throws ModelException
736         *             in case there is a fatal SCXML object model problem.
737         */
738        public void processInvokes(final TriggerEvent[] events,
739                final ErrorReporter errRep, final SCInstance scInstance)
740        throws ModelException {
741            Set allEvents = new HashSet();
742            allEvents.addAll(Arrays.asList(events));
743            for (Iterator invokeIter = scInstance.getInvokers().entrySet().
744                    iterator(); invokeIter.hasNext();) {
745                Map.Entry iEntry = (Map.Entry) invokeIter.next();
746                String parentId = ((TransitionTarget) iEntry.getKey()).getId();
747                if (!finalizeMatch(parentId, allEvents)) { // prevent cycles
748                    Invoker inv = (Invoker) iEntry.getValue();
749                    try {
750                        inv.parentEvents(events);
751                    } catch (InvokerException ie) {
752                        appLog.error(ie.getMessage(), ie);
753                        throw new ModelException(ie.getMessage(), ie.getCause());
754                    }
755                }
756            }
757        }
758    
759        /**
760         * Initiate any new invokes.
761         *
762         * @param step
763         *            The current Step
764         * @param errRep
765         *            ErrorReporter callback
766         * @param scInstance
767         *            The state chart instance
768         */
769        public void initiateInvokes(final Step step, final ErrorReporter errRep,
770                final SCInstance scInstance) {
771            Evaluator eval = scInstance.getEvaluator();
772            Collection internalEvents = step.getAfterStatus().getEvents();
773            for (Iterator iter = step.getAfterStatus().getStates().iterator();
774                    iter.hasNext();) {
775                State s = (State) iter.next();
776                Context ctx = scInstance.getContext(s);
777                Invoke i = s.getInvoke();
778                if (i != null && scInstance.getInvoker(s) == null) {
779                    String src = i.getSrc();
780                    if (src == null) {
781                        String srcexpr = i.getSrcexpr();
782                        Object srcObj = null;
783                        try {
784                            ctx.setLocal(NAMESPACES_KEY, i.getNamespaces());
785                            srcObj = eval.eval(ctx, srcexpr);
786                            ctx.setLocal(NAMESPACES_KEY, null);
787                            src = String.valueOf(srcObj);
788                        } catch (SCXMLExpressionException see) {
789                            errRep.onError(ErrorConstants.EXPRESSION_ERROR,
790                                see.getMessage(), i);
791                        }
792                    }
793                    String source = src;
794                    PathResolver pr = i.getPathResolver();
795                    if (pr != null) {
796                        source = i.getPathResolver().resolvePath(src);
797                    }
798                    String ttype = i.getTargettype();
799                    Invoker inv = null;
800                    try {
801                        inv = scInstance.newInvoker(ttype);
802                    } catch (InvokerException ie) {
803                        TriggerEvent te = new TriggerEvent(s.getId()
804                            + ".invoke.failed", TriggerEvent.ERROR_EVENT);
805                        internalEvents.add(te);
806                        continue;
807                    }
808                    inv.setParentStateId(s.getId());
809                    inv.setSCInstance(scInstance);
810                    List params = i.params();
811                    Map args = new HashMap();
812                    for (Iterator pIter = params.iterator(); pIter.hasNext();) {
813                        Param p = (Param) pIter.next();
814                        String argExpr = p.getExpr();
815                        Object argValue = null;
816                        ctx.setLocal(NAMESPACES_KEY, p.getNamespaces());
817                        // Do we have an "expr" attribute?
818                        if (argExpr != null && argExpr.trim().length() > 0) {
819                            // Yes, evaluate and store as parameter value
820                            try {
821                                argValue = eval.eval(ctx, argExpr);
822                            } catch (SCXMLExpressionException see) {
823                                errRep.onError(ErrorConstants.EXPRESSION_ERROR,
824                                    see.getMessage(), i);
825                            }
826                        } else {
827                            // No. Does value of "name" attribute refer to a valid
828                            // location in the data model?
829                            try {
830                                argValue = eval.evalLocation(ctx, p.getName());
831                                if (argValue == null) {
832                                    // Generate error, 4.3.1 in WD-scxml-20080516
833                                    TriggerEvent te = new TriggerEvent(s.getId()
834                                        + ERR_ILLEGAL_ALLOC,
835                                        TriggerEvent.ERROR_EVENT);
836                                    internalEvents.add(te);
837                                }
838                            } catch (SCXMLExpressionException see) {
839                                errRep.onError(ErrorConstants.EXPRESSION_ERROR,
840                                    see.getMessage(), i);
841                            }
842                        }
843                        ctx.setLocal(NAMESPACES_KEY, null);
844                        args.put(p.getName(), argValue);
845                    }
846                    try {
847                        inv.invoke(source, args);
848                    } catch (InvokerException ie) {
849                        TriggerEvent te = new TriggerEvent(s.getId()
850                            + ".invoke.failed", TriggerEvent.ERROR_EVENT);
851                        internalEvents.add(te);
852                        continue;
853                    }
854                    scInstance.setInvoker(s, inv);
855                }
856            }
857        }
858    
859        /**
860         * Implements prefix match, that is, if, for example,
861         * "mouse.click" is a member of eventOccurrences and a
862         * transition is triggered by "mouse", the method returns true.
863         *
864         * @param transEvent
865         *            a trigger event of a transition
866         * @param eventOccurrences
867         *            current events
868         * @return true/false
869         */
870        protected boolean eventMatch(final String transEvent,
871                final Set eventOccurrences) {
872            if (SCXMLHelper.isStringEmpty(transEvent)) { // Eventless transition
873                return true;
874            } else {
875                String trimTransEvent = transEvent.trim();
876                Iterator i = eventOccurrences.iterator();
877                while (i.hasNext()) {
878                    TriggerEvent te = (TriggerEvent) i.next();
879                    String event = te.getName();
880                    if (event == null) {
881                        continue; // Unnamed events
882                    }
883                    String trimEvent = event.trim();
884                    if (trimEvent.equals(trimTransEvent)) {
885                        return true; // Match
886                    } else if (te.getType() != TriggerEvent.CHANGE_EVENT
887                            && trimTransEvent.equals("*")) {
888                        return true; // Wildcard, skip gen'ed ones like .done etc.
889                    } else if (trimTransEvent.endsWith(".*")
890                            && trimEvent.startsWith(trimTransEvent.substring(0,
891                                    trimTransEvent.length() - 1))) {
892                        return true; // Prefixed wildcard
893                    }
894                }
895                return false;
896            }
897        }
898    
899        /**
900         * Implements event prefix match to ascertain <finalize> execution.
901         *
902         * @param parentStateId
903         *            the ID of the parent state of the <invoke> holding
904         *            the <finalize>
905         * @param eventOccurrences
906         *            current events
907         * @return true/false
908         */
909        protected boolean finalizeMatch(final String parentStateId,
910                final Set eventOccurrences) {
911            String prefix = parentStateId + ".invoke."; // invoke prefix
912            Iterator i = eventOccurrences.iterator();
913            while (i.hasNext()) {
914                String evt = ((TriggerEvent) i.next()).getName();
915                if (evt == null) {
916                    continue; // Unnamed events
917                } else if (evt.trim().startsWith(prefix)) {
918                    return true;
919                }
920            }
921            return false;
922        }
923    
924        /**
925         * TransitionTargetComparator factory method.
926         * @return Comparator The TransitionTarget comparator
927         */
928        protected Comparator getTTComparator() {
929            return targetComparator;
930        }
931    
932        /**
933         * Set the log used by this <code>SCXMLSemantics</code> instance.
934         *
935         * @param log The new log.
936         */
937        protected void setLog(final Log log) {
938            this.appLog = log;
939        }
940    
941        /**
942         * Get the log used by this <code>SCXMLSemantics</code> instance.
943         *
944         * @return Log The log being used.
945         */
946        protected Log getLog() {
947            return appLog;
948        }
949    
950    }
951