001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.commons.scxml;
018:
019: import java.io.Serializable;
020: import java.util.ArrayList;
021: import java.util.Arrays;
022: import java.util.HashMap;
023: import java.util.Iterator;
024: import java.util.List;
025: import java.util.Map;
026:
027: import org.apache.commons.logging.Log;
028: import org.apache.commons.logging.LogFactory;
029: import org.apache.commons.scxml.model.Datamodel;
030: import org.apache.commons.scxml.model.History;
031: import org.apache.commons.scxml.model.ModelException;
032: import org.apache.commons.scxml.model.SCXML;
033: import org.apache.commons.scxml.model.State;
034: import org.apache.commons.scxml.model.Transition;
035: import org.apache.commons.scxml.model.TransitionTarget;
036: import org.apache.commons.scxml.semantics.SCXMLSemanticsImpl;
037:
038: /**
039: * <p>The SCXML "engine" that executes SCXML documents. The
040: * particular semantics used by this engine for executing the SCXML are
041: * encapsulated in the SCXMLSemantics implementation that it uses.</p>
042: *
043: * <p>The default implementation is
044: * <code>org.apache.commons.scxml.semantics.SCXMLSemanticsImpl</code></p>
045: *
046: * @see SCXMLSemantics
047: */
048: public class SCXMLExecutor implements Serializable {
049:
050: /**
051: * Serial version UID.
052: */
053: private static final long serialVersionUID = 1L;
054:
055: /**
056: * The Logger for the SCXMLExecutor.
057: */
058: private Log log = LogFactory.getLog(SCXMLExecutor.class);
059:
060: /**
061: * The stateMachine being executed.
062: */
063: private SCXML stateMachine;
064:
065: /**
066: * The current status of the stateMachine.
067: */
068: private Status currentStatus;
069:
070: /**
071: * The event dispatcher to interface with external documents etc.
072: */
073: private EventDispatcher eventdispatcher;
074:
075: /**
076: * The environment specific error reporter.
077: */
078: private ErrorReporter errorReporter = null;
079:
080: /**
081: * Run-to-completion.
082: */
083: private boolean super Step = true;
084:
085: /**
086: * Interpretation semantics.
087: */
088: private SCXMLSemantics semantics;
089:
090: /**
091: * The SCInstance.
092: */
093: private SCInstance scInstance;
094:
095: /**
096: * The worker method.
097: * Re-evaluates current status whenever any events are triggered.
098: *
099: * @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: Object[] oldData = setEventData(evts);
109:
110: // Forward events (external only) to any existing invokes,
111: // and finalize processing
112: semantics.processInvokes(evts, errorReporter, scInstance);
113:
114: List evs = new ArrayList(Arrays.asList(evts));
115: Step step = null;
116:
117: do {
118: // CreateStep
119: step = new Step(evs, currentStatus);
120: // EnumerateReachableTransitions
121: semantics.enumerateReachableTransitions(stateMachine, step,
122: errorReporter);
123: // FilterTransitionSet
124: semantics.filterTransitionsSet(step, eventdispatcher,
125: errorReporter, scInstance);
126: // FollowTransitions
127: semantics
128: .followTransitions(step, errorReporter, scInstance);
129: // UpdateHistoryStates
130: semantics.updateHistoryStates(step, errorReporter,
131: scInstance);
132: // ExecuteActions
133: semantics.executeActions(step, stateMachine,
134: eventdispatcher, errorReporter, scInstance);
135: // AssignCurrentStatus
136: updateStatus(step);
137: // ***Cleanup external events if superStep
138: if (super Step) {
139: evs.clear();
140: }
141: } while (super Step && currentStatus.getEvents().size() > 0);
142:
143: // InitiateInvokes only after state machine has stabilized
144: semantics.initiateInvokes(step, errorReporter, scInstance);
145:
146: // Restore event data
147: restoreEventData(oldData);
148: logState();
149: }
150:
151: /**
152: * Convenience method when only one event needs to be triggered.
153: *
154: * @param evt
155: * the external events which triggered during the last
156: * time quantum
157: * @throws ModelException in case there is a fatal SCXML object
158: * model problem.
159: */
160: public void triggerEvent(final TriggerEvent evt)
161: throws ModelException {
162: triggerEvents(new TriggerEvent[] { evt });
163: }
164:
165: /**
166: * Constructor.
167: *
168: * @param expEvaluator The expression evaluator
169: * @param evtDisp The event dispatcher
170: * @param errRep The error reporter
171: */
172: public SCXMLExecutor(final Evaluator expEvaluator,
173: final EventDispatcher evtDisp, final ErrorReporter errRep) {
174: this (expEvaluator, evtDisp, errRep, null);
175: }
176:
177: /**
178: * Convenience constructor.
179: */
180: public SCXMLExecutor() {
181: this (null, null, null, null);
182: }
183:
184: /**
185: * Constructor.
186: *
187: * @param expEvaluator The expression evaluator
188: * @param evtDisp The event dispatcher
189: * @param errRep The error reporter
190: * @param semantics The SCXML semantics
191: */
192: public SCXMLExecutor(final Evaluator expEvaluator,
193: final EventDispatcher evtDisp, final ErrorReporter errRep,
194: final SCXMLSemantics semantics) {
195: this .eventdispatcher = evtDisp;
196: this .errorReporter = errRep;
197: this .currentStatus = new Status();
198: this .stateMachine = null;
199: if (semantics == null) {
200: // Use default semantics, if none provided
201: this .semantics = new SCXMLSemanticsImpl();
202: } else {
203: this .semantics = semantics;
204: }
205: this .scInstance = new SCInstance(this );
206: this .scInstance.setEvaluator(expEvaluator);
207: }
208:
209: /**
210: * Clear all state and begin from "initialstate" indicated
211: * on root SCXML element.
212: *
213: * @throws ModelException in case there is a fatal SCXML object
214: * model problem.
215: */
216: public synchronized void reset() throws ModelException {
217: // Reset all variable contexts
218: Context rootCtx = scInstance.getRootContext();
219: // Clone root datamodel
220: if (stateMachine == null) {
221: log.error(ERR_NO_STATE_MACHINE);
222: throw new ModelException(ERR_NO_STATE_MACHINE);
223: } else {
224: Datamodel rootdm = stateMachine.getDatamodel();
225: SCXMLHelper.cloneDatamodel(rootdm, rootCtx, scInstance
226: .getEvaluator(), log);
227: }
228: // all states and parallels, only states have variable contexts
229: for (Iterator i = stateMachine.getTargets().values().iterator(); i
230: .hasNext();) {
231: TransitionTarget tt = (TransitionTarget) i.next();
232: if (tt instanceof State) {
233: Context context = scInstance.lookupContext(tt);
234: if (context != null) {
235: context.reset();
236: Datamodel dm = tt.getDatamodel();
237: if (dm != null) {
238: SCXMLHelper.cloneDatamodel(dm, context,
239: scInstance.getEvaluator(), log);
240: }
241: }
242: } else if (tt instanceof History) {
243: scInstance.reset((History) tt);
244: }
245: }
246: // CreateEmptyStatus
247: currentStatus = new Status();
248: Step step = new Step(null, currentStatus);
249: // DetermineInitialStates
250: semantics.determineInitialStates(stateMachine, step
251: .getAfterStatus().getStates(), step.getEntryList(),
252: errorReporter, scInstance);
253: // ExecuteActions
254: semantics.executeActions(step, stateMachine, eventdispatcher,
255: errorReporter, scInstance);
256: // AssignCurrentStatus
257: updateStatus(step);
258: // Execute Immediate Transitions
259: if (super Step && currentStatus.getEvents().size() > 0) {
260: this .triggerEvents(new TriggerEvent[0]);
261: } else {
262: // InitiateInvokes only after state machine has stabilized
263: semantics.initiateInvokes(step, errorReporter, scInstance);
264: logState();
265: }
266: }
267:
268: /**
269: * Get the current status.
270: *
271: * @return The current Status
272: */
273: public synchronized Status getCurrentStatus() {
274: return currentStatus;
275: }
276:
277: /**
278: * Set the expression evaluator.
279: *
280: * @param evaluator The evaluator to set.
281: */
282: public void setEvaluator(final Evaluator evaluator) {
283: this .scInstance.setEvaluator(evaluator);
284: }
285:
286: /**
287: * Get the expression evaluator in use.
288: *
289: * @return Evaluator The evaluator in use.
290: */
291: public Evaluator getEvaluator() {
292: return scInstance.getEvaluator();
293: }
294:
295: /**
296: * Set the root context for this execution.
297: *
298: * @param rootContext The Context that ties to the host environment.
299: */
300: public void setRootContext(final Context rootContext) {
301: this .scInstance.setRootContext(rootContext);
302: }
303:
304: /**
305: * Get the root context for this execution.
306: *
307: * @return Context The root context.
308: */
309: public Context getRootContext() {
310: return scInstance.getRootContext();
311: }
312:
313: /**
314: * Get the state machine that is being executed.
315: *
316: * @return Returns the stateMachine.
317: */
318: public SCXML getStateMachine() {
319: return stateMachine;
320: }
321:
322: /**
323: * Set the state machine to be executed.
324: *
325: * @param stateMachine The stateMachine to set.
326: */
327: public void setStateMachine(final SCXML stateMachine) {
328: // NormalizeStateMachine
329: SCXML sm = semantics.normalizeStateMachine(stateMachine,
330: errorReporter);
331: // StoreStateMachine
332: this .stateMachine = sm;
333: }
334:
335: /**
336: * Initiate state machine execution.
337: *
338: * @throws ModelException in case there is a fatal SCXML object
339: * model problem.
340: */
341: public void go() throws ModelException {
342: // same as reset
343: this .reset();
344: }
345:
346: /**
347: * Get the environment specific error reporter.
348: *
349: * @return Returns the errorReporter.
350: */
351: public ErrorReporter getErrorReporter() {
352: return errorReporter;
353: }
354:
355: /**
356: * Set the environment specific error reporter.
357: *
358: * @param errorReporter The errorReporter to set.
359: */
360: public void setErrorReporter(final ErrorReporter errorReporter) {
361: this .errorReporter = errorReporter;
362: }
363:
364: /**
365: * Get the event dispatcher.
366: *
367: * @return Returns the eventdispatcher.
368: */
369: public EventDispatcher getEventdispatcher() {
370: return eventdispatcher;
371: }
372:
373: /**
374: * Set the event dispatcher.
375: *
376: * @param eventdispatcher The eventdispatcher to set.
377: */
378: public void setEventdispatcher(final EventDispatcher eventdispatcher) {
379: this .eventdispatcher = eventdispatcher;
380: }
381:
382: /**
383: * Use "super-step", default is <code>true</code>
384: * (that is, run-to-completion is default).
385: *
386: * @return Returns the superStep property.
387: * @see #setSuperStep(boolean)
388: */
389: public boolean isSuperStep() {
390: return super Step;
391: }
392:
393: /**
394: * Set the super step.
395: *
396: * @param superStep
397: * if true, the internal derived events are also processed
398: * (run-to-completion);
399: * if false, the internal derived events are stored in the
400: * CurrentStatus property and processed within the next
401: * triggerEvents() invocation, also the immediate (empty event) transitions
402: * are deferred until the next step
403: */
404: public void setSuperStep(final boolean super Step) {
405: this .super Step = super Step;
406: }
407:
408: /**
409: * Add a listener to the document root.
410: *
411: * @param scxml The document root to attach listener to.
412: * @param listener The SCXMLListener.
413: */
414: public void addListener(final SCXML scxml,
415: final SCXMLListener listener) {
416: Object observable = scxml;
417: scInstance.getNotificationRegistry().addListener(observable,
418: listener);
419: }
420:
421: /**
422: * Remove this listener from the document root.
423: *
424: * @param scxml The document root.
425: * @param listener The SCXMLListener to be removed.
426: */
427: public void removeListener(final SCXML scxml,
428: final SCXMLListener listener) {
429: Object observable = scxml;
430: scInstance.getNotificationRegistry().removeListener(observable,
431: listener);
432: }
433:
434: /**
435: * Add a listener to this transition target.
436: *
437: * @param transitionTarget The <code>TransitionTarget</code> to
438: * attach listener to.
439: * @param listener The SCXMLListener.
440: */
441: public void addListener(final TransitionTarget transitionTarget,
442: final SCXMLListener listener) {
443: Object observable = transitionTarget;
444: scInstance.getNotificationRegistry().addListener(observable,
445: listener);
446: }
447:
448: /**
449: * Remove this listener for this transition target.
450: *
451: * @param transitionTarget The <code>TransitionTarget</code>.
452: * @param listener The SCXMLListener to be removed.
453: */
454: public void removeListener(final TransitionTarget transitionTarget,
455: final SCXMLListener listener) {
456: Object observable = transitionTarget;
457: scInstance.getNotificationRegistry().removeListener(observable,
458: listener);
459: }
460:
461: /**
462: * Add a listener to this transition.
463: *
464: * @param transition The <code>Transition</code> to attach listener to.
465: * @param listener The SCXMLListener.
466: */
467: public void addListener(final Transition transition,
468: final SCXMLListener listener) {
469: Object observable = transition;
470: scInstance.getNotificationRegistry().addListener(observable,
471: listener);
472: }
473:
474: /**
475: * Remove this listener for this transition.
476: *
477: * @param transition The <code>Transition</code>.
478: * @param listener The SCXMLListener to be removed.
479: */
480: public void removeListener(final Transition transition,
481: final SCXMLListener listener) {
482: Object observable = transition;
483: scInstance.getNotificationRegistry().removeListener(observable,
484: listener);
485: }
486:
487: /**
488: * Register an <code>Invoker</code> for this target type.
489: *
490: * @param targettype The target type (specified by "targettype"
491: * attribute of <invoke> tag).
492: * @param invokerClass The <code>Invoker</code> <code>Class</code>.
493: */
494: public void registerInvokerClass(final String targettype,
495: final Class invokerClass) {
496: scInstance.registerInvokerClass(targettype, invokerClass);
497: }
498:
499: /**
500: * Remove the <code>Invoker</code> registered for this target
501: * type (if there is one registered).
502: *
503: * @param targettype The target type (specified by "targettype"
504: * attribute of <invoke> tag).
505: */
506: public void unregisterInvokerClass(final String targettype) {
507: scInstance.unregisterInvokerClass(targettype);
508: }
509:
510: /**
511: * Get the state chart instance for this executor.
512: *
513: * @return The SCInstance for this executor.
514: */
515: SCInstance getSCInstance() {
516: return scInstance;
517: }
518:
519: /**
520: * Log the current set of active states.
521: */
522: private void logState() {
523: if (log.isInfoEnabled()) {
524: Iterator si = currentStatus.getStates().iterator();
525: StringBuffer sb = new StringBuffer("Current States: [");
526: while (si.hasNext()) {
527: State s = (State) si.next();
528: sb.append(s.getId());
529: if (si.hasNext()) {
530: sb.append(", ");
531: }
532: }
533: sb.append(']');
534: log.info(sb.toString());
535: }
536: }
537:
538: /**
539: * @param step The most recent Step
540: */
541: private void updateStatus(final Step step) {
542: currentStatus = step.getAfterStatus();
543: scInstance.getRootContext().setLocal(
544: "_ALL_STATES",
545: SCXMLHelper.getAncestorClosure(currentStatus
546: .getStates(), null));
547: }
548:
549: /**
550: * @param evts The events being triggered.
551: * @return Object[] Previous values.
552: */
553: private Object[] setEventData(final TriggerEvent[] evts) {
554: Context rootCtx = scInstance.getRootContext();
555: Object[] oldData = { rootCtx.get(EVENT_DATA),
556: rootCtx.get(EVENT_DATA_MAP) };
557: Object eventData = null;
558: Map payloadMap = new HashMap();
559: int len = evts.length;
560: for (int i = 0; i < len; i++) {
561: TriggerEvent te = evts[i];
562: payloadMap.put(te.getName(), te.getPayload());
563: }
564: if (len == 1) {
565: // we have only one event
566: eventData = evts[0].getPayload();
567: }
568: rootCtx.setLocal(EVENT_DATA, eventData);
569: rootCtx.setLocal(EVENT_DATA_MAP, payloadMap);
570: return oldData;
571: }
572:
573: /**
574: * @param oldData The old values to restore to.
575: */
576: private void restoreEventData(final Object[] oldData) {
577: scInstance.getRootContext().setLocal(EVENT_DATA, oldData[0]);
578: scInstance.getRootContext()
579: .setLocal(EVENT_DATA_MAP, oldData[1]);
580: }
581:
582: /**
583: * The special variable for storing single event data / payload.
584: */
585: private static final String EVENT_DATA = "_eventdata";
586:
587: /**
588: * The special variable for storing event data / payload,
589: * when multiple events are triggered, keyed by event name.
590: */
591: private static final String EVENT_DATA_MAP = "_eventdatamap";
592:
593: /**
594: * SCXMLExecutor put into motion without setting a model (state machine).
595: */
596: private static final String ERR_NO_STATE_MACHINE = "SCXMLExecutor: State machine not set";
597:
598: }
|