1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 "engine" 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 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 private ErrorReporter errorReporter = null;
79
80 /***
81 * Run-to-completion.
82 */
83 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
108 Object[] oldData = setEventData(evts);
109
110
111
112 semantics.processInvokes(evts, errorReporter, scInstance);
113
114 List evs = new ArrayList(Arrays.asList(evts));
115 Step step = null;
116
117 do {
118
119 step = new Step(evs, currentStatus);
120
121 semantics.enumerateReachableTransitions(stateMachine, step,
122 errorReporter);
123
124 semantics.filterTransitionsSet(step, eventdispatcher,
125 errorReporter, scInstance);
126
127 semantics.followTransitions(step, errorReporter, scInstance);
128
129 semantics.updateHistoryStates(step, errorReporter, scInstance);
130
131 semantics.executeActions(step, stateMachine, eventdispatcher,
132 errorReporter, scInstance);
133
134 updateStatus(step);
135
136 if (superStep) {
137 evs.clear();
138 }
139 } while (superStep && currentStatus.getEvents().size() > 0);
140
141
142 semantics.initiateInvokes(step, errorReporter, scInstance);
143
144
145 restoreEventData(oldData);
146 logState();
147 }
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 triggerEvents(new TriggerEvent[] {evt});
161 }
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 this(expEvaluator, evtDisp, errRep, null);
173 }
174
175 /***
176 * Convenience constructor.
177 */
178 public SCXMLExecutor() {
179 this(null, null, null, null);
180 }
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 final SCXMLSemantics semantics) {
193 this.eventdispatcher = evtDisp;
194 this.errorReporter = errRep;
195 this.currentStatus = new Status();
196 this.stateMachine = null;
197 if (semantics == null) {
198
199 this.semantics = new SCXMLSemanticsImpl();
200 } else {
201 this.semantics = semantics;
202 }
203 this.scInstance = new SCInstance(this);
204 this.scInstance.setEvaluator(expEvaluator);
205 }
206
207 /***
208 * Clear all state and begin from "initialstate" 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
216 Context rootCtx = scInstance.getRootContext();
217
218 if (stateMachine == null) {
219 log.error(ERR_NO_STATE_MACHINE);
220 throw new ModelException(ERR_NO_STATE_MACHINE);
221 } else {
222 Datamodel rootdm = stateMachine.getDatamodel();
223 SCXMLHelper.cloneDatamodel(rootdm, rootCtx,
224 scInstance.getEvaluator(), log);
225 }
226
227 for (Iterator i = stateMachine.getTargets().values().iterator();
228 i.hasNext();) {
229 TransitionTarget tt = (TransitionTarget) i.next();
230 if (tt instanceof State) {
231 Context context = scInstance.lookupContext(tt);
232 if (context != null) {
233 context.reset();
234 Datamodel dm = tt.getDatamodel();
235 if (dm != null) {
236 SCXMLHelper.cloneDatamodel(dm, context,
237 scInstance.getEvaluator(), log);
238 }
239 }
240 } else if (tt instanceof History) {
241 scInstance.reset((History) tt);
242 }
243 }
244
245 currentStatus = new Status();
246 Step step = new Step(null, currentStatus);
247
248 semantics.determineInitialStates(stateMachine,
249 step.getAfterStatus().getStates(),
250 step.getEntryList(), errorReporter, scInstance);
251
252 semantics.executeActions(step, stateMachine, eventdispatcher,
253 errorReporter, scInstance);
254
255 updateStatus(step);
256
257 if (superStep && currentStatus.getEvents().size() > 0) {
258 this.triggerEvents(new TriggerEvent[0]);
259 } else {
260
261 semantics.initiateInvokes(step, errorReporter, scInstance);
262 logState();
263 }
264 }
265
266 /***
267 * Get the current status.
268 *
269 * @return The current Status
270 */
271 public synchronized Status getCurrentStatus() {
272 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 this.scInstance.setEvaluator(evaluator);
282 }
283
284 /***
285 * Get the expression evaluator in use.
286 *
287 * @return Evaluator The evaluator in use.
288 */
289 public Evaluator getEvaluator() {
290 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 this.scInstance.setRootContext(rootContext);
300 }
301
302 /***
303 * Get the root context for this execution.
304 *
305 * @return Context The root context.
306 */
307 public Context getRootContext() {
308 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 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
327 SCXML sm = semantics.normalizeStateMachine(stateMachine,
328 errorReporter);
329
330 this.stateMachine = sm;
331 }
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
341 this.reset();
342 }
343
344 /***
345 * Get the environment specific error reporter.
346 *
347 * @return Returns the errorReporter.
348 */
349 public ErrorReporter getErrorReporter() {
350 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 this.errorReporter = errorReporter;
360 }
361
362 /***
363 * Get the event dispatcher.
364 *
365 * @return Returns the eventdispatcher.
366 */
367 public EventDispatcher getEventdispatcher() {
368 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 this.eventdispatcher = eventdispatcher;
378 }
379
380 /***
381 * Use "super-step", 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 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 this.superStep = superStep;
404 }
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 Object observable = scxml;
414 scInstance.getNotificationRegistry().addListener(observable, listener);
415 }
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 Object observable = scxml;
426 scInstance.getNotificationRegistry().removeListener(observable,
427 listener);
428 }
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 Object observable = transitionTarget;
440 scInstance.getNotificationRegistry().addListener(observable, listener);
441 }
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 Object observable = transitionTarget;
452 scInstance.getNotificationRegistry().removeListener(observable,
453 listener);
454 }
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 Object observable = transition;
465 scInstance.getNotificationRegistry().addListener(observable, listener);
466 }
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 Object observable = transition;
477 scInstance.getNotificationRegistry().removeListener(observable,
478 listener);
479 }
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 <invoke> tag).
486 * @param invokerClass The <code>Invoker</code> <code>Class</code>.
487 */
488 public void registerInvokerClass(final String targettype,
489 final Class invokerClass) {
490 scInstance.registerInvokerClass(targettype, invokerClass);
491 }
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 <invoke> tag).
499 */
500 public void unregisterInvokerClass(final String targettype) {
501 scInstance.unregisterInvokerClass(targettype);
502 }
503
504 /***
505 * Get the state chart instance for this executor.
506 *
507 * @return The SCInstance for this executor.
508 */
509 SCInstance getSCInstance() {
510 return scInstance;
511 }
512
513 /***
514 * Log the current set of active states.
515 */
516 private void logState() {
517 if (log.isInfoEnabled()) {
518 Iterator si = currentStatus.getStates().iterator();
519 StringBuffer sb = new StringBuffer("Current States: [");
520 while (si.hasNext()) {
521 State s = (State) si.next();
522 sb.append(s.getId());
523 if (si.hasNext()) {
524 sb.append(", ");
525 }
526 }
527 sb.append(']');
528 log.info(sb.toString());
529 }
530 }
531
532 /***
533 * @param step The most recent Step
534 */
535 private void updateStatus(final Step step) {
536 currentStatus = step.getAfterStatus();
537 scInstance.getRootContext().setLocal("_ALL_STATES",
538 SCXMLHelper.getAncestorClosure(currentStatus.getStates(), null));
539 }
540
541 /***
542 * @param evts The events being triggered.
543 * @return Object[] Previous values.
544 */
545 private Object[] setEventData(final TriggerEvent[] evts) {
546 Context rootCtx = scInstance.getRootContext();
547 Object[] oldData = {rootCtx.get(EVENT_DATA),
548 rootCtx.get(EVENT_DATA_MAP)};
549 Object eventData = null;
550 Map payloadMap = new HashMap();
551 int len = evts.length;
552 for (int i = 0; i < len; i++) {
553 TriggerEvent te = evts[i];
554 payloadMap.put(te.getName(), te.getPayload());
555 }
556 if (len == 1) {
557
558 eventData = evts[0].getPayload();
559 }
560 rootCtx.setLocal(EVENT_DATA, eventData);
561 rootCtx.setLocal(EVENT_DATA_MAP, payloadMap);
562 return oldData;
563 }
564
565 /***
566 * @param oldData The old values to restore to.
567 */
568 private void restoreEventData(final Object[] oldData) {
569 scInstance.getRootContext().setLocal(EVENT_DATA, oldData[0]);
570 scInstance.getRootContext().setLocal(EVENT_DATA_MAP, oldData[1]);
571 }
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