/*
Copyright (c) 2002-2008, Dennis M. Sosnoski.
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

 * Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
 * Neither the name of JiBX nor the names of its contributors may be used
   to endorse or promote products derived from this software without specific
   prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package org.jibx.runtime.impl;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;

import org.jibx.runtime.IBindingFactory;
import org.jibx.runtime.ICharacterEscaper;
import org.jibx.runtime.IExtensibleWriter;
import org.jibx.runtime.IMarshallable;
import org.jibx.runtime.IMarshaller;
import org.jibx.runtime.IMarshallingContext;
import org.jibx.runtime.IXMLWriter;
import org.jibx.runtime.JiBXException;

/**
 * JiBX serializer supplying convenience methods for marshalling. Most of these
 * methods are designed for use in code generated by the binding generator.
 *
 * @author Dennis M. Sosnoski
 */
public class MarshallingContext implements IMarshallingContext
{
    /** Fixed XML namespace. */
    public static final String XML_NAMESPACE = 
        "http://www.w3.org/XML/1998/namespace";
        
    /** Starting size for object stack. */
    private static final int INITIAL_STACK_SIZE = 20;
    
    /** Binding factory used to create this unmarshaller. */
    private IBindingFactory m_factory;
    
    /** Map from fully-qualified class name to index in internal tables. */
    private StringIntHashMap m_classIndexMap;
        
    /** Names of classes included in mapping definition. */
    private String[] m_classes;
    
    /** Index past end of last fixed marshaller class. */
    private int m_transientBase;
    
    /** Transient marshaller classes for mapping definition (<code>null</code>
     for mappings out of context). */
    private String[] m_transientMarshallerClasses;
    
    /** Marshallers for classes in mapping definition (lazy create of actual
     marshaller instances) */
    private IMarshaller[] m_marshallers;
    
    /** URIs for namespaces used in binding. */
    private String[] m_uris;
    
    /** Current marshalling stack depth. */
    private int m_stackDepth;
    
    /** Stack of objects being marshalled. */
    private Object[] m_objectStack;
    
    /** Indent character count per level. */
    private int m_indentCount;
    
    /** Character sequence for end of line. */
    private String m_newLine;
    
    /** Character used for indenting. */
    private char m_indentChar;
    
    /** Shared map from IDs to objects. This is not used directly by the
      marshalling code, but is available for user extensions (lazy create). */
    private HashMap m_idMap;
    
    /** Output document handler. */
    private IXMLWriter m_writer;
    
    /** Buffer used when writing to stream (lazy create, <code>null</code> if
     unused). */
    private OutByteBuffer m_byteBuffer;
    
    /** User context object (not used by JiBX, only for user convenience). */
    protected Object m_userContext;
    
    /**
     * Constructor.
     *
     * @param classes ordered array of class names included in mapping
     * definition (reference kept, must be constant)
     * @param mcs names of marshaller classes for indexes with fixed marshallers
     * (as opposed to mapping slots, which may be overridden; reference kept,
     * must be constant)
     * @param uris ordered array of URIs for namespaces used in binding (must
     * be constant; the value in position 0 must always be the empty string "",
     * and the value in position 1 must always be the XML namespace
     * "http://www.w3.org/XML/1998/namespace")
     * @param ifact binding factory creating this unmarshaller
     */
    public MarshallingContext(String[] classes, String[] mcs, String[] uris,
        IBindingFactory ifact) {
        m_classes = classes;
        m_marshallers = new IMarshaller[classes.length];
        m_transientBase = classes.length - mcs.length;
        m_transientMarshallerClasses = new String[classes.length-m_transientBase];
        m_uris = uris;
        m_objectStack = new Object[INITIAL_STACK_SIZE];
        m_indentCount = -1;
        m_indentChar = ' ';
        m_newLine = "\n";
        m_factory = ifact;
        if (ifact != null) {
            m_classIndexMap = ifact.getClassIndexMap();
        } else {
            m_classIndexMap = new StringIntHashMap();
        }
    }
    
