Coverage Report - org.apache.commons.scxml.semantics.SCXMLSemanticsImpl

Classes in this File Line Coverage Branch Coverage Complexity
SCXMLSemanticsImpl
70% 
80% 
7.438

 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  37
 public class SCXMLSemanticsImpl implements SCXMLSemantics {
 72  
 
 73  
     /**
 74  
      * SCXML Logger for the application.
 75  
      */
 76  37
     private Log appLog = LogFactory.getLog("scxml.app.log");
 77  
 
 78  
     /**
 79  
      * The TransitionTarget comparator.
 80  
      */
 81  37
     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  37
         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  39
         State tmp = input.getInitialState();
 116  39
         if (tmp == null) {
 117  0
             errRep.onError(ErrorReporter.NO_INITIAL,
 118  
                     "SCXML initialstate is missing!", input);
 119  
         } else {
 120  39
             states.add(tmp);
 121  39
             determineTargetStates(states, errRep, scInstance);
 122  
             //set of ALL entered states (even if initialState is a jump-over)
 123  39
             Set onEntry = SCXMLHelper.getAncestorClosure(states, null);
 124  
             // sort onEntry according state hierarchy
 125  39
             Object[] oen = onEntry.toArray();
 126  39
             onEntry.clear();
 127  39
             Arrays.sort(oen, getTTComparator());
 128  
             // we need to impose reverse order for the onEntry list
 129  39
             List entering = Arrays.asList(oen);
 130  39
             Collections.reverse(entering);
 131  39
             entryList.addAll(entering);
 132  
 
 133  
         }
 134  39
     }
 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  244
         NotificationRegistry nr = scInstance.getNotificationRegistry();
 158  244
         Collection internalEvents = step.getAfterStatus().getEvents();
 159  244
         Map invokers = scInstance.getInvokers();
 160  
         // ExecutePhaseActions / OnExit
 161  244
         for (Iterator i = step.getExitList().iterator(); i.hasNext();) {
 162  105
             TransitionTarget tt = (TransitionTarget) i.next();
 163  105
             OnExit oe = tt.getOnExit();
 164  
             try {
 165  105
                 for (Iterator onExitIter = oe.getActions().iterator();
 166  125
                         onExitIter.hasNext();) {
 167  20
                     ((Action) onExitIter.next()).execute(evtDispatcher,
 168  
                         errRep, scInstance, appLog, internalEvents);
 169  
                 }
 170  0
             } catch (SCXMLExpressionException e) {
 171  0
                 errRep.onError(ErrorReporter.EXPRESSION_ERROR, e.getMessage(),
 172  
                         oe);
 173  105
             }
 174  
             // check if invoke is active in this state
 175  105
             if (invokers.containsKey(tt)) {
 176  0
                 Invoker toCancel = (Invoker) invokers.get(tt);
 177  
                 try {
 178  0
                     toCancel.cancel();
 179  0
                 } catch (InvokerException ie) {
 180  0
                     TriggerEvent te = new TriggerEvent(tt.getId()
 181  
                         + ".invoke.cancel.failed", TriggerEvent.ERROR_EVENT);
 182  0
                     internalEvents.add(te);
 183  0
                 }
 184  
                 // done here, don't wait for cancel response
 185  0
                 invokers.remove(tt);
 186  
             }
 187  105
             nr.fireOnExit(tt, tt);
 188  105
             nr.fireOnExit(stateMachine, tt);
 189  105
             TriggerEvent te = new TriggerEvent(tt.getId() + ".exit",
 190  
                     TriggerEvent.CHANGE_EVENT);
 191  105
             internalEvents.add(te);
 192  
         }
 193  
         // ExecutePhaseActions / Transitions
 194  244
         for (Iterator i = step.getTransitList().iterator(); i.hasNext();) {
 195  89
             Transition t = (Transition) i.next();
 196  
             try {
 197  89
                 for (Iterator transitIter = t.getActions().iterator();
 198  89
                         transitIter.hasNext();) {
 199  0
                     ((Action) transitIter.next()).execute(evtDispatcher,
 200  
                         errRep, scInstance, appLog, internalEvents);
 201  
                 }
 202  0
             } catch (SCXMLExpressionException e) {
 203  0
                 errRep.onError(ErrorReporter.EXPRESSION_ERROR,
 204  
                     e.getMessage(), t);
 205  89
             }
 206  89
             nr.fireOnTransition(t, t.getParent(), t.getRuntimeTarget(), t);
 207  89
             nr.fireOnTransition(stateMachine, t.getParent(),
 208  
                 t.getRuntimeTarget(), t);
 209  
         }
 210  
         // ExecutePhaseActions / OnEntry
 211  244
         for (Iterator i = step.getEntryList().iterator(); i.hasNext();) {
 212  187
             TransitionTarget tt = (TransitionTarget) i.next();
 213  187
             OnEntry oe = tt.getOnEntry();
 214  
             try {
 215  187
                 for (Iterator onEntryIter = oe.getActions().iterator();
 216  251
                         onEntryIter.hasNext();) {
 217  64
                     ((Action) onEntryIter.next()).execute(evtDispatcher,
 218  
                         errRep, scInstance, appLog, internalEvents);
 219  
                 }
 220  0
             } catch (SCXMLExpressionException e) {
 221  0
                 errRep.onError(ErrorReporter.EXPRESSION_ERROR, e.getMessage(),
 222  
                         oe);
 223  187
             }
 224  187
             nr.fireOnEntry(tt, tt);
 225  187
             nr.fireOnEntry(stateMachine, tt);
 226  187
             TriggerEvent te = new TriggerEvent(tt.getId() + ".entry",
 227  
                     TriggerEvent.CHANGE_EVENT);
 228  187
             internalEvents.add(te);
 229  
             //3.2.1 and 3.4 (.done events)
 230  187
             if (tt instanceof State) {
 231  184
                 State ts = (State) tt;
 232  184
                 if (ts.getIsFinal()) {
 233  30
                     State parent = (State) ts.getParent();
 234  30
                     String prefix = "";
 235  30
                     if (parent != null) {
 236  19
                         prefix = parent.getId();
 237  
                     }
 238  30
                     te = new TriggerEvent(prefix + ".done",
 239  
                             TriggerEvent.CHANGE_EVENT);
 240  30
                     internalEvents.add(te);
 241  30
                     if (parent != null) {
 242  19
                         parent.setDone(true);
 243  
                     }
 244  30
                     if (parent != null && parent.isRegion()) {
 245  
                         //3.4 we got a region, which is finalized
 246  
                         //let's check its siblings too
 247  0
                         Parallel p = (Parallel) parent.getParent();
 248  0
                         int finCount = 0;
 249  0
                         int pCount = p.getStates().size();
 250  0
                         for (Iterator regions = p.getStates().iterator();
 251  0
                                 regions.hasNext();) {
 252  0
                             State reg = (State) regions.next();
 253  0
                             if (reg.isDone()) {
 254  0
                                 finCount++;
 255  
                             }
 256  
                         }
 257  0
                         if (finCount == pCount) {
 258  0
                             te = new TriggerEvent(p.getId() + ".done",
 259  
                                         TriggerEvent.CHANGE_EVENT);
 260  0
                             internalEvents.add(te);
 261  0
                             te = new TriggerEvent(p.getParent().getId()
 262  
                                 + ".done", TriggerEvent.CHANGE_EVENT);
 263  0
                             internalEvents.add(te);
 264  
                             //this is not in the specs, but is makes sense
 265  0
                             p.getParentState().setDone(true);
 266  
                         }
 267  
                     }
 268  
                 }
 269  
             }
 270  
         }
 271  244
     }
 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  205
         Set transSet = new HashSet();
 286  
         // prevents visiting the same state multiple times
 287  205
         Set stateSet = new HashSet(step.getBeforeStatus().getStates());
 288  
         // breath-first search to-do list
 289  205
         LinkedList todoList = new LinkedList(stateSet);
 290  588
         while (!todoList.isEmpty()) {
 291  383
             State st = (State) todoList.removeFirst();
 292  383
             for (Iterator i = st.getTransitionsList().iterator();
 293  708
                     i.hasNext();) {
 294  325
                 Transition t = (Transition) i.next();
 295  325
                 if (!transSet.contains(t)) {
 296  325
                     transSet.add(t);
 297  325
                     step.getTransitList().add(t);
 298  
                 }
 299  
             }
 300  383
             State parent = st.getParentState();
 301  383
             if (parent != null && !stateSet.contains(parent)) {
 302  168
                 stateSet.add(parent);
 303  168
                 todoList.addLast(parent);
 304  
             }
 305  
         }
 306  205
         transSet.clear();
 307  205
         stateSet.clear();
 308  205
         todoList.clear();
 309  205
     }
 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  205
         Set allEvents = new HashSet(step.getBeforeStatus().getEvents().size()
 335  
             + step.getExternalEvents().size());
 336  
         //for now, we only match against event names
 337  205
         for (Iterator ei = step.getBeforeStatus().getEvents().iterator();
 338  594
                 ei.hasNext();) {
 339  389
             TriggerEvent te = (TriggerEvent) ei.next();
 340  389
             allEvents.add(te.getName());
 341  
         }
 342  205
         for (Iterator ei = step.getExternalEvents().iterator();
 343  283
                 ei.hasNext();) {
 344  78
             TriggerEvent te = (TriggerEvent) ei.next();
 345  78
             allEvents.add(te.getName());
 346  
         }
 347  
         // Finalize invokes, if applicable
 348  205
         for (Iterator iter = scInstance.getInvokers().keySet().iterator();
 349  205
                 iter.hasNext();) {
 350  0
             State s = (State) iter.next();
 351  0
             if (finalizeMatch(s.getId(), allEvents)) {
 352  0
                 Finalize fn = s.getInvoke().getFinalize();
 353  0
                 if (fn != null) {
 354  
                     try {
 355  0
                         for (Iterator fnIter = fn.getActions().iterator();
 356  0
                                 fnIter.hasNext();) {
 357  0
                             ((Action) fnIter.next()).execute(evtDispatcher,
 358  
                                 errRep, scInstance, appLog,
 359  
                                 step.getAfterStatus().getEvents());
 360  
                         }
 361  0
                     } catch (SCXMLExpressionException e) {
 362  0
                         errRep.onError(ErrorReporter.EXPRESSION_ERROR,
 363  
                             e.getMessage(), fn);
 364  0
                     }
 365  
                 }
 366  
             }
 367  
         }
 368  
         //remove list (filtered-out list)
 369  205
         List removeList = new LinkedList();
 370  
         //iterate over non-filtered transition set
 371  205
         for (Iterator iter = step.getTransitList().iterator();
 372  530
                 iter.hasNext();) {
 373  325
             Transition t = (Transition) iter.next();
 374  
             // event check
 375  325
             String event = t.getEvent();
 376  325
             if (!eventMatch(event, allEvents)) {
 377  
                 // t has a non-empty event which is not triggered
 378  219
                 removeList.add(t);
 379  219
                 continue; //makes no sense to eval guard cond.
 380  
             }
 381  
             // guard condition check
 382  
             Boolean rslt;
 383  106
             String expr = t.getCond();
 384  106
             if (SCXMLHelper.isStringEmpty(expr)) {
 385  46
                 rslt = Boolean.TRUE;
 386  
             } else {
 387  
                 try {
 388  60
                     rslt = scInstance.getEvaluator().evalCond(scInstance.
 389  
                             getContext(t.getParent()), t.getCond());
 390  0
                 } catch (SCXMLExpressionException e) {
 391  0
                     rslt = Boolean.FALSE;
 392  0
                     errRep.onError(ErrorReporter.EXPRESSION_ERROR, e
 393  
                             .getMessage(), t);
 394  60
                 }
 395  
             }
 396  106
             if (!rslt.booleanValue()) {
 397  
                 // guard condition has not passed
 398  17
                 removeList.add(t);
 399  
             }
 400  
         }
 401  
         // apply event + guard condition filter
 402  205
         step.getTransitList().removeAll(removeList);
 403  
         // cleanup temporary structures
 404  205
         allEvents.clear();
 405  205
         removeList.clear();
 406  
         // optimization - global precedence potentially applies
 407  
         // only if there are multiple enabled transitions
 408  205
         if (step.getTransitList().size() > 1) {
 409  
             // global transition precedence check
 410  0
             Object[] trans = step.getTransitList().toArray();
 411  0
             Set currentStates = step.getBeforeStatus().getStates();
 412  
             // non-determinism candidates
 413  0
             Set nonDeterm = new HashSet();
 414  0
             for (int i = 0; i < trans.length; i++) {
 415  0
                 Transition t = (Transition) trans[i];
 416  0
                 TransitionTarget tsrc = t.getParent();
 417  0
                 for (int j = i + 1; j < trans.length; j++) {
 418  0
                     Transition t2 = (Transition) trans[j];
 419  0
                     boolean conflict = SCXMLHelper.inConflict(t, t2,
 420  
                             currentStates);
 421  0
                     if (conflict) {
 422  
                         //potentially conflicting transitions
 423  0
                         TransitionTarget t2src = t2.getParent();
 424  0
                         if (SCXMLHelper.isDescendant(t2src, tsrc)) {
 425  
                             //t2 takes precedence over t
 426  0
                             removeList.add(t);
 427  0
                             break; //it makes no sense to waste cycles with t
 428  0
                         } else if (SCXMLHelper.isDescendant(tsrc, t2src)) {
 429  
                             //t takes precendence over t2
 430  0
                             removeList.add(t2);
 431  
                         } else {
 432  
                             //add both to the non-determinism candidates
 433  0
                             nonDeterm.add(t);
 434  0
                             nonDeterm.add(t2);
 435  
                         }
 436  
                     }
 437  
                 }
 438  
             }
 439  
             // check if all non-deterministic situations have been resolved
 440  0
             nonDeterm.removeAll(removeList);
 441  0
             if (nonDeterm.size() > 0) {
 442  0
                 errRep.onError(ErrorReporter.NON_DETERMINISTIC,
 443  
                     "Multiple conflicting transitions enabled.", nonDeterm);
 444  
             }
 445  
             // apply global transition filter
 446  0
             step.getTransitList().removeAll(removeList);
 447  0
             removeList.clear();
 448  0
             nonDeterm.clear();
 449  
         }
 450  205
     }
 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  205
         Set seedSet = new HashSet();
 470  205
         Set regions = new HashSet();
 471  205
         for (Iterator i = transitList.iterator(); i.hasNext();) {
 472  89
             Transition t = (Transition) i.next();
 473  
             //iterate over transitions and add target states
 474  89
             if (t.getTarget() != null) {
 475  89
                 seedSet.add(t.getTarget());
 476  
             }
 477  
             //build a set of all entered regions
 478  89
             Path p = t.getPath();
 479  89
             if (p.isCrossRegion()) {
 480  0
                 List regs = p.getRegionsEntered();
 481  0
                 for (Iterator j = regs.iterator(); j.hasNext();) {
 482  0
                     State region = (State) j.next();
 483  0
                     regions.addAll(((Parallel) region.getParent()).
 484  
                         getStates());
 485  
                 }
 486  
             }
 487  
         }
 488  
         //check whether all active regions have their siblings active too
 489  205
         Set allStates = new HashSet(residual);
 490  205
         allStates.addAll(seedSet);
 491  205
         allStates = SCXMLHelper.getAncestorClosure(allStates, null);
 492  205
         regions.removeAll(allStates);
 493  
         //iterate over inactive regions and visit them implicitly using initial
 494  205
         for (Iterator i = regions.iterator(); i.hasNext();) {
 495  0
             State reg = (State) i.next();
 496  0
             seedSet.add(reg);
 497  
         }
 498  205
         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  244
         LinkedList wrkSet = new LinkedList(states);
 515  
         // clear the seed-set - will be populated by leaf states
 516  244
         states.clear();
 517  428
         while (!wrkSet.isEmpty()) {
 518  184
             TransitionTarget tt = (TransitionTarget) wrkSet.removeFirst();
 519  184
             if (tt instanceof State) {
 520  170
                 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  170
                 if (st.isSimple()) {
 525  132
                     states.add(st); //leaf
 526  38
                 } else if (st.isOrthogonal()) {
 527  3
                     wrkSet.addLast(st.getParallel()); //parallel
 528  
                 } else {
 529  
                     // composite state
 530  35
                     Initial ini = st.getInitial();
 531  35
                     if (ini == null) {
 532  0
                         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  35
                         Transition initialTransition = ini.getTransition();
 538  35
                         if (initialTransition == null) {
 539  0
                             errRep.onError(ErrorReporter.ILLEGAL_INITIAL,
 540  
                                 "Initial transition is null!", st);
 541  
                         } else {
 542  35
                             TransitionTarget init = initialTransition.
 543  
                                 getTarget();
 544  35
                             if (init == null
 545  
                                 ||
 546  
                                 !(init instanceof State
 547  
                                   || init instanceof History)) {
 548  0
                                 errRep.onError(ErrorReporter.ILLEGAL_INITIAL,
 549  
                                 "Initial not pointing to a State or History!",
 550  
                                 st);
 551  
                             } else {
 552  35
                                 wrkSet.addLast(init);
 553  
                             }
 554  
                         }
 555  
                     }
 556  
                 }
 557  14
             } else if (tt instanceof Parallel) {
 558  3
                 Parallel prl = (Parallel) tt;
 559  3
                 for (Iterator i = prl.getStates().iterator(); i.hasNext();) {
 560  
                     //fork
 561  7
                     wrkSet.addLast(i.next());
 562  
                 }
 563  11
             } else if (tt instanceof History) {
 564  11
                 History h = (History) tt;
 565  11
                 if (scInstance.isEmpty(h)) {
 566  3
                     wrkSet.addLast(h.getTransition().getRuntimeTarget());
 567  
                 } else {
 568  8
                     wrkSet.addAll(scInstance.getLastConfiguration(h));
 569  
                 }
 570  
             } else {
 571  0
                 throw new ModelException("Unknown TransitionTarget subclass:"
 572  
                         + tt.getClass().getName());
 573  
             }
 574  
         }
 575  244
     }
 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  205
         Set oldState = step.getBeforeStatus().getStates();
 591  205
         for (Iterator i = step.getExitList().iterator(); i.hasNext();) {
 592  105
             Object o = i.next();
 593  105
             if (o instanceof State) {
 594  105
                 State s = (State) o;
 595  105
                 if (s.hasHistory()) {
 596  10
                     Set shallow = null;
 597  10
                     Set deep = null;
 598  10
                     for (Iterator j = s.getHistory().iterator();
 599  20
                             j.hasNext();) {
 600  10
                         History h = (History) j.next();
 601  10
                         if (h.isDeep()) {
 602  5
                             if (deep == null) {
 603  
                                 //calculate deep history for a given state once
 604  5
                                 deep = new HashSet();
 605  5
                                 Iterator k = oldState.iterator();
 606  10
                                 while (k.hasNext()) {
 607  5
                                     State os = (State) k.next();
 608  5
                                     if (SCXMLHelper.isDescendant(os, s)) {
 609  5
                                         deep.add(os);
 610  
                                     }
 611  
                                 }
 612  
                             }
 613  5
                             scInstance.setLastConfiguration(h, deep);
 614  
                         } else {
 615  5
                             if (shallow == null) {
 616  
                                 //calculate shallow history for a given state
 617  
                                 // once
 618  5
                                 shallow = new HashSet();
 619  5
                                 shallow.addAll(s.getChildren().values());
 620  5
                                 shallow.retainAll(SCXMLHelper
 621  
                                         .getAncestorClosure(oldState, null));
 622  
                             }
 623  5
                             scInstance.setLastConfiguration(h, shallow);
 624  
                         }
 625  
                     }
 626  10
                     shallow = null;
 627  10
                     deep = null;
 628  
                 }
 629  
             }
 630  
         }
 631  205
     }
 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  205
         Set currentStates = step.getBeforeStatus().getStates();
 648  205
         List transitions = step.getTransitList();
 649  
         // DetermineExitedStates (currentStates, transitList) -> exitedStates
 650  205
         Set exitedStates = new HashSet();
 651  205
         for (Iterator i = transitions.iterator(); i.hasNext();) {
 652  89
             Transition t = (Transition) i.next();
 653  89
             Set ext = SCXMLHelper.getStatesExited(t, currentStates);
 654  89
             exitedStates.addAll(ext);
 655  
         }
 656  
         // compute residual states - these are preserved from the previous step
 657  205
         Set residual = new HashSet(currentStates);
 658  205
         residual.removeAll(exitedStates);
 659  
         // SeedTargetSet (residual, transitList) -> seedSet
 660  205
         Set seedSet = seedTargetSet(residual, transitions, errorReporter);
 661  
         // DetermineTargetStates (initialTargetSet) -> targetSet
 662  205
         Set targetSet = step.getAfterStatus().getStates();
 663  205
         targetSet.addAll(seedSet); //copy to preserve seedSet
 664  205
         determineTargetStates(targetSet, errorReporter, scInstance);
 665  
         // BuildOnEntryList (targetSet, seedSet) -> entryList
 666  205
         Set entered = SCXMLHelper.getAncestorClosure(targetSet, seedSet);
 667  205
         seedSet.clear();
 668  205
         for (Iterator i = transitions.iterator(); i.hasNext();) {
 669  89
             Transition t = (Transition) i.next();
 670  89
             entered.addAll(t.getPath().getDownwardSegment());
 671  
             // If target is a History pseudo state, remove from entered list
 672  89
             if (t.getRuntimeTarget() instanceof History) {
 673  9
                 entered.remove(t.getRuntimeTarget());
 674  
             }
 675  
         }
 676  
         // Check whether the computed state config is legal
 677  205
         targetSet.addAll(residual);
 678  205
         residual.clear();
 679  205
         if (!SCXMLHelper.isLegalConfig(targetSet, errorReporter)) {
 680  0
             throw new ModelException("Illegal state machine configuration!");
 681  
         }
 682  
         // sort onEntry and onExit according state hierarchy
 683  205
         Object[] oex = exitedStates.toArray();
 684  205
         exitedStates.clear();
 685  205
         Object[] oen = entered.toArray();
 686  205
         entered.clear();
 687  205
         Arrays.sort(oex, getTTComparator());
 688  205
         Arrays.sort(oen, getTTComparator());
 689  205
         step.getExitList().addAll(Arrays.asList(oex));
 690  
         // we need to impose reverse order for the onEntry list
 691  205
         List entering = Arrays.asList(oen);
 692  205
         Collections.reverse(entering);
 693  205
         step.getEntryList().addAll(entering);
 694  
         // reset 'done' flag
 695  205
         for (Iterator reset = entering.iterator(); reset.hasNext();) {
 696  116
             Object o = reset.next();
 697  116
             if (o instanceof State) {
 698  115
                 ((State) o).setDone(false);
 699  
             }
 700  
         }
 701  205
     }
 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  116
         Set eventNames = new HashSet();
 719  
         //for now, we only match against event names
 720  194
         for (int i = 0; i < events.length; i++) {
 721  78
             eventNames.add(events[i].getName());
 722  
         }
 723  116
         for (Iterator invokeIter = scInstance.getInvokers().entrySet().
 724  116
                 iterator(); invokeIter.hasNext();) {
 725  0
             Map.Entry iEntry = (Map.Entry) invokeIter.next();
 726  0
             String parentId = ((TransitionTarget) iEntry.getKey()).getId();
 727  0
             if (!finalizeMatch(parentId, eventNames)) { // prevent cycles
 728  0
                 Invoker inv = (Invoker) iEntry.getValue();
 729  
                 try {
 730  0
                     inv.parentEvents(events);
 731  0
                 } catch (InvokerException ie) {
 732  0
                     appLog.error(ie.getMessage(), ie);
 733  0
                     throw new ModelException(ie.getMessage(), ie.getCause());
 734  0
                 }
 735  
             }
 736  
         }
 737  116
     }
 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  116
         Evaluator eval = scInstance.getEvaluator();
 752  116
         Collection internalEvents = step.getAfterStatus().getEvents();
 753  116
         for (Iterator iter = step.getAfterStatus().getStates().iterator();
 754  238
                 iter.hasNext();) {
 755  122
             State s = (State) iter.next();
 756  122
             Context ctx = scInstance.getContext(s);
 757  122
             Invoke i = s.getInvoke();
 758  122
             if (i != null && scInstance.getInvoker(s) == null) {
 759  1
                 String src = i.getSrc();
 760  1
                 if (src == null) {
 761  0
                     String srcexpr = i.getSrcexpr();
 762  0
                     Object srcObj = null;
 763  
                     try {
 764  0
                         srcObj = eval.eval(ctx, srcexpr);
 765  0
                         src = String.valueOf(srcObj);
 766  0
                     } catch (SCXMLExpressionException see) {
 767  0
                         errRep.onError(ErrorReporter.EXPRESSION_ERROR,
 768  
                             see.getMessage(), i);
 769  0
                     }
 770  
                 }
 771  1
                 String source = src;
 772  1
                 PathResolver pr = i.getPathResolver();
 773  1
                 if (pr != null) {
 774  1
                     source = i.getPathResolver().resolvePath(src);
 775  
                 }
 776  1
                 String ttype = i.getTargettype();
 777  1
                 Invoker inv = null;
 778  
                 try {
 779  1
                     inv = scInstance.newInvoker(ttype);
 780  0
                 } catch (InvokerException ie) {
 781  0
                     TriggerEvent te = new TriggerEvent(s.getId()
 782  
                         + ".invoke.failed", TriggerEvent.ERROR_EVENT);
 783  0
                     internalEvents.add(te);
 784  0
                     continue;
 785  1
                 }
 786  1
                 inv.setParentStateId(s.getId());
 787  1
                 inv.setSCInstance(scInstance);
 788  1
                 Map params = i.getParams();
 789  1
                 Map args = new HashMap();
 790  1
                 for (Iterator pIter = params.entrySet().iterator();
 791  3
                         pIter.hasNext();) {
 792  2
                     Map.Entry entry = (Map.Entry) pIter.next();
 793  2
                     String argName = (String) entry.getKey();
 794  2
                     String argExpr = (String) entry.getValue();
 795  2
                     Object argValue = null;
 796  2
                     if (argExpr != null && argExpr.trim().length() > 0) {
 797  
                         try {
 798  2
                             argValue = eval.eval(ctx, argExpr);
 799  0
                         } catch (SCXMLExpressionException see) {
 800  0
                             errRep.onError(ErrorReporter.EXPRESSION_ERROR,
 801  
                                 see.getMessage(), i);
 802  2
                         }
 803  
                     }
 804  2
                     args.put(argName, argValue);
 805  
                 }
 806  
                 try {
 807  1
                     inv.invoke(source, args);
 808  0
                 } catch (InvokerException ie) {
 809  0
                     TriggerEvent te = new TriggerEvent(s.getId()
 810  
                         + ".invoke.failed", TriggerEvent.ERROR_EVENT);
 811  0
                     internalEvents.add(te);
 812  0
                     continue;
 813  1
                 }
 814  1
                 scInstance.setInvoker(s, inv);
 815  
             }
 816  
         }
 817  116
     }
 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  325
         if (SCXMLHelper.isStringEmpty(transEvent)) {
 833  15
             return true;
 834  
         } else {
 835  310
             String transEventDot = transEvent + "."; // prefix event support
 836  310
             Iterator i = eventOccurrences.iterator();
 837  894
             while (i.hasNext()) {
 838  675
                 String evt = (String) i.next();
 839  675
                 if (evt == null) {
 840  0
                     continue; // Unnamed events
 841  675
                 } else if (evt.equals("*")) {
 842  3
                     return true; // Wildcard
 843  672
                 } else if (evt.equals(transEvent)
 844  
                             || evt.startsWith(transEventDot)) {
 845  88
                     return true;
 846  
                 }
 847  
             }
 848  219
             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  0
         String prefix = parentStateId + ".invoke."; // invoke prefix
 865  0
         Iterator i = eventOccurrences.iterator();
 866  0
         while (i.hasNext()) {
 867  0
             String evt = (String) i.next();
 868  0
             if (evt == null) {
 869  0
                 continue; // Unnamed events
 870  0
             } else if (evt.startsWith(prefix)) {
 871  0
                 return true;
 872  
             }
 873  
         }
 874  0
         return false;
 875  
     }
 876  
 
 877  
     /**
 878  
      * TransitionTargetComparator factory method.
 879  
      * @return Comparator The TransitionTarget comparator
 880  
      */
 881  
     protected Comparator getTTComparator() {
 882  449
         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  0
         this.appLog = log;
 892  0
     }
 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  0
         return appLog;
 901  
     }
 902  
 
 903  
 }
 904