Source code: org/apache/taglibs/rdc/scxml/SCXMLDigester.java
1 /*
2 *
3 * Copyright 2004 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.taglibs.rdc.scxml;
19
20 import java.net.URL;
21 import java.text.MessageFormat;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Set;
26
27 import javax.servlet.ServletContext;
28
29 import org.apache.commons.digester.Digester;
30 import org.apache.commons.digester.ExtendedBaseRules;
31 import org.apache.commons.digester.ObjectCreateRule;
32 import org.apache.commons.digester.Rule;
33 import org.apache.commons.digester.SetNextRule;
34 import org.apache.commons.digester.SetPropertiesRule;
35 import org.apache.commons.logging.LogFactory;
36
37 import org.xml.sax.Attributes;
38 import org.xml.sax.ErrorHandler;
39
40 import org.apache.taglibs.rdc.scxml.env.ServletContextResolver;
41 import org.apache.taglibs.rdc.scxml.env.URLResolver;
42 import org.apache.taglibs.rdc.scxml.model.Action;
43 import org.apache.taglibs.rdc.scxml.model.Assign;
44 import org.apache.taglibs.rdc.scxml.model.Cancel;
45 import org.apache.taglibs.rdc.scxml.model.Else;
46 import org.apache.taglibs.rdc.scxml.model.ElseIf;
47 import org.apache.taglibs.rdc.scxml.model.Executable;
48 import org.apache.taglibs.rdc.scxml.model.Exit;
49 import org.apache.taglibs.rdc.scxml.model.History;
50 import org.apache.taglibs.rdc.scxml.model.If;
51 import org.apache.taglibs.rdc.scxml.model.Initial;
52 import org.apache.taglibs.rdc.scxml.model.Log;
53 import org.apache.taglibs.rdc.scxml.model.OnEntry;
54 import org.apache.taglibs.rdc.scxml.model.OnExit;
55 import org.apache.taglibs.rdc.scxml.model.Parallel;
56 import org.apache.taglibs.rdc.scxml.model.SCXML;
57 import org.apache.taglibs.rdc.scxml.model.Send;
58 import org.apache.taglibs.rdc.scxml.model.State;
59 import org.apache.taglibs.rdc.scxml.model.Transition;
60 import org.apache.taglibs.rdc.scxml.model.TransitionTarget;
61 import org.apache.taglibs.rdc.scxml.model.Var;
62
63 /**
64 * The SCXMLDigester can be used to: <br>
65 * a) Digest a SCXML file placed in a web application context <br>
66 * b) Obtain a Digester instance configured with rules for SCXML digestion <br>
67 * c) Serialize an SCXML object (primarily for debugging) <br>
68 *
69 * @author Rahul Akolkar
70 */
71 public class SCXMLDigester {
72
73 private static final String ERR_PARSE_FAIL = "<!-- Error parsing " +
74 "SCXML document for group: \"{0}\", with message: \"{1}\" -->\n";
75
76 // Logging
77 private static org.apache.commons.logging.Log log = LogFactory
78 .getLog(SCXMLDigester.class);
79
80 //-- PUBLIC METHODS --//
81 /**
82 * Convenience method for the RDC SCXML DM strategy impl
83 *
84 * @param ServletContext
85 * The ServletContext within which this RDC group is hosted
86 * @param String
87 * The absolute file from the base of the application context
88 * @param ErrorHandler
89 * The SAX ErrorHandler
90 *
91 * @return SCXML The SCXML object corresponding to the file argument
92 */
93 public static SCXML digest(ServletContext sc, String file,
94 ErrorHandler errHandler, Context evalCtx, Evaluator evalEngine) {
95
96 SCXML scxml = null;
97 Digester scxmlDigester = SCXMLDigester.newInstance(null,
98 new ServletContextResolver(sc));
99 scxmlDigester.setErrorHandler(errHandler);
100
101 try {
102 scxml = (SCXML) scxmlDigester.parse(sc.getRealPath(file));
103 } catch (Exception e) {
104 MessageFormat msgFormat = new MessageFormat(ERR_PARSE_FAIL);
105 String errMsg = msgFormat.format(new Object[] {sc.
106 getRealPath(file), e.getMessage()});
107 log.error(errMsg, e);
108 }
109
110 if (scxml != null) {
111 updateSCXML(scxml, evalCtx, evalEngine);
112 }
113
114 return scxml;
115
116 }
117
118 /**
119 * API for standalone usage.
120 *
121 * @param scxmlURL
122 * a canonical absolute URL to parse (relative URLs within the
123 * top level document are to be resovled against this URL).
124 * @param errHandler
125 * The SAX ErrorHandler
126 * @param evalCtx
127 * the document-level variable context for guard condition
128 * evaluation
129 * @param evalEngine
130 * the scripting/expression language engine for creating local
131 * state-level variable contexts (if supported by a given
132 * scripting engine)
133 * @see Context
134 * @see Evaluator
135 *
136 * @return SCXML The SCXML object corresponding to the file argument
137 */
138 public static SCXML digest(URL scxmlURL, ErrorHandler errHandler,
139 Context evalCtx, Evaluator evalEngine) {
140
141 SCXML scxml = null;
142 Digester scxmlDigester = SCXMLDigester
143 .newInstance(null, new URLResolver(scxmlURL));
144 scxmlDigester.setErrorHandler(errHandler);
145
146 try {
147 scxml = (SCXML) scxmlDigester.parse(scxmlURL.toString());
148 } catch (Exception e) {
149 MessageFormat msgFormat = new MessageFormat(ERR_PARSE_FAIL);
150 String errMsg = msgFormat.format(new Object[] {scxmlURL.toString(),
151 e.getMessage()});
152 log.error(errMsg, e);
153 }
154
155 if (scxml != null) {
156 updateSCXML(scxml, evalCtx, evalEngine);
157 }
158
159 return scxml;
160
161 }
162
163 /**
164 * Serialize this SCXML object (primarily for debugging)
165 *
166 * @param scxml
167 * The SCXML to be serialized
168 * @return String The serialized SCXML
169 */
170 public static String serializeSCXML(SCXML scxml) {
171 StringBuffer b = new StringBuffer("<scxml xmlns=\"").append(
172 scxml.getXmlns()).append("\" version=\"").append(
173 scxml.getVersion()).append("\" initialstate=\"").append(
174 scxml.getInitialstate()).append("\">\n");
175 Map s = scxml.getStates();
176 Iterator i = s.keySet().iterator();
177 while (i.hasNext()) {
178 serializeState(b, (State) s.get(i.next()), INDENT);
179 }
180 b.append("</scxml>\n");
181 return b.toString();
182 }
183
184 //-- PRIVATE CONSTANTS --//
185 //// Patterns to get the digestion going
186 private static final String XP_SM = "scxml";
187
188 private static final String XP_SM_ST = "scxml/state";
189
190 //// Universal matches
191 // State
192 private static final String XP_ST_ST = "!*/state/state";
193
194 private static final String XP_PAR_ST = "!*/parallel/state";
195
196 private static final String XP_TR_TAR_ST = "!*/transition/target/state";
197
198 //private static final String XP_ST_TAR_ST = "!*/state/target/state";
199
200 // Parallel
201 private static final String XP_ST_PAR = "!*/state/parallel";
202
203 // If
204 private static final String XP_IF = "!*/if";
205
206 //// Path Fragments
207 // Onentries and Onexits
208 private static final String XP_ONEN = "/onentry";
209
210 private static final String XP_ONEX = "/onexit";
211
212 // Initial
213 private static final String XP_INI = "/initial";
214
215 // History
216 private static final String XP_HIST = "/history";
217
218 // Transition, target and exit
219 private static final String XP_TR = "/transition";
220
221 private static final String XP_TAR = "/target";
222
223 private static final String XP_ST = "/state";
224
225 private static final String XP_EXT = "/exit";
226
227 // Actions
228 private static final String XP_VAR = "/var";
229
230 private static final String XP_ASN = "/assign";
231
232 private static final String XP_LOG = "/log";
233
234 private static final String XP_SND = "/send";
235
236 private static final String XP_CAN = "/cancel";
237
238 private static final String XP_EIF = "/elseif";
239
240 private static final String XP_ELS = "/else";
241
242 //// Other constants
243 private static final String INDENT = " ";
244
245 //-- PRIVATE UTILITY METHODS --//
246 /*
247 * Get a SCXML digester instance
248 *
249 * @return Digester A newly configured SCXML digester instance
250 */
251 private static Digester newInstance(SCXML scxml, PathResolver sc) {
252
253 Digester digester = new Digester();
254 //Uncomment next line after SCXML DTD is available
255 //digester.setValidating(true);
256 digester.setRules(initRules(scxml, sc));
257 return digester;
258 }
259
260 /*
261 * Private utility functions for configuring digester rule base for SCXML
262 */
263 private static ExtendedBaseRules initRules(SCXML scxml, PathResolver sc) {
264
265 ExtendedBaseRules scxmlRules = new ExtendedBaseRules();
266
267 //// SCXML
268 scxmlRules.add(XP_SM, new ObjectCreateRule(SCXML.class));
269 scxmlRules.add(XP_SM, new SetPropertiesRule());
270
271 //// States
272 // Level one states
273 addStateRules(XP_SM_ST, scxmlRules, scxml, sc, 0);
274 scxmlRules.add(XP_SM_ST, new SetNextRule("addState"));
275 // Nested states
276 addStateRules(XP_ST_ST, scxmlRules, scxml, sc, 1);
277 scxmlRules.add(XP_ST_ST, new SetNextRule("addChild"));
278 // Initial states (no longer needed due to addition of Initial)
279 //addStateRules(XP_ST_TAR_ST, scxmlRules, scxml, sc, 1);
280 //scxmlRules.add(XP_ST_TAR_ST, new SetNextRule("addChild"));
281 //scxmlRules.add(XP_ST_TAR_ST, new SetNextRule("setInitial"));
282 // Parallel states
283 addStateRules(XP_PAR_ST, scxmlRules, scxml, sc, 1);
284 scxmlRules.add(XP_PAR_ST, new SetNextRule("addState"));
285 // Target states
286 addStateRules(XP_TR_TAR_ST, scxmlRules, scxml, sc, 2);
287 scxmlRules.add(XP_TR_TAR_ST, new SetNextRule("setTarget"));
288
289 //// Parallels
290 addParallelRules(XP_ST_PAR, scxmlRules, scxml);
291
292 //// Ifs
293 addIfRules(XP_IF, scxmlRules);
294
295 return scxmlRules;
296
297 }
298
299 private static void addStateRules(String xp, ExtendedBaseRules scxmlRules,
300 SCXML scxml, PathResolver sc, int parent) {
301 scxmlRules.add(xp, new ObjectCreateRule(State.class));
302 addStatePropertiesRules(xp, scxmlRules, sc);
303 //scxmlRules.add(xp + XP_TAR, new SetPropertiesRule());
304 //scxmlRules.add(xp + XP_INI_TR_TAR, new SetPropertiesRule());
305 addInitialRule(xp + XP_INI, scxmlRules, sc, scxml);
306 addHistoryRules(xp + XP_HIST, scxmlRules, sc, scxml);
307 addParentRule(xp, scxmlRules, parent);
308 addTransitionRules(xp + XP_TR, scxmlRules, "addTransition");
309 addHandlerRules(xp, scxmlRules);
310 scxmlRules.add(xp, new UpdateModelRule(scxml));
311 }
312
313 private static void addParallelRules(String xp,
314 ExtendedBaseRules scxmlRules, SCXML scxml) {
315 addSimpleRulesTuple(xp, scxmlRules, Parallel.class, null, null,
316 "setParallel");
317 addHandlerRules(xp, scxmlRules);
318 addParentRule(xp, scxmlRules, 1);
319 scxmlRules.add(xp, new UpdateModelRule(scxml));
320 }
321
322 private static void addStatePropertiesRules(String xp,
323 ExtendedBaseRules scxmlRules, PathResolver sc) {
324 scxmlRules.add(xp, new SetPropertiesRule(
325 new String[] { "id", "final" },
326 new String[] { "id", "isFinal" }));
327 scxmlRules.add(xp, new DigestSrcAttributeRule(sc));
328 }
329
330 private static void addInitialRule(String xp,
331 ExtendedBaseRules scxmlRules, PathResolver sc, SCXML scxml) {
332 scxmlRules.add(xp, new ObjectCreateRule(Initial.class));
333 addPseudoStatePropertiesRules(xp, scxmlRules, sc);
334 scxmlRules.add(xp, new UpdateModelRule(scxml));
335 addTransitionRules(xp + XP_TR, scxmlRules, "setTransition");
336 scxmlRules.add(xp, new SetNextRule("setInitial"));
337 }
338
339 private static void addHistoryRules(String xp,
340 ExtendedBaseRules scxmlRules, PathResolver sc, SCXML scxml) {
341 scxmlRules.add(xp, new ObjectCreateRule(History.class));
342 addPseudoStatePropertiesRules(xp, scxmlRules, sc);
343 scxmlRules.add(xp, new UpdateModelRule(scxml));
344 scxmlRules.add(xp, new SetPropertiesRule(
345 new String[] { "type" }, new String[] { "type" }));
346 addTransitionRules(xp + XP_TR, scxmlRules, "setTransition");
347 scxmlRules.add(xp, new SetNextRule("addHistory"));
348 }
349
350 private static void addPseudoStatePropertiesRules(String xp,
351 ExtendedBaseRules scxmlRules, PathResolver sc) {
352 scxmlRules.add(xp, new SetPropertiesRule(
353 new String[] { "id" }, new String[] { "id" }));
354 scxmlRules.add(xp, new DigestSrcAttributeRule(sc));
355 addParentRule(xp, scxmlRules, 1);
356 }
357
358 private static void addParentRule(String xp, ExtendedBaseRules scxmlRules,
359 final int parent) {
360 if (parent < 1) {
361 return;
362 }
363 scxmlRules.add(xp, new Rule() {
364 // A generic version of setTopRule
365 public void body(String namespace, String name, String text)
366 throws Exception {
367 TransitionTarget t = (TransitionTarget) getDigester().peek();
368 TransitionTarget p = (TransitionTarget) getDigester().peek(
369 parent);
370 // CHANGE - Moved parent property to TransitionTarget
371 t.setParent(p);
372 }
373 });
374 }
375
376 private static void addTransitionRules(String xp,
377 ExtendedBaseRules scxmlRules, String setNextMethod) {
378 scxmlRules.add(xp, new ObjectCreateRule(Transition.class));
379 scxmlRules.add(xp, new SetPropertiesRule());
380 scxmlRules.add(xp + XP_TAR, new SetPropertiesRule());
381 addActionRules(xp, scxmlRules);
382 scxmlRules.add(xp + XP_EXT, new Rule() {
383 public void end(String namespace, String name) {
384 Transition t = (Transition) getDigester().peek(1);
385 State exitState = new State();
386 exitState.setIsFinal(true);
387 t.setTarget(exitState);
388 }
389 });
390 scxmlRules.add(xp, new SetNextRule(setNextMethod));
391 }
392
393 private static void addHandlerRules(String xp, ExtendedBaseRules scxmlRules) {
394 scxmlRules.add(xp + XP_ONEN, new ObjectCreateRule(OnEntry.class));
395 addActionRules(xp + XP_ONEN, scxmlRules);
396 scxmlRules.add(xp + XP_ONEN, new SetNextRule("setOnEntry"));
397 scxmlRules.add(xp + XP_ONEX, new ObjectCreateRule(OnExit.class));
398 addActionRules(xp + XP_ONEX, scxmlRules);
399 scxmlRules.add(xp + XP_ONEX, new SetNextRule("setOnExit"));
400 }
401
402 private static void addActionRules(String xp, ExtendedBaseRules scxmlRules) {
403 addActionRulesTuple(xp + XP_ASN, scxmlRules, Assign.class);
404 addActionRulesTuple(xp + XP_VAR, scxmlRules, Var.class);
405 addActionRulesTuple(xp + XP_LOG, scxmlRules, Log.class);
406 addActionRulesTuple(xp + XP_SND, scxmlRules, Send.class);
407 addActionRulesTuple(xp + XP_CAN, scxmlRules, Cancel.class);
408 addActionRulesTuple(xp + XP_EXT, scxmlRules, Exit.class);
409 }
410
411 private static void addIfRules(String xp, ExtendedBaseRules scxmlRules) {
412 addActionRulesTuple(xp, scxmlRules, If.class);
413 addActionRules(xp, scxmlRules);
414 addActionRulesTuple(xp + XP_EIF, scxmlRules, ElseIf.class);
415 addActionRulesTuple(xp + XP_ELS, scxmlRules, Else.class);
416 }
417
418 private static void addActionRulesTuple(String xp,
419 ExtendedBaseRules scxmlRules, Class klass) {
420 addSimpleRulesTuple(xp, scxmlRules, klass, null, null, "addAction");
421 scxmlRules.add(xp, new SetExecutableParentRule());
422 }
423
424 private static void addSimpleRulesTuple(String xp,
425 ExtendedBaseRules scxmlRules, Class klass, String[] args,
426 String[] props, String addMethod) {
427 scxmlRules.add(xp, new ObjectCreateRule(klass));
428 if (args == null) {
429 scxmlRules.add(xp, new SetPropertiesRule());
430 } else {
431 scxmlRules.add(xp, new SetPropertiesRule(args, props));
432 }
433 scxmlRules.add(xp, new SetNextRule(addMethod));
434 }
435
436 /*
437 * Post-processing methods to make the SCXML object Executor-ready.
438 */
439 private static void updateSCXML(SCXML scxml, Context evalCtx,
440 Evaluator evalEngine) {
441 // Watch case, slightly unfortunate naming ;-)
442 String initialstate = scxml.getInitialstate();
443 //we have to use getTargets() here since the initialState can be
444 //an indirect descendant
445 //TODO: better type check, now ClassCastException happens for Parallel
446 State initialState = (State) scxml.getTargets().get(initialstate);
447 if (initialState == null) {
448 // Where do we, where do we go?
449 System.err.println("ERROR: SCXMLDigester - No SCXML child state "
450 + "with ID \"" + initialstate
451 + "\" found i.e. no initialstate" + " for SCXML ;-)");
452 }
453 scxml.setInitialState(initialState);
454 scxml.setRootContext(evalCtx);
455 Map targets = scxml.getTargets();
456 Map states = scxml.getStates();
457 Iterator i = states.keySet().iterator();
458 while (i.hasNext()) {
459 updateState((State) states.get(i.next()), targets, evalCtx,
460 evalEngine);
461 }
462 }
463
464 private static void updateState(State s, Map targets, Context evalCtx,
465 Evaluator evalEngine) {
466 //setup local variable context
467 Context localCtx = null;
468 if (s.getParent() == null) {
469 localCtx = evalEngine.newContext(evalCtx);
470 } else {
471 State parentState = null;
472 if (s.getParent() instanceof Parallel) {
473 parentState = (State) (s.getParent().getParent());
474 } else {
475 parentState = (State) (s.getParent());
476 }
477 localCtx = evalEngine.newContext(parentState.getContext());
478 }
479 s.setContext(localCtx);
480 //ensure both onEntry and onExit have parent
481 //TODO: add this rather as a Digester rule for OnEntry/OnExit
482 s.getOnEntry().setParent(s);
483 s.getOnExit().setParent(s);
484 //ENDTODO
485 //initialize next / inital
486 Initial ini = s.getInitial();
487 Map c = s.getChildren();
488 if (!c.isEmpty()) {
489 if (ini == null) {
490 System.err.println("WARNING: SCXMLDigester - Initial "
491 + "null for " + (SCXMLHelper.isStringEmpty(s.getId()) ?
492 "anonymous state" : s.getId()));
493 }
494 Transition initialTransition = ini.getTransition();
495 updateTransition(initialTransition, targets);
496 TransitionTarget initialState = initialTransition.getTarget();
497 // we have to allow for an indirect descendant initial (targets)
498 //check that initialState is a descendant of s
499 if (initialState == null || !SCXMLHelper.isDescendant(initialState, s)) {
500 System.err.println("WARNING: SCXMLDigester - Initial state "
501 + "null or not descendant for " + (SCXMLHelper.
502 isStringEmpty(s.getId()) ? "anonymous state" : s.getId()));
503 }
504 }
505 List histories = s.getHistory();
506 Iterator histIter = histories.iterator();
507 while (histIter.hasNext()) {
508 History h = (History) histIter.next();
509 Transition historyTransition = h.getTransition();
510 updateTransition(historyTransition, targets);
511 State historyState = (State)historyTransition.getTarget();
512 if (historyState == null) {
513 System.err.println("WARNING: SCXMLDigester - History state "
514 + "null " + (SCXMLHelper.isStringEmpty(s.getId()) ?
515 "anonymous state" : s.getId()));
516 }
517 if (!h.isDeep()) {
518 if (!c.containsValue(historyState)) {
519 System.err.println("WARNING: SCXMLDigester - History state "
520 + "for shallow history is not child for " + (SCXMLHelper.
521 isStringEmpty(s.getId()) ? "anonymous state" : s.getId()));
522 }
523 } else {
524 if (!SCXMLHelper.isDescendant(historyState, s)) {
525 System.err.println("WARNING: SCXMLDigester - History state "
526 + "for deep history is not descendant for " + (SCXMLHelper.
527 isStringEmpty(s.getId()) ? "anonymous state" : s.getId()));
528 }
529 }
530 }
531 Map t = s.getTransitions();
532 Iterator i = t.keySet().iterator();
533 while (i.hasNext()) {
534 Iterator j = ((List) t.get(i.next())).iterator();
535 while (j.hasNext()) {
536 Transition trn = (Transition) j.next();
537 // TODO: add this rather as a Digester rule for Transition
538 trn.setNotificationRegistry(s.getNotificationRegistry());
539 trn.setParent(s);
540 // ENDTODO
541 updateTransition(trn, targets);
542 }
543 }
544 Parallel p = s.getParallel();
545 if (p != null) {
546 updateParallel(p, targets, evalCtx, evalEngine);
547 } else {
548 Iterator j = c.keySet().iterator();
549 while (j.hasNext()) {
550 updateState((State) c.get(j.next()), targets, evalCtx,
551 evalEngine);
552 }
553 }
554 }
555
556 private static void updateParallel(Parallel p, Map targets,
557 Context evalCtx, Evaluator evalEngine) {
558 Iterator i = p.getStates().iterator();
559 while (i.hasNext()) {
560 updateState((State) i.next(), targets, evalCtx, evalEngine);
561 }
562 }
563
564 private static void updateTransition(Transition t, Map targets) {
565 String next = t.getNext();
566 TransitionTarget tt = t.getTarget();
567 if (tt == null) {
568 tt = (TransitionTarget) targets.get(next);
569 if (tt == null) {
570 // TODO: Move Digester warnings to errors
571 System.err.println("WARNING: SCXMLDigester - Transition "
572 + "target \"" + next + "\" not found");
573 }
574 t.setTarget(tt);
575 }
576 }
577
578 /*
579 * Private SCXML object serialization utility functions
580 */
581 private static void serializeState(StringBuffer b, State s, String indent) {
582 b.append(indent).append("<state");
583 serializeTransitionTargetAttributes(b, s);
584 boolean f = s.getIsFinal();
585 if (f) {
586 b.append(" final=\"true\"");
587 }
588 b.append(">\n");
589 Initial ini = s.getInitial();
590 if (ini != null) {
591 serializeInitial(b, ini, indent + INDENT);
592 }
593 List h = s.getHistory();
594 if (h != null) {
595 serializeHistory(b, h, indent + INDENT);
596 }
597 serializeOnEntry(b, (TransitionTarget) s, indent + INDENT);
598 Map t = s.getTransitions();
599 Iterator i = t.keySet().iterator();
600 while (i.hasNext()) {
601 List et = (List) t.get(i.next());
602 for (int len = 0; len < et.size(); len++) {
603 serializeTransition(b, (Transition) et.get(len), indent
604 + INDENT);
605 }
606 }
607 Parallel p = s.getParallel();
608 if (p != null) {
609 serializeParallel(b, p, indent + INDENT);
610 } else {
611 Map c = s.getChildren();
612 Iterator j = c.keySet().iterator();
613 while (j.hasNext()) {
614 State cs = (State) c.get(j.next());
615 serializeState(b, cs, indent + INDENT);
616 }
617 }
618 serializeOnExit(b, (TransitionTarget) s, indent + INDENT);
619 b.append(indent).append("</state>\n");
620 }
621
622 private static void serializeParallel(StringBuffer b, Parallel p,
623 String indent) {
624 b.append(indent).append("<parallel");
625 serializeTransitionTargetAttributes(b, p);
626 b.append(">\n");
627 serializeOnEntry(b, (TransitionTarget) p, indent + INDENT);
628 Set s = p.getStates();
629 Iterator i = s.iterator();
630 while (i.hasNext()) {
631 serializeState(b, (State) i.next(), indent + INDENT);
632 }
633 serializeOnExit(b, (TransitionTarget) p, indent + INDENT);
634 b.append(indent).append("</parallel>\n");
635 }
636
637 private static void serializeInitial(StringBuffer b, Initial i,
638 String indent) {
639 b.append(indent).append("<initial");
640 serializeTransitionTargetAttributes(b, i);
641 b.append(">\n");
642 serializeTransition(b, i.getTransition(), indent + INDENT);
643 b.append(indent).append("</initial>\n");
644 }
645
646 private static void serializeHistory(StringBuffer b, List l,
647 String indent) {
648 if (l.size() > 0) {
649 for (int i = 0; i < l.size(); i++) {
650 History h = (History) l.get(i);
651 b.append(indent).append("<history");
652 serializeTransitionTargetAttributes(b, h);
653 if(h.isDeep()) {
654 b.append(" type=\"deep\"");
655 } else {
656 b.append(" type=\"shallow\"");
657 }
658 b.append(">\n");
659 serializeTransition(b, h.getTransition(), indent + INDENT);
660 b.append(indent).append("</history>\n");
661 }
662 }
663 }
664
665 private static void serializeTransitionTargetAttributes(StringBuffer b,
666 TransitionTarget t) {
667 String id = t.getId();
668 if (id != null) {
669 b.append(" id=\"" + id + "\"");
670 }
671 TransitionTarget pt = t.getParent();
672 if (pt != null) {
673 String pid = pt.getId();
674 if (pid != null) {
675 b.append(" parentid=\"").append(pid).append("\"");
676 }
677 }
678 }
679
680 private static void serializeTransition(StringBuffer b, Transition t,
681 String indent) {
682 b.append(indent).append("<transition event=\"").append(t.getEvent())
683 .append("\" cond=\"").append(t.getCond()).append("\">\n");
684 boolean exit = serializeActions(b, t.getActions(), indent + INDENT);
685 if (!exit) {
686 serializeTarget(b, t, indent + INDENT);
687 }
688 b.append(indent).append("</transition>\n");
689 }
690
691 private static void serializeTarget(StringBuffer b, Transition t,
692 String indent) {
693 b.append(indent).append("<target");
694 String n = t.getNext();
695 if (n != null) {
696 b.append(" next=\"" + n + "\">\n");
697 } else {
698 b.append(">\n");
699 if (t.getTarget() != null) {
700 // The inline transition target can only be a state
701 serializeState(b, (State) t.getTarget(), indent + INDENT);
702 }
703 }
704 b.append(indent).append("</target>\n");
705 }
706
707 private static void serializeOnEntry(StringBuffer b, TransitionTarget t,
708 String indent) {
709 OnEntry e = t.getOnEntry();
710 if (e != null && e.getActions().size() > 0) {
711 b.append(indent).append("<onentry>\n");
712 serializeActions(b, e.getActions(), indent + INDENT);
713 b.append(indent).append("</onentry>\n");
714 }
715 }
716
717 private static void serializeOnExit(StringBuffer b, TransitionTarget t,
718 String indent) {
719 OnExit x = t.getOnExit();
720 if (x != null && x.getActions().size() > 0) {
721 b.append(indent).append("<onexit>\n");
722 serializeActions(b, x.getActions(), indent + INDENT);
723 b.append(indent).append("</onexit>\n");
724 }
725 }
726
727 private static boolean serializeActions(StringBuffer b, List l,
728 String indent) {
729 if (l == null) {
730 return false;
731 }
732 boolean exit = false;
733 Iterator i = l.iterator();
734 while (i.hasNext()) {
735 Action a = (Action) i.next();
736 // TODO - Serialize action attrs, bodies; Priority: Very low ;-)
737 if (a instanceof Var) {
738 Var v = (Var) a;
739 b.append(indent).append("<var name=\"").append(v.getName())
740 .append("\" expr=\"").append(v.getExpr()).append(
741 "\"/>\n");
742 } else if (a instanceof Assign) {
743 Assign asn = (Assign) a;
744 b.append(indent).append("<assign name=\"")
745 .append(asn.getName()).append("\" expr=\"").append(
746 asn.getExpr()).append("\"/>\n");
747 } else if (a instanceof Send) {
748 Send s = (Send) a;
749 b.append(indent).append("<send/>\n");
750 } else if (a instanceof Cancel) {
751 Cancel c = (Cancel) a;
752 b.append(indent).append("<cancel/>\n");
753 } else if (a instanceof Log) {
754 Log lg = (Log) a;
755 b.append(indent).append("<log expr=\"").append(lg.getExpr())
756 .append("\"/>\n");
757 } else if (a instanceof Exit) {
758 Exit e = (Exit) a;
759 b.append(indent).append("<exit");
760 String expr = e.getExpr();
761 String nl = e.getNamelist();
762 if (expr != null) {
763 b.append(" expr=\"" + expr + "\"");
764 }
765 if (nl != null) {
766 b.append(" namelist=\"" + nl + "\"");
767 }
768 b.append("/>\n");
769 exit = true;
770 } else if (a instanceof If) {
771 If IF = (If) a;
772 serializeIf(b, IF, indent);
773 } else if (a instanceof Else) {
774 Else el = (Else) a;
775 b.append(indent).append("<else/>\n");
776 } else if (a instanceof ElseIf) {
777 ElseIf eif = (ElseIf) a;
778 b.append(indent).append("<elseif cond=\"")
779 .append(eif.getCond()).append("\" />\n");
780 }
781 }
782 return exit;
783 }
784
785 private static void serializeIf(StringBuffer b, If IF, String indent) {
786 b.append(indent).append("<if cond=\"").append(IF.getCond()).append(
787 "\">\n");
788 serializeActions(b, IF.getActions(), indent + INDENT);
789 b.append(indent).append("</if>\n");
790 }
791
792 /**
793 * Custom digestion rule for establishing necessary associations within the
794 * SCXML object, which include: <br>
795 * 1) Updation of the SCXML object's global targets Map <br>
796 * 2) Obtaining a handle to the SCXML object's NotificationRegistry <br>
797 *
798 * @author Rahul Akolkar
799 */
800 public static class UpdateModelRule extends Rule {
801
802 private SCXML scxml;
803
804 public UpdateModelRule(SCXML scxml) {
805 super();
806 this.scxml = scxml;
807 }
808
809 public void end(String namespace, String name) {
810 if (scxml == null) {
811 scxml = (SCXML) getDigester()
812 .peek(getDigester().getCount() - 1);
813 }
814 NotificationRegistry notifReg = scxml.getNotificationRegistry();
815 TransitionTarget tt = (TransitionTarget) getDigester().peek();
816 scxml.addTarget(tt);
817 tt.setNotificationRegistry(notifReg);
818 }
819 }
820
821 /**
822 * Custom digestion rule for setting Executable parent of Action elements
823 *
824 * @author Rahul Akolkar
825 */
826 public static class SetExecutableParentRule extends Rule {
827
828 public SetExecutableParentRule() {
829 super();
830 }
831
832 public void end(String namespace, String name) {
833 Action child = (Action) getDigester().peek();
834 for (int i = 1; i < getDigester().getCount() - 1; i++) {
835 Object ancestor = (Object) getDigester().peek(i);
836 if (ancestor instanceof Executable) {
837 child.setParent((Executable) ancestor);
838 return;
839 }
840 }
841 }
842 }
843
844 /**
845 * Custom digestion rule for external sources, that is, the src attribute of
846 * the <state> element
847 *
848 * @author Rahul Akolkar
849 */
850 public static class DigestSrcAttributeRule extends Rule {
851
852 private PathResolver ctx;
853
854 public DigestSrcAttributeRule(PathResolver sc) {
855 super();
856 this.ctx = sc;
857 }
858
859 public void begin(String namespace, String name, Attributes attributes) {
860 String src = attributes.getValue("src");
861 if (SCXMLHelper.isStringEmpty(src)) {
862 return;
863 }
864 Digester digester = getDigester();
865 SCXML scxml = (SCXML) digester.peek(digester.getCount() - 1);
866 // 1) Digest the external SCXML file
867 Digester externalSrcDigester = newInstance(scxml, ctx.getResolver(src));
868 SCXML externalSCXML = null;
869 String path = ctx == null ? src : ctx.resolvePath(src);
870
871 try {
872 externalSCXML = (SCXML) externalSrcDigester.parse(path);
873 } catch (Exception e) {
874 log.error(null, e);
875 }
876 // 2) Adopt the children
877 // TODO - Clarify spec; Priority: High
878 if (externalSCXML == null) {
879 return;
880 }
881 State s = (State) digester.peek();
882 Transition t = new Transition();
883 t.setNext(externalSCXML.getInitialstate());
884 Initial ini = new Initial();
885 ini.setTransition(t);
886 s.setInitial(ini);
887 Map children = externalSCXML.getStates();
888 Object[] ids = children.keySet().toArray();
889 for (int i = 0; i < ids.length; i++) {
890 s.addChild((State) children.get(ids[i]));
891 }
892 }
893 }
894 }