    /**
     * Create character escaper for encoding.
     *
     * @param enc document output encoding, or <code>null</code> for default
     * @return character escaper for encoding
     * @throws JiBXException if error creating setting output
     */
    private ICharacterEscaper createEscaper(String enc) throws JiBXException {
        if (enc.equalsIgnoreCase("UTF-8") || enc.equalsIgnoreCase("UTF-16") ||
            enc.equalsIgnoreCase("UTF-16BE") ||
            enc.equalsIgnoreCase("UTF-16LE")) {
            return UTF8Escaper.getInstance();
        } else if (enc.equalsIgnoreCase("ISO-8859-1")) {
            return ISO88591Escaper.getInstance();
        } else if (enc.equalsIgnoreCase("US-ASCII")) {
            return USASCIIEscaper.getInstance();
        } else {
            throw new JiBXException
                ("No character escaper defined for encoding " + enc);
        }
    }
    
    /**
     * Set output stream with encoding and escaper. This forces handling of the
     * output stream to use the Java character encoding support with the
     * supplied escaper.
     *
     * @param outs stream for document data output
     * @param enc document output encoding, or <code>null</code> uses UTF-8
     * default
     * @param esc escaper for writing characters to stream
     * @throws JiBXException if error setting output
     */
    public void setOutput(OutputStream outs, String enc, ICharacterEscaper esc)
        throws JiBXException {
        try {
            
            // set UTF-8 encoding if not specified
            if (enc == null) {
                enc = "UTF-8";
            }
                
            // make sure the writer can work with an escaper
            if (!(m_writer instanceof GenericXMLWriter)) {
                m_writer = new GenericXMLWriter(m_uris);
                m_writer.setIndentSpaces(m_indentCount, m_newLine,
                     m_indentChar);
            }
            
            // handle encoding using standard libraries
            Writer writer = new BufferedWriter
                (new OutputStreamWriter(outs, enc));
            ((GenericXMLWriter)m_writer).setOutput(writer, esc);
            reset();
            
        } catch (IOException ex) {
            throw new JiBXException("Error setting output", ex);
        }
    }
    
    /**
     * Set output stream and encoding. This uses the standard escaper for the
     * specified encoding.
     *
     * @param outs stream for document data output
     * @param enc document output encoding, or <code>null</code> for default
     * @throws JiBXException if error creating setting output
     */
    public void setOutput(OutputStream outs, String enc) throws JiBXException {
        
        // set UTF-8 encoding if not specified
        if (enc == null) {
            enc = "UTF-8";
        }
        if ("UTF-8".equalsIgnoreCase(enc)) {
                
            // handle UTF-8 output to stream directly
            if (!(m_writer instanceof UTF8StreamWriter)) {
                if (m_byteBuffer == null) {
                    m_byteBuffer = new OutByteBuffer();
                }
                UTF8StreamWriter wrtr = new UTF8StreamWriter(m_uris);
                m_writer = wrtr;
                wrtr.setBuffer(m_byteBuffer);
                wrtr.setIndentSpaces(m_indentCount, m_newLine,
                     m_indentChar);
            }
            reset();
            m_byteBuffer.setOutput(outs);
                
        } else if ("ISO-8859-1".equalsIgnoreCase(enc)) {
                
            // handle ISO-8859-1 output to stream directly
            if (!(m_writer instanceof ISO88591StreamWriter)) {
                if (m_byteBuffer == null) {
                    m_byteBuffer = new OutByteBuffer();
                }
                ISO88591StreamWriter wrtr = new ISO88591StreamWriter(m_uris);
                m_writer = wrtr;
                wrtr.setBuffer(m_byteBuffer);
                wrtr.setIndentSpaces(m_indentCount, m_newLine,
                     m_indentChar);
            }
            reset();
            m_byteBuffer.setOutput(outs);
                
        } else {
            setOutput(outs, enc, createEscaper(enc));
        }
    }
    
    /**
     * Set output writer and escaper.
     *
     * @param outw writer for document data output
     * @param esc escaper for writing characters
     */
    public void setOutput(Writer outw, ICharacterEscaper esc) {
        if (!(m_writer instanceof GenericXMLWriter)) {
            m_writer = new GenericXMLWriter(m_uris);
            m_writer.setIndentSpaces(m_indentCount, m_newLine,
                 m_indentChar);
        }
        ((GenericXMLWriter)m_writer).setOutput(outw, esc);
        reset();
    }
    
    /**
     * Set output writer.
     *
     * @param outw writer for document data output
     */
    public void setOutput(Writer outw) {
        setOutput(outw, UTF8Escaper.getInstance());
    }

    /**
     * Get the writer being used for output.
     *
     * @return XML writer used for output
     */
    public IXMLWriter getXmlWriter() {
        return m_writer;
    }

    /**
     * Set the writer being used for output.
     *
     * @param xwrite XML writer used for output
     */
    public void setXmlWriter(IXMLWriter xwrite) {
        m_writer = xwrite;
    }
    
