javolution.xml.XMLFormat Maven / Gradle / Ivy
/*
* Javolution - Java(TM) Solution for Real-Time and Embedded Systems
* Copyright (C) 2012 - Javolution (http://javolution.org/)
* All rights reserved.
*
* Permission to use, copy, modify, and distribute this software is
* freely granted, provided that this notice is preserved.
*/
package javolution.xml;
import javolution.text.CharArray;
import javolution.text.TextBuilder;
import javolution.text.TextContext;
import javolution.text.TextFormat;
import javolution.xml.internal.stream.XMLStreamReaderImpl;
import javolution.xml.internal.stream.XMLStreamWriterImpl;
import javolution.xml.sax.Attributes;
import javolution.xml.stream.XMLStreamException;
import javolution.xml.stream.XMLStreamReader;
import javolution.xml.stream.XMLStreamWriter;
/**
* This class represents the format base class for XML serialization and
* deserialization.
*
* Instances of this class are typically retrieved from the
* {@link XMLContext} (OSGi service or not).
* [code]
* @Format(xml=GraphicXML.class)
* public abstract class Graphic implements XMLSerializable {
* private boolean isVisible;
* private Paint paint; // null if none.
* private Stroke stroke; // null if none.
* private Transform transform; // null if none.
*
* // XML format with positional associations (members identified by their position),
* // see XML package description for examples of name associations.
* public static class GraphicXML extends XMLFormat {
* public void write(Graphic g, OutputElement xml) {
* xml.setAttribute("isVisible", g.isVisible);
* xml.add(g.paint); // First.
* xml.add(g.stroke); // Second.
* xml.add(g.transform); // Third.
* }
* public void read(InputElement xml, Graphic g) {
* g.isVisible = xml.getAttribute("isVisible", true);
* g.paint = xml.getNext();
* g.stroke = xml.getNext();
* g.transform = xml.getNext();
* return g;
* }
* };
* }
* [/code]
*
* Due to the sequential nature of XML serialization/deserialization,
* formatting/parsing of XML attributes should always be performed before
* formatting/parsing of the XML content.
*
* The mapping between classes and XML formats can be overriden
* through {@link XMLBinding} instances.
* Here is an example of serialization/deserialization:
* [code]
* // Creates a list holding diverse objects.
* List list = new ArrayList();
* list.add("John Doe");
* list.add(null);
* Map map = new FastMap();
* map.put("ONE", 1);
* map.put("TWO", 2);
* list.add(map);
*
* // Use of custom binding.
* XMLBinding binding = new XMLBinding();
* binding.setAlias(FastMap.class, "Map");
* binding.setAlias(String.class, "String");
* binding.setAlias(Integer.class, "Integer");
*
* // Formats the list to XML .
* OutputStream out = new FileOutputStream("C:/list.xml");
* XMLObjectWriter writer = new XMLObjectWriter().setOutput(out).setBinding(binding);
* writer.write(list, "MyList", ArrayList.class);
* writer.close();
* [/code]
*
* Here is the output list.xml
document produced:[code]
*
*
*
*
*
*
* [/code]
*
* The list can be read back with the following code:
* [code]
* // Reads back to a FastTable instance.
* InputStream in = new FileInputStream("C:/list.xml");
* XMLObjectReader reader = new XMLObjectReader().setInput(in).setBinding(binding);
* FastTable table = reader.read("MyList", FastTable.class);
* reader.close();
* [/code]
*
*
* Note: Any type for which a {@link TextFormat text format} is
* defined can be represented as a XML attribute.
*
* @author Jean-Marie Dautelle
* @version 5.4, December 1, 2009
*/
public abstract class XMLFormat {
/**
* Holds null
representation.
*/
private static final String NULL = "Null";
/**
* Default constructor.
*/
protected XMLFormat() {}
/**
* Indicates if the object serialized through this format can be referenced
* to (default true
). This method can be overriden to return
* false
if serialized objects are manipulated "by value".
*
* @return true
if serialized object may hold a reference;
* false
otherwise.
* @see XMLReferenceResolver
*/
public boolean isReferenceable() {
return true;
}
/**
* Allocates a new object of the specified class from the specified
* XML input element. By default, this method returns an object created
* using the public no-arg constructor of the specified class.
* XML formats may override this method in order to use private/multi-arg
* constructors.
*
* @param cls the class of the object to return.
* @param xml the XML input element.
* @return the object corresponding to the specified XML element.
*/
public T newInstance(Class extends T> cls, InputElement xml)
throws XMLStreamException {
try {
return cls.newInstance();
} catch (InstantiationException e) {
throw new XMLStreamException(e);
} catch (IllegalAccessException e) {
throw new XMLStreamException(e);
}
}
/**
* Formats an object into the specified XML output element.
*
* @param obj the object to format.
* @param xml the XMLElement
destination.
*/
public abstract void write(T obj, OutputElement xml)
throws XMLStreamException;
/**
* Parses an XML input element into the specified object.
*
* @param xml the XML element to parse.
* @param obj the object created through {@link #newInstance}
* and to setup from the specified XML element.
*/
public abstract void read(InputElement xml, T obj)
throws XMLStreamException;
/**
* This class represents an input XML element (unmarshalling).
*/
@SuppressWarnings("unchecked")
public static final class InputElement {
/**
* Holds the stream reader.
*/
final XMLStreamReaderImpl _reader = new XMLStreamReaderImpl();
/**
* Holds the XML binding.
*/
private XMLBinding _binding;
/**
* Holds the reference resolver.
*/
private XMLReferenceResolver _referenceResolver;
/**
* Indicates if the reader is currently positioned on the next element.
*/
private boolean _isReaderAtNext;
/**
* Default constructor.
*/
InputElement() {
reset();
}
/**
* Returns the StAX-like stream reader (provides complete control
* over the unmarshalling process).
*
* @return the stream reader.
*/
public XMLStreamReader getStreamReader() {
return _reader;
}
/**
* Indicates if more nested XML element can be read. This method
* positions the {@link #getStreamReader reader} at the start of the
* next XML element to be read (if any).
*
* @return true
if there is more XML element to be read;
* false
otherwise.
*/
public boolean hasNext() throws XMLStreamException {
if (!_isReaderAtNext) {
_isReaderAtNext = true;
_reader.nextTag();
}
return _reader.getEventType() == XMLStreamReader.START_ELEMENT;
}
/**
* Returns the next object whose type is identified by the local name
* and URI of the current XML element.
*
* @return the next nested object which can be null
.
* @throws XMLStreamException if hasNext() == false
.
*/
public T getNext() throws XMLStreamException {
if (!hasNext()) // Asserts isReaderAtNext == true
throw new XMLStreamException("No more element to read",
_reader.getLocation());
// Checks for null.
if (_reader.getLocalName().equals(NULL)) {
if (_reader.next() != XMLStreamReader.END_ELEMENT)
throw new XMLStreamException("Non Empty Null Element");
_isReaderAtNext = false;
return null;
}
Object ref = readReference();
if (ref != null)
return (T) ref;
// Retrieves object's class from element tag.
Class> cls = _binding.readClass(_reader, false);
return readInstanceOf(cls);
}
/**
* Returns the object whose type is identified by a XML class attribute
* only if the XML element has the specified local name.
*
* @param name the local name of the next element.
* @return the next nested object or null
.
*/
public T get(String name) throws XMLStreamException {
if (!hasNext()// Asserts isReaderAtNext == true
|| !_reader.getLocalName().equals(name))
return null;
Object ref = readReference();
if (ref != null)
return (T) ref;
// Retrieves object's class from class attribute.
Class> cls = _binding.readClass(_reader, true);
return readInstanceOf(cls);
}
/**
* Returns the object whose type is identified by a XML class attribute
* only if the XML element has the specified local name and URI.
*
* @param localName the local name.
* @param uri the namespace URI or null
.
* @return the next nested object or null
.
*/
public T get(String localName, String uri)
throws XMLStreamException {
if (uri == null)
return (T) get(localName);
if (!hasNext()// Asserts isReaderAtNext == true
|| !_reader.getLocalName().equals(localName)
|| !_reader.getNamespaceURI().equals(uri))
return null;
Object ref = readReference();
if (ref != null)
return (T) ref;
// Retrieves object's class from class attribute.
Class> cls = _binding.readClass(_reader, true);
return readInstanceOf(cls);
}
/**
* Returns the object of specified type only if the XML element has the
* specified local name.
*
* @param name the local name of the element to match.
* @param cls the class identifying the format of the object to return.
* @return the next nested object or null
.
*/
public T get(String name, Class cls) throws XMLStreamException {
if (!hasNext()// Asserts isReaderAtNext == true
|| !_reader.getLocalName().equals(name))
return null;
Object ref = readReference();
if (ref != null)
return (T) ref;
return readInstanceOf(cls);
}
/**
* Returns the object of specified type only if the
* XML element has the specified local name and namespace URI.
*
* @param localName the local name.
* @param uri the namespace URI or null
.
* @param cls the class identifying the format of the object to return.
* @return the next nested object or null
.
*/
public T get(String localName, String uri, Class cls)
throws XMLStreamException {
if (uri == null)
return get(localName, cls);
if (!hasNext()// Asserts isReaderAtNext == true
|| !_reader.getLocalName().equals(localName)
|| !_reader.getNamespaceURI().equals(uri))
return null;
Object ref = readReference();
if (ref != null)
return (T) ref;
return readInstanceOf(cls);
}
// Returns the referenced object if any.
private Object readReference() throws XMLStreamException {
if (_referenceResolver == null)
return null;
Object ref = _referenceResolver.readReference(this);
if (ref == null)
return null;
if (_reader.next() != XMLStreamReader.END_ELEMENT)
throw new XMLStreamException("Non Empty Reference Element");
_isReaderAtNext = false;
return ref;
}
// Builds object of specified class.
@SuppressWarnings("rawtypes")
private T readInstanceOf(Class cls) throws XMLStreamException {
// Retrieves format.
XMLFormat xmlFormat = _binding.getFormat(cls);
// Creates object.
_isReaderAtNext = false; // Makes attributes accessible.
Object obj = xmlFormat.newInstance(cls, this);
// Adds reference (before reading to support circular reference).
if (_referenceResolver != null) {
_referenceResolver.createReference(obj, this);
}
// Parses xml.
xmlFormat.read(this, obj);
if (hasNext()) // Asserts _isReaderAtNext == true
throw new XMLStreamException("Incomplete element reading",
_reader.getLocation());
_isReaderAtNext = false; // Skips end element.
return (T) obj;
}
/**
* Returns the content of a text-only element (equivalent to
* {@link javolution.xml.stream.XMLStreamReader#getElementText
* getStreamReader().getElementText()}).
*
* @return the element text content or an empty sequence if none.
*/
public CharArray getText() throws XMLStreamException {
CharArray txt = _reader.getElementText();
_isReaderAtNext = true; // End element is next.
return txt;
}
/**
* Returns the attributes for this XML input element.
*
* @return the attributes mapping.
*/
public Attributes getAttributes() throws XMLStreamException {
if (_isReaderAtNext)
throw new XMLStreamException(
"Attributes should be read before content");
return _reader.getAttributes();
}
/**
* Searches for the attribute having the specified name.
*
* @param name the name of the attribute.
* @return the value for the specified attribute or null
* if the attribute is not found.
*/
public CharArray getAttribute(String name) throws XMLStreamException {
if (_isReaderAtNext)
throw new XMLStreamException(
"Attributes should be read before reading content");
return _reader.getAttributeValue(null, name);
}
/**
* Returns the specified String
attribute.
*
* @param name the name of the attribute.
* @param defaultValue a default value.
* @return the value for the specified attribute or
* the defaultValue
if the attribute is not found.
*/
public String getAttribute(String name, String defaultValue)
throws XMLStreamException {
CharArray value = getAttribute(name);
return (value != null) ? value.toString() : defaultValue;
}
/**
* Returns the specified boolean
attribute.
*
* @param name the name of the attribute searched for.
* @param defaultValue the value returned if the attribute is not found.
* @return the boolean
value for the specified attribute or
* the default value if the attribute is not found.
*/
public boolean getAttribute(String name, boolean defaultValue)
throws XMLStreamException {
CharArray value = getAttribute(name);
return (value != null) ? value.toBoolean() : defaultValue;
}
/**
* Returns the specified char
attribute.
*
* @param name the name of the attribute searched for.
* @param defaultValue the value returned if the attribute is not found.
* @return the char
value for the specified attribute or
* the default value if the attribute is not found.
*/
public char getAttribute(String name, char defaultValue)
throws XMLStreamException {
CharArray value = getAttribute(name);
if (value == null)
return defaultValue;
if (value.length() != 1)
throw new XMLStreamException(
"Single character expected (read '" + value + "')");
return value.charAt(0);
}
/**
* Returns the specified byte
attribute. This method handles
* string formats that are used to represent octal and hexadecimal numbers.
*
* @param name the name of the attribute searched for.
* @param defaultValue the value returned if the attribute is not found.
* @return the byte
value for the specified attribute or
* the default value if the attribute is not found.
*/
public byte getAttribute(String name, byte defaultValue)
throws XMLStreamException {
CharArray value = getAttribute(name);
return (value != null) ? (byte) value.toInt() : defaultValue;
}
/**
* Returns the specified short
attribute. This method handles
* string formats that are used to represent octal and hexadecimal numbers.
*
* @param name the name of the attribute searched for.
* @param defaultValue the value returned if the attribute is not found.
* @return the short
value for the specified attribute or
* the default value if the attribute is not found.
*/
public short getAttribute(String name, short defaultValue)
throws XMLStreamException {
CharArray value = getAttribute(name);
return (value != null) ? (short) value.toInt() : defaultValue;
}
/**
* Returns the specified int
attribute. This method handles
* string formats that are used to represent octal and hexadecimal numbers.
*
* @param name the name of the attribute searched for.
* @param defaultValue the value returned if the attribute is not found.
* @return the int
value for the specified attribute or
* the default value if the attribute is not found.
*/
public int getAttribute(String name, int defaultValue)
throws XMLStreamException {
CharArray value = getAttribute(name);
return (value != null) ? value.toInt() : defaultValue;
}
/**
* Returns the specified long
attribute. This method handles
* string formats that are used to represent octal and hexadecimal numbers.
*
* @param name the name of the attribute searched for.
* @param defaultValue the value returned if the attribute is not found.
* @return the long
value for the specified attribute or
* the default value if the attribute is not found.
*/
public long getAttribute(String name, long defaultValue)
throws XMLStreamException {
CharArray value = getAttribute(name);
return (value != null) ? value.toLong() : defaultValue;
}
/**
* Returns the specified float
attribute.
*
* @param name the name of the attribute searched for.
* @param defaultValue the value returned if the attribute is not found.
* @return the float
value for the specified attribute or
* the default value if the attribute is not found.
*/
public float getAttribute(String name, float defaultValue)
throws XMLStreamException {
CharArray value = getAttribute(name);
return (value != null) ? value.toFloat() : defaultValue;
}
/**
* Returns the specified double
attribute.
*
* @param name the name of the attribute searched for.
* @param defaultValue the value returned if the attribute is not found.
* @return the double
value for the specified attribute or
* the default value if the attribute is not found.
*/
public double getAttribute(String name, double defaultValue)
throws XMLStreamException {
CharArray value = getAttribute(name);
return (value != null) ? value.toDouble() : defaultValue;
}
/**
* Returns the attribute of same type as the specified
* default value.
*
* @param name the name of the attribute.
* @param defaultValue the value returned if the attribute is not found.
* @return the parse value for the specified attribute or
* the default value if the attribute is not found.
*/
public T getAttribute(String name, T defaultValue)
throws XMLStreamException {
CharArray value = getAttribute(name);
if (value == null)
return defaultValue;
// Parses attribute value.
Class> type = defaultValue.getClass();
TextFormat> format = TextContext.getFormat(type);
if (format == null)
throw new XMLStreamException("No TextFormat defined for "
+ type);
return (T) format.parse(value);
}
// Sets XML binding.
void setBinding(XMLBinding xmlBinding) {
_binding = xmlBinding;
}
// Sets XML reference resolver.
void setReferenceResolver(XMLReferenceResolver xmlReferenceResolver) {
_referenceResolver = xmlReferenceResolver;
}
// Resets for reuse.
void reset() {
_binding = XMLBinding.DEFAULT;
_isReaderAtNext = false;
_reader.reset();
_referenceResolver = null;
}
}
/**
* This class represents an output XML element (marshalling).
*/
@SuppressWarnings("unchecked")
public static final class OutputElement {
/**
* Holds the stream writer.
*/
final XMLStreamWriterImpl _writer = new XMLStreamWriterImpl();
/**
* Holds the XML binding.
*/
private XMLBinding _binding;
/**
* Holds the reference resolver.
*/
private XMLReferenceResolver _referenceResolver;
/**
* Default constructor.
*/
OutputElement() {
reset();
}
/**
* Returns the StAX-like stream writer (provides complete control over
* the marshalling process).
*
* @return the stream writer.
*/
public XMLStreamWriter getStreamWriter() {
return _writer;
}
/**
* Adds the specified object or null
as an anonymous
* nested element of unknown type.
*
* @param obj the object added as nested element or null
.
*/
public void add(Object obj) throws XMLStreamException {
if (obj == null) {
_writer.writeEmptyElement(NULL);
return;
}
// Writes start element.
Class> cls = obj.getClass();
_binding.writeClass(cls, _writer, false);
// Checks if reference written.
XMLFormat