001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.commons.scxml.io;
018:
019: import java.io.StringWriter;
020: import java.util.Iterator;
021: import java.util.List;
022: import java.util.Map;
023: import java.util.Properties;
024: import java.util.Set;
025:
026: import javax.xml.transform.OutputKeys;
027: import javax.xml.transform.Result;
028: import javax.xml.transform.Source;
029: import javax.xml.transform.Transformer;
030: import javax.xml.transform.TransformerException;
031: import javax.xml.transform.TransformerFactory;
032: import javax.xml.transform.dom.DOMSource;
033: import javax.xml.transform.stream.StreamResult;
034:
035: import org.apache.commons.logging.LogFactory;
036: import org.apache.commons.scxml.SCXMLHelper;
037: import org.apache.commons.scxml.model.Action;
038: import org.apache.commons.scxml.model.Assign;
039: import org.apache.commons.scxml.model.Cancel;
040: import org.apache.commons.scxml.model.Data;
041: import org.apache.commons.scxml.model.Datamodel;
042: import org.apache.commons.scxml.model.Else;
043: import org.apache.commons.scxml.model.ElseIf;
044: import org.apache.commons.scxml.model.Exit;
045: import org.apache.commons.scxml.model.ExternalContent;
046: import org.apache.commons.scxml.model.Finalize;
047: import org.apache.commons.scxml.model.History;
048: import org.apache.commons.scxml.model.If;
049: import org.apache.commons.scxml.model.Initial;
050: import org.apache.commons.scxml.model.Invoke;
051: import org.apache.commons.scxml.model.Log;
052: import org.apache.commons.scxml.model.OnEntry;
053: import org.apache.commons.scxml.model.OnExit;
054: import org.apache.commons.scxml.model.Parallel;
055: import org.apache.commons.scxml.model.Param;
056: import org.apache.commons.scxml.model.SCXML;
057: import org.apache.commons.scxml.model.Send;
058: import org.apache.commons.scxml.model.State;
059: import org.apache.commons.scxml.model.Transition;
060: import org.apache.commons.scxml.model.TransitionTarget;
061: import org.apache.commons.scxml.model.Var;
062: import org.w3c.dom.Node;
063:
064: /**
065: * Utility class for serializing the Commons SCXML Java object
066: * model. Class uses the visitor pattern to trace through the
067: * object heirarchy. Used primarily for testing, debugging and
068: * visual verification.
069: *
070: */
071: public class SCXMLSerializer {
072:
073: /** The indent to be used while serializing an SCXML object. */
074: private static final String INDENT = " ";
075: /** The JAXP transformer. */
076: private static final Transformer XFORMER = getTransformer();
077:
078: /**
079: * Serialize this SCXML object (primarily for debugging).
080: *
081: * @param scxml
082: * The SCXML to be serialized
083: * @return String The serialized SCXML
084: */
085: public static String serialize(final SCXML scxml) {
086: StringBuffer b = new StringBuffer(
087: "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n").append(
088: "<scxml xmlns=\"").append(scxml.getXmlns()).append(
089: "\" version=\"").append(scxml.getVersion()).append(
090: "\" initialstate=\"").append(scxml.getInitialstate())
091: .append("\">\n");
092: if (XFORMER == null) {
093: org.apache.commons.logging.Log log = LogFactory
094: .getLog(SCXMLSerializer.class);
095: log.warn("SCXMLSerializer: DOM serialization pertinent to"
096: + " the document will be skipped since a suitable"
097: + " JAXP Transformer could not be instantiated.");
098: }
099: 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: // Prefer src
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=\"").append(
218: 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
224: + INDENT);
225: b.append(indent).append(INDENT).append("</finalize>\n");
226: }
227: b.append(indent).append("</invoke>\n");
228: }
229:
230: /**
231: * Serialize this Initial object.
232: *
233: * @param b The buffer to append the serialization to
234: * @param i The Initial to serialize
235: * @param indent The indent for this XML element
236: */
237: public static void serializeInitial(final StringBuffer b,
238: final Initial i, final String indent) {
239: b.append(indent).append("<initial");
240: serializeTransitionTargetAttributes(b, i);
241: b.append(">\n");
242: serializeTransition(b, i.getTransition(), indent + INDENT);
243: b.append(indent).append("</initial>\n");
244: }
245:
246: /**
247: * Serialize the History.
248: *
249: * @param b The buffer to append the serialization to
250: * @param l The List of History objects to serialize
251: * @param indent The indent for this XML element
252: */
253: public static void serializeHistory(final StringBuffer b,
254: final List l, final String indent) {
255: if (l.size() > 0) {
256: for (int i = 0; i < l.size(); i++) {
257: History h = (History) l.get(i);
258: b.append(indent).append("<history");
259: serializeTransitionTargetAttributes(b, h);
260: if (h.isDeep()) {
261: b.append(" type=\"deep\"");
262: } else {
263: b.append(" type=\"shallow\"");
264: }
265: b.append(">\n");
266: serializeTransition(b, h.getTransition(), indent
267: + INDENT);
268: b.append(indent).append("</history>\n");
269: }
270: }
271: }
272:
273: /**
274: * Serialize this Transition object.
275: *
276: * @param b The buffer to append the serialization to
277: * @param t The Transition to serialize
278: * @param indent The indent for this XML element
279: */
280: public static void serializeTransition(final StringBuffer b,
281: final Transition t, final String indent) {
282: b.append(indent).append("<transition event=\"").append(
283: t.getEvent()).append("\" cond=\"").append(t.getCond())
284: .append("\">\n");
285: boolean exit = serializeActions(b, t.getActions(), indent
286: + INDENT);
287: if (!exit) {
288: serializeTarget(b, t, indent + INDENT);
289: }
290: b.append(indent).append("</transition>\n");
291: }
292:
293: /**
294: * Serialize this Transition's Target.
295: *
296: *
297: * @param b The buffer to append the serialization to
298: * @param t The Transition whose Target needs to be serialized
299: * @param indent The indent for this XML element
300: */
301: public static void serializeTarget(final StringBuffer b,
302: final Transition t, final String indent) {
303: b.append(indent).append("<target");
304: String n = t.getNext();
305: if (n != null) {
306: b.append(" next=\"" + n + "\">\n");
307: } else {
308: b.append(">\n");
309: if (t.getTarget() != null) {
310: // The inline transition target can only be a state
311: serializeState(b, (State) t.getTarget(), indent
312: + INDENT);
313: }
314: }
315: b.append(indent).append("</target>\n");
316: }
317:
318: /**
319: * Serialize this Datamodel object.
320: *
321: * @param b The buffer to append the serialization to
322: * @param dm The Datamodel to be serialized
323: * @param indent The indent for this XML element
324: */
325: public static void serializeDatamodel(final StringBuffer b,
326: final Datamodel dm, final String indent) {
327: List data = dm.getData();
328: if (data != null && data.size() > 0) {
329: b.append(indent).append("<datamodel>\n");
330: if (XFORMER == null) {
331: b.append(indent).append(INDENT).append(
332: "<!-- Body content was not serialized -->\n");
333: b.append(indent).append("</datamodel>\n");
334: return;
335: }
336: for (Iterator iter = data.iterator(); iter.hasNext();) {
337: Data datum = (Data) iter.next();
338: Node dataNode = datum.getNode();
339: if (dataNode != null) {
340: StringWriter out = new StringWriter();
341: try {
342: Source input = new DOMSource(dataNode);
343: Result output = new StreamResult(out);
344: XFORMER.transform(input, output);
345: } catch (TransformerException te) {
346: org.apache.commons.logging.Log log = LogFactory
347: .getLog(SCXMLSerializer.class);
348: log.error(te.getMessage(), te);
349: b
350: .append(indent)
351: .append(INDENT)
352: .append(
353: "<!-- Data content not serialized -->\n");
354: }
355: b.append(indent).append(INDENT).append(
356: out.toString());
357: } else {
358: b.append(indent).append(INDENT).append(
359: "<data name=\"").append(datum.getName())
360: .append("\" expr=\"").append(
361: datum.getExpr()).append("\" />\n");
362: }
363: }
364: b.append(indent).append("</datamodel>\n");
365: }
366: }
367:
368: /**
369: * Serialize this OnEntry object.
370: *
371: * @param b The buffer to append the serialization to
372: * @param t The TransitionTarget whose OnEntry is to be serialized
373: * @param indent The indent for this XML element
374: */
375: public static void serializeOnEntry(final StringBuffer b,
376: final TransitionTarget t, final String indent) {
377: OnEntry e = t.getOnEntry();
378: if (e != null && e.getActions().size() > 0) {
379: b.append(indent).append("<onentry>\n");
380: serializeActions(b, e.getActions(), indent + INDENT);
381: b.append(indent).append("</onentry>\n");
382: }
383: }
384:
385: /**
386: * Serialize this OnExit object.
387: *
388: * @param b The buffer to append the serialization to
389: * @param t The TransitionTarget whose OnExit is to be serialized
390: * @param indent The indent for this XML element
391: */
392: public static void serializeOnExit(final StringBuffer b,
393: final TransitionTarget t, final String indent) {
394: OnExit x = t.getOnExit();
395: if (x != null && x.getActions().size() > 0) {
396: b.append(indent).append("<onexit>\n");
397: serializeActions(b, x.getActions(), indent + INDENT);
398: b.append(indent).append("</onexit>\n");
399: }
400: }
401:
402: /**
403: * Serialize this List of actions.
404: *
405: * @param b The buffer to append the serialization to
406: * @param l The List of actions to serialize
407: * @param indent The indent for this XML element
408: * @return boolean true if the list of actions contains an <exit/>
409: */
410: public static boolean serializeActions(final StringBuffer b,
411: final List l, final String indent) {
412: if (l == null) {
413: return false;
414: }
415: boolean exit = false;
416: Iterator i = l.iterator();
417: while (i.hasNext()) {
418: Action a = (Action) i.next();
419: if (a instanceof Var) {
420: Var v = (Var) a;
421: b.append(indent).append("<var name=\"").append(
422: v.getName()).append("\" expr=\"").append(
423: v.getExpr()).append("\"/>\n");
424: } else if (a instanceof Assign) {
425: Assign asn = (Assign) a;
426: b.append(indent).append("<assign");
427: if (!SCXMLHelper.isStringEmpty(asn.getLocation())) {
428: b.append(" location=\"").append(asn.getLocation());
429: if (!SCXMLHelper.isStringEmpty(asn.getSrc())) {
430: b.append("\" src=\"").append(asn.getSrc());
431: } else {
432: b.append("\" expr=\"").append(asn.getExpr());
433: }
434: } else {
435: b.append(" name=\"").append(asn.getName()).append(
436: "\" expr=\"").append(asn.getExpr());
437: }
438: b.append("\"/>\n");
439: } else if (a instanceof Send) {
440: serializeSend(b, (Send) a, indent);
441: } else if (a instanceof Cancel) {
442: Cancel c = (Cancel) a;
443: b.append(indent).append("<cancel sendid=\"").append(
444: c.getSendid()).append("\"/>\n");
445: } else if (a instanceof Log) {
446: Log lg = (Log) a;
447: b.append(indent).append("<log expr=\"").append(
448: lg.getExpr()).append("\"/>\n");
449: } else if (a instanceof Exit) {
450: Exit e = (Exit) a;
451: b.append(indent).append("<exit");
452: String expr = e.getExpr();
453: String nl = e.getNamelist();
454: if (expr != null) {
455: b.append(" expr=\"" + expr + "\"");
456: }
457: if (nl != null) {
458: b.append(" namelist=\"" + nl + "\"");
459: }
460: b.append("/>\n");
461: exit = true;
462: } else if (a instanceof If) {
463: If iff = (If) a;
464: serializeIf(b, iff, indent);
465: } else if (a instanceof Else) {
466: b.append(indent).append("<else/>\n");
467: } else if (a instanceof ElseIf) {
468: ElseIf eif = (ElseIf) a;
469: b.append(indent).append("<elseif cond=\"").append(
470: eif.getCond()).append("\" />\n");
471: }
472: }
473: return exit;
474: }
475:
476: /**
477: * Serialize this Send object.
478: *
479: * @param b The buffer to append the serialization to
480: * @param send The Send object to serialize
481: * @param indent The indent for this XML element
482: */
483: public static void serializeSend(final StringBuffer b,
484: final Send send, final String indent) {
485: b.append(indent).append("<send sendid=\"").append(
486: send.getSendid()).append("\" target=\"").append(
487: send.getTarget()).append("\" targetType=\"").append(
488: send.getTargettype()).append("\" namelist=\"").append(
489: send.getNamelist()).append("\" delay=\"").append(
490: send.getDelay()).append("\" events=\"").append(
491: send.getEvent()).append("\" hints=\"").append(
492: send.getHints()).append("\">\n").append(
493: getBodyContent(send)).append(indent)
494: .append("</send>\n");
495: }
496:
497: /**
498: * Return serialized body of <code>ExternalContent</code>.
499: *
500: * @param externalContent The model element containing the body content
501: * @return String The serialized body content
502: */
503: public static final String getBodyContent(
504: final ExternalContent externalContent) {
505: StringBuffer buf = new StringBuffer();
506: List externalNodes = externalContent.getExternalNodes();
507: if (externalNodes.size() > 0 && XFORMER == null) {
508: buf.append("<!-- Body content was not serialized -->\n");
509: return buf.toString();
510: }
511: for (int i = 0; i < externalNodes.size(); i++) {
512: Source input = new DOMSource((Node) externalNodes.get(i));
513: StringWriter out = new StringWriter();
514: Result output = new StreamResult(out);
515: try {
516: XFORMER.transform(input, output);
517: } catch (TransformerException te) {
518: org.apache.commons.logging.Log log = LogFactory
519: .getLog(SCXMLSerializer.class);
520: log.error(te.getMessage(), te);
521: buf
522: .append("<!-- Not all body content was serialized -->");
523: }
524: buf.append(out.toString()).append("\n");
525: }
526: return buf.toString();
527: }
528:
529: /**
530: * Serialize this If object.
531: *
532: * @param b The buffer to append the serialization to
533: * @param iff The If object to serialize
534: * @param indent The indent for this XML element
535: */
536: public static void serializeIf(final StringBuffer b, final If iff,
537: final String indent) {
538: b.append(indent).append("<if cond=\"").append(iff.getCond())
539: .append("\">\n");
540: serializeActions(b, iff.getActions(), indent + INDENT);
541: b.append(indent).append("</if>\n");
542: }
543:
544: /**
545: * Serialize properties of TransitionTarget which are element attributes.
546: *
547: * @param b The buffer to append the serialization to
548: * @param t The TransitionTarget
549: */
550: private static void serializeTransitionTargetAttributes(
551: final StringBuffer b, final TransitionTarget t) {
552: String id = t.getId();
553: if (id != null) {
554: b.append(" id=\"").append(id).append("\"");
555: }
556: TransitionTarget pt = t.getParent();
557: if (pt != null) {
558: String pid = pt.getId();
559: if (pid != null) {
560: b.append(" parentid=\"").append(pid).append("\"");
561: }
562: }
563: }
564:
565: /**
566: * Get a <code>Transformer</code> instance.
567: *
568: * @return Transformer The <code>Transformer</code> instance.
569: */
570: private static Transformer getTransformer() {
571: Transformer transformer = null;
572: Properties outputProps = new Properties();
573: outputProps.put(OutputKeys.OMIT_XML_DECLARATION, "yes");
574: outputProps.put(OutputKeys.STANDALONE, "no");
575: outputProps.put(OutputKeys.INDENT, "yes");
576: try {
577: TransformerFactory tfFactory = TransformerFactory
578: .newInstance();
579: transformer = tfFactory.newTransformer();
580: transformer.setOutputProperties(outputProps);
581: } catch (Throwable t) {
582: return null;
583: }
584: return transformer;
585: }
586:
587: /*
588: * Private methods.
589: */
590: /**
591: * Discourage instantiation since this is a utility class.
592: */
593: private SCXMLSerializer() {
594: super();
595: }
596:
597: }
|