View Javadoc

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      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      private ErrorReporter errorReporter = null;
74  
75      /***
76       * Run-to-completion.
77       */
78      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         Object[] oldData = setEventData(evts);
104 
105         // Forward events (external only) to any existing invokes,
106         // and finalize processing
107         semantics.processInvokes(evts, errorReporter, scInstance);
108 
109         List evs = new ArrayList(Arrays.asList(evts));
110         Step step = null;
111 
112         do {
113             // CreateStep
114             step = new Step(evs, currentStatus);
115             // EnumerateReachableTransitions
116             semantics.enumerateReachableTransitions(stateMachine, step,
117                 errorReporter);
118             // FilterTransitionSet
119             semantics.filterTransitionsSet(step, eventdispatcher,
120                 errorReporter, scInstance);
121             // FollowTransitions
122             semantics.followTransitions(step, errorReporter, scInstance);
123             // UpdateHistoryStates
124             semantics.updateHistoryStates(step, errorReporter, scInstance);
125             // ExecuteActions
126             semantics.executeActions(step, stateMachine, eventdispatcher,
127                 errorReporter, scInstance);
128             // AssignCurrentStatus
129             updateStatus(step);
130             // ***Cleanup external events if superStep
131             if (superStep) {
132                 evs.clear();
133             }
134         } while (superStep && currentStatus.getEvents().size() > 0);
135 
136         // InitiateInvokes only after state machine has stabilized
137         semantics.initiateInvokes(step, errorReporter, scInstance);
138 
139         // Restore event data
140         restoreEventData(oldData);
141         logState();
142     }
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         triggerEvents(new TriggerEvent[] {evt});
156     }
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         this(expEvaluator, evtDisp, errRep, null);
168     }
169 
170     /***
171      * Convenience constructor.
172      */
173     public SCXMLExecutor() {
174         this(null, null, null, null);
175     }
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             final SCXMLSemantics semantics) {
188         this.eventdispatcher = evtDisp;
189         this.errorReporter = errRep;
190         this.currentStatus = null;
191         this.stateMachine = null;
192         if (semantics == null) {
193             // Use default semantics, if none provided
194             this.semantics = new SCXMLSemanticsImpl();
195         } else {
196             this.semantics = semantics;
197         }
198         this.currentStatus = null;
199         this.stateMachine = null;
200         this.scInstance = new SCInstance(this);
201         this.scInstance.setEvaluator(expEvaluator);
202     }
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         Context rootCtx = scInstance.getRootContext();
214         // Clone root datamodel
215         if (stateMachine == null) {
216             log.error(ERR_NO_STATE_MACHINE);
217             throw new ModelException(ERR_NO_STATE_MACHINE);
218         } else {
219             Datamodel rootdm = stateMachine.getDatamodel();
220             SCXMLHelper.cloneDatamodel(rootdm, rootCtx,
221                 scInstance.getEvaluator(), log);
222         }
223         // all states and parallels, only states have variable contexts
224         for (Iterator i = stateMachine.getTargets().values().iterator();
225                 i.hasNext();) {
226             TransitionTarget tt = (TransitionTarget) i.next();
227             if (tt instanceof State) {
228                 Context context = scInstance.lookupContext(tt);
229                 if (context != null) {
230                     context.reset();
231                     Datamodel dm = tt.getDatamodel();
232                     if (dm != null) {
233                         SCXMLHelper.cloneDatamodel(dm, context,
234                             scInstance.getEvaluator(), log);
235                     }
236                 }
237             } else if (tt instanceof History) {
238                 scInstance.reset((History) tt);
239             }
240         }
241         // CreateEmptyStatus
242         currentStatus = new Status();
243         Step step = new Step(null, currentStatus);
244         // DetermineInitialStates
245         semantics.determineInitialStates(stateMachine,
246                 step.getAfterStatus().getStates(),
247                 step.getEntryList(), errorReporter, scInstance);
248         // ExecuteActions
249         semantics.executeActions(step, stateMachine, eventdispatcher,
250                 errorReporter, scInstance);
251         // AssignCurrentStatus
252         updateStatus(step);
253         // Execute Immediate Transitions
254         if (superStep && currentStatus.getEvents().size() > 0) {
255             this.triggerEvents(new TriggerEvent[0]);
256         } else {
257             // InitiateInvokes only after state machine has stabilized
258             semantics.initiateInvokes(step, errorReporter, scInstance);
259             logState();
260         }
261     }
262 
263     /***
264      * Get the current status.
265      *
266      * @return The current Status
267      */
268     public Status getCurrentStatus() {
269         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         this.scInstance.setEvaluator(evaluator);
279     }
280 
281     /***
282      * Get the expression evaluator in use.
283      *
284      * @return Evaluator The evaluator in use.
285      */
286     public Evaluator getEvaluator() {
287         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         this.scInstance.setRootContext(rootContext);
297     }
298 
299     /***
300      * Get the root context for this execution.
301      *
302      * @return Context The root context.
303      */
304     public Context getRootContext() {
305         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         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         SCXML sm = semantics.normalizeStateMachine(stateMachine,
325                 errorReporter);
326         // StoreStateMachine
327         this.stateMachine = sm;
328     }
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         this.reset();
339     }
340 
341     /***
342      * Get the environment specific error reporter.
343      *
344      * @return Returns the errorReporter.
345      */
346     public ErrorReporter getErrorReporter() {
347         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         this.errorReporter = errorReporter;
357     }
358 
359     /***
360      * Get the event dispatcher.
361      *
362      * @return Returns the eventdispatcher.
363      */
364     public EventDispatcher getEventdispatcher() {
365         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         this.eventdispatcher = eventdispatcher;
375     }
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         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         this.superStep = superStep;
401     }
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         Object observable = scxml;
411         scInstance.getNotificationRegistry().addListener(observable, listener);
412     }
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         Object observable = scxml;
423         scInstance.getNotificationRegistry().removeListener(observable,
424             listener);
425     }
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         Object observable = transitionTarget;
437         scInstance.getNotificationRegistry().addListener(observable, listener);
438     }
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         Object observable = transitionTarget;
449         scInstance.getNotificationRegistry().removeListener(observable,
450             listener);
451     }
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         Object observable = transition;
462         scInstance.getNotificationRegistry().addListener(observable, listener);
463     }
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         Object observable = transition;
474         scInstance.getNotificationRegistry().removeListener(observable,
475             listener);
476     }
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         scInstance.registerInvokerClass(targettype, invokerClass);
488     }
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         scInstance.unregisterInvokerClass(targettype);
499     }
500 
501     /***
502      * Get the state chart instance for this executor.
503      *
504      * @return The SCInstance for this executor.
505      */
506     SCInstance getSCInstance() {
507         return scInstance;
508     }
509 
510     /***
511      * Log the current set of active states.
512      */
513     private void logState() {
514         if (log.isInfoEnabled()) {
515             Iterator si = currentStatus.getStates().iterator();
516             StringBuffer sb = new StringBuffer("Current States: [");
517             while (si.hasNext()) {
518                 State s = (State) si.next();
519                 sb.append(s.getId());
520                 if (si.hasNext()) {
521                     sb.append(", ");
522                 }
523             }
524             sb.append(']');
525             log.info(sb.toString());
526         }
527     }
528 
529     /***
530      * @param step The most recent Step
531      */
532     private void updateStatus(final Step step) {
533         currentStatus = step.getAfterStatus();
534         scInstance.getRootContext().setLocal("_ALL_STATES",
535             SCXMLHelper.getAncestorClosure(currentStatus.getStates(), null));
536     }
537 
538     /***
539      * @param evts The events being triggered.
540      * @return Object[] Previous values.
541      */
542     private Object[] setEventData(final TriggerEvent[] evts) {
543         Context rootCtx = scInstance.getRootContext();
544         Object[] oldData = {rootCtx.get(EVENT_DATA),
545             rootCtx.get(EVENT_DATA_MAP)};
546         Object eventData = null;
547         Map payloadMap = new HashMap();
548         int len = evts.length;
549         for (int i = 0; i < len; i++) {
550             TriggerEvent te = evts[i];
551             payloadMap.put(te.getName(), te.getPayload());
552         }
553         if (len == 1) {
554             // we have only one event
555             eventData = evts[0].getPayload();
556         }
557         rootCtx.setLocal(EVENT_DATA, eventData);
558         rootCtx.setLocal(EVENT_DATA_MAP, payloadMap);
559         return oldData;
560     }
561 
562     /***
563      * @param oldData The old values to restore to.
564      */
565     private void restoreEventData(final Object[] oldData) {
566         scInstance.getRootContext().setLocal(EVENT_DATA, oldData[0]);
567         scInstance.getRootContext().setLocal(EVENT_DATA_MAP, oldData[1]);
568     }
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