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

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

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