/*
* Copyright (C) 2012 Jason Gedge <http://www.gedge.ca>
*
* This file is part of the OpGraph project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package ca.gedge.opgraph.nodes.xml;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import ca.gedge.opgraph.InputField;
import ca.gedge.opgraph.OpGraph;
import ca.gedge.opgraph.OpNode;
import ca.gedge.opgraph.OutputField;
import ca.gedge.opgraph.extensions.Extendable;
import ca.gedge.opgraph.io.xml.XMLSerializer;
import ca.gedge.opgraph.io.xml.XMLSerializerFactory;
import ca.gedge.opgraph.nodes.general.MacroNode;
import ca.gedge.opgraph.nodes.iteration.ForEachNode;
/**
* A default serializer for reading/writing {@link OpNode} to/from XML.
*/
public class MacroNodeXMLSerializer implements XMLSerializer {
static final String NAMESPACE = "http://gedge.ca/ns/opgraph-common-nodes";
static final String PREFIX = "ogcn";
// qualified names
static final QName MACRO_QNAME = new QName(NAMESPACE, "macro", PREFIX);
@Override
public void write(XMLSerializerFactory serializerFactory, Document doc, Element parentElem, Object obj)
throws IOException
{
if(obj == null)
throw new IOException("Null object given to serializer");
if(!(obj instanceof MacroNode))
throw new IOException(MacroNodeXMLSerializer.class.getName() + " cannot write objects of type " + obj.getClass().getName());
// setup namespace for document
final Element rootEle = doc.getDocumentElement();
rootEle.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI,
XMLConstants.XMLNS_ATTRIBUTE + ":" + PREFIX, NAMESPACE);
// Create node element
final MacroNode macro = (MacroNode)obj;
final Element macroElem = doc.createElementNS(NAMESPACE, PREFIX + ":" + MACRO_QNAME.getLocalPart());
macroElem.setAttribute("id", macro.getId());
macroElem.setAttribute("type", obj.getClass().getName());
if(!macro.getName().equals(macro.getDefaultName()))
macroElem.setAttribute("name", macro.getName());
if(!macro.getDescription().equals(macro.getDefaultDescription())) {
final Element descriptionElem = doc.createElementNS(NAMESPACE, PREFIX + ":description");
descriptionElem.setTextContent(macro.getDescription());
macroElem.appendChild(descriptionElem);
}
// Macro graph
final XMLSerializer graphSerializer = serializerFactory.getHandler(OpGraph.class);
if(graphSerializer == null)
throw new IOException("No handler for graph");
graphSerializer.write(serializerFactory, doc, macroElem, macro.getGraph());
// Input fields
for(InputField field : macro.getInputFields()) {
final XMLSerializer serializer = serializerFactory.getHandler(field.getClass());
if(serializer == null)
throw new IOException("Cannot get handler for input field: " + field.getClass().getName());
serializer.write(serializerFactory, doc, macroElem, field);
}
// Output fields
for(OutputField field : macro.getOutputFields()) {
final XMLSerializer serializer = serializerFactory.getHandler(field.getClass());
if(serializer == null)
throw new IOException("Cannot get handler for output field: " + field.getClass().getName());
serializer.write(serializerFactory, doc, macroElem, field);
}
// Extensions last
if(macro.getExtensionClasses().size() > 0) {
final XMLSerializer serializer = serializerFactory.getHandler(Extendable.class);
if(serializer == null)
throw new IOException("No XML serializer for extensions");
serializer.write(serializerFactory, doc, macroElem, macro);
}
//
parentElem.appendChild(macroElem);
}
@Override
public Object read(XMLSerializerFactory serializerFactory, OpGraph graph, Object parent, Document doc, Element elem)
throws IOException
{
MacroNode macro = null;
if(MACRO_QNAME.equals(XMLSerializerFactory.getQName(elem))) {
// Read graph
final XMLSerializer graphSerializer = serializerFactory.getHandler(OpGraph.class);
if(graphSerializer == null)
throw new IOException("No handler for graph");
// Get the type of macro node
Class<? extends MacroNode> cls = MacroNode.class;
if(elem.hasAttribute("type")) {
try {
final Class<?> type = Class.forName(elem.getAttribute("type"));
cls = type.asSubclass(MacroNode.class);
} catch(ClassCastException exc) {
throw new IOException("Macro node type not castable to MacroNode");
} catch(ClassNotFoundException exc) {
throw new IOException("Macro node type unknown");
}
}
// Get the OpGraph constructor
Constructor<? extends MacroNode> constructor = null;
try {
constructor = cls.getConstructor(OpGraph.class);
} catch(SecurityException exc) {
throw new IOException("Cannot construct macro node with given type: " + cls.getName());
} catch(NoSuchMethodException exc) {
throw new IOException("Cannot construct macro node with given type: " + cls.getName());
}
// Read children
final NodeList children = elem.getChildNodes();
for(int childIndex = 0; childIndex < children.getLength(); ++childIndex) {
final Node node = children.item(childIndex);
if(node instanceof Element) {
final Element childElem = (Element)node;
final QName name = XMLSerializerFactory.getQName(childElem);
if(graphSerializer.handles(name)) {
final Object objRead = graphSerializer.read(serializerFactory, graph, macro, doc, childElem);
if(objRead == null || !(objRead instanceof OpGraph))
throw new IOException("Could not read graph for macro");
try {
macro = constructor.newInstance(objRead);
} catch(IllegalArgumentException exc) {
throw new IOException("Could not instantiate macro node");
} catch(InstantiationException exc) {
throw new IOException("Could not instantiate macro node");
} catch(IllegalAccessException exc) {
throw new IOException("Could not instantiate macro node");
} catch(InvocationTargetException exc) {
throw new IOException("Could not instantiate macro node");
}
} else {
if(macro == null)
throw new IOException("Reading other macro data before macro graph read");
// Get a handler for the element
final XMLSerializer serializer = serializerFactory.getHandler(name);
if(serializer == null)
throw new IOException("Could not get handler for element: " + name);
// Published fields and extensions all take care of adding
// themselves to the passed in object
//
serializer.read(serializerFactory, graph, macro, doc, childElem);
}
}
}
// Set attributes
if(macro != null) {
if(elem.hasAttribute("id"))
macro.setId(elem.getAttribute("id"));
if(elem.hasAttribute("name"))
macro.setName(elem.getAttribute("name"));
}
}
return macro;
}
@Override
public boolean handles(Class<?> cls) {
return (cls == MacroNode.class || cls == ForEachNode.class);
}
@Override
public boolean handles(QName name) {
return MACRO_QNAME.equals(name);
}
}