    /**
     * Get current nesting indent spaces. This returns the number of spaces used
     * to show indenting, if used.
     *
     * @return number of spaces indented per level, or negative if indentation
     * disabled
     */
    public int getIndent() {
        return m_indentCount;
    }
    
    /**
     * Set nesting indent spaces. This is advisory only, and implementations of
     * this interface are free to ignore it. The intent is to indicate that the
     * generated output should use indenting to illustrate element nesting.
     *
     * @param count number of spaces to indent per level, or disable
     * indentation if negative
     */
    public void setIndent(int count) {
        if (m_writer != null) {
            m_writer.setIndentSpaces(count, m_newLine, m_indentChar);
        }
        m_indentCount = count;
    }
    
    /**
     * Set nesting indentation. This is advisory only, and implementations of
     * this interface are free to ignore it. The intent is to indicate that the
     * generated output should use indenting to illustrate element nesting.
     *
     * @param count number of character to indent per level, or disable
     * indentation if negative (zero means new line only)
     * @param newline sequence of characters used for a line ending
     * (<code>null</code> means use the single character '\n')
     * @param indent whitespace character used for indentation
     */
    public void setIndent(int count, String newline, char indent) {
        if (m_writer != null) {
            m_writer.setIndentSpaces(count, newline, indent);
        }
        m_indentCount = count;
        m_newLine = newline;
        m_indentChar = indent;
    }

    /**
     * Initializes the context to use the same marshalled text destination and
     * parameters as another marshalling context. This method is designed for
     * use when an initial context needs to create and invoke a secondary
     * context (generally from a different binding) in the course of an
     * marshalling operation. Note that once the secondary context has been used
     * it's generally necessary to do a {@link XMLWriterBase#flush()} operation
     * on the writer used by the that context before resuming output on the
     * parent.
     *
     * @param parent context supplying target for marshalled document text
     * @throws IOException on error writing output
     */
    public void setFromContext(MarshallingContext parent) throws IOException {
        reset();
        m_indentCount = parent.m_indentCount;
        m_newLine = parent.m_newLine;
        m_indentChar = parent.m_indentChar;
        if (parent.m_writer instanceof IExtensibleWriter) {
            IExtensibleWriter base = (IExtensibleWriter)parent.m_writer;
            base.flush();
            m_writer = base.createChildWriter(m_uris);
        } else if (parent.m_writer instanceof StAXWriter) {
            m_writer = ((StAXWriter)parent.m_writer).createChildWriter(m_uris);
        } else {
            m_writer = parent.m_writer;
        }
    }
    
    /**
     * Reset to initial state for reuse. The context is serially reusable,
     * as long as this method is called to clear any retained state information
     * between uses. It is automatically called when output is set.
     */
    public void reset() {
        if (m_writer != null) {
            m_writer.reset();
        }
        for (int i = 0; i < m_marshallers.length; i++) {
            m_marshallers[i] = null;
        }
        for (int i = 0; i < m_transientMarshallerClasses.length; i++) {
            m_transientMarshallerClasses[i] = null;
        }
        for (int i = 0; i < m_objectStack.length; i++) {
            m_objectStack[i] = null;
        }
        m_stackDepth = 0;
    }

    /**
     * Return the binding factory used to create this unmarshaller.
     *
     * @return binding factory
     */
    public IBindingFactory getFactory() {
        return m_factory;
    }
    
    /**
     * Get namespace URIs for mapping. This gets the full ordered array of
     * namespaces known in the binding used for this marshalling, where the
     * index number of each namespace URI is the namespace index used to lookup
     * the prefix when marshalling a name in that namespace. The returned array
     * must not be modified.
     *
     * @return array of namespaces
     */
    public String[] getNamespaces() {
        return m_uris;
    }
    
