/*
* Javolution - Java(TM) Solution for Real-Time and Embedded Systems
* Copyright (C) 2005 - 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 javolution37.javolution.xml;
import java.io.IOException;
import org.xml.sax.SAXException;
import java.lang.CharSequence;
import java.lang.IllegalStateException;
import java.util.NoSuchElementException;
import javolution37.javolution.lang.Text;
import javolution37.javolution.lang.TextBuilder;
import javolution37.javolution.lang.TypeFormat;
import javolution37.javolution.util.FastComparator;
import javolution37.javolution.util.FastList;
import javolution37.javolution.util.FastMap;
import javolution37.javolution.util.FastTable;
import javolution37.javolution.xml.pull.XmlPullParser;
import javolution37.javolution.xml.pull.XmlPullParserException;
import javolution37.javolution.xml.pull.XmlPullParserImpl;
import javolution37.javolution.xml.sax.Attributes;
import javolution37.javolution.xml.sax.ContentHandler;
/**
* This class represents a XML element. Instances of this class are made
* available only during the XML serialization/deserialization process.
*
* During serialization, {@link XmlFormat#format
* XmlFormat.format(XmlElement)} is used to represent the Java objects
* into XML.
*
*
During deserialization, {@link XmlFormat#format
* XmlFormat.parse(XmlElement)} is used to restore the objects from their
* XML representations.
*
* @author Jean-Marie Dautelle
* @version 3.6, October 13, 2005
*/
public final class XmlElement {
/**
* Holds the content handler during serialization.
*/
ContentHandler _formatHandler;
/**
* Holds the pull parser during deserialization.
*/
final XmlPullParserImpl _parser;
/**
* Holds the corresponding object.
*/
private Object _object;
/**
* Holds the class of the corresponding object.
*/
private Class _objectClass;
/**
* Indicates if the parser is currently positioned on the next element.
*/
private boolean _isParserAtNext;
/**
* Indicates if the parser is on a closure element (e.g. end-tag).
*/
private boolean _isClosure;
/**
* Holds this element attributes when formatting.
*/
final FormatAttributes _formatAttributes;
/**
* Holds intermediate text builder instance
*/
private final TextBuilder _textBuilder = new TextBuilder();
/**
* Holds the current element qualified name when formatting.
*/
private CharSequence _elemName;
/**
* Holds the package prefixes (prefix/package String pairs).
*/
final FastTable _packagePrefixes = new FastTable();
/**
* Indicates if cross references are enabled.
*/
boolean _areReferencesEnabled;
/**
* Indicates if references are expanded.
*/
boolean _expandReferences = false;
/**
* Indicates if class identifers are enabled.
*/
boolean _isClassIdentifierEnabled;
/**
* Maps unique identifiers to objects (deserialization).
*/
private final FastMap _idToObject;
/**
* Maps objects to unique identifiers (serialization).
*/
private final FastMap _objectToId;
/**
* Olds stack of formatted object in order to detect circular references.
*/
private final FastTable _formatStack;
/**
* Creates a new xml element.
*
* @param parser the pull parser or null when formatting.
*/
XmlElement(XmlPullParserImpl parser) {
if (parser != null) { // Parsing.
_parser = parser;
_parseContents = new FastTable();
_formatAttributes = null;
_idToObject = new FastMap();
_objectToId = null;
_formatStack = null;
} else { // Formatting.
_parser = null;
_parseContents = null;
_formatAttributes = new FormatAttributes();
_idToObject = null;
_objectToId = new FastMap()
.setKeyComparator(FastComparator.IDENTITY);
_formatStack = (FastTable) new FastTable()
.setValueComparator(FastComparator.IDENTITY);
}
}
/**
* Returns the object corresponding to this xml element; this is the
* object which has been {@link XmlFormat#allocate allocated} by
* the xml format or using the public no-arg constructor
* of {@link #objectClass()}.
*
* @return the (uninitialized) object corresponding to this xml element.
*/
public T object() throws XmlException {
if (_object == null) {
if (_objectClass == null)
throw new XmlException(
"Object type unknown, cannot use reflection");
try {
_object = _objectClass.newInstance();
} catch (IllegalAccessException e2) {
throw new XmlException(_objectClass
+ " default constructor inaccessible");
} catch (InstantiationException e3) {
throw new XmlException(_objectClass
+ " default constructor throws an exception");
}
}
return ( T ) _object;
}
/**
* Returns the Java(tm) class corresponding to this XML element;
* the class is identified by the tag name of this xml element or the
* "j:class" attribute when present.
*
* @return this XML element's corresponding class.
*/
public Class objectClass() {
return _objectClass;
}
///////////////////
// Serialization //
///////////////////
/**
* Returns the content handler used during serialization (typically
* a {@link javolution37.javolution.xml.sax.WriterHandler WriterHandler}).
*
* @return the content handler receiving the SAX-2 events.
*/
public ContentHandler formatter() {
return _formatHandler;
}
/**
* Adds the specified object as an anonymous nested element of unknown type.
*
* @param obj the object added as nested element or null.
*/
public void add(Object obj) {
try {
// Adds null value as NULL
if (obj == null) {
obj = XmlFormat.NULL;
}
// Flushes outer start element if any.
if (_elemName != null)
flushStart();
// Checks for Character Data
if (obj instanceof CharacterData) {
CharacterData charData = (CharacterData) obj;
_formatHandler.characters(charData.getChars(), charData.offset(),
charData.length());
return;
}
// Checks if content name set.
Class cls = _objectClass = obj.getClass();
XmlFormat xmlFormat = XmlFormat.getInstance(cls);
String defaultName = xmlFormat.defaultName();
if (defaultName != null) {
_objectClass = obj.getClass();
String alias = XmlFormat.aliasFor(_objectClass);
if (_isClassIdentifierEnabled) {
_formatAttributes.addAttribute("j:class",
(alias != null) ? toCharSeq(alias)
: toCharSeq(_objectClass.getName()));
}
CharSequence elemName = _elemName = toCharSeq(defaultName);
format(obj, xmlFormat);
if (_elemName != null)
flushStart();
_formatHandler.endElement(Text.EMPTY, elemName, elemName);
return;
}
// Formats the specified object using classname as element name.
CharSequence elemName = _elemName = qNameFor(cls);
format(obj, xmlFormat);
if (_elemName != null)
flushStart();
if (elemName == _textBuilder) { // Mutable, likely lost.
elemName = qNameFor(cls);
}
_formatHandler.endElement(Text.EMPTY, elemName, elemName);
} catch (SAXException e) {
throw new XmlException(e);
}
}
/**
* Adds the specified object as a named nested element of unknown type
* (null objects are ignored). The nested xml element
* will contain a "j:class" attribute identifying the object type.
*
* @param obj the object added as nested element or null.
* @param qName the qualified name of the nested element.
*/
public void add(Object obj, String qName) {
if (obj == null)
return;
try {
// Flushes outer start element if any.
if (_elemName != null)
flushStart();
// Formats the specified object.
_objectClass = obj.getClass();
String alias = XmlFormat.aliasFor(_objectClass);
if (_isClassIdentifierEnabled) {
_formatAttributes.addAttribute("j:class",
(alias != null) ? toCharSeq(alias)
: toCharSeq(_objectClass.getName()));
}
CharSequence elemName = _elemName = toCharSeq(qName);
format(obj, XmlFormat.getInstance(_objectClass));
if (_elemName != null)
flushStart();
_formatHandler.endElement(Text.EMPTY, elemName, elemName);
} catch (SAXException e) {
throw new XmlException(e);
}
}
/**
* Adds the specified object as a named nested element of known type
* (null objects are ignored).
*
* @param obj the object added as nested element or null.
* @param qName the qualified name of the nested element.
* @param clazz the class identifying the xml format to use.
*/
public void add(Object obj, String qName, Class clazz) {
if (obj == null)
return;
try {
// Flushes outer start element if any.
if (_elemName != null)
flushStart();
// Formats the specified object.
CharSequence elemName = _elemName = toCharSeq(qName);
format(obj, XmlFormat.getInstance(clazz));
if (_elemName != null)
flushStart();
_formatHandler.endElement(Text.EMPTY, elemName, elemName);
} catch (SAXException e) {
throw new XmlException(e);
}
}
/**
* Creates a new attribute for this xml element.
*
* This method allows for custom attribute formatting. For example:[code]
* // Formats the color RGB value in hexadecimal.
* xml.newAttribute("color").append(_color.getRGB(), 16);
*
* // Formats the error using 4 digits.
* xml.newAttribute("error").append(error, 4, false, false);[/code]
*
* @param name the attribute name.
* @return the text builder to hold the attribute value.
*/
public TextBuilder newAttribute(String name) {
if ((_formatAttributes == null) || (_elemName == null))
attributeSettingError();
return _formatAttributes.newAttribute(name);
}
/**
* Sets the specified CharSequence attribute
* (null values are ignored).
*
* @param name the attribute name.
* @param value the attribute value or null.
*/
public void setAttribute(String name, CharSequence value) {
if ((_formatAttributes == null) || (_elemName == null))
attributeSettingError();
if (value == null)
return;
_formatAttributes.addAttribute(name, value);
}
/**
* Sets the specified String attribute
* (null values are ignored).
*
* @param name the attribute name.
* @param value the attribute value.
*/
public void setAttribute(String name, String value) {
if ((_formatAttributes == null) || (_elemName == null))
attributeSettingError();
if (value == null)
return;
CharSequence csqValue = toCharSeq(value);
_formatAttributes.addAttribute(name, csqValue);
}
/**
* Sets the specified boolean attribute.
*
* @param name the attribute name.
* @param value the boolean value for the specified attribute.
* @see #getAttribute(String, boolean)
*/
public void setAttribute(String name, boolean value) {
newAttribute(name).append(value);
}
/**
* Sets the specified int attribute.
*
* @param name the attribute name.
* @param value the int value for the specified attribute.
* @see #getAttribute(String, int)
*/
public void setAttribute(String name, int value) {
newAttribute(name).append(value);
}
/**
* Sets the specified long attribute.
*
* @param name the attribute name.
* @param value the long value for the specified attribute.
* @see #getAttribute(String, long)
*/
public void setAttribute(String name, long value) {
newAttribute(name).append(value);
}
/**
* Sets the specified float attribute.
*
* @param name the attribute name.
* @param value the float value for the specified attribute.
* @see #getAttribute(String, float)
/**/
public void setAttribute(String name, float value) {
newAttribute(name).append(value);
}
/**/
/**
* Sets the specified double attribute.
*
* @param name the attribute name.
* @param value the double value for the specified attribute.
* @see #getAttribute(String, double)
/**/
public void setAttribute(String name, double value) {
newAttribute(name).append(value);
}
/**/
////////////////////////
// Primitive Wrappers //
////////////////////////
/**
* Sets the specified Boolean attribute.
*
* @param name the name of the attribute.
* @param value the Boolean value for the specified attribute
* or null in which case the attribute is not set.
* @see #getAttribute(String, Boolean)
*/
public void setAttribute(String name, Boolean value) {
if (value == null)
return;
newAttribute(name).append(value.booleanValue());
}
/**
* Sets the specified Byte attribute.
*
* @param name the name of the attribute.
* @param value the Byte value for the specified attribute
* or null in which case the attribute is not set.
* @see #getAttribute(String, Byte)
*/
public void setAttribute(String name, Byte value) {
if (value == null)
return;
newAttribute(name).append(value.byteValue());
}
/**
* Sets the specified Short attribute.
*
* @param name the name of the attribute.
* @param value the Short value for the specified attribute
* or null in which case the attribute is not set.
* @see #getAttribute(String, Short)
*/
public void setAttribute(String name, Short value) {
if (value == null)
return;
newAttribute(name).append(value.shortValue());
}
/**
* Sets the specified Integer attribute.
*
* @param name the name of the attribute.
* @param value the Integer value for the specified attribute
* or null in which case the attribute is not set.
* @see #getAttribute(String, Integer)
*/
public void setAttribute(String name, Integer value) {
if (value == null)
return;
newAttribute(name).append(value.intValue());
}
/**
* Sets the specified Long attribute.
*
* @param name the name of the attribute.
* @param value the Long value for the specified attribute
* or null in which case the attribute is not set.
* @see #getAttribute(String, Long)
*/
public void setAttribute(String name, Long value) {
if (value == null)
return;
newAttribute(name).append(value.longValue());
}
/**
* Sets the specified Float attribute.
*
* @param name the name of the attribute.
* @param value the Float value for the specified attribute
* or null in which case the attribute is not set.
* @see #getAttribute(String, Float)
/**/
public void setAttribute(String name, Float value) {
if (value == null)
return;
newAttribute(name).append(value.floatValue());
}
/**/
/**
* Sets the specified Double attribute.
*
* @param name the name of the attribute.
* @param value the Double value for the specified attribute
* or null in which case the attribute is not set.
* @see #getAttribute(String, Double)
/**/
public void setAttribute(String name, Double value) {
if (value == null)
return;
newAttribute(name).append(value.doubleValue());
}
/**/
/**
* Removes the specified attribute.
*
* @param name the name of the attribute to remove.
*/
public void removeAttribute(String name) {
if ((_formatAttributes == null) || (_elemName == null))
attributeSettingError();
int index = _formatAttributes.getIndex(name);
if (index >= 0) {
_formatAttributes.remove(index);
}
}
/////////////////////
// Deserialization //
/////////////////////
/**
* Returns the pull parser used during deserialization.
*
* @return the pull parser.
*/
public XmlPullParser parser() {
return _parser;
}
/**
* Indicates if more nested elements can be read.
*
* @return true if more nested elements can be read;
* false otherwise.
*/
public boolean hasNext() {
if (!_isParserAtNext) {
nextToken();
_isParserAtNext = true;
}
return !_isClosure;
}
/**
* Returns the object corresponding to the next nested element.
*
* @return the next nested object.
* @throws java.util.NoSuchElementException if
* this.hasNext() == false
*/
public T getNext() {
try {
if (!hasNext())
throw new NoSuchElementException();
_isParserAtNext = false; // Move forward.
if (_parser.getEventType() == XmlPullParser.START_TAG) {
// Search for the object class.
CharSequence className = _parser.getSaxAttributes().getValue(
"j:class");
if (className == null) { // Element name as class name.
CharSequence uri = _parser.getNamespace();
CharSequence name = _parser.getName();
_objectClass = ((uri.length() > 0) && (!uri
.equals(ObjectWriter.JAVOLUTION_URI))) ? XmlFormat
.classFor(uri, name) : XmlFormat.classFor(name);
} else {
_objectClass = XmlFormat.classFor(className);
}
XmlFormat xmlFormat = XmlFormat.getInstance(_objectClass);
_parseContentIndex++;
Object obj = parse(xmlFormat);
_parseContentIndex--;
if (hasNext())
incompleteReadError();
_isParserAtNext = false; // Move forward.
return ( T ) obj;
} else { // Character Data.
CharacterData cdata = CharacterData.valueOf(_parser.getText());
return ( T ) cdata;
}
} catch (XmlPullParserException e) {
throw new XmlException(e);
}
}
/**
* Returns the object corresponding to the next nested element only
* if it has the specified qualified name.
*
* @param qName the nested element qualified name required.
* @return the next content object or null if the qName
* does not match.
* @throws XmlException if the specified object has no "j:class" attribute
* identifying the object type.
*/
public T get(String qName) {
try {
if (hasNext()
&& (_parser.getEventType() == XmlPullParser.START_TAG)
&& _parser.getQName().equals(qName)) {
_isParserAtNext = false; // Move forward.
CharSequence name = _parser.getSaxAttributes().getValue(
"j:class");
if (name == null)
throw new XmlException("\"j:class\" attribute missing");
_objectClass = XmlFormat.classFor(name);
XmlFormat xmlFormat = XmlFormat.getInstance(_objectClass);
_parseContentIndex++;
Object obj = parse(xmlFormat);
_parseContentIndex--;
if (hasNext())
incompleteReadError();
_isParserAtNext = false; // Move forward.
return ( T ) obj;
}
return null;
} catch (XmlPullParserException e) {
throw new XmlException(e);
}
}
/**
* Returns the object corresponding to the next nested element only
* if it has the specified qualified name; the object type is identified
* by the specified class parameter.
*
* @param qName the nested element qualified name required.
* @param clazz the class identifying the object to return.
* @return the next content object or null if the qName
* does not match.
*/
public T get(String qName, Class clazz) {
try {
if (hasNext()
&& (_parser.getEventType() == XmlPullParser.START_TAG)
&& _parser.getQName().equals(qName)) {
_isParserAtNext = false; // Move forward.
_objectClass = clazz;
XmlFormat xmlFormat = XmlFormat.getInstance(clazz);
_parseContentIndex++;
Object obj = parse(xmlFormat);
_parseContentIndex--;
if (hasNext())
incompleteReadError();
_isParserAtNext = false; // Move forward.
return ( T ) obj;
}
return null;
} catch (XmlPullParserException e) {
throw new XmlException(e);
}
}
/**
* Returns the attributes for this xml element (parsing or formatting).
*
* @return the attributes mapping.
*/
public Attributes getAttributes() {
if (_formatAttributes != null)
return _formatAttributes;
if (_isParserAtNext)
throw new XmlException(
"Attributes should be read before any content");
return _parser.getSaxAttributes();
}
/**
* Searches for the attribute having the specified name.
*
* @param name the qualified name of the attribute (qName).
* @return the value for the specified attribute or null
* if the attribute is not found.
*/
public CharSequence getAttribute(String name) {
return getAttributes().getValue(name);
}
/**
* Indicates if the specified attribute is present.
*
* @param name the qualified name of the attribute (qName).
* @return true if this xml element contains the specified
* attribute; false otherwise.
*/
public boolean isAttribute(String name) {
return getAttributes().getIndex(name) >= 0;
}
/**
* Returns the specified CharSequence 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 CharSequence getAttribute(String name, CharSequence defaultValue) {
CharSequence value = getAttributes().getValue(name);
return (value != null) ? value : defaultValue;
}
/**
* 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) {
CharSequence value = getAttributes().getValue(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) {
CharSequence value = getAttributes().getValue(name);
return (value != null) ? TypeFormat.parseBoolean(value) : 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) {
CharSequence value = getAttributes().getValue(name);
return (value != null) ? TypeFormat.parseInt(value) : 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) {
CharSequence value = getAttributes().getValue(name);
return (value != null) ? TypeFormat.parseLong(value) : 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) {
CharSequence value = getAttributes().getValue(name);
return (value != null) ? (float) TypeFormat.parseDouble(value)
: 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) {
CharSequence value = getAttributes().getValue(name);
return (value != null) ? TypeFormat.parseDouble(value) : defaultValue;
}
/**/
////////////////////////
// Primitive Wrappers //
////////////////////////
/**
* Searches for the specified Boolean attribute.
*
* @param name the name of the attribute.
* @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) {
CharSequence value = getAttributes().getValue(name);
return (value != null) ? new Boolean(TypeFormat.parseBoolean(value))
: defaultValue;
}
/**
* Searches for the specified Byte attribute.
*
* @param name the name of the attribute.
* @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) {
CharSequence value = getAttributes().getValue(name);
return (value != null) ? new Byte(TypeFormat.parseByte(value))
: defaultValue;
}
/**
* Searches for the specified Short attribute.
*
* @param name the name of the attribute.
* @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) {
CharSequence value = getAttributes().getValue(name);
return (value != null) ? new Short(TypeFormat.parseShort(value))
: defaultValue;
}
/**
* Searches for the specified Integer attribute.
*
* @param name the name of the attribute.
* @param defaultValue the value returned if the attribute is not found.
* @return the Integer value for the specified attribute or
* the default value if the attribute is not found.
*/
public Integer getAttribute(String name, Integer defaultValue) {
CharSequence value = getAttributes().getValue(name);
return (value != null) ? new Integer(TypeFormat.parseInt(value))
: defaultValue;
}
/**
* Searches for the specified Long attribute.
*
* @param name the name of the attribute.
* @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) {
CharSequence value = getAttributes().getValue(name);
return (value != null) ? new Long(TypeFormat.parseLong(value))
: defaultValue;
}
/**
* Searches for the specified Float attribute.
*
* @param name the name of the attribute.
* @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) {
CharSequence value = getAttributes().getValue(name);
return (value != null) ? new Float( TypeFormat.parseFloat(value) ) : defaultValue;
}
/**/
/**
* Searches for the specified Double attribute.
*
* @param name the name of the attribute.
* @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) {
CharSequence value = getAttributes().getValue(name);
return (value != null) ? new Double( TypeFormat.parseDouble(value) ) : defaultValue;
}
/**/
/**
* Flushes the start element with the its attributes.
*/
void reset() {
_elemName = null;
_isClosure = false;
_object = null;
_objectClass = null;
_textBuilder.reset();
if (_parser != null) { // Parsing.
_parser.reset();
for (int i = 0; i < _parseContents.size(); i++) {
FastList list = (FastList) _parseContents.get(i);
list.reset();
}
_parseContentIndex = 0;
_isParserAtNext = false;
_idToObject.clear();
} else { // Formatting.
_formatAttributes.reset();
_formatContent.reset();
_formatHandler = null;
_objectToId.clear();
_formatStack.clear();
}
}
/**
* Flushes the current start element.
*/
private void flushStart() throws SAXException {
_formatHandler.startElement(Text.EMPTY, _elemName, _elemName,
_formatAttributes);
_formatAttributes.reset();
_elemName = null;
}
/**
* Moves the parser to the next valid token.
*
* @return the current event.
*/
private int nextToken() {
try {
while (true) {
int event = _parser.nextToken();
switch (event) {
case XmlPullParser.START_TAG:
case XmlPullParser.CDSECT:
_isClosure = false;
return event;
case XmlPullParser.END_TAG:
case XmlPullParser.END_DOCUMENT:
_isClosure = true;
return event;
case XmlPullParser.TEXT:
if (!_parser.isWhitespace()) {
_isClosure = false;
return event;
}
break;
}
}
} catch (XmlPullParserException e) {
throw new XmlException(e);
} catch (IOException e) {
throw new XmlException(e);
}
}
/**
* Notifies there is a problem setting the attribute.
*/
private void attributeSettingError() {
if (_formatAttributes == null)
throw new XmlException("Attributes cannot be set during parsing");
if (_elemName == null)
throw new XmlException(
"Attributes should be set before adding nested elements");
throw new XmlException();
}
/**
* Notifies that an xml element has not been read fully.
*
* @throws XmlPullParserException
*/
private void incompleteReadError() throws XmlPullParserException {
if (_parser.getEventType() == XmlPullParser.START_TAG) {
throw new XmlException("Incomplete read error (nested "
+ _parser.getQName() + " has not been read)");
} else {
throw new XmlException("Incomplete read error (character data"
+ "has not been read)");
}
}
/**
* Returns the qualified name for the specified class.
*
* @param cls the class for which the qualified name is returned.
*/
private CharSequence qNameFor(Class cls) {
String alias = XmlFormat.aliasFor(cls);
String className = (alias != null) ? alias : cls.getName();
// Searches package prefixes.
String matchPfx = null;
String matchPkg = "";
for (int i = 0; i < _packagePrefixes.size();) {
String pfx = (String) _packagePrefixes.get(i++);
String pkg = (String) _packagePrefixes.get(i++);
if (className.startsWith(pkg)
&& (pkg.length() >= matchPkg.length())
&& ((alias == null) || (pfx.equals("j")))) {
// For aliases only javolution uri can be used.
matchPfx = pfx;
matchPkg = pkg;
}
}
if (matchPfx != null) {
_textBuilder.reset();
if (matchPfx.length() > 0) {
_textBuilder.append(matchPfx).append(':');
}
if (matchPkg.length() > 0) {
_textBuilder.append(className, matchPkg.length() + 1, className
.length());
} else {
_textBuilder.append(className);
}
return _textBuilder;
}
return toCharSeq(className);
}
/**
* Converts a String to a CharSequence (for J2ME compatibility)
*
* @param str the string to convert.
* @return the corresponding CharSequence instance.
*/
private static CharSequence toCharSeq(Object str) {
if (str instanceof CharSequence)
return (CharSequence) str;
return Text.valueOf((String) str);
}
/**
* Parses this xml element using the specified format.
*
* @param xmlFormat the xml format to use.
* @return the parsed object.
*/
private Object parse(XmlFormat xmlFormat) {
if (_areReferencesEnabled && (xmlFormat.identifier(false) != null)) {
// Checks if reference.
CharSequence ref = getAttribute(xmlFormat.identifier(true));
if (ref != null) { // Reference.
Object obj = _idToObject.get(ref);
if (obj == null)
throw new IllegalStateException("Reference " + ref
+ " not found.");
// Flushes/ignores content (if references have been expanded).
int depth = _parser.getDepth();
while ((nextToken() != XmlPullParser.END_TAG)
&& (_parser.getDepth() <= depth)) {
}
_isParserAtNext = true;
return obj;
}
// Not a reference.
Object obj = _object = xmlFormat.allocate(this);
CharSequence id = getAttribute(xmlFormat.identifier(false));
if (id != null) { // Identifier found.
if (obj != null) { // Stores reference now in case of circularity.
_idToObject.put(Text.valueOf(id), obj);
if (xmlFormat.parse(this) != obj)
throw new XmlException(
"Parse should return xml.object() when allocate(xml) != null");
return obj;
} else {
obj = xmlFormat.parse(this);
_idToObject.put(Text.valueOf(id), obj);
return obj;
}
} else { // Identifier not present, disables references for this object and its content.
_areReferencesEnabled = false;
obj = xmlFormat.parse(this);
_areReferencesEnabled = true;
return obj;
}
} else { // References disabled.
_object = null;
Object obj = _object = xmlFormat.allocate(this);
if (obj != null) {
if (xmlFormat.parse(this) != obj)
throw new XmlException(
"Parse should return xml.object() when allocate(xml) != null");
return obj;
} else {
return xmlFormat.parse(this);
}
}
}
/**
* Format the specified object to this xml element using the specified
* format.
*
* @param obj the object to format.
* @param xmlFormat the xml format to use.
*/
private void format(Object obj, XmlFormat xmlFormat) {
if (_areReferencesEnabled && (xmlFormat.identifier(false) != null)) {
// Checks if reference exists.
Text ref = (Text) _objectToId.get(obj);
if (ref != null) { // Reference.
setAttribute(xmlFormat.identifier(true), ref);
} else { // New identifier.
Text id = Text.valueOf(_objectToId.size());
_objectToId.put(obj, id);
setAttribute(xmlFormat.identifier(false), id);
}
if ((ref == null)
|| (_expandReferences && !_formatStack.contains(obj))) {
_formatStack.addLast(obj);
xmlFormat.format(obj, this);
_formatStack.removeLast();
}
return;
}
xmlFormat.format(obj, this);
}
/**
* @deprecated Formats should use {@link #getNext()} and {@link #hasNext()}
* to parse anonymous content and {@link #add(Object)}
* to serialize anonymous content.
*/
public FastList getContent() {
if (_parser != null) { // Parsing.
while (_parseContents.size() <= _parseContentIndex) {
_parseContents.add(new FastList());
}
FastList content = (FastList) _parseContents
.get(_parseContentIndex);
while (hasNext()) {
content.add(getNext());
}
return content;
} else { // Formatting.
return _formatContent;
}
}
private final FastList _formatContent = new FastList() {
public void addLast(Object obj) { // TBD: Put back final in FastList
XmlElement.this.add(obj);
}
};
private final FastTable _parseContents;
private int _parseContentIndex;
/**
* @deprecated Replaced by {@link #add(Object, String, Class)}
*/
public void add(Object obj, String qName, XmlFormat xmlFormat) {
if (obj == null)
return;
try {
// Flushes outer start element if any.
if (_elemName != null)
flushStart();
// Formats the specified object.
CharSequence elemName = _elemName = toCharSeq(qName);
format(obj, xmlFormat);
if (_elemName != null)
flushStart();
_formatHandler.endElement(Text.EMPTY, elemName, elemName);
} catch (SAXException e) {
throw new XmlException(e);
}
}
/**
* @deprecated Replaced by {@link #get(String, Class)}
*/
public T get(String qName, XmlFormat xmlFormat) {
try {
if (hasNext()
&& (_parser.getEventType() == XmlPullParser.START_TAG)
&& _parser.getQName().equals(qName)) {
_isParserAtNext = false; // Move forward.
_parseContentIndex++;
Object obj = parse(xmlFormat);
_parseContentIndex--;
if (hasNext())
incompleteReadError();
_isParserAtNext = false; // Move forward.
return ( T ) obj;
}
return null;
} catch (XmlPullParserException e) {
throw new XmlException(e);
}
}
}
Ads help maintain this website.