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