    /**
     * Start document. This can only be validly called immediately following 
     * one of the set output methods; otherwise the output document will be
     * corrupt.
     *
     * @param enc document encoding, <code>null</code> if not specified
     * @param alone standalone document flag, <code>null</code> if not
     * specified
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public void startDocument(String enc, Boolean alone) throws JiBXException {
        try {
            String atext = null;
            if (alone != null) {
                atext = alone.booleanValue() ? "yes" : "no";
            }
            m_writer.writeXMLDecl("1.0", enc, atext);
        } catch (IOException ex) {
            throw new JiBXException("Error writing marshalled document", ex);
        }
    }
    
    /**
     * Start document with output stream and encoding. The effect is the same
     * as from first setting the output stream and encoding, then making the
     * call to start document.
     *
     * @param enc document encoding, <code>null</code> if not specified
     * @param alone standalone document flag, <code>null</code> if not
     * specified
     * @param outs stream for document data output
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public void startDocument(String enc, Boolean alone, OutputStream outs)
        throws JiBXException {
        setOutput(outs, enc);
        startDocument(enc, alone);
    }
    
    /**
     * Start document with writer. The effect is the same as from first
     * setting the writer, then making the call to start document.
     *
     * @param enc document encoding, <code>null</code> if not specified
     * @param alone standalone document flag, <code>null</code> if not
     * specified
     * @param outw writer for document data output
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public void startDocument(String enc, Boolean alone, Writer outw)
        throws JiBXException {
        setOutput(outw);
        startDocument(enc, alone);
    }
    
    /**
     * End document. Finishes all output and closes the document. Note that if
     * this is called with an imcomplete marshalling the result will not be
     * well-formed XML.
     *
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public void endDocument() throws JiBXException {
        try {
            m_writer.close();
        } catch (IOException ex) {
            throw new JiBXException("Error writing marshalled document", ex);
        }
    }

    /**
     * Build name with optional namespace. Just returns the appropriate
     * name format.
     *
     * @param index namespace URI index number
     * @param name local name part of name
     * @return formatted name string
     */
    public String buildNameString(int index, String name) {
        String ns = m_writer.getNamespaceUri(index);
        if (ns == null || "".equals(ns)) {
            return "\"" + name + "\"";
        } else {
            return "\"{" + ns + "}" + name + "\"";
        }
    }
    
