1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 "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 {
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
103 Object[] oldData = setEventData(evts);
104
105
106
107 semantics.processInvokes(evts, errorReporter, scInstance);
108
109 List evs = new ArrayList(Arrays.asList(evts));
110 Step step = null;
111
112 do {
113
114 step = new Step(evs, currentStatus);
115
116 semantics.enumerateReachableTransitions(stateMachine, step,
117 errorReporter);
118
119 semantics.filterTransitionsSet(step, eventdispatcher,
120 errorReporter, scInstance);
121
122 semantics.followTransitions(step, errorReporter, scInstance);
123
124 semantics.updateHistoryStates(step, errorReporter, scInstance);
125
126 semantics.executeActions(step, stateMachine, eventdispatcher,
127 errorReporter, scInstance);
128
129 updateStatus(step);
130
131 if (superStep) {
132 evs.clear();
133 }
134 } while (superStep && currentStatus.getEvents().size() > 0);
135
136
137 semantics.initiateInvokes(step, errorReporter, scInstance);
138
139
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
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 "initialstate" 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
213 Context rootCtx = scInstance.getRootContext();
214
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
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
242 currentStatus = new Status();
243 Step step = new Step(null, currentStatus);
244
245 semantics.determineInitialStates(stateMachine,
246 step.getAfterStatus().getStates(),
247 step.getEntryList(), errorReporter, scInstance);
248
249 semantics.executeActions(step, stateMachine, eventdispatcher,
250 errorReporter, scInstance);
251
252 updateStatus(step);
253
254 if (superStep && currentStatus.getEvents().size() > 0) {
255 this.triggerEvents(new TriggerEvent[0]);
256 } else {
257
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
324 SCXML sm = semantics.normalizeStateMachine(stateMachine,
325 errorReporter);
326
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
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 "super-step", 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 <invoke> 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 <invoke> 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
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