1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.scxml.io;
18
19 import java.io.StringWriter;
20 import java.util.Iterator;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Properties;
24 import java.util.Set;
25
26 import javax.xml.transform.OutputKeys;
27 import javax.xml.transform.Result;
28 import javax.xml.transform.Source;
29 import javax.xml.transform.Transformer;
30 import javax.xml.transform.TransformerException;
31 import javax.xml.transform.TransformerFactory;
32 import javax.xml.transform.dom.DOMSource;
33 import javax.xml.transform.stream.StreamResult;
34
35 import org.apache.commons.logging.LogFactory;
36 import org.apache.commons.scxml.SCXMLHelper;
37 import org.apache.commons.scxml.model.Action;
38 import org.apache.commons.scxml.model.Assign;
39 import org.apache.commons.scxml.model.Cancel;
40 import org.apache.commons.scxml.model.Data;
41 import org.apache.commons.scxml.model.Datamodel;
42 import org.apache.commons.scxml.model.Else;
43 import org.apache.commons.scxml.model.ElseIf;
44 import org.apache.commons.scxml.model.Exit;
45 import org.apache.commons.scxml.model.ExternalContent;
46 import org.apache.commons.scxml.model.Finalize;
47 import org.apache.commons.scxml.model.History;
48 import org.apache.commons.scxml.model.If;
49 import org.apache.commons.scxml.model.Initial;
50 import org.apache.commons.scxml.model.Invoke;
51 import org.apache.commons.scxml.model.Log;
52 import org.apache.commons.scxml.model.OnEntry;
53 import org.apache.commons.scxml.model.OnExit;
54 import org.apache.commons.scxml.model.Parallel;
55 import org.apache.commons.scxml.model.Param;
56 import org.apache.commons.scxml.model.SCXML;
57 import org.apache.commons.scxml.model.Send;
58 import org.apache.commons.scxml.model.State;
59 import org.apache.commons.scxml.model.Transition;
60 import org.apache.commons.scxml.model.TransitionTarget;
61 import org.apache.commons.scxml.model.Var;
62 import org.w3c.dom.Node;
63
64 /***
65 * Utility class for serializing the Commons SCXML Java object
66 * model. Class uses the visitor pattern to trace through the
67 * object heirarchy. Used primarily for testing, debugging and
68 * visual verification.
69 *
70 */
71 public class SCXMLSerializer {
72
73 /*** The indent to be used while serializing an SCXML object. */
74 private static final String INDENT = " ";
75 /*** The JAXP transformer. */
76 private static final Transformer XFORMER = getTransformer();
77
78 /***
79 * Serialize this SCXML object (primarily for debugging).
80 *
81 * @param scxml
82 * The SCXML to be serialized
83 * @return String The serialized SCXML
84 */
85 public static String serialize(final SCXML scxml) {
86 StringBuffer b =
87 new StringBuffer("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n").
88 append("<scxml xmlns=\"").append(scxml.getXmlns()).
89 append("\" version=\"").append(scxml.getVersion()).
90 append("\" initialstate=\"").append(scxml.getInitialstate()).
91 append("\">\n");
92 if (XFORMER == null) {
93 org.apache.commons.logging.Log log = LogFactory.
94 getLog(SCXMLSerializer.class);
95 log.warn("SCXMLSerializer: DOM serialization pertinent to"
96 + " the document will be skipped since a suitable"
97 + " JAXP Transformer could not be instantiated.");
98 }
99 Datamodel dm = scxml.getDatamodel();
100 if (dm != null) {
101 serializeDatamodel(b, dm, INDENT);
102 }
103 Map s = scxml.getStates();
104 Iterator i = s.keySet().iterator();
105 while (i.hasNext()) {
106 serializeState(b, (State) s.get(i.next()), INDENT);
107 }
108 b.append("</scxml>\n");
109 return b.toString();
110 }
111
112 /***
113 * Serialize this State object.
114 *
115 * @param b The buffer to append the serialization to
116 * @param s The State to serialize
117 * @param indent The indent for this XML element
118 */
119 public static void serializeState(final StringBuffer b,
120 final State s, final String indent) {
121 b.append(indent).append("<state");
122 serializeTransitionTargetAttributes(b, s);
123 boolean f = s.getIsFinal();
124 if (f) {
125 b.append(" final=\"true\"");
126 }
127 b.append(">\n");
128 Initial ini = s.getInitial();
129 if (ini != null) {
130 serializeInitial(b, ini, indent + INDENT);
131 }
132 List h = s.getHistory();
133 if (h != null) {
134 serializeHistory(b, h, indent + INDENT);
135 }
136 Datamodel dm = s.getDatamodel();
137 if (dm != null) {
138 serializeDatamodel(b, dm, indent + INDENT);
139 }
140 serializeOnEntry(b, s, indent + INDENT);
141 Map t = s.getTransitions();
142 Iterator i = t.keySet().iterator();
143 while (i.hasNext()) {
144 List et = (List) t.get(i.next());
145 for (int len = 0; len < et.size(); len++) {
146 serializeTransition(b, (Transition) et.get(len), indent
147 + INDENT);
148 }
149 }
150 Parallel p = s.getParallel();
151 Invoke inv = s.getInvoke();
152 if (p != null) {
153 serializeParallel(b, p, indent + INDENT);
154 } else if (inv != null) {
155 serializeInvoke(b , inv, indent + INDENT);
156 } else {
157 Map c = s.getChildren();
158 Iterator j = c.keySet().iterator();
159 while (j.hasNext()) {
160 State cs = (State) c.get(j.next());
161 serializeState(b, cs, indent + INDENT);
162 }
163 }
164 serializeOnExit(b, s, indent + INDENT);
165 b.append(indent).append("</state>\n");
166 }
167
168 /***
169 * Serialize this Parallel object.
170 *
171 * @param b The buffer to append the serialization to
172 * @param p The Parallel to serialize
173 * @param indent The indent for this XML element
174 */
175 public static void serializeParallel(final StringBuffer b,
176 final Parallel p, final String indent) {
177 b.append(indent).append("<parallel");
178 serializeTransitionTargetAttributes(b, p);
179 b.append(">\n");
180 serializeOnEntry(b, p, indent + INDENT);
181 Set s = p.getStates();
182 Iterator i = s.iterator();
183 while (i.hasNext()) {
184 serializeState(b, (State) i.next(), indent + INDENT);
185 }
186 serializeOnExit(b, p, indent + INDENT);
187 b.append(indent).append("</parallel>\n");
188 }
189
190 /***
191 * Serialize this Invoke object.
192 *
193 * @param b The buffer to append the serialization to
194 * @param i The Invoke to serialize
195 * @param indent The indent for this XML element
196 */
197 public static void serializeInvoke(final StringBuffer b,
198 final Invoke i, final String indent) {
199 b.append(indent).append("<invoke");
200 String ttype = i.getTargettype();
201 String src = i.getSrc();
202 String srcexpr = i.getSrcexpr();
203 if (ttype != null) {
204 b.append(" targettype=\"").append(ttype).append("\"");
205 }
206
207 if (src != null) {
208 b.append(" src=\"").append(src).append("\"");
209 } else if (srcexpr != null) {
210 b.append(" srcexpr=\"").append(srcexpr).append("\"");
211 }
212 b.append(">\n");
213 List params = i.params();
214 for (Iterator iter = params.iterator(); iter.hasNext();) {
215 Param p = (Param) iter.next();
216 b.append(indent).append(INDENT).append("<param name=\"").
217 append(p.getName()).append("\" expr=\"").
218 append(p.getExpr()).append("\"/>\n");
219 }
220 Finalize f = i.getFinalize();
221 if (f != null) {
222 b.append(indent).append(INDENT).append("<finalize>\n");
223 serializeActions(b, f.getActions(), indent + INDENT + INDENT);
224 b.append(indent).append(INDENT).append("</finalize>\n");
225 }
226 b.append(indent).append("</invoke>\n");
227 }
228
229 /***
230 * Serialize this Initial object.
231 *
232 * @param b The buffer to append the serialization to
233 * @param i The Initial to serialize
234 * @param indent The indent for this XML element
235 */
236 public static void serializeInitial(final StringBuffer b, final Initial i,
237 final String indent) {
238 b.append(indent).append("<initial");
239 serializeTransitionTargetAttributes(b, i);
240 b.append(">\n");
241 serializeTransition(b, i.getTransition(), indent + INDENT);
242 b.append(indent).append("</initial>\n");
243 }
244
245 /***
246 * Serialize the History.
247 *
248 * @param b The buffer to append the serialization to
249 * @param l The List of History objects to serialize
250 * @param indent The indent for this XML element
251 */
252 public static void serializeHistory(final StringBuffer b, final List l,
253 final String indent) {
254 if (l.size() > 0) {
255 for (int i = 0; i < l.size(); i++) {
256 History h = (History) l.get(i);
257 b.append(indent).append("<history");
258 serializeTransitionTargetAttributes(b, h);
259 if (h.isDeep()) {
260 b.append(" type=\"deep\"");
261 } else {
262 b.append(" type=\"shallow\"");
263 }
264 b.append(">\n");
265 serializeTransition(b, h.getTransition(), indent + INDENT);
266 b.append(indent).append("</history>\n");
267 }
268 }
269 }
270
271 /***
272 * Serialize this Transition object.
273 *
274 * @param b The buffer to append the serialization to
275 * @param t The Transition to serialize
276 * @param indent The indent for this XML element
277 */
278 public static void serializeTransition(final StringBuffer b,
279 final Transition t, final String indent) {
280 b.append(indent).append("<transition event=\"").append(t.getEvent())
281 .append("\" cond=\"").append(t.getCond()).append("\">\n");
282 boolean exit = serializeActions(b, t.getActions(), indent + INDENT);
283 if (!exit) {
284 serializeTarget(b, t, indent + INDENT);
285 }
286 b.append(indent).append("</transition>\n");
287 }
288
289 /***
290 * Serialize this Transition's Target.
291 *
292 *
293 * @param b The buffer to append the serialization to
294 * @param t The Transition whose Target needs to be serialized
295 * @param indent The indent for this XML element
296 */
297 public static void serializeTarget(final StringBuffer b,
298 final Transition t, final String indent) {
299 b.append(indent).append("<target");
300 String n = t.getNext();
301 if (n != null) {
302 b.append(" next=\"" + n + "\">\n");
303 } else {
304 b.append(">\n");
305 if (t.getTarget() != null) {
306
307 serializeState(b, (State) t.getTarget(), indent + INDENT);
308 }
309 }
310 b.append(indent).append("</target>\n");
311 }
312
313 /***
314 * Serialize this Datamodel object.
315 *
316 * @param b The buffer to append the serialization to
317 * @param dm The Datamodel to be serialized
318 * @param indent The indent for this XML element
319 */
320 public static void serializeDatamodel(final StringBuffer b,
321 final Datamodel dm, final String indent) {
322 List data = dm.getData();
323 if (data != null && data.size() > 0) {
324 b.append(indent).append("<datamodel>\n");
325 if (XFORMER == null) {
326 b.append(indent).append(INDENT).
327 append("<!-- Body content was not serialized -->\n");
328 b.append(indent).append("</datamodel>\n");
329 return;
330 }
331 for (Iterator iter = data.iterator(); iter.hasNext();) {
332 Data datum = (Data) iter.next();
333 Node dataNode = datum.getNode();
334 if (dataNode != null) {
335 StringWriter out = new StringWriter();
336 try {
337 Source input = new DOMSource(dataNode);
338 Result output = new StreamResult(out);
339 XFORMER.transform(input, output);
340 } catch (TransformerException te) {
341 org.apache.commons.logging.Log log = LogFactory.
342 getLog(SCXMLSerializer.class);
343 log.error(te.getMessage(), te);
344 b.append(indent).append(INDENT).
345 append("<!-- Data content not serialized -->\n");
346 }
347 b.append(indent).append(INDENT).append(out.toString());
348 } else {
349 b.append(indent).append(INDENT).append("<data name=\"").
350 append(datum.getName()).append("\" expr=\"").
351 append(datum.getExpr()).append("\" />\n");
352 }
353 }
354 b.append(indent).append("</datamodel>\n");
355 }
356 }
357
358 /***
359 * Serialize this OnEntry object.
360 *
361 * @param b The buffer to append the serialization to
362 * @param t The TransitionTarget whose OnEntry is to be serialized
363 * @param indent The indent for this XML element
364 */
365 public static void serializeOnEntry(final StringBuffer b,
366 final TransitionTarget t, final String indent) {
367 OnEntry e = t.getOnEntry();
368 if (e != null && e.getActions().size() > 0) {
369 b.append(indent).append("<onentry>\n");
370 serializeActions(b, e.getActions(), indent + INDENT);
371 b.append(indent).append("</onentry>\n");
372 }
373 }
374
375 /***
376 * Serialize this OnExit object.
377 *
378 * @param b The buffer to append the serialization to
379 * @param t The TransitionTarget whose OnExit is to be serialized
380 * @param indent The indent for this XML element
381 */
382 public static void serializeOnExit(final StringBuffer b,
383 final TransitionTarget t, final String indent) {
384 OnExit x = t.getOnExit();
385 if (x != null && x.getActions().size() > 0) {
386 b.append(indent).append("<onexit>\n");
387 serializeActions(b, x.getActions(), indent + INDENT);
388 b.append(indent).append("</onexit>\n");
389 }
390 }
391
392 /***
393 * Serialize this List of actions.
394 *
395 * @param b The buffer to append the serialization to
396 * @param l The List of actions to serialize
397 * @param indent The indent for this XML element
398 * @return boolean true if the list of actions contains an <exit/>
399 */
400 public static boolean serializeActions(final StringBuffer b, final List l,
401 final String indent) {
402 if (l == null) {
403 return false;
404 }
405 boolean exit = false;
406 Iterator i = l.iterator();
407 while (i.hasNext()) {
408 Action a = (Action) i.next();
409 if (a instanceof Var) {
410 Var v = (Var) a;
411 b.append(indent).append("<var name=\"").append(v.getName())
412 .append("\" expr=\"").append(v.getExpr()).append(
413 "\"/>\n");
414 } else if (a instanceof Assign) {
415 Assign asn = (Assign) a;
416 b.append(indent).append("<assign");
417 if (!SCXMLHelper.isStringEmpty(asn.getLocation())) {
418 b.append(" location=\"").append(asn.getLocation());
419 if (!SCXMLHelper.isStringEmpty(asn.getSrc())) {
420 b.append("\" src=\"").append(asn.getSrc());
421 } else {
422 b.append("\" expr=\"").append(asn.getExpr());
423 }
424 } else {
425 b.append(" name=\"").append(asn.getName()).
426 append("\" expr=\"").append(asn.getExpr());
427 }
428 b.append("\"/>\n");
429 } else if (a instanceof Send) {
430 serializeSend(b, (Send) a, indent);
431 } else if (a instanceof Cancel) {
432 Cancel c = (Cancel) a;
433 b.append(indent).append("<cancel sendid=\"")
434 .append(c.getSendid()).append("\"/>\n");
435 } else if (a instanceof Log) {
436 Log lg = (Log) a;
437 b.append(indent).append("<log expr=\"").append(lg.getExpr())
438 .append("\"/>\n");
439 } else if (a instanceof Exit) {
440 Exit e = (Exit) a;
441 b.append(indent).append("<exit");
442 String expr = e.getExpr();
443 String nl = e.getNamelist();
444 if (expr != null) {
445 b.append(" expr=\"" + expr + "\"");
446 }
447 if (nl != null) {
448 b.append(" namelist=\"" + nl + "\"");
449 }
450 b.append("/>\n");
451 exit = true;
452 } else if (a instanceof If) {
453 If iff = (If) a;
454 serializeIf(b, iff, indent);
455 } else if (a instanceof Else) {
456 b.append(indent).append("<else/>\n");
457 } else if (a instanceof ElseIf) {
458 ElseIf eif = (ElseIf) a;
459 b.append(indent).append("<elseif cond=\"")
460 .append(eif.getCond()).append("\" />\n");
461 }
462 }
463 return exit;
464 }
465
466 /***
467 * Serialize this Send object.
468 *
469 * @param b The buffer to append the serialization to
470 * @param send The Send object to serialize
471 * @param indent The indent for this XML element
472 */
473 public static void serializeSend(final StringBuffer b,
474 final Send send, final String indent) {
475 b.append(indent).append("<send sendid=\"")
476 .append(send.getSendid()).append("\" target=\"")
477 .append(send.getTarget()).append("\" targetType=\"")
478 .append(send.getTargettype()).append("\" namelist=\"")
479 .append(send.getNamelist()).append("\" delay=\"")
480 .append(send.getDelay()).append("\" events=\"")
481 .append(send.getEvent()).append("\" hints=\"")
482 .append(send.getHints()).append("\">\n")
483 .append(getBodyContent(send))
484 .append(indent).append("</send>\n");
485 }
486
487 /***
488 * Return serialized body of <code>ExternalContent</code>.
489 *
490 * @param externalContent The model element containing the body content
491 * @return String The serialized body content
492 */
493 public static final String getBodyContent(
494 final ExternalContent externalContent) {
495 StringBuffer buf = new StringBuffer();
496 List externalNodes = externalContent.getExternalNodes();
497 if (externalNodes.size() > 0 && XFORMER == null) {
498 buf.append("<!-- Body content was not serialized -->\n");
499 return buf.toString();
500 }
501 for (int i = 0; i < externalNodes.size(); i++) {
502 Source input = new DOMSource((Node) externalNodes.get(i));
503 StringWriter out = new StringWriter();
504 Result output = new StreamResult(out);
505 try {
506 XFORMER.transform(input, output);
507 } catch (TransformerException te) {
508 org.apache.commons.logging.Log log = LogFactory.
509 getLog(SCXMLSerializer.class);
510 log.error(te.getMessage(), te);
511 buf.append("<!-- Not all body content was serialized -->");
512 }
513 buf.append(out.toString()).append("\n");
514 }
515 return buf.toString();
516 }
517
518 /***
519 * Serialize this If object.
520 *
521 * @param b The buffer to append the serialization to
522 * @param iff The If object to serialize
523 * @param indent The indent for this XML element
524 */
525 public static void serializeIf(final StringBuffer b,
526 final If iff, final String indent) {
527 b.append(indent).append("<if cond=\"").append(iff.getCond()).append(
528 "\">\n");
529 serializeActions(b, iff.getActions(), indent + INDENT);
530 b.append(indent).append("</if>\n");
531 }
532
533 /***
534 * Serialize properties of TransitionTarget which are element attributes.
535 *
536 * @param b The buffer to append the serialization to
537 * @param t The TransitionTarget
538 */
539 private static void serializeTransitionTargetAttributes(
540 final StringBuffer b, final TransitionTarget t) {
541 String id = t.getId();
542 if (id != null) {
543 b.append(" id=\"").append(id).append("\"");
544 }
545 TransitionTarget pt = t.getParent();
546 if (pt != null) {
547 String pid = pt.getId();
548 if (pid != null) {
549 b.append(" parentid=\"").append(pid).append("\"");
550 }
551 }
552 }
553
554 /***
555 * Get a <code>Transformer</code> instance.
556 *
557 * @return Transformer The <code>Transformer</code> instance.
558 */
559 private static Transformer getTransformer() {
560 Transformer transformer = null;
561 Properties outputProps = new Properties();
562 outputProps.put(OutputKeys.OMIT_XML_DECLARATION, "yes");
563 outputProps.put(OutputKeys.STANDALONE, "no");
564 outputProps.put(OutputKeys.INDENT, "yes");
565 try {
566 TransformerFactory tfFactory = TransformerFactory.newInstance();
567 transformer = tfFactory.newTransformer();
568 transformer.setOutputProperties(outputProps);
569 } catch (Throwable t) {
570 return null;
571 }
572 return transformer;
573 }
574
575
576
577
578 /***
579 * Discourage instantiation since this is a utility class.
580 */
581 private SCXMLSerializer() {
582 super();
583 }
584
585 }
586