    /**
     * Generate start tag for element without attributes.
     *
     * @param index namespace URI index number
     * @param name element name
     * @return this context (to allow chained calls)
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public MarshallingContext startTag(int index, String name)
        throws JiBXException {
        try {
            m_writer.startTagClosed(index, name);
            return this;
        } catch (IOException ex) {
            throw new JiBXException("Error writing marshalled document", ex);
        }
    }
    
    /**
     * Generate start tag for element with attributes. This only opens the start
     * tag, allowing attributes to be added immediately following this call.
     *
     * @param index namespace URI index number
     * @param name element name
     * @return this context (to allow chained calls)
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public MarshallingContext startTagAttributes(int index, String name)
        throws JiBXException {
        try {
            m_writer.startTagOpen(index, name);
            return this;
        } catch (IOException ex) {
            throw new JiBXException("Error writing marshalled document", ex);
        }
    }
    
    /**
     * Generate text attribute. This can only be used following an open start
     * tag with attributes.
     *
     * @param index namespace URI index number
     * @param name attribute name
     * @param value text value for attribute (cannot be <code>null</code>)
     * @return this context (to allow chained calls)
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public MarshallingContext attribute(int index, String name, String value) 
        throws JiBXException {
        try {
            m_writer.addAttribute(index, name, value);
            return this;
        } catch (IOException ex) {
            throw new JiBXException("Error writing marshalled document", ex);
        } catch (Exception ex) {
            String text = buildNameString(index, name);
            if (value == null) {
                throw new JiBXException("null value for attribute " +
                    text + " from object of type " +
                    getStackTop().getClass().getName());

            } else {
                throw new JiBXException
                    ("Exception while marshalling attribute " + text, ex);
            }
        }
    }
    
    /**
     * Generate integer attribute. This can only be used following an open start
     * tag.
     *
     * @param index namespace URI index number
     * @param name attribute name
     * @param value integer value for attribute
     * @return this context (to allow chained calls)
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public MarshallingContext attribute(int index, String name, int value) 
        throws JiBXException {
        return attribute(index, name, Integer.toString(value));
    }
    
    /**
     * Generate enumeration attribute. The actual text to be written is obtained
     * by indexing into the supplied array of values. This can only be used
     * following an open start tag.
     *
     * @param index namespace URI index number
     * @param name attribute name
     * @param value integer enumeration value (zero-based)
     * @param table text values in enumeration
     * @return this context (to allow chained calls)
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public MarshallingContext attribute(int index, String name, int value,
        String[] table) throws JiBXException {
        try {
            return attribute(index, name, table[value]);
        } catch (ArrayIndexOutOfBoundsException ex) {
            throw new JiBXException("Enumeration value of " + value +
                " is outside to allowed range of 0 to " + table.length);
        }
    }
    
    /**
     * Close start tag with content to follow.
     *
     * @return this context (to allow chained calls)
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public MarshallingContext closeStartContent() throws JiBXException {
        try {
            m_writer.closeStartTag();
            return this;
        } catch (IOException ex) {
            throw new JiBXException("Error writing marshalled document", ex);
        }
    }
    
    /**
     * Close start tag with no content (empty tag).
     *
     * @return this context (to allow chained calls)
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public MarshallingContext closeStartEmpty() throws JiBXException {
        try {
            m_writer.closeEmptyTag();
            return this;
        } catch (IOException ex) {
            throw new JiBXException("Error writing marshalled document", ex);
        }
    }
    
    /**
     * Add text content to current element.
     *
     * @param value text element content
     * @return this context (to allow chained calls)
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public MarshallingContext content(String value) throws JiBXException {
        try {
            m_writer.writeTextContent(value);
            return this;
        } catch (IOException ex) {
            throw new JiBXException("Error writing marshalled document", ex);
        }
    }
    
    /**
     * Add integer content to current element.
     *
     * @param value integer element content
     * @return this context (to allow chained calls)
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public MarshallingContext content(int value) throws JiBXException {
        content(Integer.toString(value));
        return this;
    }
    
    /**
     * Add enumeration content to current element. The actual text to be
     * written is obtained by indexing into the supplied array of values.
     *
     * @param value integer enumeration value (zero-based)
     * @param table text values in enumeration
     * @return this context (to allow chained calls)
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public MarshallingContext content(int value, String[] table)
        throws JiBXException {
        try {
            content(table[value]);
            return this;
        } catch (ArrayIndexOutOfBoundsException ex) {
            throw new JiBXException("Enumeration value of " + value +
                " is outside to allowed range of 0 to " + table.length);
        }
    }
    
    /**
     * Generate end tag for element.
     *
     * @param index namespace URI index number
     * @param name element name
     * @return this context (to allow chained calls)
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public MarshallingContext endTag(int index, String name)
        throws JiBXException {
        try {
            m_writer.endTag(index, name);
            return this;
        } catch (IOException ex) {
            throw new JiBXException("Error writing marshalled document", ex);
        }
    }

    /**
     * Generate complete element with text content.
     *
     * @param index namespace URI index number
     * @param name element name
     * @param value text element content
     * @return this context (to allow chained calls)
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public MarshallingContext element(int index, String name, String value) 
        throws JiBXException {
        try {
            if (value.length() == 0) {
                m_writer.startTagOpen(index, name);
                m_writer.closeEmptyTag();
            } else {
                m_writer.startTagClosed(index, name);
                m_writer.writeTextContent(value);
                m_writer.endTag(index, name);
            }
            return this;
        } catch (IOException ex) {
            throw new JiBXException("Error writing marshalled document", ex);
        } catch (Exception ex) {
            String text = buildNameString(index, name);
            if (value == null) {
                throw new JiBXException("null value for element " +
                    text + " from object of type " +
                    getStackTop().getClass().getName());
            } else {
                throw new JiBXException
                    ("Exception while marshalling element " + text, ex);
            }
        }
    }
    
    /**
     * Generate complete element with integer content.
     *
     * @param index namespace URI index number
     * @param name element name
     * @param value integer element content
     * @return this context (to allow chained calls)
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public MarshallingContext element(int index, String name, int value) 
        throws JiBXException {
        return element(index, name, Integer.toString(value));
    }
    
    /**
     * Generate complete element with enumeration content. The actual text to be
     * written is obtained by indexing into the supplied array of values.
     *
     * @param index namespace URI index number
     * @param name element name
     * @param value integer enumeration value (zero-based)
     * @param table text values in enumeration
     * @return this context (to allow chained calls)
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public MarshallingContext element(int index, String name, int value,
        String[] table) throws JiBXException {
        try {
            return element(index, name, table[value]);
        } catch (ArrayIndexOutOfBoundsException ex) {
            throw new JiBXException("Enumeration value of " + value +
                " is outside to allowed range of 0 to " + table.length);
        }
    }
    
    /**
     * Write CDATA text to document.
     *
     * @param text content value text
     * @return this context (to allow chained calls)
     * @throws IOException on error writing to document
     */
    public MarshallingContext writeCData(String text) throws IOException {
        try {
            m_writer.writeCData(text);
            return this;
        } catch (NullPointerException e) {
            if (text == null) {
                throw new IOException
                    ("Null value writing CDATA from object of type " +
                    getStackTop().getClass().getName());
            } else {
                throw e;
            }
        }
    }
    
