/*
 * Javolution - Java(TM) Solution for Real-Time and Embedded Systems
 * Copyright (C) 2006 - 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 java.lang.CharSequence;
import javolution.lang.Reflection;
import javolution.text.CharArray;
import javolution.text.Text;
import javolution.text.TextBuilder;
import javolution.text.TextFormat;
import javolution.xml.sax.Attributes;
import javolution.xml.stream.XMLStreamException;
import javolution.xml.stream.XMLStreamReader;
import javolution.xml.stream.XMLStreamReaderImpl;
import javolution.xml.stream.XMLStreamWriter;
import javolution.xml.stream.XMLStreamWriterImpl;

/**
 * <p> This class represents the format base class for XML serialization and
 *     deserialization.</p>
 *     
 * <p> Application classes typically define a default XML format for their 
 *     instances using protected static {@link XMLFormat} class members.
 *     Formats are inherited by sub-classes. For example:[code]
 *     
 *     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.
 *         protected static final XMLFormat<Graphic> GRAPHIC_XML = new XMLFormat<Graphic>(Graphic.class) {
 *              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]
 *    
 * <p> 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.</p>
 * 
 * <p> 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 <code>list.xml</code> document produced:[code]
 *     
 *     <MyList>
 *         <String value="John Doe"/>
 *         <Null/>
 *         <Map>
 *             <Key class="String" value="ONE"/>
 *             <Value class="Integer" value="1"/>
 *             <Key class="String" value="TWO"/>
 *             <Value class="Integer" value="2"/>
 *         </Map>
 *     </MyList>[/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]
 *     </p>
 *     
 * <p> <i>Note:</i> Any type for which a text format is 
 *    {@link TextFormat#getInstance known} can be represented as 
 *    a XML attribute.</p>
 * 
 * @author  <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
 * @version 5.4, December 1, 2009
 */
public abstract class XMLFormat <T>  {

    /**
     * Holds <code>null</code> representation.
     */
    private static final String NULL = "Null";

    /**
     * Holds the class associated to this format (static instances)
     * or <code>null</code> if format is unbound.
     */
    private final Class <T>  _class;

    /**
     * Defines the default XML format bound to the specified class.
     * If the specified class is <code>null</code> then the format is unbound
     * (unbound formats are used by custom {@link XMLBinding binding} instances).
     * The static binding is unique and can only be overriden by custom
     * {@link XMLBinding}. For example:[code]
     *    // Overrides default binding for java.util.Collection.
     *    class MyBinding extends XMLBinding {
     *        XMLFormat<Collection> collectionXML = new XMLFormat<Collection>(null) { ... }; // Unbound.
     *        public XMLFormat getFormat(Class cls) {
     *            if (Collection.isAssignableFrom(cls)) {
     *                return collectionXML; // Overrides default XML format.
     *            } else {
     *                return super.getFormat(cls);
     *            }
     *        }
     *    }[/code]
     * 
     * @param forClass the root class/interface to associate to this XML format
     *        or <code>null</code> if this format is not bound.
     * @throws IllegalArgumentException if a XMLFormat is already bound to 
     *         the specified class.
     */
    protected XMLFormat(Class <T>  forClass) {
        _class = forClass;
        if (forClass == null)
            return; // Dynamic format.
        Reflection.getInstance().setField(this, forClass, XMLFormat.class);
    }

    /**
     * <p> Returns the default format for the specified class/interface.
     *     If there no direct mapping for the specified class, the mapping
     *     for the specified class interfaces is searched, if none is found
     *     the mapping for the parents classes is searched, if still none is
     *     found the format for <code>java.lang.Object</code> is returned.</p>
     *
     * <p> A default xml format exists for the following predefined types:
     *     <code><ul>
     *       <li>java.lang.Object</li>
     *       <li>java.util.Collection</li>
     *       <li>java.util.Map</li>
     *    </ul></code>
     *    The default XML representation (java.lang.Object) consists of the
     *    of a "value" attribute holding its textual representation
     *    (see {@link TextFormat#getInstance}).</p>
     *
     * @return the class/interface bound to this format.
     */
    public static  <T>  XMLFormat <T>  getInstance(Class <? extends T>  forClass) {
        XMLFormat objectFormat = XMLBinding.OBJECT_XML; // Also forces initialization or XMLBinding.
        XMLFormat xmlFormat = (XMLFormat) Reflection.getInstance().getField(forClass, XMLFormat.class, true);
        return (xmlFormat != null) ? xmlFormat : objectFormat;
    }

