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