    /**
     * Write content value with character entity substitutions.
     *
     * @param text content value text
     * @return this context (to allow chained calls)
     * @throws IOException on error writing to document
     */
    public MarshallingContext writeContent(String text) throws IOException {
        try {
            m_writer.writeTextContent(text);
            return this;
        } catch (NullPointerException e) {
            if (text == null) {
                throw new IOException
                    ("Null value writing text content from object " +
                    getStackTop().getClass().getName());
            } else {
                throw e;
            }
        }
    }
    
    /**
     * Marshal all items in a collection. This variation is for generic
     * collections.
     *
     * @param col collection of items to be marshalled
     * @return this context (to allow chained calls)
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public MarshallingContext marshalCollection(Collection col) 
        throws JiBXException {
        Iterator iter = col.iterator();
        while (iter.hasNext()) {
            Object obj = iter.next();
            if (obj instanceof IMarshallable) {
                ((IMarshallable)obj).marshal(this);
            } else {
                throw new JiBXException
                    ("Unmarshallable object of class " + obj.getClass() +
                    " found in marshalling");
            }
        }
        return this;
    }
    
    /**
     * Marshal all items in a collection. This variation is for ArrayList
     * collections.
     *
     * @param col collection of items to be marshalled
     * @return this context (to allow chained calls)
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public MarshallingContext marshalCollection(ArrayList col) 
        throws JiBXException {
        for (int i = 0; i < col.size(); i++) {
            Object obj = col.get(i);
            if (obj instanceof IMarshallable) {
                ((IMarshallable)obj).marshal(this);
            } else {
                throw new JiBXException
                    ("Unmarshallable object of class " +
                     obj.getClass().getName() + " found in marshalling");
            }
        }
        return this;
    }
    
    /**
     * Marshal all items in a collection. This variation is for Vector
     * collections.
     *
     * @param col collection of items to be marshalled
     * @return this context (to allow chained calls)
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public MarshallingContext marshalCollection(Vector col) 
        throws JiBXException {
        for (int i = 0; i < col.size(); i++) {
            Object obj = col.elementAt(i);
            if (obj instanceof IMarshallable) {
                ((IMarshallable)obj).marshal(this);
            } else {
                throw new JiBXException
                    ("Unmarshallable object of class " +
                     obj.getClass().getName() + " found in marshalling");
            }
        }
        return this;
    }
    
    /**
     * Define marshalling for class. Adds the marshalling definition for a
     * particular mapping name.
     *
     * @param mapname mapping name associated with unmarshaller
     * @param name marshaller class name handling 
     * @throws JiBXException if unknown mapping name
     */
    public void addMarshalling(String mapname, String name)
        throws JiBXException {
        int index = m_classIndexMap.get(mapname);
        if (index < 0) {
            throw new JiBXException("No marshal mapping defined for class " + mapname);
        }
        m_transientMarshallerClasses[index-m_transientBase] = name;
    }
    
    /**
     * Undefine marshalling for element. Removes the marshalling
     * definition for a particular mapping name.
     *
     * @param mapname mapping name associated with unmarshaller
     * @throws JiBXException if unknown mapping name
     */
    public void removeMarshalling(String mapname) throws JiBXException {
        int index = m_classIndexMap.get(mapname);
        if (index < 0) {
            throw new JiBXException("No marshal mapping defined for class " + mapname);
        }
        m_transientMarshallerClasses[index-m_transientBase] = null;
        m_marshallers[index] = null;
    }
    
