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