    /**
     * Returns the class/interface statically bound to this format or 
     * <code>null</code> if none.
     * 
     * @return the class/interface bound to this format.
     */
    public final Class <T>  getBoundClass() {
        return _class;
    }

    /**
     * Indicates if the object serialized through this format can be referenced
     * to (default <code>true</code>). This method can be overriden to return
     * <code>false</code> if  serialized objects are manipulated "by value".
     *
     * @return <code>true</code> if serialized object may hold a reference;
     *         <code>false</code> 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 <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 <code>XMLElement</code> 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;

    /**
     * Returns textual information about this format.
     *
     * @return this format textual information.
     */
    public String toString() {
        Class boundClass = getBoundClass();
        return (boundClass != null)
                ? "Default XMLFormat for " + boundClass.getName()
                : "Dynamic XMLtFormat (" + this.hashCode() + ")";
    }

    /**
     * This class represents an input XML element (unmarshalling).
     */
    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 <code>true</code> if there is more XML element to be read; 
         *         <code>false</code> 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 <code>null</code>.
         * @throws XMLStreamException if <code>hasNext() == false</code>.
         */
        public <T>   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 ( T ) 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 <code>null</code>.
         */
        public <T>   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 ( T ) 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 <code>null</code>.
         * @return the next nested object or <code>null</code>.
         */
        public <T>   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 ( T ) 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 <code>null</code>.
         */
        public <T>   T  get(String name, Class <T>  cls)
                throws XMLStreamException {
            if (!hasNext()// Asserts isReaderAtNext == true
                    || !_reader.getLocalName().equals(name))
                return null;

            Object ref = readReference();
            if (ref != null)
                return ( T ) ref;

            return ( T ) 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 <code>null</code>.
         * @param cls the class identifying the format of the object to return.
         * @return the next nested object or <code>null</code>.
         */
        public <T>   T  get(String localName, String uri,
                Class <T>  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 ( T ) 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.
        private Object 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 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 <code>null</code>
         *         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, toCsq(name));
        }

        /**
         * Returns the specified <code>String</code> attribute.
         *
         * @param  name the name of the attribute.
         * @param  defaultValue a default value.
         * @return the value for the specified attribute or
         *         the <code>defaultValue</code> 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 <code>boolean</code> attribute.
         *
         * @param  name the name of the attribute searched for.
         * @param  defaultValue the value returned if the attribute is not found.
         * @return the <code>boolean</code> 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 <code>char</code> attribute.
         *
         * @param  name the name of the attribute searched for.
         * @param  defaultValue the value returned if the attribute is not found.
         * @return the <code>char</code> 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 <code>byte</code> 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 <code>byte</code> 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 <code>short</code> 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 <code>short</code> 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 <code>int</code> 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 <code>int</code> 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 <code>long</code> 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 <code>long</code> 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 <code>float</code> attribute.
         *
         * @param  name the name of the attribute searched for.
         * @param  defaultValue the value returned if the attribute is not found.
         * @return the <code>float</code> 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 <code>double</code> attribute.
         *
         * @param  name the name of the attribute searched for.
         * @param  defaultValue the value returned if the attribute is not found.
         * @return the <code>double</code> 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. The default value 
         * {@link javolution.text.TextFormat#getInstance TextFormat} is
         * used to parse the attribute 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>   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 = TextFormat.getInstance(type);
            if (!format.isParsingSupported())
                throw new XMLStreamException("No TextFormat instance 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).
     */
    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 <code>null</code> as an anonymous 
         * nested element of unknown type. 
         *
         * @param obj the object added as nested element or <code>null</code>.
         */
        public void add(Object obj) throws XMLStreamException {
            if (obj == null) {
                _writer.writeEmptyElement(toCsq(NULL));
                return;
            }

            // Writes start element.
            Class cls = obj.getClass();
            _binding.writeClass(obj.getClass(), _writer, false);

            // Checks if reference written.
            XMLFormat xmlFormat = _binding.getFormat(cls);
            if (xmlFormat.isReferenceable() && writeReference(obj))
                return;

            xmlFormat.write(obj, this);
            _writer.writeEndElement();
        }