    /**
     * Generate start tag for element with namespaces. This creates the actual
     * start tag, along with any necessary namespace declarations. Previously
     * active namespace declarations are not duplicated. The tag is
     * left incomplete, allowing other attributes to be added.
     *
     * TODO: Handle nested default namespaces declarations, prefixes for outers
     *
     * @param index namespace URI index number
     * @param name element name
     * @param nums array of namespace indexes defined by this element (must
     * be constant, reference is kept until end of element)
     * @param prefs array of namespace prefixes mapped by this element (no
     * <code>null</code> values, use "" for default namespace declaration)
     * @return this context (to allow chained calls)
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public MarshallingContext startTagNamespaces(int index, String name,
        int[] nums, String[] prefs) throws JiBXException {
        try {
            m_writer.startTagNamespaces(index, name, nums, prefs);
            return this;
        } catch (IOException ex) {
            throw new JiBXException("Error writing marshalled document", ex);
        }
    }
    
    /**
     * Find the marshaller for a particular class index in the current context.
     *
     * @param mapname marshaller mapping name (generally the class name to be
     * handled, or abstract mapping type name)
     * @return marshalling handler for class
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public IMarshaller getMarshaller(String mapname) throws JiBXException {
        int index = m_classIndexMap.get(mapname);
        if (index < 0) {
            throw new JiBXException("No marshal mapping defined for class " + mapname);
        }
        if (m_marshallers[index] == null) {
            
            // load the marshaller class and create an instance
            Class clas;
            String mname = m_factory.getMarshallerClasses()[index];
            if (mname != null) {
                
                // get global marshaller class through factory
                clas = m_factory.getMarshallerClass(index);
                
            } else {
                
                // load transient marshaller class directly
                mname = m_transientMarshallerClasses[index-m_transientBase];
                if (mname == null) {
                    throw new JiBXException("No marshaller defined for class " + mapname);
                }
                clas = m_factory.loadClass(mname);
                
            }
            try {
                
                // make sure we have a class
                if (clas == null) {
                    throw new JiBXException("Unable to load marshaller class " + mname);
                }
                
                // create and cache an instance of the class
                IMarshaller m = (IMarshaller)clas.newInstance();
                m_marshallers[index] = m;
                
            } catch (JiBXException e) {
                throw e;
            } catch (Exception e) {
                throw new JiBXException("Unable to create marshaller of class " + mname + ":", e);
            }
        }
        return m_marshallers[index];
    }
    
    /**
     * Marshal document from root object. This internal method just verifies
     * that the object is marshallable, then calls the marshal method on the
     * object itself.
     *
     * @param root object at root of structure to be marshalled, which must have
     * a top-level mapping in the binding
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    protected void marshalRoot(Object root) throws JiBXException {
        if (root instanceof IMarshallable) {
            ((IMarshallable)root).marshal(this);
        } else {
            throw new JiBXException("Supplied root object of class " +
                root.getClass().getName() +
                " cannot be marshalled without top-level mapping");
        }
    }
    
    /**
     * Marshal document from root object without XML declaration. This can only
     * be validly called immediately following one of the set output methods;
     * otherwise the output document will be corrupt. The effect of this method
     * is the same as the sequence of a call to marshal the root object using
     * this context followed by a call to {@link #endDocument}.
     *
     * @param root object at root of structure to be marshalled, which must have
     * a top-level mapping in the binding
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public void marshalDocument(Object root) throws JiBXException {
        marshalRoot(root);
        endDocument();
    }
    
    /**
     * Marshal document from root object. This can only be validly called
     * immediately following one of the set output methods; otherwise the output
     * document will be corrupt. The effect of this method is the same as the
     * sequence of a call to {@link #startDocument}, a call to marshal the root
     * object using this context, and finally a call to {@link #endDocument}.
     *
     * @param root object at root of structure to be marshalled, which must have
     * a top-level mapping in the binding
     * @param enc document encoding, <code>null</code> if not specified
     * @param alone standalone document flag, <code>null</code> if not
     * specified
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public void marshalDocument(Object root, String enc, Boolean alone)
        throws JiBXException {
        startDocument(enc, alone);
        marshalRoot(root);
        endDocument();
    }
    
    /**
     * Marshal document from root object to output stream with encoding. The
     * effect of this method is the same as the sequence of a call to {@link
     * #startDocument}, a call to marshal the root object using this context,
     * and finally a call to {@link #endDocument}.
     *
     * @param root object at root of structure to be marshalled, which must have
     * a top-level mapping in the binding
     * @param enc document encoding, <code>null</code> if not specified
     * @param alone standalone document flag, <code>null</code> if not
     * specified
     * @param outs stream for document data output
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public void marshalDocument(Object root, String enc, Boolean alone,
        OutputStream outs) throws JiBXException {
        startDocument(enc, alone, outs);
        marshalRoot(root);
        endDocument();
    }
    
    /**
     * Marshal document from root object to writer. The effect of this method
     * is the same as the sequence of a call to {@link #startDocument}, a call
     * to marshal the root object using this context, and finally a call to
     * {@link #endDocument}.
     *
     * @param root object at root of structure to be marshalled, which must have
     * a top-level mapping in the binding
     * @param enc document encoding, <code>null</code> if not specified
     * @param alone standalone document flag, <code>null</code> if not
     * specified
     * @param outw writer for document data output
     * @throws JiBXException on any error (possibly wrapping other exception)
     */
    public void marshalDocument(Object root, String enc, Boolean alone,
        Writer outw) throws JiBXException {
        startDocument(enc, alone, outw);
        marshalRoot(root);
        endDocument();
    }
    
