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