##### Coverage Report - org.apache.commons.scxml.semantics.SCXMLSemanticsImpl

Classes in this File | Line Coverage | Branch Coverage | Complexity
SCXMLSemanticsImpl |
70% |
80% |
| 7.4375;7.438

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