    /**
     * Use namespace indexes from a separate binding, as identified by that
     * binding's factory class name. The target binding must be a precompiled
     * base binding of the binding used to create this marshalling context,
     * either directly or by way of some other precompiled base binding(s).
     *
     * @param factname binding factory class name for binding defining namespaces
     */
    public void pushNamespaces(String factname) {
        Map tablemap = m_factory.getNamespaceTranslationTableMap();
        m_writer.pushTranslationTable((int[])tablemap.get(factname));
    }
    
    /**
     * End use of namespace indexes from a separate binding. This will undo the
     * effect of the most-recent call to {@link #pushNamespaces(String)},
     * restoring whatever namespace usage was in effect prior to that call.
     */
    public void popNamespaces() {
        m_writer.popTranslationTable();
    }
    
    /**
     * Get shared ID map. The ID map returned is not used directly by the
     * marshalling code, but is provided to support user extensions.
     *
     * @return ID map
     */
    public HashMap getIdMap() {
        if (m_idMap == null) {
            m_idMap = new HashMap();
        }
        return m_idMap;
    }
    
    /**
     * Set a user context object. This context object is not used directly by
     * JiBX, but can be accessed by all types of user extension methods. The
     * context object is automatically cleared by the {@link #reset()} method,
     * so to make use of this you need to first call the appropriate version of
     * the <code>setOutput()</code> method, then this method, and finally one of
     * the <code>marshalDocument</code> methods which uses the previously-set
     * output (not the ones which take a stream or writer as parameter, since
     * they call <code>setOutput()</code> themselves).
     * 
     * @param obj user context object, or <code>null</code> if clearing existing
     * context object
     * @see #getUserContext()
     */
    public void setUserContext(Object obj) {
        m_userContext = obj;
    }
    
    /**
     * Get the user context object.
     * 
     * @return user context object, or <code>null</code> if no context object
     * set
     * @see #setUserContext(Object)
     */
    public Object getUserContext() {
        return m_userContext;
    }

    /**
     * Push created object to marshalling stack. This must be called before
     * beginning the marshalling of the object. It is only called for objects
     * with structure, not for those converted directly to and from text.
     *
     * @param obj object being marshalled
     */
    public void pushObject(Object obj) {
        if (obj == null) {
            throw new IllegalStateException("Missing required object");
        }
        int depth = m_stackDepth;
        if (depth >= m_objectStack.length) {
            Object[] stack = new Object[depth*2];
            System.arraycopy(m_objectStack, 0, stack, 0, depth);
            m_objectStack = stack;
        }
        m_objectStack[depth] = obj;
        m_stackDepth++;
    }

    /**
     * Pop marshalled object from stack.
     *
     * @throws JiBXException if no object on stack
     */
    public void popObject() throws JiBXException {
        if (m_stackDepth > 0) {
            --m_stackDepth;
        } else {
            throw new JiBXException("No object on stack");
        }
    }
    
    /**
     * Get current marshalling object stack depth. This allows tracking
     * nested calls to marshal one object while in the process of
     * marshalling another object. The bottom item on the stack is always the
     * root object being marshalled.
     *
     * @return number of objects in marshalling stack
     */
    public int getStackDepth() {
        return m_stackDepth;
    }
    
    /**
     * Get object from marshalling stack. This stack allows tracking nested
     * calls to marshal one object while in the process of marshalling
     * another object. The bottom item on the stack is always the root object
     * being marshalled.
     *
     * @param depth object depth in stack to be retrieved (must be in the range
     * of zero to the current depth minus one).
     * @return object from marshalling stack
     */
    public Object getStackObject(int depth) {
        return m_objectStack[m_stackDepth-depth-1];
    }
    
    /**
     * Get top object on marshalling stack. This is safe to call even when no
     * objects are on the stack.
     *
     * @return object from marshalling stack, or <code>null</code> if none
     */
    public Object getStackTop() {
        if (m_stackDepth > 0) {
            return m_objectStack[m_stackDepth-1];
        } else {
            return null;
        }
    }
}