Coverage Report - org.apache.commons.scxml.SCXMLExecutor

Classes in this File Line Coverage Branch Coverage Complexity
SCXMLExecutor
67% 
88% 
1.485

 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;
 18  
 
 19  
 import java.io.Serializable;
 20  
 import java.util.ArrayList;
 21  
 import java.util.Arrays;
 22  
 import java.util.HashMap;
 23  
 import java.util.Iterator;
 24  
 import java.util.List;
 25  
 import java.util.Map;
 26  
 
 27  
 import org.apache.commons.logging.Log;
 28  
 import org.apache.commons.logging.LogFactory;
 29  
 import org.apache.commons.scxml.model.Datamodel;
 30  
 import org.apache.commons.scxml.model.History;
 31  
 import org.apache.commons.scxml.model.ModelException;
 32  
 import org.apache.commons.scxml.model.SCXML;
 33  
 import org.apache.commons.scxml.model.State;
 34  
 import org.apache.commons.scxml.model.Transition;
 35  
 import org.apache.commons.scxml.model.TransitionTarget;
 36  
 import org.apache.commons.scxml.semantics.SCXMLSemanticsImpl;
 37  
 
 38  
 /**
 39  
  * <p>The SCXML &quot;engine&quot; that executes SCXML documents. The
 40  
  * particular semantics used by this engine for executing the SCXML are
 41  
  * encapsulated in the SCXMLSemantics implementation that it uses.</p>
 42  
  *
 43  
  * <p>The default implementation is
 44  
  * <code>org.apache.commons.scxml.semantics.SCXMLSemanticsImpl</code></p>
 45  
  *
 46  
  * @see SCXMLSemantics
 47  
  */
 48  
 public class SCXMLExecutor implements Serializable {
 49  
 
 50  
     /**
 51  
      * Serial version UID.
 52  
      */
 53  
     private static final long serialVersionUID = 1L;
 54  
 
 55  
     /**
 56  
      * The Logger for the SCXMLExecutor.
 57  
      */
 58  54
     private Log log = LogFactory.getLog(SCXMLExecutor.class);
 59  
 
 60  
     /**
 61  
      * The stateMachine being executed.
 62  
      */
 63  
     private SCXML stateMachine;
 64  
 
 65  
     /**
 66  
      * The current status of the stateMachine.
 67  
      */
 68  
     private Status currentStatus;
 69  
 
 70  
     /**
 71  
      * The event dispatcher to interface with external documents etc.
 72  
      */
 73  
     private EventDispatcher eventdispatcher;
 74  
 
 75  
     /**
 76  
      * The environment specific error reporter.
 77  
      */
 78  48
     private ErrorReporter errorReporter = null;
 79  
 
 80  
     /**
 81  
      * Run-to-completion.
 82  
      */
 83  48
     private boolean superStep = true;
 84  
 
 85  
     /**
 86  
      *  Interpretation semantics.
 87  
      */
 88  
     private SCXMLSemantics semantics;
 89  
 
 90  
     /**
 91  
      * The SCInstance.
 92  
      */
 93  
     private SCInstance scInstance;
 94  
 
 95  
     /**
 96  
      * The worker method.
 97  
      * Re-evaluates current status whenever any events are triggered.
 98  
      *
 99  
      * @param evts
 100  
      *            an array of external events which triggered during the last
 101  
      *            time quantum
 102  
      * @throws ModelException in case there is a fatal SCXML object
 103  
      *            model problem.
 104  
      */
 105  
     public synchronized void triggerEvents(final TriggerEvent[] evts)
 106  
             throws ModelException {
 107  
         // Set event data, saving old values
 108  151
         Object[] oldData = setEventData(evts);
 109  
 
 110  
         // Forward events (external only) to any existing invokes,
 111  
         // and finalize processing
 112  151
         semantics.processInvokes(evts, errorReporter, scInstance);
 113  
 
 114  151
         List evs = new ArrayList(Arrays.asList(evts));
 115  151
         Step step = null;
 116  
 
 117  
         do {
 118  
             // CreateStep
 119  266
             step = new Step(evs, currentStatus);
 120  
             // EnumerateReachableTransitions
 121  266
             semantics.enumerateReachableTransitions(stateMachine, step,
 122  
                 errorReporter);
 123  
             // FilterTransitionSet
 124  266
             semantics.filterTransitionsSet(step, eventdispatcher,
 125  
                 errorReporter, scInstance);
 126  
             // FollowTransitions
 127  266
             semantics.followTransitions(step, errorReporter, scInstance);
 128  
             // UpdateHistoryStates
 129  266
             semantics.updateHistoryStates(step, errorReporter, scInstance);
 130  
             // ExecuteActions
 131  266
             semantics.executeActions(step, stateMachine, eventdispatcher,
 132  
                 errorReporter, scInstance);
 133  
             // AssignCurrentStatus
 134  266
             updateStatus(step);
 135  
             // ***Cleanup external events if superStep
 136  266
             if (superStep) {
 137  266
                 evs.clear();
 138  
             }
 139  266
         } while (superStep && currentStatus.getEvents().size() > 0);
 140  
 
 141  
         // InitiateInvokes only after state machine has stabilized
 142  151
         semantics.initiateInvokes(step, errorReporter, scInstance);
 143  
 
 144  
         // Restore event data
 145  151
         restoreEventData(oldData);
 146  151
         logState();
 147  151
     }
 148  
 
 149  
     /**
 150  
      * Convenience method when only one event needs to be triggered.
 151  
      *
 152  
      * @param evt
 153  
      *            the external events which triggered during the last
 154  
      *            time quantum
 155  
      * @throws ModelException in case there is a fatal SCXML object
 156  
      *            model problem.
 157  
      */
 158  
     public void triggerEvent(final TriggerEvent evt)
 159  
             throws ModelException {
 160  0
         triggerEvents(new TriggerEvent[] {evt});
 161  0
     }
 162  
 
 163  
     /**
 164  
      * Constructor.
 165  
      *
 166  
      * @param expEvaluator The expression evaluator
 167  
      * @param evtDisp The event dispatcher
 168  
      * @param errRep The error reporter
 169  
      */
 170  
     public SCXMLExecutor(final Evaluator expEvaluator,
 171  
             final EventDispatcher evtDisp, final ErrorReporter errRep) {
 172  48
         this(expEvaluator, evtDisp, errRep, null);
 173  48
     }
 174  
 
 175  
     /**
 176  
      * Convenience constructor.
 177  
      */
 178  
     public SCXMLExecutor() {
 179  0
         this(null, null, null, null);
 180  0
     }
 181  
 
 182  
     /**
 183  
      * Constructor.
 184  
      *
 185  
      * @param expEvaluator The expression evaluator
 186  
      * @param evtDisp The event dispatcher
 187  
      * @param errRep The error reporter
 188  
      * @param semantics The SCXML semantics
 189  
      */
 190  
     public SCXMLExecutor(final Evaluator expEvaluator,
 191  
             final EventDispatcher evtDisp, final ErrorReporter errRep,
 192  48
             final SCXMLSemantics semantics) {
 193  48
         this.eventdispatcher = evtDisp;
 194  48
         this.errorReporter = errRep;
 195  48
         this.currentStatus = new Status();
 196  48
         this.stateMachine = null;
 197  48
         if (semantics == null) {
 198  
             // Use default semantics, if none provided
 199  48
             this.semantics = new SCXMLSemanticsImpl();
 200  
         } else {
 201  0
             this.semantics = semantics;
 202  
         }
 203  48
         this.scInstance = new SCInstance(this);
 204  48
         this.scInstance.setEvaluator(expEvaluator);
 205  48
     }
 206  
 
 207  
     /**
 208  
      * Clear all state and begin from &quot;initialstate&quot; indicated
 209  
      * on root SCXML element.
 210  
      *
 211  
      * @throws ModelException in case there is a fatal SCXML object
 212  
      *         model problem.
 213  
      */
 214  
     public synchronized void reset() throws ModelException {
 215  
         // Reset all variable contexts
 216  50
         Context rootCtx = scInstance.getRootContext();
 217  
         // Clone root datamodel
 218  50
         if (stateMachine == null) {
 219  0
             log.error(ERR_NO_STATE_MACHINE);
 220  0
             throw new ModelException(ERR_NO_STATE_MACHINE);
 221  
         } else {
 222  50
             Datamodel rootdm = stateMachine.getDatamodel();
 223  50
             SCXMLHelper.cloneDatamodel(rootdm, rootCtx,
 224  
                 scInstance.getEvaluator(), log);
 225  
         }
 226  
         // all states and parallels, only states have variable contexts
 227  50
         for (Iterator i = stateMachine.getTargets().values().iterator();
 228  294
                 i.hasNext();) {
 229  244
             TransitionTarget tt = (TransitionTarget) i.next();
 230  244
             if (tt instanceof State) {
 231  234
                 Context context = scInstance.lookupContext(tt);
 232  234
                 if (context != null) {
 233  11
                     context.reset();
 234  11
                     Datamodel dm = tt.getDatamodel();
 235  11
                     if (dm != null) {
 236  0
                         SCXMLHelper.cloneDatamodel(dm, context,
 237  
                             scInstance.getEvaluator(), log);
 238  
                     }
 239  
                 }
 240  10
             } else if (tt instanceof History) {
 241  7
                 scInstance.reset((History) tt);
 242  
             }
 243  
         }
 244  
         // CreateEmptyStatus
 245  50
         currentStatus = new Status();
 246  50
         Step step = new Step(null, currentStatus);
 247  
         // DetermineInitialStates
 248  50
         semantics.determineInitialStates(stateMachine,
 249  
                 step.getAfterStatus().getStates(),
 250  
                 step.getEntryList(), errorReporter, scInstance);
 251  
         // ExecuteActions
 252  50
         semantics.executeActions(step, stateMachine, eventdispatcher,
 253  
                 errorReporter, scInstance);
 254  
         // AssignCurrentStatus
 255  50
         updateStatus(step);
 256  
         // Execute Immediate Transitions
 257  50
         if (superStep && currentStatus.getEvents().size() > 0) {
 258  50
             this.triggerEvents(new TriggerEvent[0]);
 259  
         } else {
 260  
             // InitiateInvokes only after state machine has stabilized
 261  0
             semantics.initiateInvokes(step, errorReporter, scInstance);
 262  0
             logState();
 263  
         }
 264  50
     }
 265  
 
 266  
     /**
 267  
      * Get the current status.
 268  
      *
 269  
      * @return The current Status
 270  
      */
 271  
     public synchronized Status getCurrentStatus() {
 272  152
         return currentStatus;
 273  
     }
 274  
 
 275  
     /**
 276  
      * Set the expression evaluator.
 277  
      *
 278  
      * @param evaluator The evaluator to set.
 279  
      */
 280  
     public void setEvaluator(final Evaluator evaluator) {
 281  0
         this.scInstance.setEvaluator(evaluator);
 282  0
     }
 283  
 
 284  
     /**
 285  
      * Get the expression evaluator in use.
 286  
      *
 287  
      * @return Evaluator The evaluator in use.
 288  
      */
 289  
     public Evaluator getEvaluator() {
 290  0
         return scInstance.getEvaluator();
 291  
     }
 292  
 
 293  
     /**
 294  
      * Set the root context for this execution.
 295  
      *
 296  
      * @param rootContext The Context that ties to the host environment.
 297  
      */
 298  
     public void setRootContext(final Context rootContext) {
 299  48
         this.scInstance.setRootContext(rootContext);
 300  48
     }
 301  
 
 302  
     /**
 303  
      * Get the root context for this execution.
 304  
      *
 305  
      * @return Context The root context.
 306  
      */
 307  
     public Context getRootContext() {
 308  4
         return scInstance.getRootContext();
 309  
     }
 310  
 
 311  
     /**
 312  
      * Get the state machine that is being executed.
 313  
      *
 314  
      * @return Returns the stateMachine.
 315  
      */
 316  
     public SCXML getStateMachine() {
 317  2
         return stateMachine;
 318  
     }
 319  
 
 320  
     /**
 321  
      * Set the state machine to be executed.
 322  
      *
 323  
      * @param stateMachine The stateMachine to set.
 324  
      */
 325  
     public void setStateMachine(final SCXML stateMachine) {
 326  
         // NormalizeStateMachine
 327  48
         SCXML sm = semantics.normalizeStateMachine(stateMachine,
 328  
                 errorReporter);
 329  
         // StoreStateMachine
 330  48
         this.stateMachine = sm;
 331  48
     }
 332  
 
 333  
     /**
 334  
      * Initiate state machine execution.
 335  
      *
 336  
      * @throws ModelException in case there is a fatal SCXML object
 337  
      *  model problem.
 338  
      */
 339  
     public void go() throws ModelException {
 340  
         // same as reset
 341  48
         this.reset();
 342  48
     }
 343  
 
 344  
     /**
 345  
      * Get the environment specific error reporter.
 346  
      *
 347  
      * @return Returns the errorReporter.
 348  
      */
 349  
     public ErrorReporter getErrorReporter() {
 350  0
         return errorReporter;
 351  
     }
 352  
 
 353  
     /**
 354  
      * Set the environment specific error reporter.
 355  
      *
 356  
      * @param errorReporter The errorReporter to set.
 357  
      */
 358  
     public void setErrorReporter(final ErrorReporter errorReporter) {
 359  0
         this.errorReporter = errorReporter;
 360  0
     }
 361  
 
 362  
     /**
 363  
      * Get the event dispatcher.
 364  
      *
 365  
      * @return Returns the eventdispatcher.
 366  
      */
 367  
     public EventDispatcher getEventdispatcher() {
 368  0
         return eventdispatcher;
 369  
     }
 370  
 
 371  
     /**
 372  
      * Set the event dispatcher.
 373  
      *
 374  
      * @param eventdispatcher The eventdispatcher to set.
 375  
      */
 376  
     public void setEventdispatcher(final EventDispatcher eventdispatcher) {
 377  0
         this.eventdispatcher = eventdispatcher;
 378  0
     }
 379  
 
 380  
     /**
 381  
      * Use &quot;super-step&quot;, default is <code>true</code>
 382  
      * (that is, run-to-completion is default).
 383  
      *
 384  
      * @return Returns the superStep property.
 385  
      * @see #setSuperStep(boolean)
 386  
      */
 387  
     public boolean isSuperStep() {
 388  0
         return superStep;
 389  
     }
 390  
 
 391  
     /**
 392  
      * Set the super step.
 393  
      *
 394  
      * @param superStep
 395  
      * if true, the internal derived events are also processed
 396  
      *    (run-to-completion);
 397  
      * if false, the internal derived events are stored in the
 398  
      * CurrentStatus property and processed within the next
 399  
      * triggerEvents() invocation, also the immediate (empty event) transitions
 400  
      * are deferred until the next step
 401  
       */
 402  
     public void setSuperStep(final boolean superStep) {
 403  46
         this.superStep = superStep;
 404  46
     }
 405  
 
 406  
     /**
 407  
      * Add a listener to the document root.
 408  
      *
 409  
      * @param scxml The document root to attach listener to.
 410  
      * @param listener The SCXMLListener.
 411  
      */
 412  
     public void addListener(final SCXML scxml, final SCXMLListener listener) {
 413  47
         Object observable = scxml;
 414  47
         scInstance.getNotificationRegistry().addListener(observable, listener);
 415  47
     }
 416  
 
 417  
     /**
 418  
      * Remove this listener from the document root.
 419  
      *
 420  
      * @param scxml The document root.
 421  
      * @param listener The SCXMLListener to be removed.
 422  
      */
 423  
     public void removeListener(final SCXML scxml,
 424  
             final SCXMLListener listener) {
 425  0
         Object observable = scxml;
 426  0
         scInstance.getNotificationRegistry().removeListener(observable,
 427  
             listener);
 428  0
     }
 429  
 
 430  
     /**
 431  
      * Add a listener to this transition target.
 432  
      *
 433  
      * @param transitionTarget The <code>TransitionTarget</code> to
 434  
      *                         attach listener to.
 435  
      * @param listener The SCXMLListener.
 436  
      */
 437  
     public void addListener(final TransitionTarget transitionTarget,
 438  
             final SCXMLListener listener) {
 439  0
         Object observable = transitionTarget;
 440  0
         scInstance.getNotificationRegistry().addListener(observable, listener);
 441  0
     }
 442  
 
 443  
     /**
 444  
      * Remove this listener for this transition target.
 445  
      *
 446  
      * @param transitionTarget The <code>TransitionTarget</code>.
 447  
      * @param listener The SCXMLListener to be removed.
 448  
      */
 449  
     public void removeListener(final TransitionTarget transitionTarget,
 450  
             final SCXMLListener listener) {
 451  0
         Object observable = transitionTarget;
 452  0
         scInstance.getNotificationRegistry().removeListener(observable,
 453  
             listener);
 454  0
     }
 455  
 
 456  
     /**
 457  
      * Add a listener to this transition.
 458  
      *
 459  
      * @param transition The <code>Transition</code> to attach listener to.
 460  
      * @param listener The SCXMLListener.
 461  
      */
 462  
     public void addListener(final Transition transition,
 463  
             final SCXMLListener listener) {
 464  0
         Object observable = transition;
 465  0
         scInstance.getNotificationRegistry().addListener(observable, listener);
 466  0
     }
 467  
 
 468  
     /**
 469  
      * Remove this listener for this transition.
 470  
      *
 471  
      * @param transition The <code>Transition</code>.
 472  
      * @param listener The SCXMLListener to be removed.
 473  
      */
 474  
     public void removeListener(final Transition transition,
 475  
             final SCXMLListener listener) {
 476  0
         Object observable = transition;
 477  0
         scInstance.getNotificationRegistry().removeListener(observable,
 478  
             listener);
 479  0
     }
 480  
 
 481  
     /**
 482  
      * Register an <code>Invoker</code> for this target type.
 483  
      *
 484  
      * @param targettype The target type (specified by "targettype"
 485  
      *                   attribute of &lt;invoke&gt; tag).
 486  
      * @param invokerClass The <code>Invoker</code> <code>Class</code>.
 487  
      */
 488  
     public void registerInvokerClass(final String targettype,
 489  
             final Class invokerClass) {
 490  1
         scInstance.registerInvokerClass(targettype, invokerClass);
 491  1
     }
 492  
 
 493  
     /**
 494  
      * Remove the <code>Invoker</code> registered for this target
 495  
      * type (if there is one registered).
 496  
      *
 497  
      * @param targettype The target type (specified by "targettype"
 498  
      *                   attribute of &lt;invoke&gt; tag).
 499  
      */
 500  
     public void unregisterInvokerClass(final String targettype) {
 501  0
         scInstance.unregisterInvokerClass(targettype);
 502  0
     }
 503  
 
 504  
     /**
 505  
      * Get the state chart instance for this executor.
 506  
      *
 507  
      * @return The SCInstance for this executor.
 508  
      */
 509  
     SCInstance getSCInstance() {
 510  2
         return scInstance;
 511  
     }
 512  
 
 513  
     /**
 514  
      * Log the current set of active states.
 515  
      */
 516  
     private void logState() {
 517  151
         if (log.isInfoEnabled()) {
 518  0
             Iterator si = currentStatus.getStates().iterator();
 519  0
             StringBuffer sb = new StringBuffer("Current States: [");
 520  0
             while (si.hasNext()) {
 521  0
                 State s = (State) si.next();
 522  0
                 sb.append(s.getId());
 523  0
                 if (si.hasNext()) {
 524  0
                     sb.append(", ");
 525  
                 }
 526  
             }
 527  0
             sb.append(']');
 528  0
             log.info(sb.toString());
 529  
         }
 530  151
     }
 531  
 
 532  
     /**
 533  
      * @param step The most recent Step
 534  
      */
 535  
     private void updateStatus(final Step step) {
 536  316
         currentStatus = step.getAfterStatus();
 537  316
         scInstance.getRootContext().setLocal("_ALL_STATES",
 538  
             SCXMLHelper.getAncestorClosure(currentStatus.getStates(), null));
 539  316
     }
 540  
 
 541  
     /**
 542  
      * @param evts The events being triggered.
 543  
      * @return Object[] Previous values.
 544  
      */
 545  
     private Object[] setEventData(final TriggerEvent[] evts) {
 546  151
         Context rootCtx = scInstance.getRootContext();
 547  151
         Object[] oldData = {rootCtx.get(EVENT_DATA),
 548  
             rootCtx.get(EVENT_DATA_MAP)};
 549  151
         Object eventData = null;
 550  151
         Map payloadMap = new HashMap();
 551  151
         int len = evts.length;
 552  253
         for (int i = 0; i < len; i++) {
 553  102
             TriggerEvent te = evts[i];
 554  102
             payloadMap.put(te.getName(), te.getPayload());
 555  
         }
 556  151
         if (len == 1) {
 557  
             // we have only one event
 558  100
             eventData = evts[0].getPayload();
 559  
         }
 560  151
         rootCtx.setLocal(EVENT_DATA, eventData);
 561  151
         rootCtx.setLocal(EVENT_DATA_MAP, payloadMap);
 562  151
         return oldData;
 563  
     }
 564  
 
 565  
     /**
 566  
      * @param oldData The old values to restore to.
 567  
      */
 568  
     private void restoreEventData(final Object[] oldData) {
 569  151
         scInstance.getRootContext().setLocal(EVENT_DATA, oldData[0]);
 570  151
         scInstance.getRootContext().setLocal(EVENT_DATA_MAP, oldData[1]);
 571  151
     }
 572  
 
 573  
     /**
 574  
      * The special variable for storing single event data / payload.
 575  
      */
 576  
     private static final String EVENT_DATA = "_eventdata";
 577  
 
 578  
     /**
 579  
      * The special variable for storing event data / payload,
 580  
      * when multiple events are triggered, keyed by event name.
 581  
      */
 582  
     private static final String EVENT_DATA_MAP = "_eventdatamap";
 583  
 
 584  
     /**
 585  
      * SCXMLExecutor put into motion without setting a model (state machine).
 586  
      */
 587  
     private static final String ERR_NO_STATE_MACHINE =
 588  
         "SCXMLExecutor: State machine not set";
 589  
 
 590  
 }
 591