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

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

 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  48
 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  48
     private Log appLog = LogFactory.getLog("scxml.app.log");
 83  
 
 84  
     /**
 85  
      * The TransitionTarget comparator.
 86  
      */
 87  48
     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  48
         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  50
         State tmp = input.getInitialState();
 128  50
         if (tmp == null) {
 129  0
             errRep.onError(ErrorConstants.NO_INITIAL,
 130  
                     "SCXML initialstate is missing!", input);
 131  
         } else {
 132  50
             states.add(tmp);
 133  50
             determineTargetStates(states, errRep, scInstance);
 134  
             //set of ALL entered states (even if initialState is a jump-over)
 135  50
             Set onEntry = SCXMLHelper.getAncestorClosure(states, null);
 136  
             // sort onEntry according state hierarchy
 137  50
             Object[] oen = onEntry.toArray();
 138  50
             onEntry.clear();
 139  50
             Arrays.sort(oen, getTTComparator());
 140  
             // we need to impose reverse order for the onEntry list
 141  50
             List entering = Arrays.asList(oen);
 142  50
             Collections.reverse(entering);
 143  50
             entryList.addAll(entering);
 144  
 
 145  
         }
 146  50
     }
 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  316
         NotificationRegistry nr = scInstance.getNotificationRegistry();
 170  316
         Collection internalEvents = step.getAfterStatus().getEvents();
 171  316
         Map invokers = scInstance.getInvokers();
 172  
         // ExecutePhaseActions / OnExit
 173  316
         for (Iterator i = step.getExitList().iterator(); i.hasNext();) {
 174  134
             TransitionTarget tt = (TransitionTarget) i.next();
 175  134
             OnExit oe = tt.getOnExit();
 176  
             try {
 177  134
                 for (Iterator onExitIter = oe.getActions().iterator();
 178  158
                         onExitIter.hasNext();) {
 179  24
                     ((Action) onExitIter.next()).execute(evtDispatcher,
 180  
                         errRep, scInstance, appLog, internalEvents);
 181  
                 }
 182  0
             } catch (SCXMLExpressionException e) {
 183  0
                 errRep.onError(ErrorConstants.EXPRESSION_ERROR, e.getMessage(),
 184  
                         oe);
 185  134
             }
 186  
             // check if invoke is active in this state
 187  134
             if (invokers.containsKey(tt)) {
 188  0
                 Invoker toCancel = (Invoker) invokers.get(tt);
 189  
                 try {
 190  0
                     toCancel.cancel();
 191  0
                 } catch (InvokerException ie) {
 192  0
                     TriggerEvent te = new TriggerEvent(tt.getId()
 193  
                         + ".invoke.cancel.failed", TriggerEvent.ERROR_EVENT);
 194  0
                     internalEvents.add(te);
 195  0
                 }
 196  
                 // done here, don't wait for cancel response
 197  0
                 invokers.remove(tt);
 198  
             }
 199  134
             nr.fireOnExit(tt, tt);
 200  134
             nr.fireOnExit(stateMachine, tt);
 201  134
             TriggerEvent te = new TriggerEvent(tt.getId() + ".exit",
 202  
                     TriggerEvent.CHANGE_EVENT);
 203  134
             internalEvents.add(te);
 204  
         }
 205  
         // ExecutePhaseActions / Transitions
 206  316
         for (Iterator i = step.getTransitList().iterator(); i.hasNext();) {
 207  115
             Transition t = (Transition) i.next();
 208  
             try {
 209  115
                 for (Iterator transitIter = t.getActions().iterator();
 210  124
                         transitIter.hasNext();) {
 211  9
                     ((Action) transitIter.next()).execute(evtDispatcher,
 212  
                         errRep, scInstance, appLog, internalEvents);
 213  
                 }
 214  0
             } catch (SCXMLExpressionException e) {
 215  0
                 errRep.onError(ErrorConstants.EXPRESSION_ERROR,
 216  
                     e.getMessage(), t);
 217  115
             }
 218  115
             nr.fireOnTransition(t, t.getParent(), t.getRuntimeTarget(), t);
 219  115
             nr.fireOnTransition(stateMachine, t.getParent(),
 220  
                 t.getRuntimeTarget(), t);
 221  
         }
 222  
         // ExecutePhaseActions / OnEntry
 223  316
         for (Iterator i = step.getEntryList().iterator(); i.hasNext();) {
 224  227
             TransitionTarget tt = (TransitionTarget) i.next();
 225  227
             OnEntry oe = tt.getOnEntry();
 226  
             try {
 227  227
                 for (Iterator onEntryIter = oe.getActions().iterator();
 228  302
                         onEntryIter.hasNext();) {
 229  75
                     ((Action) onEntryIter.next()).execute(evtDispatcher,
 230  
                         errRep, scInstance, appLog, internalEvents);
 231  
                 }
 232  0
             } catch (SCXMLExpressionException e) {
 233  0
                 errRep.onError(ErrorConstants.EXPRESSION_ERROR, e.getMessage(),
 234  
                         oe);
 235  227
             }
 236  227
             nr.fireOnEntry(tt, tt);
 237  227
             nr.fireOnEntry(stateMachine, tt);
 238  227
             TriggerEvent te = new TriggerEvent(tt.getId() + ".entry",
 239  
                     TriggerEvent.CHANGE_EVENT);
 240  227
             internalEvents.add(te);
 241  
             //3.2.1 and 3.4 (.done events)
 242  227
             if (tt instanceof State) {
 243  224
                 State ts = (State) tt;
 244  224
                 if (ts.getIsFinal()) {
 245  41
                     State parent = (State) ts.getParent();
 246  41
                     String prefix = "";
 247  41
                     if (parent != null) {
 248  21
                         prefix = parent.getId();
 249  
                     }
 250  41
                     te = new TriggerEvent(prefix + ".done",
 251  
                             TriggerEvent.CHANGE_EVENT);
 252  41
                     internalEvents.add(te);
 253  41
                     if (parent != null) {
 254  21
                         parent.setDone(true);
 255  
                     }
 256  41
                     if (parent != null && parent.isRegion()) {
 257  
                         //3.4 we got a region, which is finalized
 258  
                         //let's check its siblings too
 259  0
                         Parallel p = (Parallel) parent.getParent();
 260  0
                         int finCount = 0;
 261  0
                         int pCount = p.getStates().size();
 262  0
                         for (Iterator regions = p.getStates().iterator();
 263  0
                                 regions.hasNext();) {
 264  0
                             State reg = (State) regions.next();
 265  0
                             if (reg.isDone()) {
 266  0
                                 finCount++;
 267  
                             }
 268  
                         }
 269  0
                         if (finCount == pCount) {
 270  0
                             te = new TriggerEvent(p.getId() + ".done",
 271  
                                         TriggerEvent.CHANGE_EVENT);
 272  0
                             internalEvents.add(te);
 273  0
                             te = new TriggerEvent(p.getParent().getId()
 274  
                                 + ".done", TriggerEvent.CHANGE_EVENT);
 275  0
                             internalEvents.add(te);
 276  
                             //this is not in the specs, but is makes sense
 277  0
                             p.getParentState().setDone(true);
 278  
                         }
 279  
                     }
 280  
                 }
 281  
             }
 282  
         }
 283  316
     }
 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  266
         Set transSet = new HashSet();
 298  
         // prevents visiting the same state multiple times
 299  266
         Set stateSet = new HashSet(step.getBeforeStatus().getStates());
 300  
         // breath-first search to-do list
 301  266
         LinkedList todoList = new LinkedList(stateSet);
 302  717
         while (!todoList.isEmpty()) {
 303  451
             State st = (State) todoList.removeFirst();
 304  451
             for (Iterator i = st.getTransitionsList().iterator();
 305  847
                     i.hasNext();) {
 306  396
                 Transition t = (Transition) i.next();
 307  396
                 if (!transSet.contains(t)) {
 308  396
                     transSet.add(t);
 309  396
                     step.getTransitList().add(t);
 310  
                 }
 311  
             }
 312  451
             State parent = st.getParentState();
 313  451
             if (parent != null && !stateSet.contains(parent)) {
 314  175
                 stateSet.add(parent);
 315  175
                 todoList.addLast(parent);
 316  
             }
 317  
         }
 318  266
         transSet.clear();
 319  266
         stateSet.clear();
 320  266
         todoList.clear();
 321  266
     }
 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  266
         Set allEvents = new HashSet(step.getBeforeStatus().getEvents().size()
 347  
             + step.getExternalEvents().size());
 348  
         //for now, we only match against event names
 349  266
         for (Iterator ei = step.getBeforeStatus().getEvents().iterator();
 350  750
                 ei.hasNext();) {
 351  484
             TriggerEvent te = (TriggerEvent) ei.next();
 352  484
             allEvents.add(te.getName());
 353  
         }
 354  266
         for (Iterator ei = step.getExternalEvents().iterator();
 355  368
                 ei.hasNext();) {
 356  102
             TriggerEvent te = (TriggerEvent) ei.next();
 357  102
             allEvents.add(te.getName());
 358  
         }
 359  
         // Finalize invokes, if applicable
 360  266
         for (Iterator iter = scInstance.getInvokers().keySet().iterator();
 361  266
                 iter.hasNext();) {
 362  0
             State s = (State) iter.next();
 363  0
             if (finalizeMatch(s.getId(), allEvents)) {
 364  0
                 Finalize fn = s.getInvoke().getFinalize();
 365  0
                 if (fn != null) {
 366  
                     try {
 367  0
                         for (Iterator fnIter = fn.getActions().iterator();
 368  0
                                 fnIter.hasNext();) {
 369  0
                             ((Action) fnIter.next()).execute(evtDispatcher,
 370  
                                 errRep, scInstance, appLog,
 371  
                                 step.getAfterStatus().getEvents());
 372  
                         }
 373  0
                     } catch (SCXMLExpressionException e) {
 374  0
                         errRep.onError(ErrorConstants.EXPRESSION_ERROR,
 375  
                             e.getMessage(), fn);
 376  0
                     }
 377  
                 }
 378  
             }
 379  
         }
 380  
         //remove list (filtered-out list)
 381  266
         List removeList = new LinkedList();
 382  
         //iterate over non-filtered transition set
 383  266
         for (Iterator iter = step.getTransitList().iterator();
 384  662
                 iter.hasNext();) {
 385  396
             Transition t = (Transition) iter.next();
 386  
             // event check
 387  396
             String event = t.getEvent();
 388  396
             if (!eventMatch(event, allEvents)) {
 389  
                 // t has a non-empty event which is not triggered
 390  264
                 removeList.add(t);
 391  264
                 continue; //makes no sense to eval guard cond.
 392  
             }
 393  
             // guard condition check
 394  
             Boolean rslt;
 395  132
             String expr = t.getCond();
 396  132
             if (SCXMLHelper.isStringEmpty(expr)) {
 397  54
                 rslt = Boolean.TRUE;
 398  
             } else {
 399  
                 try {
 400  78
                     Context ctx = scInstance.getContext(t.getParent());
 401  78
                     ctx.setLocal(NAMESPACES_KEY, t.getNamespaces());
 402  78
                     rslt = scInstance.getEvaluator().evalCond(ctx,
 403  
                         t.getCond());
 404  78
                     ctx.setLocal(NAMESPACES_KEY, null);
 405  0
                 } catch (SCXMLExpressionException e) {
 406  0
                     rslt = Boolean.FALSE;
 407  0
                     errRep.onError(ErrorConstants.EXPRESSION_ERROR, e
 408  
                             .getMessage(), t);
 409  78
                 }
 410  
             }
 411  132
             if (!rslt.booleanValue()) {
 412  
                 // guard condition has not passed
 413  17
                 removeList.add(t);
 414  
             }
 415  
         }
 416  
         // apply event + guard condition filter
 417  266
         step.getTransitList().removeAll(removeList);
 418  
         // cleanup temporary structures
 419  266
         allEvents.clear();
 420  266
         removeList.clear();
 421  
         // optimization - global precedence potentially applies
 422  
         // only if there are multiple enabled transitions
 423  266
         if (step.getTransitList().size() > 1) {
 424  
             // global transition precedence check
 425  0
             Object[] trans = step.getTransitList().toArray();
 426  0
             Set currentStates = step.getBeforeStatus().getStates();
 427  
             // non-determinism candidates
 428  0
             Set nonDeterm = new HashSet();
 429  0
             for (int i = 0; i < trans.length; i++) {
 430  0
                 Transition t = (Transition) trans[i];
 431  0
                 TransitionTarget tsrc = t.getParent();
 432  0
                 for (int j = i + 1; j < trans.length; j++) {
 433  0
                     Transition t2 = (Transition) trans[j];
 434  0
                     boolean conflict = SCXMLHelper.inConflict(t, t2,
 435  
                             currentStates);
 436  0
                     if (conflict) {
 437  
                         //potentially conflicting transitions
 438  0
                         TransitionTarget t2src = t2.getParent();
 439  0
                         if (SCXMLHelper.isDescendant(t2src, tsrc)) {
 440  
                             //t2 takes precedence over t
 441  0
                             removeList.add(t);
 442  0
                             break; //it makes no sense to waste cycles with t
 443  0
                         } else if (SCXMLHelper.isDescendant(tsrc, t2src)) {
 444  
                             //t takes precendence over t2
 445  0
                             removeList.add(t2);
 446  
                         } else {
 447  
                             //add both to the non-determinism candidates
 448  0
                             nonDeterm.add(t);
 449  0
                             nonDeterm.add(t2);
 450  
                         }
 451  
                     }
 452  
                 }
 453  
             }
 454  
             // check if all non-deterministic situations have been resolved
 455  0
             nonDeterm.removeAll(removeList);
 456  0
             if (nonDeterm.size() > 0) {
 457  0
                 errRep.onError(ErrorConstants.NON_DETERMINISTIC,
 458  
                     "Multiple conflicting transitions enabled.", nonDeterm);
 459  
             }
 460  
             // apply global transition filter
 461  0
             step.getTransitList().removeAll(removeList);
 462  0
             removeList.clear();
 463  0
             nonDeterm.clear();
 464  
         }
 465  266
     }
 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  266
         Set seedSet = new HashSet();
 485  266
         Set regions = new HashSet();
 486  266
         for (Iterator i = transitList.iterator(); i.hasNext();) {
 487  115
             Transition t = (Transition) i.next();
 488  
             //iterate over transitions and add target states
 489  115
             if (t.getTarget() != null) {
 490  114
                 seedSet.add(t.getTarget());
 491  
             }
 492  
             //build a set of all entered regions
 493  115
             Path p = t.getPath();
 494  115
             if (p.isCrossRegion()) {
 495  0
                 List regs = p.getRegionsEntered();
 496  0
                 for (Iterator j = regs.iterator(); j.hasNext();) {
 497  0
                     State region = (State) j.next();
 498  0
                     regions.addAll(((Parallel) region.getParent()).
 499  
                         getStates());
 500  
                 }
 501  
             }
 502  
         }
 503  
         //check whether all active regions have their siblings active too
 504  266
         Set allStates = new HashSet(residual);
 505  266
         allStates.addAll(seedSet);
 506  266
         allStates = SCXMLHelper.getAncestorClosure(allStates, null);
 507  266
         regions.removeAll(allStates);
 508  
         //iterate over inactive regions and visit them implicitly using initial
 509  266
         for (Iterator i = regions.iterator(); i.hasNext();) {
 510  0
             State reg = (State) i.next();
 511  0
             seedSet.add(reg);
 512  
         }
 513  266
         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  316
         LinkedList wrkSet = new LinkedList(states);
 530  
         // clear the seed-set - will be populated by leaf states
 531  316
         states.clear();
 532  538
         while (!wrkSet.isEmpty()) {
 533  222
             TransitionTarget tt = (TransitionTarget) wrkSet.removeFirst();
 534  222
             if (tt instanceof State) {
 535  208
                 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  208
                 if (st.isSimple()) {
 540  168
                     states.add(st); //leaf
 541  40
                 } else if (st.isOrthogonal()) {
 542  3
                     wrkSet.addLast(st.getParallel()); //parallel
 543  
                 } else {
 544  
                     // composite state
 545  37
                     Initial ini = st.getInitial();
 546  37
                     if (ini == null) {
 547  0
                         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  37
                         Transition initialTransition = ini.getTransition();
 553  37
                         if (initialTransition == null) {
 554  0
                             errRep.onError(ErrorConstants.ILLEGAL_INITIAL,
 555  
                                 "Initial transition is null!", st);
 556  
                         } else {
 557  37
                             TransitionTarget init = initialTransition.
 558  
                                 getTarget();
 559  37
                             if (init == null
 560  
                                 ||
 561  
                                 !(init instanceof State
 562  
                                   || init instanceof History)) {
 563  0
                                 errRep.onError(ErrorConstants.ILLEGAL_INITIAL,
 564  
                                 "Initial not pointing to a State or History!",
 565  
                                 st);
 566  
                             } else {
 567  37
                                 wrkSet.addLast(init);
 568  
                             }
 569  
                         }
 570  
                     }
 571  
                 }
 572  14
             } else if (tt instanceof Parallel) {
 573  3
                 Parallel prl = (Parallel) tt;
 574  3
                 for (Iterator i = prl.getStates().iterator(); i.hasNext();) {
 575  
                     //fork
 576  7
                     wrkSet.addLast(i.next());
 577  
                 }
 578  11
             } else if (tt instanceof History) {
 579  11
                 History h = (History) tt;
 580  11
                 if (scInstance.isEmpty(h)) {
 581  3
                     wrkSet.addLast(h.getTransition().getRuntimeTarget());
 582  
                 } else {
 583  8
                     wrkSet.addAll(scInstance.getLastConfiguration(h));
 584  
                 }
 585  
             } else {
 586  0
                 throw new ModelException("Unknown TransitionTarget subclass:"
 587  
                         + tt.getClass().getName());
 588  
             }
 589  
         }
 590  316
     }
 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  266
         Set oldState = step.getBeforeStatus().getStates();
 606  266
         for (Iterator i = step.getExitList().iterator(); i.hasNext();) {
 607  134
             Object o = i.next();
 608  134
             if (o instanceof State) {
 609  134
                 State s = (State) o;
 610  134
                 if (s.hasHistory()) {
 611  10
                     Set shallow = null;
 612  10
                     Set deep = null;
 613  10
                     for (Iterator j = s.getHistory().iterator();
 614  20
                             j.hasNext();) {
 615  10
                         History h = (History) j.next();
 616  10
                         if (h.isDeep()) {
 617  5
                             if (deep == null) {
 618  
                                 //calculate deep history for a given state once
 619  5
                                 deep = new HashSet();
 620  5
                                 Iterator k = oldState.iterator();
 621  10
                                 while (k.hasNext()) {
 622  5
                                     State os = (State) k.next();
 623  5
                                     if (SCXMLHelper.isDescendant(os, s)) {
 624  5
                                         deep.add(os);
 625  
                                     }
 626  
                                 }
 627  
                             }
 628  5
                             scInstance.setLastConfiguration(h, deep);
 629  
                         } else {
 630  5
                             if (shallow == null) {
 631  
                                 //calculate shallow history for a given state
 632  
                                 // once
 633  5
                                 shallow = new HashSet();
 634  5
                                 shallow.addAll(s.getChildren().values());
 635  5
                                 shallow.retainAll(SCXMLHelper
 636  
                                         .getAncestorClosure(oldState, null));
 637  
                             }
 638  5
                             scInstance.setLastConfiguration(h, shallow);
 639  
                         }
 640  
                     }
 641  10
                     shallow = null;
 642  10
                     deep = null;
 643  
                 }
 644  
             }
 645  
         }
 646  266
     }
 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  266
         Set currentStates = step.getBeforeStatus().getStates();
 663  266
         List transitions = step.getTransitList();
 664  
         // DetermineExitedStates (currentStates, transitList) -> exitedStates
 665  266
         Set exitedStates = new HashSet();
 666  266
         for (Iterator i = transitions.iterator(); i.hasNext();) {
 667  115
             Transition t = (Transition) i.next();
 668  115
             Set ext = SCXMLHelper.getStatesExited(t, currentStates);
 669  115
             exitedStates.addAll(ext);
 670  
         }
 671  
         // compute residual states - these are preserved from the previous step
 672  266
         Set residual = new HashSet(currentStates);
 673  266
         residual.removeAll(exitedStates);
 674  
         // SeedTargetSet (residual, transitList) -> seedSet
 675  266
         Set seedSet = seedTargetSet(residual, transitions, errorReporter);
 676  
         // DetermineTargetStates (initialTargetSet) -> targetSet
 677  266
         Set targetSet = step.getAfterStatus().getStates();
 678  266
         targetSet.addAll(seedSet); //copy to preserve seedSet
 679  266
         determineTargetStates(targetSet, errorReporter, scInstance);
 680  
         // BuildOnEntryList (targetSet, seedSet) -> entryList
 681  266
         Set entered = SCXMLHelper.getAncestorClosure(targetSet, seedSet);
 682  266
         seedSet.clear();
 683  266
         for (Iterator i = transitions.iterator(); i.hasNext();) {
 684  115
             Transition t = (Transition) i.next();
 685  115
             entered.addAll(t.getPath().getDownwardSegment());
 686  
             // If target is a History pseudo state, remove from entered list
 687  115
             if (t.getRuntimeTarget() instanceof History) {
 688  9
                 entered.remove(t.getRuntimeTarget());
 689  
             }
 690  
         }
 691  
         // Check whether the computed state config is legal
 692  266
         targetSet.addAll(residual);
 693  266
         residual.clear();
 694  266
         if (!SCXMLHelper.isLegalConfig(targetSet, errorReporter)) {
 695  0
             throw new ModelException("Illegal state machine configuration!");
 696  
         }
 697  
         // sort onEntry and onExit according state hierarchy
 698  266
         Object[] oex = exitedStates.toArray();
 699  266
         exitedStates.clear();
 700  266
         Object[] oen = entered.toArray();
 701  266
         entered.clear();
 702  266
         Arrays.sort(oex, getTTComparator());
 703  266
         Arrays.sort(oen, getTTComparator());
 704  266
         step.getExitList().addAll(Arrays.asList(oex));
 705  
         // we need to impose reverse order for the onEntry list
 706  266
         List entering = Arrays.asList(oen);
 707  266
         Collections.reverse(entering);
 708  266
         step.getEntryList().addAll(entering);
 709  
         // reset 'done' flag
 710  266
         for (Iterator reset = entering.iterator(); reset.hasNext();) {
 711  143
             Object o = reset.next();
 712  143
             if (o instanceof State) {
 713  142
                 ((State) o).setDone(false);
 714  
             }
 715  
         }
 716  266
     }
 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  151
         Set eventNames = new HashSet();
 734  
         //for now, we only match against event names
 735  253
         for (int i = 0; i < events.length; i++) {
 736  102
             eventNames.add(events[i].getName());
 737  
         }
 738  151
         for (Iterator invokeIter = scInstance.getInvokers().entrySet().
 739  151
                 iterator(); invokeIter.hasNext();) {
 740  0
             Map.Entry iEntry = (Map.Entry) invokeIter.next();
 741  0
             String parentId = ((TransitionTarget) iEntry.getKey()).getId();
 742  0
             if (!finalizeMatch(parentId, eventNames)) { // prevent cycles
 743  0
                 Invoker inv = (Invoker) iEntry.getValue();
 744  
                 try {
 745  0
                     inv.parentEvents(events);
 746  0
                 } catch (InvokerException ie) {
 747  0
                     appLog.error(ie.getMessage(), ie);
 748  0
                     throw new ModelException(ie.getMessage(), ie.getCause());
 749  0
                 }
 750  
             }
 751  
         }
 752  151
     }
 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  151
         Evaluator eval = scInstance.getEvaluator();
 767  151
         Collection internalEvents = step.getAfterStatus().getEvents();
 768  151
         for (Iterator iter = step.getAfterStatus().getStates().iterator();
 769  308
                 iter.hasNext();) {
 770  157
             State s = (State) iter.next();
 771  157
             Context ctx = scInstance.getContext(s);
 772  157
             Invoke i = s.getInvoke();
 773  157
             if (i != null && scInstance.getInvoker(s) == null) {
 774  1
                 String src = i.getSrc();
 775  1
                 if (src == null) {
 776  0
                     String srcexpr = i.getSrcexpr();
 777  0
                     Object srcObj = null;
 778  
                     try {
 779  0
                         ctx.setLocal(NAMESPACES_KEY, i.getNamespaces());
 780  0
                         srcObj = eval.eval(ctx, srcexpr);
 781  0
                         ctx.setLocal(NAMESPACES_KEY, null);
 782  0
                         src = String.valueOf(srcObj);
 783  0
                     } catch (SCXMLExpressionException see) {
 784  0
                         errRep.onError(ErrorConstants.EXPRESSION_ERROR,
 785  
                             see.getMessage(), i);
 786  0
                     }
 787  
                 }
 788  1
                 String source = src;
 789  1
                 PathResolver pr = i.getPathResolver();
 790  1
                 if (pr != null) {
 791  1
                     source = i.getPathResolver().resolvePath(src);
 792  
                 }
 793  1
                 String ttype = i.getTargettype();
 794  1
                 Invoker inv = null;
 795  
                 try {
 796  1
                     inv = scInstance.newInvoker(ttype);
 797  0
                 } catch (InvokerException ie) {
 798  0
                     TriggerEvent te = new TriggerEvent(s.getId()
 799  
                         + ".invoke.failed", TriggerEvent.ERROR_EVENT);
 800  0
                     internalEvents.add(te);
 801  0
                     continue;
 802  1
                 }
 803  1
                 inv.setParentStateId(s.getId());
 804  1
                 inv.setSCInstance(scInstance);
 805  1
                 List params = i.params();
 806  1
                 Map args = new HashMap();
 807  1
                 for (Iterator pIter = params.iterator(); pIter.hasNext();) {
 808  2
                     Param p = (Param) pIter.next();
 809  2
                     String argExpr = p.getExpr();
 810  2
                     Object argValue = null;
 811  2
                     if (argExpr != null && argExpr.trim().length() > 0) {
 812  
                         try {
 813  2
                             ctx.setLocal(NAMESPACES_KEY, p.getNamespaces());
 814  2
                             argValue = eval.eval(ctx, argExpr);
 815  2
                             ctx.setLocal(NAMESPACES_KEY, null);
 816  0
                         } catch (SCXMLExpressionException see) {
 817  0
                             errRep.onError(ErrorConstants.EXPRESSION_ERROR,
 818  
                                 see.getMessage(), i);
 819  2
                         }
 820  
                     }
 821  2
                     args.put(p.getName(), argValue);
 822  
                 }
 823  
                 try {
 824  1
                     inv.invoke(source, args);
 825  0
                 } catch (InvokerException ie) {
 826  0
                     TriggerEvent te = new TriggerEvent(s.getId()
 827  
                         + ".invoke.failed", TriggerEvent.ERROR_EVENT);
 828  0
                     internalEvents.add(te);
 829  0
                     continue;
 830  1
                 }
 831  1
                 scInstance.setInvoker(s, inv);
 832  
             }
 833  
         }
 834  151
     }
 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  396
         if (SCXMLHelper.isStringEmpty(transEvent)) {
 850  15
             return true;
 851  
         } else {
 852  381
             String transEventDot = transEvent + "."; // prefix event support
 853  381
             Iterator i = eventOccurrences.iterator();
 854  1071
             while (i.hasNext()) {
 855  807
                 String evt = (String) i.next();
 856  807
                 if (evt == null) {
 857  0
                     continue; // Unnamed events
 858  807
                 } else if (evt.equals("*")) {
 859  3
                     return true; // Wildcard
 860  804
                 } else if (evt.equals(transEvent)
 861  
                             || evt.startsWith(transEventDot)) {
 862  114
                     return true;
 863  
                 }
 864  
             }
 865  264
             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  0
         String prefix = parentStateId + ".invoke."; // invoke prefix
 882  0
         Iterator i = eventOccurrences.iterator();
 883  0
         while (i.hasNext()) {
 884  0
             String evt = (String) i.next();
 885  0
             if (evt == null) {
 886  0
                 continue; // Unnamed events
 887  0
             } else if (evt.startsWith(prefix)) {
 888  0
                 return true;
 889  
             }
 890  
         }
 891  0
         return false;
 892  
     }
 893  
 
 894  
     /**
 895  
      * TransitionTargetComparator factory method.
 896  
      * @return Comparator The TransitionTarget comparator
 897  
      */
 898  
     protected Comparator getTTComparator() {
 899  582
         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  0
         this.appLog = log;
 909  0
     }
 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  0
         return appLog;
 918  
     }
 919  
 
 920  
 }
 921