/*
* This software is Copyright 2005,2006,2007,2008 Langdale Consultants.
* Langdale Consultants can be contacted at: http://www.langdale.com.au
*/
package au.com.langdale.profiles;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.regex.Pattern;
import javax.xml.transform.ErrorListener;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.SourceLocator;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import au.com.langdale.jena.JenaTreeModelBase;
import au.com.langdale.jena.TreeModelBase.Node;
import au.com.langdale.profiles.ProfileModel.CatalogNode;
import au.com.langdale.profiles.ProfileModel.EnvelopeNode;
import au.com.langdale.profiles.ProfileModel.TypeNode;
import au.com.langdale.profiles.ProfileModel.EnvelopeNode.MessageNode;
import au.com.langdale.profiles.ProfileModel.NaturalNode.ElementNode;
import au.com.langdale.profiles.ProfileModel.NaturalNode.EnumValueNode;
import au.com.langdale.profiles.ProfileModel.NaturalNode.SuperTypeNode;
import au.com.langdale.profiles.ProfileModel.NaturalNode.ElementNode.SubTypeNode;
import au.com.langdale.sax.AbstractReader;
import au.com.langdale.xmi.UML;
import au.com.langdale.kena.OntResource;
import au.com.langdale.kena.ResIterator;
import com.hp.hpl.jena.vocabulary.XSD;
/**
* Convert a message model to XML. The form of the XML is designed
* to be easily transformed to specific schemas in any schema language.
*
* A MessageSerializer is a SAX XMLReader. Calling parse(InputSource)
* causes it to emit SAX events representing a message definition.
*
* However, the write() method is a more convenient way to generate
* XML to a file.
*
* If setStylesheet() is called before write, the output is transformed
* into a schema according to the given templates.
*/
public class ProfileSerializer extends AbstractReader {
public static final String XSDGEN = "http://langdale.com.au/2005/xsdgen";
private JenaTreeModelBase model;
private String baseURI = "";
private String version = "";
private ArrayList templates = new ArrayList();
TransformerFactory factory = TransformerFactory.newInstance();
private final String xsd = XSD.anyURI.getNameSpace();
private HashSet deferred = new HashSet();
/**
* Construct a serializer for the given MessageModel.
*/
public ProfileSerializer(JenaTreeModelBase model) {
this.model = model;
}
/**
* A URI that is passed as the baseURI attribute of the root element
* and is generally used to establish a namespace for the generated
* schema.
*/
public String getBaseURI() {
return baseURI;
}
/**
* Set the base URI (see above).
*/
public void setBaseURI(String baseURI) {
this.baseURI = baseURI;
}
/**
* Install a stylesheet to transform the abstract message definition to a schema.
*/
public void setStyleSheet(InputStream s, String base) throws TransformerConfigurationException {
templates.clear();
addStyleSheet(s, base);
}
/**
* Install a stylesheet to apply after any previously installed stylesheets.
*/
public void addStyleSheet(InputStream s, String base) throws TransformerConfigurationException {
templates.add( factory.newTemplates(new StreamSource(s, base)));
}
/**
* Install a stylesheet from the standard set. Use null for no stylesheet.
*/
public void setStyleSheet(String name) throws TransformerConfigurationException {
if( name == null)
templates.clear();
else
setStyleSheet(getClass().getResourceAsStream(name + ".xsl"), XSDGEN);
}
/**
* Install a stylesheet to apply after any previously installed stylesheets.
*/
public void addStyleSheet(String name) throws TransformerConfigurationException {
addStyleSheet(getClass().getResourceAsStream(name + ".xsl"), "");
}
/**
* Install a standard SAX ErrorHandler for errors in the stylesheet.
* This will be wrapped in the ErrorListener required by the transform
* framework.
*/
@Override
public void setErrorHandler(final ErrorHandler errors) {
ErrorListener listener = new ErrorListener() {
public void error(TransformerException ex) throws TransformerException {
try {
errors.error(convert(ex));
}
catch (SAXException e) {
throw convert(e);
}
}
public void warning(TransformerException ex) throws TransformerException {
try {
errors.warning(convert(ex));
}
catch (SAXException e) {
throw convert(e);
}
}
public void fatalError(TransformerException ex) throws TransformerException {
try {
errors.fatalError(convert(ex));
}
catch (SAXException e) {
throw convert(e);
}
}
private SAXParseException convert(TransformerException ex) {
Throwable cause = ex.getCause();
if( cause instanceof SAXParseException)
return (SAXParseException)cause;
SourceLocator loc = ex.getLocator();
if(loc != null)
return new SAXParseException(ex.getMessage(), loc.getPublicId(), loc.getSystemId(), loc.getLineNumber(), loc.getColumnNumber());
return new SAXParseException(ex.getMessage(), "", "", 0, 0);
}
private TransformerException convert(SAXException ex) {
return new TransformerException(ex.getMessage(), ex);
}
};
factory.setErrorListener(listener);
}
/**
* Generate a schema from the message definition
* and write it to the given stream.
*/
@Override
public void write(OutputStream ostream) throws TransformerException {
Transformer[] tx;
if( ! templates.isEmpty()) {
tx = new Transformer[templates.size()];
for( int ix = 0; ix < templates.size(); ix++) {
Transformer ti = ((Templates)templates.get(ix)).newTransformer();
ti.setParameter("baseURI", baseURI);
ti.setParameter("version", version);
ti.setParameter("envelope", model.getRoot().getName());
tx[ix] = ti;
}
}
else {
tx = new Transformer[] { factory.newTransformer() };
}
Result result = new StreamResult(ostream);
Source source = new SAXSource(this, new InputSource());
for( int ix = 0; ix < tx.length-1; ix++ ) {
DOMResult inter = new DOMResult();
tx[0].transform(source, inter);
source = new DOMSource(inter.getNode());
}
tx[tx.length-1].transform(source, result);
}
protected void emit() throws SAXException {
emit(model.getRoot());
}
private void emit(Node node) throws SAXException {
if( node instanceof CatalogNode)
emit((CatalogNode)node);
else if( node instanceof EnvelopeNode)
emit((EnvelopeNode)node);
else if( node instanceof MessageNode)
emit((MessageNode)node);
else if( node instanceof TypeNode)
emit((TypeNode)node);
else if( node instanceof SuperTypeNode)
emit((SuperTypeNode)node);
else if( node instanceof ElementNode)
emit((ElementNode)node);
else if( node instanceof EnumValueNode)
emit((EnumValueNode)node);
else if( node instanceof SubTypeNode)
emit((SubTypeNode)node);
else if( node != null)
emitChildren(node);
}
private void emitChildren(Node node) throws SAXException {
Iterator it = node.iterator();
while( it.hasNext())
emit((Node)it.next());
}
private void emit(CatalogNode node) throws SAXException {
Element elem = new Element("Catalog", MESSAGE.NS);
elem.set("baseURI", baseURI);
elem.set("xmlns:m", baseURI);
elem.set("name", node.getName());
emitNote(node);
emitChildren(node);
Iterator it = deferred.iterator();
while( it.hasNext()) {
emit((OntResource)it.next());
}
elem.close();
}
private void emit(EnvelopeNode node) throws SAXException {
Element elem = new Element("Message", MESSAGE.NS);
elem.set("name", node.getName());
emitChildren(node);
elem.close();
}
private void emit(SuperTypeNode node) throws SAXException {
Element elem = new Element("SuperType");
elem.set("name", node.getName());
elem.close();
}
private void emit(SubTypeNode node) throws SAXException {
Element elem = select(node);
elem.set("name", node.getName());
elem.set("minOccurs", "1");
elem.set("maxOccurs", "1");
emitNote(node);
if(node.getSubject().isAnon())
emitChildren(node);
elem.close();
}
private Element select(SubTypeNode node) throws SAXException {
Element elem;
boolean anon = node.getSubject().isAnon();
if( node.isEnumerated()) {
if(anon)
elem = new Element("SimpleEnumerated");
else
elem = new Element("Enumerated");
}
else {
if( anon )
elem = new Element("Complex");
else {
if( node.getParent() instanceof ElementNode && ((ElementNode)node.getParent()).isReference())
elem = new Element("Reference");
else
elem = new Element("Instance");
}
}
elem.set("baseClass", node.getBaseClass().getURI());
if( ! anon )
elem.set("type", node.getName());
return elem;
}
private void emit(ElementNode node) throws SAXException {
Element elem;
if( node.isDatatype() ) {
OntResource range = node.getBaseProperty().getRange();
if( range == null )
range = model.getOntModel().createResource(XSD.xstring.asNode());
if( range.getNameSpace().equals(xsd)) {
elem = new Element("Simple");
}
else {
elem = new Element("Domain");
elem.set("type", range.getLocalName());
deferred.add(range);
}
elem.set("dataType", range.getURI());
elem.set("xstype", xstype(range));
emit(node, elem);
}
else {
int size = node.getChildren().size();
if( size == 0 ) {
OntResource range = node.getBaseProperty().getRange();
elem = new Element("Reference");
if( range != null && range.isURIResource())
elem.set("type", range.getLocalName());
emit(node, elem);
}
else if( size == 1) {
SubTypeNode child = (SubTypeNode) node.getChildren().get(0);
elem = select(child);
emit(node, elem);
if(child.getSubject().isAnon())
emitChildren(child);
}
else {
elem = new Element("Choice");
emit(node, elem);
emitChildren(node);
}
}
elem.close();
}
private void emit(ElementNode node, Element elem) throws SAXException {
elem.set("name", node.getName());
elem.set("baseProperty", node.getBaseProperty().getURI());
elem.set("minOccurs", ProfileModel.cardString(node.getMinCardinality()));
elem.set("maxOccurs", ProfileModel.cardString(node.getMaxCardinality(), "unbounded"));
emit(node.getBaseProperty().getComment(null));
emitNote(node);
}
private void emit(String comment) throws SAXException {
emit("Comment", comment);
}
private static Pattern delimiter = Pattern.compile(" *([\r\n] *)+");
private void emit(String ename, String comment) throws SAXException {
if( comment == null)
return;
String[] pars = delimiter.split(comment.trim());
for (int ix = 0; ix < pars.length; ix++) {
Element elem = new Element(ename);
elem.append(pars[ix]);
elem.close();
}
}
private void emitNote(Node node) throws SAXException {
emit("Note", node.getSubject().getComment(null));
emitStereotypes(node.getSubject());
}
private void emitStereotypes(OntResource subject) throws SAXException {
ResIterator it = subject.listProperties(UML.hasStereotype);
while (it.hasNext()) {
emitStereotype(it.nextResource());
}
}
private void emitStereotype(OntResource stereo) throws SAXException {
if( ! stereo.isURIResource())
return;
Element elem = new Element("Stereotype");
elem.append(stereo.getURI());
elem.close();
}
private String xstype(OntResource type) {
if(type.getNameSpace().equals(xsd))
return type.getLocalName();
OntResource xtype = type.getEquivalentClass();
if( xtype != null && xtype.getNameSpace().equals(xsd))
return xtype.getLocalName();
System.out.println("Warning: undefined datatype: " + type);
return "string";
}
private void emit(TypeNode node) throws SAXException {
Element elem;
if( node.hasStereotype(UML.concrete))
elem = new Element("Root");
else if(node.isEnumerated())
elem = new Element("EnumeratedType");
else if( node.hasStereotype(UML.compound))
elem = new Element("CompoundType");
else
elem = new Element("ComplexType");
elem.set("name", node.getName());
elem.set("baseClass", node.getBaseClass().getURI());
elem.set("package", node.getPackageName());
elem.set("minOccurs", ProfileModel.cardString(node.getMinCardinality()));
elem.set("maxOccurs", ProfileModel.cardString(node.getMaxCardinality(), "unbounded"));
emit(node.getBaseClass().getComment(null));
emitNote(node);
emitChildren(node);
elem.close();
}
private void emit(MessageNode node) throws SAXException {
Element elem = new Element("Root");
elem.set("name", node.getName());
elem.set("baseClass", node.getBaseClass().getURI());
emit(node.getBaseClass().getComment(null));
emitNote(node);
emitChildren(node);
elem.close();
}
private void emit(OntResource type) throws SAXException {
Element elem = new Element("SimpleType");
elem.set("dataType", type.getURI());
elem.set("name", type.getLocalName());
elem.set("xstype", xstype(type));
emit(type.getComment(null));
elem.close();
}
private void emit(EnumValueNode node) throws SAXException {
OntResource value = node.getSubject();
Element elem = new Element("EnumeratedValue");
elem.set("name", node.getName());
elem.set("baseResource", node.getBase().getURI());
emit(value.getComment(null));
elem.close();
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
}