        /**
         * Adds the specified object as a named nested element of unknown type
         * (<code>null</code> objects are ignored).
         * The nested XML element contains a class attribute identifying
         * the object type.
         *
         * @param obj the object added as nested element or <code>null</code>.
         * @param name the name of the nested element.
         */
        public void add(Object obj, String name) throws XMLStreamException {
            if (obj == null)
                return;

            // Writes start element.
            _writer.writeStartElement(toCsq(name));

            // Writes class attribute.
            Class cls = obj.getClass();
            _binding.writeClass(cls, _writer, true);

            // Checks if reference written.
            XMLFormat xmlFormat = _binding.getFormat(cls);
            if (xmlFormat.isReferenceable() && writeReference(obj))
                return;

            xmlFormat.write(obj, this);
            _writer.writeEndElement();
        }

        /**
         * Adds the specified object as a fully qualified nested element of 
         * unknown type (<code>null</code> objects are ignored). 
         * The nested XML element contains a class attribute identifying
         * the object type.
         *
         * @param obj the object added as nested element or <code>null</code>.
         * @param localName the local name of the nested element.
         * @param uri the namespace URI of the nested element.
         */
        public void add(Object obj, String localName, String uri)
                throws XMLStreamException {
            if (obj == null)
                return;

            // Writes start element.
            _writer.writeStartElement(toCsq(uri), toCsq(localName));

            // Writes class attribute.
            Class cls = obj.getClass();
            _binding.writeClass(cls, _writer, true);

            // Checks if reference written.
            XMLFormat xmlFormat = _binding.getFormat(cls);
            if (xmlFormat.isReferenceable() && writeReference(obj))
                return;

            xmlFormat.write(obj, this);
            _writer.writeEndElement();
        }

        /**
         * Adds the specified object as a named nested element of specified  
         * actual type (<code>null</code> objects are ignored).
         * The nested XML element does not contain any class attribute.
         *
         * @param obj the object added as nested element or <code>null</code>.
         * @param name the name of the nested element.
         * @param cls the class identifying the format of the specified object.
         */
        public <T>  void add( T  obj, String name, Class <T>  cls)
                throws XMLStreamException {
            if (obj == null)
                return;

            // Writes start element.
            _writer.writeStartElement(toCsq(name));

            // Checks if reference written.
            XMLFormat xmlFormat = _binding.getFormat(cls);
            if (xmlFormat.isReferenceable() && writeReference(obj))
                return;

            xmlFormat.write(obj, this);
            _writer.writeEndElement();
        }

        /**
         * Adds the specified object as a fully qualified nested element of
         * specified actual type (<code>null</code> objects are ignored). 
         * The nested XML element does not contain any class attribute.
         *
         * @param obj the object added as nested element or <code>null</code>.
         * @param localName the local name of the nested element.
         * @param uri the namespace URI of the nested element.
         * @param cls the class identifying the format of the specified object.
         */
        public <T>  void add( T  obj, String localName, String uri,
                Class <T>  cls) throws XMLStreamException {
            if (obj == null)
                return;

            // Writes start element.
            _writer.writeStartElement(toCsq(uri), toCsq(localName));

            // Checks if reference written.
            XMLFormat xmlFormat = _binding.getFormat(cls);
            if (xmlFormat.isReferenceable() && writeReference(obj))
                return;

            xmlFormat.write(obj, this);
            _writer.writeEndElement();
        }

        // Returns true if reference written.
        private boolean writeReference(Object obj) throws XMLStreamException {
            if ((_referenceResolver == null) || !_referenceResolver.writeReference(obj, this))
                return false;
            _writer.writeEndElement();
            return true; // Reference written.
        }

        /**
         * Adds the content of a text-only element (equivalent to {@link 
         * javolution.xml.stream.XMLStreamWriter#writeCharacters(CharSequence) 
         * getStreamWriter().writeCharacters(text)}).
         *
         * @param text the element text content or an empty sequence if none.
         */
        public void addText(CharSequence text) throws XMLStreamException {
            _writer.writeCharacters(text);
        }

        /**
         * Equivalent to {@link #addText(CharSequence)} 
         * (for J2ME compatibility).
         *
         * @param text the element text content or an empty sequence if none.
         */
        public void addText(String text) throws XMLStreamException {
            _writer.writeCharacters(toCsq(text));
        }

