Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

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 &lt;state&gt; 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 }