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.semantics;
018:
019: import java.io.Serializable;
020: import java.util.Arrays;
021: import java.util.Collection;
022: import java.util.Collections;
023: import java.util.Comparator;
024: import java.util.HashMap;
025: import java.util.HashSet;
026: import java.util.Iterator;
027: import java.util.LinkedList;
028: import java.util.List;
029: import java.util.Map;
030: import java.util.Set;
031:
032: import org.apache.commons.logging.Log;
033: import org.apache.commons.logging.LogFactory;
034: import org.apache.commons.scxml.Context;
035: import org.apache.commons.scxml.ErrorReporter;
036: import org.apache.commons.scxml.Evaluator;
037: import org.apache.commons.scxml.EventDispatcher;
038: import org.apache.commons.scxml.NotificationRegistry;
039: import org.apache.commons.scxml.PathResolver;
040: import org.apache.commons.scxml.SCInstance;
041: import org.apache.commons.scxml.SCXMLExpressionException;
042: import org.apache.commons.scxml.SCXMLHelper;
043: import org.apache.commons.scxml.SCXMLSemantics;
044: import org.apache.commons.scxml.Step;
045: import org.apache.commons.scxml.TriggerEvent;
046: import org.apache.commons.scxml.invoke.Invoker;
047: import org.apache.commons.scxml.invoke.InvokerException;
048: import org.apache.commons.scxml.model.Action;
049: import org.apache.commons.scxml.model.Finalize;
050: import org.apache.commons.scxml.model.History;
051: import org.apache.commons.scxml.model.Initial;
052: import org.apache.commons.scxml.model.Invoke;
053: import org.apache.commons.scxml.model.ModelException;
054: import org.apache.commons.scxml.model.OnEntry;
055: import org.apache.commons.scxml.model.OnExit;
056: import org.apache.commons.scxml.model.Parallel;
057: import org.apache.commons.scxml.model.Param;
058: import org.apache.commons.scxml.model.Path;
059: import org.apache.commons.scxml.model.SCXML;
060: import org.apache.commons.scxml.model.State;
061: import org.apache.commons.scxml.model.Transition;
062: import org.apache.commons.scxml.model.TransitionTarget;
063:
064: /**
065: * <p>This class encapsulates a particular SCXML semantics, that is, a
066: * particular semantic interpretation of Harel Statecharts, which aligns
067: * mostly with W3C SCXML July 5 public draft (that is, UML 1.5). However,
068: * certain aspects are taken from STATEMATE.</p>
069: *
070: * <p>Specific semantics can be created by subclassing this class.</p>
071: */
072: public class SCXMLSemanticsImpl implements SCXMLSemantics, Serializable {
073:
074: /**
075: * Serial version UID.
076: */
077: private static final long serialVersionUID = 1L;
078:
079: /**
080: * SCXML Logger for the application.
081: */
082: private Log appLog = LogFactory.getLog("scxml.app.log");
083:
084: /**
085: * The TransitionTarget comparator.
086: */
087: private TransitionTargetComparator targetComparator = new TransitionTargetComparator();
088:
089: /**
090: * Current document namespaces are saved under this key in the parent
091: * state's context.
092: */
093: private static final String NAMESPACES_KEY = "_ALL_NAMESPACES";
094:
095: /**
096: * @param input
097: * SCXML state machine
098: * @return normalized SCXML state machine, pseudo states are removed, etc.
099: * @param errRep
100: * ErrorReporter callback
101: */
102: public SCXML normalizeStateMachine(final SCXML input,
103: final ErrorReporter errRep) {
104: //it is a no-op for now
105: return input;
106: }
107:
108: /**
109: * @param input
110: * SCXML state machine [in]
111: * @param states
112: * a set of States to populate [out]
113: * @param entryList
114: * a list of States and Parallels to enter [out]
115: * @param errRep
116: * ErrorReporter callback [inout]
117: * @param scInstance
118: * The state chart instance [in]
119: * @throws ModelException
120: * in case there is a fatal SCXML object model problem.
121: */
122: public void determineInitialStates(final SCXML input,
123: final Set states, final List entryList,
124: final ErrorReporter errRep, final SCInstance scInstance)
125: throws ModelException {
126: State tmp = input.getInitialState();
127: if (tmp == null) {
128: errRep.onError(ErrorConstants.NO_INITIAL,
129: "SCXML initialstate is missing!", input);
130: } else {
131: states.add(tmp);
132: determineTargetStates(states, errRep, scInstance);
133: //set of ALL entered states (even if initialState is a jump-over)
134: Set onEntry = SCXMLHelper.getAncestorClosure(states, null);
135: // sort onEntry according state hierarchy
136: Object[] oen = onEntry.toArray();
137: onEntry.clear();
138: Arrays.sort(oen, getTTComparator());
139: // we need to impose reverse order for the onEntry list
140: List entering = Arrays.asList(oen);
141: Collections.reverse(entering);
142: entryList.addAll(entering);
143:
144: }
145: }
146:
147: /**
148: * Executes all OnExit/Transition/OnEntry transitional actions.
149: *
150: * @param step
151: * provides EntryList, TransitList, ExitList gets
152: * updated its AfterStatus/Events
153: * @param stateMachine
154: * state machine - SCXML instance
155: * @param evtDispatcher
156: * the event dispatcher - EventDispatcher instance
157: * @param errRep
158: * error reporter
159: * @param scInstance
160: * The state chart instance
161: * @throws ModelException
162: * in case there is a fatal SCXML object model problem.
163: */
164: public void executeActions(final Step step,
165: final SCXML stateMachine,
166: final EventDispatcher evtDispatcher,
167: final ErrorReporter errRep, final SCInstance scInstance)
168: throws ModelException {
169: NotificationRegistry nr = scInstance.getNotificationRegistry();
170: Collection internalEvents = step.getAfterStatus().getEvents();
171: Map invokers = scInstance.getInvokers();
172: // ExecutePhaseActions / OnExit
173: for (Iterator i = step.getExitList().iterator(); i.hasNext();) {
174: TransitionTarget tt = (TransitionTarget) i.next();
175: OnExit oe = tt.getOnExit();
176: try {
177: for (Iterator onExitIter = oe.getActions().iterator(); onExitIter
178: .hasNext();) {
179: ((Action) onExitIter.next()).execute(evtDispatcher,
180: errRep, scInstance, appLog, internalEvents);
181: }
182: } catch (SCXMLExpressionException e) {
183: errRep.onError(ErrorConstants.EXPRESSION_ERROR, e
184: .getMessage(), oe);
185: }
186: // check if invoke is active in this state
187: if (invokers.containsKey(tt)) {
188: Invoker toCancel = (Invoker) invokers.get(tt);
189: try {
190: toCancel.cancel();
191: } catch (InvokerException ie) {
192: TriggerEvent te = new TriggerEvent(tt.getId()
193: + ".invoke.cancel.failed",
194: TriggerEvent.ERROR_EVENT);
195: internalEvents.add(te);
196: }
197: // done here, don't wait for cancel response
198: invokers.remove(tt);
199: }
200: nr.fireOnExit(tt, tt);
201: nr.fireOnExit(stateMachine, tt);
202: TriggerEvent te = new TriggerEvent(tt.getId() + ".exit",
203: TriggerEvent.CHANGE_EVENT);
204: internalEvents.add(te);
205: }
206: // ExecutePhaseActions / Transitions
207: for (Iterator i = step.getTransitList().iterator(); i.hasNext();) {
208: Transition t = (Transition) i.next();
209: try {
210: for (Iterator transitIter = t.getActions().iterator(); transitIter
211: .hasNext();) {
212: ((Action) transitIter.next()).execute(
213: evtDispatcher, errRep, scInstance, appLog,
214: internalEvents);
215: }
216: } catch (SCXMLExpressionException e) {
217: errRep.onError(ErrorConstants.EXPRESSION_ERROR, e
218: .getMessage(), t);
219: }
220: nr.fireOnTransition(t, t.getParent(), t.getRuntimeTarget(),
221: t);
222: nr.fireOnTransition(stateMachine, t.getParent(), t
223: .getRuntimeTarget(), t);
224: }
225: // ExecutePhaseActions / OnEntry
226: for (Iterator i = step.getEntryList().iterator(); i.hasNext();) {
227: TransitionTarget tt = (TransitionTarget) i.next();
228: OnEntry oe = tt.getOnEntry();
229: try {
230: for (Iterator onEntryIter = oe.getActions().iterator(); onEntryIter
231: .hasNext();) {
232: ((Action) onEntryIter.next()).execute(
233: evtDispatcher, errRep, scInstance, appLog,
234: internalEvents);
235: }
236: } catch (SCXMLExpressionException e) {
237: errRep.onError(ErrorConstants.EXPRESSION_ERROR, e
238: .getMessage(), oe);
239: }
240: nr.fireOnEntry(tt, tt);
241: nr.fireOnEntry(stateMachine, tt);
242: TriggerEvent te = new TriggerEvent(tt.getId() + ".entry",
243: TriggerEvent.CHANGE_EVENT);
244: internalEvents.add(te);
245: //3.2.1 and 3.4 (.done events)
246: if (tt instanceof State) {
247: State ts = (State) tt;
248: if (ts.getIsFinal()) {
249: State parent = (State) ts.getParent();
250: String prefix = "";
251: if (parent != null) {
252: prefix = parent.getId();
253: }
254: te = new TriggerEvent(prefix + ".done",
255: TriggerEvent.CHANGE_EVENT);
256: internalEvents.add(te);
257: if (parent != null) {
258: parent.setDone(true);
259: }
260: if (parent != null && parent.isRegion()) {
261: //3.4 we got a region, which is finalized
262: //let's check its siblings too
263: Parallel p = (Parallel) parent.getParent();
264: int finCount = 0;
265: int pCount = p.getStates().size();
266: for (Iterator regions = p.getStates()
267: .iterator(); regions.hasNext();) {
268: State reg = (State) regions.next();
269: if (reg.isDone()) {
270: finCount++;
271: }
272: }
273: if (finCount == pCount) {
274: te = new TriggerEvent(p.getId() + ".done",
275: TriggerEvent.CHANGE_EVENT);
276: internalEvents.add(te);
277: te = new TriggerEvent(p.getParent().getId()
278: + ".done",
279: TriggerEvent.CHANGE_EVENT);
280: internalEvents.add(te);
281: //this is not in the specs, but is makes sense
282: p.getParentState().setDone(true);
283: }
284: }
285: }
286: }
287: }
288: }
289:
290: /**
291: * @param stateMachine
292: * a SM to traverse [in]
293: * @param step
294: * with current status and list of transitions to populate
295: * [inout]
296: * @param errRep
297: * ErrorReporter callback [inout]
298: */
299: public void enumerateReachableTransitions(final SCXML stateMachine,
300: final Step step, final ErrorReporter errRep) {
301: // prevents adding the same transition multiple times
302: Set transSet = new HashSet();
303: // prevents visiting the same state multiple times
304: Set stateSet = new HashSet(step.getBeforeStatus().getStates());
305: // breath-first search to-do list
306: LinkedList todoList = new LinkedList(stateSet);
307: while (!todoList.isEmpty()) {
308: State st = (State) todoList.removeFirst();
309: for (Iterator i = st.getTransitionsList().iterator(); i
310: .hasNext();) {
311: Transition t = (Transition) i.next();
312: if (!transSet.contains(t)) {
313: transSet.add(t);
314: step.getTransitList().add(t);
315: }
316: }
317: State parent = st.getParentState();
318: if (parent != null && !stateSet.contains(parent)) {
319: stateSet.add(parent);
320: todoList.addLast(parent);
321: }
322: }
323: transSet.clear();
324: stateSet.clear();
325: todoList.clear();
326: }
327:
328: /**
329: * @param step
330: * [inout]
331: * @param evtDispatcher
332: * The {@link EventDispatcher} [in]
333: * @param errRep
334: * ErrorReporter callback [inout]
335: * @param scInstance
336: * The state chart instance [in]
337: * @throws ModelException
338: * in case there is a fatal SCXML object model problem.
339: */
340: public void filterTransitionsSet(final Step step,
341: final EventDispatcher evtDispatcher,
342: final ErrorReporter errRep, final SCInstance scInstance)
343: throws ModelException {
344: /*
345: * - filter transition set by applying events
346: * (step/beforeStatus/events + step/externalEvents) (local check)
347: * - evaluating guard conditions for
348: * each transition (local check) - transition precedence (bottom-up)
349: * as defined by SCXML specs
350: */
351: Set allEvents = new HashSet(step.getBeforeStatus().getEvents()
352: .size()
353: + step.getExternalEvents().size());
354: //for now, we only match against event names
355: for (Iterator ei = step.getBeforeStatus().getEvents()
356: .iterator(); ei.hasNext();) {
357: TriggerEvent te = (TriggerEvent) ei.next();
358: allEvents.add(te.getName());
359: }
360: for (Iterator ei = step.getExternalEvents().iterator(); ei
361: .hasNext();) {
362: TriggerEvent te = (TriggerEvent) ei.next();
363: allEvents.add(te.getName());
364: }
365: // Finalize invokes, if applicable
366: for (Iterator iter = scInstance.getInvokers().keySet()
367: .iterator(); iter.hasNext();) {
368: State s = (State) iter.next();
369: if (finalizeMatch(s.getId(), allEvents)) {
370: Finalize fn = s.getInvoke().getFinalize();
371: if (fn != null) {
372: try {
373: for (Iterator fnIter = fn.getActions()
374: .iterator(); fnIter.hasNext();) {
375: ((Action) fnIter.next()).execute(
376: evtDispatcher, errRep, scInstance,
377: appLog, step.getAfterStatus()
378: .getEvents());
379: }
380: } catch (SCXMLExpressionException e) {
381: errRep.onError(ErrorConstants.EXPRESSION_ERROR,
382: e.getMessage(), fn);
383: }
384: }
385: }
386: }
387: //remove list (filtered-out list)
388: List removeList = new LinkedList();
389: //iterate over non-filtered transition set
390: for (Iterator iter = step.getTransitList().iterator(); iter
391: .hasNext();) {
392: Transition t = (Transition) iter.next();
393: // event check
394: String event = t.getEvent();
395: if (!eventMatch(event, allEvents)) {
396: // t has a non-empty event which is not triggered
397: removeList.add(t);
398: continue; //makes no sense to eval guard cond.
399: }
400: // guard condition check
401: Boolean rslt;
402: String expr = t.getCond();
403: if (SCXMLHelper.isStringEmpty(expr)) {
404: rslt = Boolean.TRUE;
405: } else {
406: try {
407: Context ctx = scInstance.getContext(t.getParent());
408: ctx.setLocal(NAMESPACES_KEY, t.getNamespaces());
409: rslt = scInstance.getEvaluator().evalCond(ctx,
410: t.getCond());
411: ctx.setLocal(NAMESPACES_KEY, null);
412: } catch (SCXMLExpressionException e) {
413: rslt = Boolean.FALSE;
414: errRep.onError(ErrorConstants.EXPRESSION_ERROR, e
415: .getMessage(), t);
416: }
417: }
418: if (!rslt.booleanValue()) {
419: // guard condition has not passed
420: removeList.add(t);
421: }
422: }
423: // apply event + guard condition filter
424: step.getTransitList().removeAll(removeList);
425: // cleanup temporary structures
426: allEvents.clear();
427: removeList.clear();
428: // optimization - global precedence potentially applies
429: // only if there are multiple enabled transitions
430: if (step.getTransitList().size() > 1) {
431: // global transition precedence check
432: Object[] trans = step.getTransitList().toArray();
433: Set currentStates = step.getBeforeStatus().getStates();
434: // non-determinism candidates
435: Set nonDeterm = new HashSet();
436: for (int i = 0; i < trans.length; i++) {
437: Transition t = (Transition) trans[i];
438: TransitionTarget tsrc = t.getParent();
439: for (int j = i + 1; j < trans.length; j++) {
440: Transition t2 = (Transition) trans[j];
441: boolean conflict = SCXMLHelper.inConflict(t, t2,
442: currentStates);
443: if (conflict) {
444: //potentially conflicting transitions
445: TransitionTarget t2src = t2.getParent();
446: if (SCXMLHelper.isDescendant(t2src, tsrc)) {
447: //t2 takes precedence over t
448: removeList.add(t);
449: break; //it makes no sense to waste cycles with t
450: } else if (SCXMLHelper
451: .isDescendant(tsrc, t2src)) {
452: //t takes precendence over t2
453: removeList.add(t2);
454: } else {
455: //add both to the non-determinism candidates
456: nonDeterm.add(t);
457: nonDeterm.add(t2);
458: }
459: }
460: }
461: }
462: // check if all non-deterministic situations have been resolved
463: nonDeterm.removeAll(removeList);
464: if (nonDeterm.size() > 0) {
465: errRep.onError(ErrorConstants.NON_DETERMINISTIC,
466: "Multiple conflicting transitions enabled.",
467: nonDeterm);
468: }
469: // apply global transition filter
470: step.getTransitList().removeAll(removeList);
471: removeList.clear();
472: nonDeterm.clear();
473: }
474: }
475:
476: /**
477: * Populate the target set.
478: * <ul>
479: * <li>take targets of selected transitions</li>
480: * <li>take exited regions into account and make sure every active
481: * parallel region has all siblings active
482: * [that is, explicitly visit or sibling regions in case of newly visited
483: * (revisited) orthogonal states]</li>
484: * </ul>
485: * @param residual [in]
486: * @param transitList [in]
487: * @param errRep
488: * ErrorReporter callback [inout]
489: * @return Set The target set
490: */
491: public Set seedTargetSet(final Set residual,
492: final List transitList, final ErrorReporter errRep) {
493: Set seedSet = new HashSet();
494: Set regions = new HashSet();
495: for (Iterator i = transitList.iterator(); i.hasNext();) {
496: Transition t = (Transition) i.next();
497: //iterate over transitions and add target states
498: if (t.getTarget() != null) {
499: seedSet.add(t.getTarget());
500: }
501: //build a set of all entered regions
502: Path p = t.getPath();
503: if (p.isCrossRegion()) {
504: List regs = p.getRegionsEntered();
505: for (Iterator j = regs.iterator(); j.hasNext();) {
506: State region = (State) j.next();
507: regions.addAll(((Parallel) region.getParent())
508: .getStates());
509: }
510: }
511: }
512: //check whether all active regions have their siblings active too
513: Set allStates = new HashSet(residual);
514: allStates.addAll(seedSet);
515: allStates = SCXMLHelper.getAncestorClosure(allStates, null);
516: regions.removeAll(allStates);
517: //iterate over inactive regions and visit them implicitly using initial
518: for (Iterator i = regions.iterator(); i.hasNext();) {
519: State reg = (State) i.next();
520: seedSet.add(reg);
521: }
522: return seedSet;
523: }
524:
525: /**
526: * @param states
527: * a set seeded in previous step [inout]
528: * @param errRep
529: * ErrorReporter callback [inout]
530: * @param scInstance
531: * The state chart instance [in]
532: * @throws ModelException On illegal configuration
533: * @see #seedTargetSet(Set, List, ErrorReporter)
534: */
535: public void determineTargetStates(final Set states,
536: final ErrorReporter errRep, final SCInstance scInstance)
537: throws ModelException {
538: LinkedList wrkSet = new LinkedList(states);
539: // clear the seed-set - will be populated by leaf states
540: states.clear();
541: while (!wrkSet.isEmpty()) {
542: TransitionTarget tt = (TransitionTarget) wrkSet
543: .removeFirst();
544: if (tt instanceof State) {
545: State st = (State) tt;
546: //state can either have parallel or substates w. initial
547: //or it is a leaf state
548: // NOTE: Digester has to verify this precondition!
549: if (st.isSimple()) {
550: states.add(st); //leaf
551: } else if (st.isOrthogonal()) {
552: wrkSet.addLast(st.getParallel()); //parallel
553: } else {
554: // composite state
555: Initial ini = st.getInitial();
556: if (ini == null) {
557: errRep.onError(ErrorConstants.NO_INITIAL,
558: "Initial pseudostate is missing!", st);
559: } else {
560: // If we are here, transition target must be a State
561: // or History
562: Transition initialTransition = ini
563: .getTransition();
564: if (initialTransition == null) {
565: errRep.onError(
566: ErrorConstants.ILLEGAL_INITIAL,
567: "Initial transition is null!", st);
568: } else {
569: TransitionTarget init = initialTransition
570: .getTarget();
571: if (init == null
572: || !(init instanceof State || init instanceof History)) {
573: errRep
574: .onError(
575: ErrorConstants.ILLEGAL_INITIAL,
576: "Initial not pointing to a State or History!",
577: st);
578: } else {
579: wrkSet.addLast(init);
580: }
581: }
582: }
583: }
584: } else if (tt instanceof Parallel) {
585: Parallel prl = (Parallel) tt;
586: for (Iterator i = prl.getStates().iterator(); i
587: .hasNext();) {
588: //fork
589: wrkSet.addLast(i.next());
590: }
591: } else if (tt instanceof History) {
592: History h = (History) tt;
593: if (scInstance.isEmpty(h)) {
594: wrkSet
595: .addLast(h.getTransition()
596: .getRuntimeTarget());
597: } else {
598: wrkSet.addAll(scInstance.getLastConfiguration(h));
599: }
600: } else {
601: throw new ModelException(
602: "Unknown TransitionTarget subclass:"
603: + tt.getClass().getName());
604: }
605: }
606: }
607:
608: /**
609: * Go over the exit list and update history information for
610: * relevant states.
611: *
612: * @param step
613: * [inout]
614: * @param errRep
615: * ErrorReporter callback [inout]
616: * @param scInstance
617: * The state chart instance [inout]
618: */
619: public void updateHistoryStates(final Step step,
620: final ErrorReporter errRep, final SCInstance scInstance) {
621: Set oldState = step.getBeforeStatus().getStates();
622: for (Iterator i = step.getExitList().iterator(); i.hasNext();) {
623: Object o = i.next();
624: if (o instanceof State) {
625: State s = (State) o;
626: if (s.hasHistory()) {
627: Set shallow = null;
628: Set deep = null;
629: for (Iterator j = s.getHistory().iterator(); j
630: .hasNext();) {
631: History h = (History) j.next();
632: if (h.isDeep()) {
633: if (deep == null) {
634: //calculate deep history for a given state once
635: deep = new HashSet();
636: Iterator k = oldState.iterator();
637: while (k.hasNext()) {
638: State os = (State) k.next();
639: if (SCXMLHelper.isDescendant(os, s)) {
640: deep.add(os);
641: }
642: }
643: }
644: scInstance.setLastConfiguration(h, deep);
645: } else {
646: if (shallow == null) {
647: //calculate shallow history for a given state
648: // once
649: shallow = new HashSet();
650: shallow
651: .addAll(s.getChildren()
652: .values());
653: shallow.retainAll(SCXMLHelper
654: .getAncestorClosure(oldState,
655: null));
656: }
657: scInstance.setLastConfiguration(h, shallow);
658: }
659: }
660: shallow = null;
661: deep = null;
662: }
663: }
664: }
665: }
666:
667: /**
668: * Follow the candidate transitions for this execution Step, and update the
669: * lists of entered and exited states accordingly.
670: *
671: * @param step The current Step
672: * @param errorReporter The ErrorReporter for the current environment
673: * @param scInstance The state chart instance
674: *
675: * @throws ModelException
676: * in case there is a fatal SCXML object model problem.
677: */
678: public void followTransitions(final Step step,
679: final ErrorReporter errorReporter,
680: final SCInstance scInstance) throws ModelException {
681: Set currentStates = step.getBeforeStatus().getStates();
682: List transitions = step.getTransitList();
683: // DetermineExitedStates (currentStates, transitList) -> exitedStates
684: Set exitedStates = new HashSet();
685: for (Iterator i = transitions.iterator(); i.hasNext();) {
686: Transition t = (Transition) i.next();
687: Set ext = SCXMLHelper.getStatesExited(t, currentStates);
688: exitedStates.addAll(ext);
689: }
690: // compute residual states - these are preserved from the previous step
691: Set residual = new HashSet(currentStates);
692: residual.removeAll(exitedStates);
693: // SeedTargetSet (residual, transitList) -> seedSet
694: Set seedSet = seedTargetSet(residual, transitions,
695: errorReporter);
696: // DetermineTargetStates (initialTargetSet) -> targetSet
697: Set targetSet = step.getAfterStatus().getStates();
698: targetSet.addAll(seedSet); //copy to preserve seedSet
699: determineTargetStates(targetSet, errorReporter, scInstance);
700: // BuildOnEntryList (targetSet, seedSet) -> entryList
701: Set entered = SCXMLHelper
702: .getAncestorClosure(targetSet, seedSet);
703: seedSet.clear();
704: for (Iterator i = transitions.iterator(); i.hasNext();) {
705: Transition t = (Transition) i.next();
706: entered.addAll(t.getPath().getDownwardSegment());
707: // If target is a History pseudo state, remove from entered list
708: if (t.getRuntimeTarget() instanceof History) {
709: entered.remove(t.getRuntimeTarget());
710: }
711: }
712: // Check whether the computed state config is legal
713: targetSet.addAll(residual);
714: residual.clear();
715: if (!SCXMLHelper.isLegalConfig(targetSet, errorReporter)) {
716: throw new ModelException(
717: "Illegal state machine configuration!");
718: }
719: // sort onEntry and onExit according state hierarchy
720: Object[] oex = exitedStates.toArray();
721: exitedStates.clear();
722: Object[] oen = entered.toArray();
723: entered.clear();
724: Arrays.sort(oex, getTTComparator());
725: Arrays.sort(oen, getTTComparator());
726: step.getExitList().addAll(Arrays.asList(oex));
727: // we need to impose reverse order for the onEntry list
728: List entering = Arrays.asList(oen);
729: Collections.reverse(entering);
730: step.getEntryList().addAll(entering);
731: // reset 'done' flag
732: for (Iterator reset = entering.iterator(); reset.hasNext();) {
733: Object o = reset.next();
734: if (o instanceof State) {
735: ((State) o).setDone(false);
736: }
737: }
738: }
739:
740: /**
741: * Process any existing invokes, includes forwarding external events,
742: * and executing any finalize handlers.
743: *
744: * @param events
745: * The events to be forwarded
746: * @param errRep
747: * ErrorReporter callback
748: * @param scInstance
749: * The state chart instance
750: * @throws ModelException
751: * in case there is a fatal SCXML object model problem.
752: */
753: public void processInvokes(final TriggerEvent[] events,
754: final ErrorReporter errRep, final SCInstance scInstance)
755: throws ModelException {
756: Set eventNames = new HashSet();
757: //for now, we only match against event names
758: for (int i = 0; i < events.length; i++) {
759: eventNames.add(events[i].getName());
760: }
761: for (Iterator invokeIter = scInstance.getInvokers().entrySet()
762: .iterator(); invokeIter.hasNext();) {
763: Map.Entry iEntry = (Map.Entry) invokeIter.next();
764: String parentId = ((TransitionTarget) iEntry.getKey())
765: .getId();
766: if (!finalizeMatch(parentId, eventNames)) { // prevent cycles
767: Invoker inv = (Invoker) iEntry.getValue();
768: try {
769: inv.parentEvents(events);
770: } catch (InvokerException ie) {
771: appLog.error(ie.getMessage(), ie);
772: throw new ModelException(ie.getMessage(), ie
773: .getCause());
774: }
775: }
776: }
777: }
778:
779: /**
780: * Initiate any new invokes.
781: *
782: * @param step
783: * The current Step
784: * @param errRep
785: * ErrorReporter callback
786: * @param scInstance
787: * The state chart instance
788: */
789: public void initiateInvokes(final Step step,
790: final ErrorReporter errRep, final SCInstance scInstance) {
791: Evaluator eval = scInstance.getEvaluator();
792: Collection internalEvents = step.getAfterStatus().getEvents();
793: for (Iterator iter = step.getAfterStatus().getStates()
794: .iterator(); iter.hasNext();) {
795: State s = (State) iter.next();
796: Context ctx = scInstance.getContext(s);
797: Invoke i = s.getInvoke();
798: if (i != null && scInstance.getInvoker(s) == null) {
799: String src = i.getSrc();
800: if (src == null) {
801: String srcexpr = i.getSrcexpr();
802: Object srcObj = null;
803: try {
804: ctx.setLocal(NAMESPACES_KEY, i.getNamespaces());
805: srcObj = eval.eval(ctx, srcexpr);
806: ctx.setLocal(NAMESPACES_KEY, null);
807: src = String.valueOf(srcObj);
808: } catch (SCXMLExpressionException see) {
809: errRep.onError(ErrorConstants.EXPRESSION_ERROR,
810: see.getMessage(), i);
811: }
812: }
813: String source = src;
814: PathResolver pr = i.getPathResolver();
815: if (pr != null) {
816: source = i.getPathResolver().resolvePath(src);
817: }
818: String ttype = i.getTargettype();
819: Invoker inv = null;
820: try {
821: inv = scInstance.newInvoker(ttype);
822: } catch (InvokerException ie) {
823: TriggerEvent te = new TriggerEvent(s.getId()
824: + ".invoke.failed",
825: TriggerEvent.ERROR_EVENT);
826: internalEvents.add(te);
827: continue;
828: }
829: inv.setParentStateId(s.getId());
830: inv.setSCInstance(scInstance);
831: List params = i.params();
832: Map args = new HashMap();
833: for (Iterator pIter = params.iterator(); pIter
834: .hasNext();) {
835: Param p = (Param) pIter.next();
836: String argExpr = p.getExpr();
837: Object argValue = null;
838: if (argExpr != null && argExpr.trim().length() > 0) {
839: try {
840: ctx.setLocal(NAMESPACES_KEY, p
841: .getNamespaces());
842: argValue = eval.eval(ctx, argExpr);
843: ctx.setLocal(NAMESPACES_KEY, null);
844: } catch (SCXMLExpressionException see) {
845: errRep.onError(
846: ErrorConstants.EXPRESSION_ERROR,
847: see.getMessage(), i);
848: }
849: }
850: args.put(p.getName(), argValue);
851: }
852: try {
853: inv.invoke(source, args);
854: } catch (InvokerException ie) {
855: TriggerEvent te = new TriggerEvent(s.getId()
856: + ".invoke.failed",
857: TriggerEvent.ERROR_EVENT);
858: internalEvents.add(te);
859: continue;
860: }
861: scInstance.setInvoker(s, inv);
862: }
863: }
864: }
865:
866: /**
867: * Implements prefix match, that is, if, for example,
868: * "mouse.click" is a member of eventOccurrences and a
869: * transition is triggered by "mouse", the method returns true.
870: *
871: * @param transEvent
872: * a trigger event of a transition
873: * @param eventOccurrences
874: * current events
875: * @return true/false
876: */
877: protected boolean eventMatch(final String transEvent,
878: final Set eventOccurrences) {
879: if (SCXMLHelper.isStringEmpty(transEvent)) {
880: return true;
881: } else {
882: String transEventDot = transEvent + "."; // prefix event support
883: Iterator i = eventOccurrences.iterator();
884: while (i.hasNext()) {
885: String evt = (String) i.next();
886: if (evt == null) {
887: continue; // Unnamed events
888: } else if (evt.equals("*")) {
889: return true; // Wildcard
890: } else if (evt.equals(transEvent)
891: || evt.startsWith(transEventDot)) {
892: return true;
893: }
894: }
895: return false;
896: }
897: }
898:
899: /**
900: * Implements event prefix match to ascertain <finalize> execution.
901: *
902: * @param parentStateId
903: * the ID of the parent state of the <invoke> holding
904: * the <finalize>
905: * @param eventOccurrences
906: * current events
907: * @return true/false
908: */
909: protected boolean finalizeMatch(final String parentStateId,
910: final Set eventOccurrences) {
911: String prefix = parentStateId + ".invoke."; // invoke prefix
912: Iterator i = eventOccurrences.iterator();
913: while (i.hasNext()) {
914: String evt = (String) i.next();
915: if (evt == null) {
916: continue; // Unnamed events
917: } else if (evt.startsWith(prefix)) {
918: return true;
919: }
920: }
921: return false;
922: }
923:
924: /**
925: * TransitionTargetComparator factory method.
926: * @return Comparator The TransitionTarget comparator
927: */
928: protected Comparator getTTComparator() {
929: return targetComparator;
930: }
931:
932: /**
933: * Set the log used by this <code>SCXMLSemantics</code> instance.
934: *
935: * @param log The new log.
936: */
937: protected void setLog(final Log log) {
938: this .appLog = log;
939: }
940:
941: /**
942: * Get the log used by this <code>SCXMLSemantics</code> instance.
943: *
944: * @return Log The log being used.
945: */
946: protected Log getLog() {
947: return appLog;
948: }
949:
950: }
|