/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.scxml.io;
import java.io.StringWriter;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.scxml.SCXMLHelper;
import org.apache.commons.scxml.model.Action;
import org.apache.commons.scxml.model.Assign;
import org.apache.commons.scxml.model.Cancel;
import org.apache.commons.scxml.model.Data;
import org.apache.commons.scxml.model.Datamodel;
import org.apache.commons.scxml.model.Else;
import org.apache.commons.scxml.model.ElseIf;
import org.apache.commons.scxml.model.Exit;
import org.apache.commons.scxml.model.ExternalContent;
import org.apache.commons.scxml.model.Finalize;
import org.apache.commons.scxml.model.History;
import org.apache.commons.scxml.model.If;
import org.apache.commons.scxml.model.Initial;
import org.apache.commons.scxml.model.Invoke;
import org.apache.commons.scxml.model.Log;
import org.apache.commons.scxml.model.OnEntry;
import org.apache.commons.scxml.model.OnExit;
import org.apache.commons.scxml.model.Parallel;
import org.apache.commons.scxml.model.Param;
import org.apache.commons.scxml.model.SCXML;
import org.apache.commons.scxml.model.Send;
import org.apache.commons.scxml.model.State;
import org.apache.commons.scxml.model.Transition;
import org.apache.commons.scxml.model.TransitionTarget;
import org.apache.commons.scxml.model.Var;
import org.w3c.dom.Node;
/**
* Utility class for serializing the Commons SCXML Java object
* model. Class uses the visitor pattern to trace through the
* object heirarchy. Used primarily for testing, debugging and
* visual verification.
*
*/
public class SCXMLSerializer {
/** The indent to be used while serializing an SCXML object. */
private static final String INDENT = " ";
/** The JAXP transformer. */
private static final Transformer XFORMER = getTransformer();
/**
* Serialize this SCXML object (primarily for debugging).
*
* @param scxml
* The SCXML to be serialized
* @return String The serialized SCXML
*/
public static String serialize(final SCXML scxml) {
StringBuffer b =
new StringBuffer("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n").
append("<scxml xmlns=\"").append(scxml.getXmlns()).
append("\" version=\"").append(scxml.getVersion()).
append("\" initialstate=\"").append(scxml.getInitialstate()).
append("\">\n");
if (XFORMER == null) {
org.apache.commons.logging.Log log = LogFactory.
getLog(SCXMLSerializer.class);
log.warn("SCXMLSerializer: DOM serialization pertinent to"
+ " the document will be skipped since a suitable"
+ " JAXP Transformer could not be instantiated.");
}
Datamodel dm = scxml.getDatamodel();
if (dm != null) {
serializeDatamodel(b, dm, INDENT);
}
Map c = scxml.getChildren();
Iterator i = c.keySet().iterator();
while (i.hasNext()) {
TransitionTarget tt = (TransitionTarget) c.get(i.next());
if (tt instanceof State) {
serializeState(b, (State) tt, INDENT);
} else {
serializeParallel(b, (Parallel) tt, INDENT);
}
}
b.append("</scxml>\n");
return b.toString();
}
/**
* Serialize this State object.
*
* @param b The buffer to append the serialization to
* @param s The State to serialize
* @param indent The indent for this XML element
*/
public static void serializeState(final StringBuffer b,
final State s, final String indent) {
b.append(indent).append("<state");
serializeTransitionTargetAttributes(b, s);
boolean f = s.isFinal();
if (f) {
b.append(" final=\"true\"");
}
b.append(">\n");
Initial ini = s.getInitial();
if (ini != null) {
serializeInitial(b, ini, indent + INDENT);
}
List h = s.getHistory();
if (h != null) {
serializeHistory(b, h, indent + INDENT);
}
Datamodel dm = s.getDatamodel();
if (dm != null) {
serializeDatamodel(b, dm, indent + INDENT);
}
serializeOnEntry(b, s, indent + INDENT);
List t = s.getTransitionsList();
for (int i = 0; i < t.size(); i++) {
serializeTransition(b, (Transition) t.get(i), indent + INDENT);
}
Parallel p = s.getParallel(); //TODO: Remove in v1.0
Invoke inv = s.getInvoke();
if (p != null) {
serializeParallel(b, p, indent + INDENT);
} else if (inv != null) {
serializeInvoke(b , inv, indent + INDENT);
} else {
Map c = s.getChildren();
Iterator j = c.keySet().iterator();
while (j.hasNext()) {
State cs = (State) c.get(j.next());
serializeState(b, cs, indent + INDENT);
}
}
serializeOnExit(b, s, indent + INDENT);
b.append(indent).append("</state>\n");
}
/**
* Serialize this Parallel object.
*
* @param b The buffer to append the serialization to
* @param p The Parallel to serialize
* @param indent The indent for this XML element
*/
public static void serializeParallel(final StringBuffer b,
final Parallel p, final String indent) {
b.append(indent).append("<parallel");
serializeTransitionTargetAttributes(b, p);
b.append(">\n");
serializeOnEntry(b, p, indent + INDENT);
Set s = p.getChildren();
Iterator i = s.iterator();
while (i.hasNext()) {
serializeState(b, (State) i.next(), indent + INDENT);
}
serializeOnExit(b, p, indent + INDENT);
b.append(indent).append("</parallel>\n");
}
/**
* Serialize this Invoke object.
*
* @param b The buffer to append the serialization to
* @param i The Invoke to serialize
* @param indent The indent for this XML element
*/
public static void serializeInvoke(final StringBuffer b,
final Invoke i, final String indent) {
b.append(indent).append("<invoke");
String ttype = i.getTargettype();
String src = i.getSrc();
String srcexpr = i.getSrcexpr();
if (ttype != null) {
b.append(" targettype=\"").append(ttype).append("\"");
}
// Prefer src
if (src != null) {
b.append(" src=\"").append(src).append("\"");
} else if (srcexpr != null) {
b.append(" srcexpr=\"").append(srcexpr).append("\"");
}
b.append(">\n");
List params = i.params();
for (Iterator iter = params.iterator(); iter.hasNext();) {
Param p = (Param) iter.next();
b.append(indent).append(INDENT).append("<param name=\"").
append(p.getName()).append("\" expr=\"").
append(p.getExpr()).append("\"/>\n");
}
Finalize f = i.getFinalize();
if (f != null) {
b.append(indent).append(INDENT).append("<finalize>\n");
serializeActions(b, f.getActions(), indent + INDENT + INDENT);
b.append(indent).append(INDENT).append("</finalize>\n");
}
b.append(indent).append("</invoke>\n");
}
/**
* Serialize this Initial object.
*
* @param b The buffer to append the serialization to
* @param i The Initial to serialize
* @param indent The indent for this XML element
*/
public static void serializeInitial(final StringBuffer b, final Initial i,
final String indent) {
b.append(indent).append("<initial");
serializeTransitionTargetAttributes(b, i);
b.append(">\n");
serializeTransition(b, i.getTransition(), indent + INDENT);
b.append(indent).append("</initial>\n");
}
/**
* Serialize the History.
*
* @param b The buffer to append the serialization to
* @param l The List of History objects to serialize
* @param indent The indent for this XML element
*/
public static void serializeHistory(final StringBuffer b, final List l,
final String indent) {
if (l.size() > 0) {
for (int i = 0; i < l.size(); i++) {
History h = (History) l.get(i);
b.append(indent).append("<history");
serializeTransitionTargetAttributes(b, h);
if (h.isDeep()) {
b.append(" type=\"deep\"");
} else {
b.append(" type=\"shallow\"");
}
b.append(">\n");
serializeTransition(b, h.getTransition(), indent + INDENT);
b.append(indent).append("</history>\n");
}
}
}
/**
* Serialize this Transition object.
*
* @param b The buffer to append the serialization to
* @param t The Transition to serialize
* @param indent The indent for this XML element
*/
public static void serializeTransition(final StringBuffer b,
final Transition t, final String indent) {
b.append(indent).append("<transition event=\"").append(t.getEvent())
.append("\" cond=\"").append(t.getCond()).append("\">\n");
boolean exit = serializeActions(b, t.getActions(), indent + INDENT);
if (!exit) {
serializeTarget(b, t, indent + INDENT);
}
b.append(indent).append("</transition>\n");
}
/**
* Serialize this Transition's Target.
*
*
* @param b The buffer to append the serialization to
* @param t The Transition whose Target needs to be serialized
* @param indent The indent for this XML element
*
* @deprecated Inline <target> element has been deprecated
* in the SCXML WD
*/
public static void serializeTarget(final StringBuffer b,
final Transition t, final String indent) {
b.append(indent).append("<target");
String n = t.getNext();
if (n != null) {
b.append(" next=\"" + n + "\">\n");
} else {
b.append(">\n");
if (t.getTarget() != null) {
// The inline transition target can only be a state
serializeState(b, (State) t.getTarget(), indent + INDENT);
}
}
b.append(indent).append("</target>\n");
}
/**
* Serialize this Datamodel object.
*
* @param b The buffer to append the serialization to
* @param dm The Datamodel to be serialized
* @param indent The indent for this XML element
*/
public static void serializeDatamodel(final StringBuffer b,
final Datamodel dm, final String indent) {
List data = dm.getData();
if (data != null && data.size() > 0) {
b.append(indent).append("<datamodel>\n");
if (XFORMER == null) {
b.append(indent).append(INDENT).
append("<!-- Body content was not serialized -->\n");
b.append(indent).append("</datamodel>\n");
return;
}
for (Iterator iter = data.iterator(); iter.hasNext();) {
Data datum = (Data) iter.next();
Node dataNode = datum.getNode();
if (dataNode != null) {
StringWriter out = new StringWriter();
try {
Source input = new DOMSource(dataNode);
Result output = new StreamResult(out);
XFORMER.transform(input, output);
} catch (TransformerException te) {
org.apache.commons.logging.Log log = LogFactory.
getLog(SCXMLSerializer.class);
log.error(te.getMessage(), te);
b.append(indent).append(INDENT).
append("<!-- Data content not serialized -->\n");
}
b.append(indent).append(INDENT).append(out.toString());
} else {
b.append(indent).append(INDENT).append("<data name=\"").
append(datum.getName()).append("\" expr=\"").
append(datum.getExpr()).append("\" />\n");
}
}
b.append(indent).append("</datamodel>\n");
}
}
/**
* Serialize this OnEntry object.
*
* @param b The buffer to append the serialization to
* @param t The TransitionTarget whose OnEntry is to be serialized
* @param indent The indent for this XML element
*/
public static void serializeOnEntry(final StringBuffer b,
final TransitionTarget t, final String indent) {
OnEntry e = t.getOnEntry();
if (e != null && e.getActions().size() > 0) {
b.append(indent).append("<onentry>\n");
serializeActions(b, e.getActions(), indent + INDENT);
b.append(indent).append("</onentry>\n");
}
}
/**
* Serialize this OnExit object.
*
* @param b The buffer to append the serialization to
* @param t The TransitionTarget whose OnExit is to be serialized
* @param indent The indent for this XML element
*/
public static void serializeOnExit(final StringBuffer b,
final TransitionTarget t, final String indent) {
OnExit x = t.getOnExit();
if (x != null && x.getActions().size() > 0) {
b.append(indent).append("<onexit>\n");
serializeActions(b, x.getActions(), indent + INDENT);
b.append(indent).append("</onexit>\n");
}
}
/**
* Serialize this List of actions.
*
* @param b The buffer to append the serialization to
* @param l The List of actions to serialize
* @param indent The indent for this XML element
* @return boolean true if the list of actions contains an <exit/>
*/
public static boolean serializeActions(final StringBuffer b, final List l,
final String indent) {
if (l == null) {
return false;
}
boolean exit = false;
Iterator i = l.iterator();
while (i.hasNext()) {
Action a = (Action) i.next();
if (a instanceof Var) {
Var v = (Var) a;
b.append(indent).append("<var name=\"").append(v.getName())
.append("\" expr=\"").append(v.getExpr()).append(
"\"/>\n");
} else if (a instanceof Assign) {
Assign asn = (Assign) a;
b.append(indent).append("<assign");
if (!SCXMLHelper.isStringEmpty(asn.getLocation())) {
b.append(" location=\"").append(asn.getLocation());
if (!SCXMLHelper.isStringEmpty(asn.getSrc())) {
b.append("\" src=\"").append(asn.getSrc());
} else {
b.append("\" expr=\"").append(asn.getExpr());
}
} else {
b.append(" name=\"").append(asn.getName()).
append("\" expr=\"").append(asn.getExpr());
}
b.append("\"/>\n");
} else if (a instanceof Send) {
serializeSend(b, (Send) a, indent);
} else if (a instanceof Cancel) {
Cancel c = (Cancel) a;
b.append(indent).append("<cancel sendid=\"")
.append(c.getSendid()).append("\"/>\n");
} else if (a instanceof Log) {
Log lg = (Log) a;
b.append(indent).append("<log expr=\"").append(lg.getExpr())
.append("\"/>\n");
} else if (a instanceof Exit) {
Exit e = (Exit) a;
b.append(indent).append("<exit");
String expr = e.getExpr();
String nl = e.getNamelist();
if (expr != null) {
b.append(" expr=\"" + expr + "\"");
}
if (nl != null) {
b.append(" namelist=\"" + nl + "\"");
}
b.append("/>\n");
exit = true;
} else if (a instanceof If) {
If iff = (If) a;
serializeIf(b, iff, indent);
} else if (a instanceof Else) {
b.append(indent).append("<else/>\n");
} else if (a instanceof ElseIf) {
ElseIf eif = (ElseIf) a;
b.append(indent).append("<elseif cond=\"")
.append(eif.getCond()).append("\" />\n");
}
}
return exit;
}
/**
* Serialize this Send object.
*
* @param b The buffer to append the serialization to
* @param send The Send object to serialize
* @param indent The indent for this XML element
*/
public static void serializeSend(final StringBuffer b,
final Send send, final String indent) {
b.append(indent).append("<send sendid=\"")
.append(send.getSendid()).append("\" target=\"")
.append(send.getTarget()).append("\" targetType=\"")
.append(send.getTargettype()).append("\" namelist=\"")
.append(send.getNamelist()).append("\" delay=\"")
.append(send.getDelay()).append("\" events=\"")
.append(send.getEvent()).append("\" hints=\"")
.append(send.getHints()).append("\">\n")
.append(getBodyContent(send))
.append(indent).append("</send>\n");
}
/**
* Return serialized body of <code>ExternalContent</code>.
*
* @param externalContent The model element containing the body content
* @return String The serialized body content
*/
public static final String getBodyContent(
final ExternalContent externalContent) {
StringBuffer buf = new StringBuffer();
List externalNodes = externalContent.getExternalNodes();
if (externalNodes.size() > 0 && XFORMER == null) {
buf.append("<!-- Body content was not serialized -->\n");
return buf.toString();
}
for (int i = 0; i < externalNodes.size(); i++) {
Source input = new DOMSource((Node) externalNodes.get(i));
StringWriter out = new StringWriter();
Result output = new StreamResult(out);
try {
XFORMER.transform(input, output);
} catch (TransformerException te) {
org.apache.commons.logging.Log log = LogFactory.
getLog(SCXMLSerializer.class);
log.error(te.getMessage(), te);
buf.append("<!-- Not all body content was serialized -->");
}
buf.append(out.toString()).append("\n");
}
return buf.toString();
}
/**
* Serialize this If object.
*
* @param b The buffer to append the serialization to
* @param iff The If object to serialize
* @param indent The indent for this XML element
*/
public static void serializeIf(final StringBuffer b,
final If iff, final String indent) {
b.append(indent).append("<if cond=\"").append(iff.getCond()).append(
"\">\n");
serializeActions(b, iff.getActions(), indent + INDENT);
b.append(indent).append("</if>\n");
}
/**
* Serialize properties of TransitionTarget which are element attributes.
*
* @param b The buffer to append the serialization to
* @param t The TransitionTarget
*/
private static void serializeTransitionTargetAttributes(
final StringBuffer b, final TransitionTarget t) {
String id = t.getId();
if (id != null) {
b.append(" id=\"").append(id).append("\"");
}
TransitionTarget pt = t.getParent();
if (pt != null) {
String pid = pt.getId();
if (pid != null) {
b.append(" parentid=\"").append(pid).append("\"");
}
}
}
/**
* Get a <code>Transformer</code> instance.
*
* @return Transformer The <code>Transformer</code> instance.
*/
private static Transformer getTransformer() {
Transformer transformer = null;
Properties outputProps = new Properties();
outputProps.put(OutputKeys.OMIT_XML_DECLARATION, "yes");
outputProps.put(OutputKeys.STANDALONE, "no");
outputProps.put(OutputKeys.INDENT, "yes");
try {
TransformerFactory tfFactory = TransformerFactory.newInstance();
transformer = tfFactory.newTransformer();
transformer.setOutputProperties(outputProps);
} catch (Throwable t) {
return null;
}
return transformer;
}
/*
* Private methods.
*/
/**
* Discourage instantiation since this is a utility class.
*/
private SCXMLSerializer() {
super();
}
}