        /**
         * Sets the specified <code>CharSequence</code> attribute
         * (<code>null</code> values are ignored).
         *
         * @param  name the attribute name.
         * @param  value the attribute value or <code>null</code>.
         */
        public void setAttribute(String name, CharSequence value)
                throws XMLStreamException {
            if (value == null)
                return;
            _writer.writeAttribute(toCsq(name), value);
        }

        /**
         * Sets the specified <code>String</code> attribute
         * (<code>null</code> values are ignored).
         *
         * @param  name the attribute name.
         * @param  value the attribute value.
         */
        public void setAttribute(String name, String value)
                throws XMLStreamException {
            if (value == null)
                return;
            _writer.writeAttribute(toCsq(name), toCsq(value));
        }

        /**
         * Sets the specified <code>boolean</code> attribute.
         * 
         * @param  name the attribute name.
         * @param  value the <code>boolean</code> value for the specified attribute.
         */
        public void setAttribute(String name, boolean value)
                throws XMLStreamException {
            setAttribute(name, _tmpTextBuilder.clear().append(value));
        }
        private TextBuilder _tmpTextBuilder = new TextBuilder();

        /**
         * Sets the specified <code>char</code> attribute.
         * 
         * @param  name the attribute name.
         * @param  value the <code>char</code> value for the specified attribute.
         */
        public void setAttribute(String name, char value)
                throws XMLStreamException {
            setAttribute(name, (TextBuilder) _tmpTextBuilder.clear().append(
                    value));
        }

        /**
         * Sets the specified <code>byte</code> attribute.
         * 
         * @param  name the attribute name.
         * @param  value the <code>byte</code> value for the specified attribute.
         */
        public void setAttribute(String name, byte value)
                throws XMLStreamException {
            setAttribute(name, _tmpTextBuilder.clear().append(value));
        }

        /**
         * Sets the specified <code>short</code> attribute.
         * 
         * @param  name the attribute name.
         * @param  value the <code>short</code> value for the specified attribute.
         */
        public void setAttribute(String name, short value)
                throws XMLStreamException {
            setAttribute(name, _tmpTextBuilder.clear().append(value));
        }

        /**
         * Sets the specified <code>int</code> attribute.
         * 
         * @param  name the attribute name.
         * @param  value the <code>int</code> value for the specified attribute.
         */
        public void setAttribute(String name, int value)
                throws XMLStreamException {
            setAttribute(name, _tmpTextBuilder.clear().append(value));
        }

        /**
         * Sets the specified <code>long</code> attribute.
         * 
         * @param  name the attribute name.
         * @param  value the <code>long</code> value for the specified attribute.
         */
        public void setAttribute(String name, long value)
                throws XMLStreamException {
            setAttribute(name, _tmpTextBuilder.clear().append(value));
        }

        /**
         * Sets the specified <code>float</code> attribute.
         * 
         * @param  name the attribute name.
         * @param  value the <code>float</code> value for the specified attribute.
         */
        public void setAttribute(String name, float value)
                throws XMLStreamException {
            setAttribute(name, _tmpTextBuilder.clear().append(value));
        }

        /**
         * Sets the specified <code>double</code> attribute.
         * 
         * @param  name the attribute name.
         * @param  value the <code>double</code> value for the specified attribute.
         */
        public void setAttribute(String name, double value)
                throws XMLStreamException {
            setAttribute(name, _tmpTextBuilder.clear().append(value));
        }

        /**
         * Sets the specified attribute using its associated 
         * {@link javolution.text.TextFormat#getInstance TextFormat}.
         * 
         * @param  name the name of the attribute.
         * @param  value the <code>Boolean</code> value for the specified attribute
         *         or <code>null</code> in which case the attribute is not set.
         */
        public void setAttribute(String name, Object value)
                throws XMLStreamException {
            if (value == null)
                return;
            Class type = value.getClass();
            TextFormat format = TextFormat.getInstance(type);
            setAttribute(name, (TextBuilder) format.format(value,
                    _tmpTextBuilder.clear()));
        }

        // 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;
            _writer.reset();
            _writer.setRepairingNamespaces(true);
            _writer.setAutomaticEmptyElements(true);
            _referenceResolver = null;
        }
    }

    // For J2ME Compatibility.
    private static CharSequence toCsq(Object str) {
        /**/
        if (true) return (CharSequence) str;
        /**/
        return str == null ? null : Text.valueOf(str);
    }
}
