/**
 * Redistribution and use of this software and associated documentation ("Software"), with or
 * without modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain copyright statements and notices. Redistributions
 * must also contain a copy of this document.
 *
 * 2. 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.
 *
 * 3. The name "Exolab" must not be used to endorse or promote products derived from this Software
 * without prior written permission of Intalio, Inc. For written permission, please contact
 * info@exolab.org.
 *
 * 4. Products derived from this Software may not be called "Exolab" nor may "Exolab" appear in
 * their names without prior written permission of Intalio, Inc. Exolab is a registered trademark of
 * Intalio, Inc.
 *
 * 5. Due credit should be given to the Exolab Project (http://www.exolab.org/).
 *
 * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED 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 INTALIO, INC. OR ITS
 * 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.
 *
 * Copyright 1999-2004 (C) Intalio, Inc. All Rights Reserved.
 *
 * This file was originally developed by Keith Visco during the course of employment at Intalio Inc.
 * All portions of this file developed by Keith Visco after Jan 19 2005 are Copyright (C) 2005 Keith
 * Visco. All Rights Reserved.
 *
 * $Id$
 */
package org.exolab.castor.xml;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import java.util.StringTokenizer;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.castor.core.util.Base64Encoder;
import org.castor.core.util.HexDecoder;
import org.castor.core.util.Messages;
import org.castor.mapping.BindingType;
import org.castor.mapping.MappingUnmarshaller;
import org.castor.xml.InternalContext;
import org.castor.xml.XMLProperties;
import org.exolab.castor.mapping.ClassDescriptor;
import org.exolab.castor.mapping.CollectionHandler;
import org.exolab.castor.mapping.FieldHandler;
import org.exolab.castor.mapping.MapHandler;
import org.exolab.castor.mapping.MapItem;
import org.exolab.castor.mapping.Mapping;
import org.exolab.castor.mapping.MappingException;
import org.exolab.castor.mapping.MappingLoader;
import org.exolab.castor.mapping.handlers.MapHandlers;
import org.exolab.castor.mapping.loader.CollectionHandlers;
import org.exolab.castor.types.AnyNode;
import org.exolab.castor.util.SafeStack;
import org.exolab.castor.xml.descriptors.RootArrayDescriptor;
import org.exolab.castor.xml.descriptors.StringClassDescriptor;
import org.exolab.castor.xml.handlers.DateFieldHandler;
import org.exolab.castor.xml.handlers.EnumFieldHandler;
import org.exolab.castor.xml.util.AnyNode2SAX2;
import org.exolab.castor.xml.util.AttributeSetImpl;
import org.exolab.castor.xml.util.DocumentHandlerAdapter;
import org.exolab.castor.xml.util.SAX2DOMHandler;
import org.exolab.castor.xml.util.StaxEventHandler;
import org.exolab.castor.xml.util.StaxStreamHandler;
import org.exolab.castor.xml.util.XMLClassDescriptorAdapter;
import org.exolab.castor.xml.util.XMLClassDescriptorImpl;
import org.exolab.castor.xml.util.XMLFieldDescriptorImpl;
import org.w3c.dom.Node;
import org.xml.sax.ContentHandler;
import org.xml.sax.DocumentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;

import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLStreamWriter;

import javax.xml.transform.Result;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamResult;

/**
 * A Marshaller that serializes Java Object's to XML
 *
 * Note: This class is not thread safe, and not intended to be, so please create a new Marshaller
 * for each thread if it is to be used in a multithreaded environment.
 *
 * @author <a href="mailto:keith AT kvisco DOT com">Keith Visco</a>
 * @version $Revision$ $Date: 2006-04-13 06:47:36 -0600 (Thu, 13 Apr 2006) $
 */
public class Marshaller extends MarshalFramework {


  // ---------------------------/
  // - Private Class variables -/
  // ---------------------------/

  /**
   * Logger from commons-logging.
   */
  private static final Log LOG = LogFactory.getLog(Marshaller.class);

  /**
   * The CDATA type..uses for SAX attributes.
   **/
  private static final String CDATA = "CDATA";

  /**
   * Default prefix for use when creating namespace prefixes.
   **/
  private static final String DEFAULT_PREFIX = "ns";


  /**
   * Message name for a non sax capable serializer error.
   **/
  private static final String SERIALIZER_NOT_SAX_CAPABLE = "conf.serializerNotSaxCapable";

  /**
   * Namespace declaration for xml schema instance.
   */
  private static final String XSI_PREFIX = "xsi";

  /**
   * The xsi:type attribute.
   */
  private static final String XSI_TYPE = "xsi:type";

  /**
   * Namespace prefix counter.
   */
  private int _namespaceCounter = 0;

  /**
   * An instance of StringClassDescriptor.
   */
  private static final StringClassDescriptor STRING_CLASS_DESCRIPTOR = new StringClassDescriptor();

  // ----------------------------/
  // - Private member variables -/
  // ----------------------------/

  /**
   * A boolean to indicate whether or not we are marshalling as a complete document or not.
   **/
  private boolean _asDocument = true;

  /**
   * The depth of the sub tree, 0 denotes document level.
   */
  private int _depth = 0;

  /**
   * The output format to use with the serializer. This will be null if the user passed in their own
   * DocumentHandler.
   **/
  private OutputFormat _format = null;

  /**
   * The ContentHandler we are marshaling to.
   **/
  private ContentHandler _handler = null;

  /**
   * Indicates whether whether or not to use xsi:type declarations in the output.
   **/
  private boolean _marshalExtendedType = true;

  /**
   * The registered {@link MarshalListener} to receive notifications of pre- and post marshal for
   * each object in the tree being marshaled.
   **/
  private MarshalListener _marshalListener = null;

  /**
   * The name space stack.
   **/
  private NamespacesStack namespacesStack = new NamespacesStack();

  /**
   * Records Java packages being used during marshaling.
   **/
  private List<String> _packages = new ArrayList<String>();

  /**
   * A stack of parent objects...to prevent circular references from being marshaled.
   **/
  private Stack _parents = new SafeStack();

  /**
   * A list of ProcessingInstructions to output upon marshalling of the document.
   **/
  private List<ProcessingInstruction> _processingInstructions =
      new ArrayList<ProcessingInstruction>();

  /**
   * Name of the root element to use.
   */
  private String _rootElement = null;

  /**
   * A boolean to indicate keys from a map should be saved when necessary.
   */
  private boolean _saveMapKeys = true;

  /**
   * The serializer that is being used for marshalling. This may be null if the user passed in a
   * DocumentHandler.
   **/
  private Serializer _serializer = null;

  /**
   * A flag to allow suppressing namespaces.
   */
  private boolean _suppressNamespaces = false;

  /**
   * A flag to allow suppressing the xsi:type attribute.
   */
  private boolean _suppressXSIType = false;

  private boolean _useXSITypeAtRoot = false;

  /**
   * The set of optional top-level attributes set by the user.
   **/
  private AttributeSetImpl _topLevelAtts = new AttributeSetImpl();

  /**
   * The AttributeList which is to be used during marshalling, instead of creating a bunch of new
   * ones.
   */
  private AttributesImpl _attributes = new AttributesImpl();

  /**
   * The validation flag.
   */
  private boolean _validate = false;

  /**
   * Set of full class names of proxy interfaces. If the class to be marshalled implements one of
   * them the superclass will be marshalled instead of the class itself.
   */
  private final Set<String> _proxyInterfaces = new HashSet<String>();

  /**
   * Creates a new {@link Marshaller} with the given SAX {@link DocumentHandler}.
   *
   * @param handler the SAX {@link DocumentHandler} to "marshal" to.
   *
   * @throws IllegalArgumentException if the given {@link DocumentHandler} is null
   * @deprecate Please use {@link XMLContext#createMarshaller()} and
   *            {@link Marshaller#setDocumentHandler(DocumentHandler)} instead
   * 
   * @see {@link XMLContext#createMarshaller()}
   * @see {@link Marshaller#setDocumentHandler(DocumentHandler)}
   * @see XMLContext
   * 
   **/
  public Marshaller(final DocumentHandler handler) {
    super(null);
    checkNotNull(handler, "The given 'org.sax.DocumentHandler' instance is null.");

    setContentHandler(new DocumentHandlerAdapter(handler));
  }

  /**
   * Sets the given SAX {@link DocumentHandler} to 'marshal' into.
   *
   * @param handler the SAX {@link DocumentHandler} to "marshal" to.
   *
   * @throws IllegalArgumentException if the given {@link DocumentHandler} is null
   **/
  public void setDocumentHandler(final DocumentHandler handler) {
    checkNotNull(handler, "The given 'org.sax.DocumentHandler' instance is null.");

    setContentHandler(new DocumentHandlerAdapter(handler));
  }

  /**
   * Creates a new {@link Marshaller} with the given SAX {@link ContentHandler}.
   *
   * @param contentHandler the {@link ContentHandler} to "marshal" to.
   * @throws IllegalArgumentException if the gievn {@link ContentHandler} is null
   * @deprecate Please use {@link XMLContext#createMarshaller()} and
   *            {@link Marshaller#setContentHandler(ContentHandler)} instead
   * 
   * @see {@link XMLContext#createMarshaller()}
   * @see {@link Marshaller#setContentHandler(ContentHandler)}
   * @see XMLContext
   * 
   **/
  public Marshaller(final ContentHandler contentHandler) {
    super(null);
    checkNotNull(contentHandler, "The given 'org.sax.ContentHandler' is null.");

    setContentHandler(contentHandler);
  }

  /**
   * The one {@link Marshaller} constructor that is used by {@link XMLContext} which sets an
   * {@link InternalContext} that comes from outside. Writer or {@link ContentHandler} have to be
   * set in a second step.
   * 
   * @param internalContext the {@link InternalContext} to initialize the {@link Marshaller}
   *        instance with
   */
  public Marshaller(final InternalContext internalContext) {
    super(internalContext);
  }

  /**
   * Creates a default instance of Marshaller, where the sink needs to be set separately.
   */
  public Marshaller() {
    super(null);
  }

  /**
   * Creates a new {@link Marshaller} with the given writer.
   * 
   * @param out the {@link Writer} to serialise to.
   * @throws IllegalArgumentException if the given {@link Writer} is null
   * @throws IOException If the given {@link Writer} instance cannot be opened.
   * @deprecate Please use {@link XMLContext#createMarshaller()} and
   *            {@link Marshaller#setWriter(Writer)} instead
   * 
   * @see {@link XMLContext#createMarshaller()}
   * @see {@link Marshaller#setWriter(Writer)}
   * @see XMLContext
   * 
   **/
  public Marshaller(final Writer out) throws IOException {
    super(null);
    setWriter(out);
  }

  /**
   * Creates a new {@link Marshaller} with the given {@link XMLStreamWriter}.
   *
   * @param xmlStreamWriter the {@link XMLStreamWriter}
   * @throws IllegalArgumentException if the given {@link XMLStreamWriter} is null
   * @see {@link XMLContext#createMarshaller()}
   * @see {@link Marshaller#setXmlStreamWriter(javax.xml.stream.XMLStreamWriter)}
   * @see XMLContext
   *
   * @since 1.3.3
   */
  public Marshaller(XMLStreamWriter xmlStreamWriter) {
    super(null);
    setXmlStreamWriter(xmlStreamWriter);
  }

  /**
   * Creates a new {@link Marshaller} with the given {@link XMLEventWriter}.
   *
   * @param xmlEventWriter the {@link XMLEventWriter}
   * @throws IllegalArgumentException if the given {@link XMLEventWriter} is null
   * @see {@link XMLContext#createMarshaller()}
   * @see {@link Marshaller#setXmlEventWriter(javax.xml.stream.XMLEventWriter)}
   * @see XMLContext
   *
   * @since 1.3.3
   */
  public Marshaller(XMLEventWriter xmlEventWriter) {
    super(null);
    setXmlEventWriter(xmlEventWriter);
  }

  /**
   * Sets the java.io.Writer to be used during marshalling.
   * 
   * @param out The writer to use for marshalling
   *
   * @throws IllegalArgumentException if out is null
   * @throws IOException If there's a problem accessing the java.io.Writer provided
   */
  public void setWriter(final Writer out) throws IOException {
    checkNotNull(out, "The given 'java.io.Writer' instance is null.");

    configureSerializer(out);
  }

  /**
   * Sets the {@link Result} into which the output xml will be written. Currently this method
   * supports {@link DOMResult}, {@link SAXResult} and {@link StreamResult}.
   *
   * @param result the {@link Result} instance to set
   *
   * @throws IllegalArgumentException if the result is null or it is not supported
   *
   * @since 1.3.3
   */
  public void setResult(Result result) throws IOException {
    checkNotNull(result, "The given 'javax.xml.transform.Result' instance is null.");

    if (result instanceof DOMResult) {
      DOMResult domResult = (DOMResult) result;

      if (domResult.getNode() != null) {
        // sets the dom node
        setNode(domResult.getNode());
        return;
      }
    } else if (result instanceof SAXResult) {
      SAXResult saxResult = (SAXResult) result;

      if (saxResult.getHandler() != null) {
        // sets the content handler
        setContentHandler(saxResult.getHandler());
        return;
        // TODO what to do with lexical handler ?
      }
    } else if (result instanceof StreamResult) {
      StreamResult streamResult = (StreamResult) result;

      if (streamResult.getWriter() != null) {
        // sets the writer
        setWriter(streamResult.getWriter());
        return;
      } else if (streamResult.getOutputStream() != null) {
        // sets the output stream, wrapping it into a print writer instance
        setWriter(new PrintWriter(streamResult.getOutputStream()));
        return;
      }
    }

    throw new IllegalArgumentException(
        "The given 'javax.transofrm.xml.Result' is not supported, or were incorrectly instantiated.");
  }


  private void configureSerializer(Writer out) throws IOException {
    _serializer = getInternalContext().getSerializer();

    if (_serializer == null)
      throw new RuntimeException("Unable to obtain serializer");

    _serializer.setOutputCharStream(out);

    // -- Due to a Xerces Serializer bug that doesn't allow declaring
    // -- multiple prefixes to the same namespace, we use the old
    // -- DocumentHandler format and process namespaces ourselves
    _handler = new DocumentHandlerAdapter(_serializer.asDocumentHandler());

    if (_handler == null) {
      String err = Messages.format(SERIALIZER_NOT_SAX_CAPABLE, _serializer.getClass().getName());
      throw new RuntimeException(err);
    }
  }

  /**
   * Creates a new {@link Marshaller} for the given DOM {@link Node}.
   *
   * @param node the DOM {@link Node} to marshal into.
   *
   * @throws IllegalArgumentException if node is null
   *
   * @deprecate Please use {@link XMLContext#createMarshaller()} and
   *            {@link Marshaller#setNode(Node)} instead
   * 
   * @see {@link XMLContext#createMarshaller()}
   * @see {@link Marshaller#setNode(Node)}
   * @see XMLContext
   **/
  public Marshaller(final Node node) {
    super(null);
    checkNotNull(node, "The given 'org.w3c.dom.Node' instance is null.");

    setContentHandler(new DocumentHandlerAdapter(new SAX2DOMHandler(node)));
  }

  /**
   * Sets the W3C {@link Node} instance to marshal to.
   *
   * @param node the DOM {@link Node} to marshal into.
   *
   * @throws IllegalArgumentException if node is null
   **/
  public void setNode(final Node node) {
    checkNotNull(node, "The given 'org.w3c.dom.Node' instance is null.");

    setContentHandler(new DocumentHandlerAdapter(new SAX2DOMHandler(node)));
  }

  /**
   * Sets the {@link XMLStreamWriter} to use.
   *
   * @param xmlStreamWriter the {@link XMLStreamWriter} instance to use
   *
   * @throws IllegalArgumentException if the xmlStreamWriter is null
   *
   * @since 1.3.3
   */
  public void setXmlStreamWriter(XMLStreamWriter xmlStreamWriter) {
    checkNotNull(xmlStreamWriter, "The given 'java.xml.stream.XMLStreamWriter' instance is null.");

    setContentHandler(new StaxStreamHandler(xmlStreamWriter));
  }

  /**
   * Sets the {@link XMLEventWriter} to use.
   *
   * @param xmlEventWriter the {@link XMLEventWriter} instance to use
   *
   * @throws IllegalArgumentException if the xmlEventReader is null
   *
   * @since 1.3.3
   */
  public void setXmlEventWriter(XMLEventWriter xmlEventWriter) {
    checkNotNull(xmlEventWriter, "The given 'java.xml.stream.XMLEventWriter' instance is null.");

    setContentHandler(new StaxEventHandler(xmlEventWriter));
  }

  /**
   * To set the {@link InternalContext} to use, and to initialize {@link Marshaller} properties
   * linked to it.
   * 
   * @param internalContext the {@link InternalContext} to use
   */
  public void setInternalContext(final InternalContext internalContext) {
    super.setInternalContext(internalContext);
    deriveProperties();
  }


  /**
   * Derive class-level properties from {@link XMLProperties} as defined {@link InternalContext}.
   * This method will be called after a new {@link InternalContext} or a property has been set.
   * 
   * @link #setInternalContext(InternalContext)
   */
  private void deriveProperties() {
    _validate = getInternalContext().marshallingValidation();
    _saveMapKeys =
        getInternalContext().getBooleanProperty(XMLProperties.SAVE_MAP_KEYS).booleanValue();

    String prop = getInternalContext().getStringProperty(XMLProperties.PROXY_INTERFACES);
    if (prop != null) {
      StringTokenizer tokenizer = new StringTokenizer(prop, ", ");
      while (tokenizer.hasMoreTokens()) {
        _proxyInterfaces.add(tokenizer.nextToken());
      }
    }
  }

  /**
   * Adds the given processing instruction data to the set of processing instructions to output
   * during marshalling.
   *
   * @param target the processing instruction target
   * @param data the processing instruction data
   *
   * @throws IllegalArgumentException if target is null or empty string or data is null
   **/
  public void addProcessingInstruction(String target, String data) {

    checkNotEmpty(target, "The argument 'target' must not be null or empty.");
    checkNotNull(data, "The argument 'data' must not be null.");

    _processingInstructions.add(new ProcessingInstruction(target, data));
  } // -- addProcessingInstruction

  /**
   * Sets the document type definition for the serializer. Note that this method cannot be called if
   * you've passed in your own DocumentHandler.
   *
   * @param publicId the public identifier
   * @param systemId the system identifier
   */
  public void setDoctype(String publicId, String systemId) {

    if (_serializer != null) {
      if (_format == null) {
        _format = getInternalContext().getOutputFormat();
      }
      _format.setDoctype(publicId, systemId);
      // -- reset output format, this needs to be done
      // -- any time a change occurs to the format.
      _serializer.setOutputFormat(_format);

      setDocumentHandler();
    } else {
      String error = "doctype cannot be set if you've passed in " + "your own DocumentHandler";
      throw new IllegalStateException(error);
    }
  } // -- setDoctype

  /**
   * Sets whether or not to marshal as a document which includes the XML declaration, and if
   * necessary the DOCTYPE declaration. By default the Marshaller will marshal as a well formed XML
   * document including the XML Declaration.
   *
   * If the given boolean is true, the Marshaller will marshal as a well formed XML fragment (no XML
   * declaration or DOCTYPE).
   *
   * This method is basically the same as calling #setMarshalAsDocument(false);
   *
   * @param supressXMLDeclaration a boolean that when true includes that generated XML should not
   *        contain the XML declaration.
   * @see #setMarshalAsDocument
   */
  public void setSupressXMLDeclaration(boolean supressXMLDeclaration) {
    setMarshalAsDocument(!supressXMLDeclaration);
  } // -- setSupressXMLDeclaration

  /**
   * Sets whether or not to marshal as a document which includes the XML declaration, and if
   * necessary the DOCTYPE declaration. By default the Marshaller will marshal as a well formed XML
   * document including the XML Declaration.
   *
   * If the given boolean is false, the Marshaller will marshal as a well formed XML fragment (no
   * XML declaration or DOCTYPE).
   *
   * This method is basically the same as calling #setSupressXMLDeclaration(true);
   *
   * @param asDocument a boolean, when true, indicating to marshal as a complete XML document.
   * @see #setSupressXMLDeclaration
   */
  public void setMarshalAsDocument(boolean asDocument) {

    _asDocument = asDocument;

    if (_serializer != null) {

      if (_format == null) {
        _format = getInternalContext().getOutputFormat();
      }
      _format.setOmitXMLDeclaration(!asDocument);
      _format.setOmitDocumentType(!asDocument);

      // -- reset output format, this needs to be done
      // -- any time a change occurs to the format.
      _serializer.setOutputFormat(_format);

      setDocumentHandler();
    }

  } // -- setMarshalAsDocument

  /**
   * Sets the given mapping to be used by the marshalling Framework. If a resolver exists this
   * mapping will be added to the existing ClassDescriptorResolver. Otherwise a new
   * ClassDescriptorResolver will be created.
   *
   * @param mapping Mapping to using during marshalling.
   */
  public void setMapping(final Mapping mapping) throws MappingException {
    // if (_cdResolver == null) {
    // _cdResolver = (XMLClassDescriptorResolver) ClassDescriptorResolverFactory
    // .createClassDescriptorResolver(BindingType.XML);
    // }
    if ((getInternalContext() == null)
        || (getInternalContext().getXMLClassDescriptorResolver() == null)) {
      String message = "No internal context or no class descriptor in context.";
      LOG.warn(message);
      throw new IllegalStateException(message);
    }

    MappingUnmarshaller mum = new MappingUnmarshaller();
    MappingLoader resolver = mum.getMappingLoader(mapping, BindingType.XML);
    getInternalContext().getXMLClassDescriptorResolver().setMappingLoader(resolver);
  }

  /**
   * Sets an optional MarshalListener to recieve pre and post marshal notification for each Object
   * in the tree. MarshalListener is only for complex objects that map into elements, simpleTypes
   * and types that map into attributes do not cause any pre and post event notifications. Current
   * only one (1) listener is allowed. If you need register multiple listeners, you will have to
   * create your own master listener that will forward the event notifications and manage the
   * multiple listeners.
   *
   * @param listener the MarshalListener to set.
   **/
  public void setMarshalListener(MarshalListener listener) {
    _marshalListener = listener;
  }

  /**
   * Sets the mapping for the given Namespace prefix.
   * 
   * @param nsPrefix the namespace prefix
   * @param nsURI the namespace that the prefix resolves to
   *
   * @throws IllegalArgumentException if nsURI is null or empty string
   **/
  public void setNamespaceMapping(final String nsPrefix, final String nsURI) {
    checkNotEmpty(nsURI, "Namespace URI must be not null.");
    namespacesStack.addNamespace(nsPrefix, nsURI);
  }

  /**
   * Sets the name of the root element to use.
   *
   * @param rootElement The name of the root element to use.
   */
  public void setRootElement(final String rootElement) {
    _rootElement = rootElement;
  }

  /**
   * Returns the name of the root element to use
   * 
   * @return Returns the name of the root element to use
   */
  public String getRootElement() {
    return _rootElement;
  } // -- getRootElement

  /**
   * Set to True to declare the given namespace mappings at the root node. Default is False.
   * 
   * @param nsPrefixAtRoot
   * @deprecated
   */
  public void setNSPrefixAtRoot(boolean nsPrefixAtRoot) {
    // leaving for now...backward compatability
    // _nsPrefixAtRoot = nsPrefixAtRoot;
  }

  /**
   * Returns True if the given namespace mappings will be declared at the root node.
   * 
   * @return Returns True if the given namespace mappings will be declared at the root node.
   * @deprecated
   */
  public boolean getNSPrefixAtRoot() {
    return true;
  }

  /**
   * Returns the ClassDescriptorResolver for use during marshalling
   *
   * @return the ClassDescriptorResolver
   * @see #setResolver
   */
  public XMLClassDescriptorResolver getResolver() {

    // if (_cdResolver == null) {
    // _cdResolver = (XMLClassDescriptorResolver)
    // ClassDescriptorResolverFactory.createClassDescriptorResolver(BindingType.XML);
    // }
    if ((getInternalContext() == null)
        || (getInternalContext().getXMLClassDescriptorResolver() == null)) {
      String message = "No internal context or no class descriptor in context.";
      LOG.warn(message);
      throw new IllegalStateException(message);
    }
    return getInternalContext().getXMLClassDescriptorResolver();

  } // -- getResolver

  /**
   * Sets the ClassDescriptorResolver to use during marshalling.
   *
   * <BR />
   * <B>Note:</B> This method will nullify any Mapping currently being used by this Marshaller
   *
   * @param cdr the ClassDescriptorResolver to use
   * @see #setMapping
   * @see #getResolver
   */
  public void setResolver(final XMLClassDescriptorResolver cdr) {

    if (cdr != null) {
      getInternalContext().setXMLClassDescriptorResolver(cdr);
      // _cdResolver = cdr;
    }

  } // -- setResolver

  /**
   * Sets whether or not to validate the object model before marshalling. By default validation is
   * enabled. This method is really for debugging. I do not recommend turning off validation, since
   * you could marshal a document, which you can then not unmarshal. If you know the object model is
   * guaranteed to be valid, disabling validation will improve performace.
   *
   * @param validate the boolean indicating whether or not to validate the object model before
   *        marshalling.
   **/
  public void setValidation(boolean validate) {
    _validate = validate;
  } // -- setValidation

  public boolean getValidation() {
    return _validate;
  }

  /**
   * If True the marshaller will use the 'xsi:type' attribute to marshall a field value that
   * extended the defined field type. Default is True.
   */
  public void setMarshalExtendedType(boolean marshalExtendedType) {
    _marshalExtendedType = marshalExtendedType;
  } // -- setMarshalExtendedType


  /**
   * If True the marshaller will use the 'xsi:type' attribute to marshall a field value that
   * extended the defined field type. Default is True.
   * 
   * @return If True the marshaller will use the 'xsi:type' attribute to marshall a field value that
   *         extended the defined field type. Default is True.
   */
  public boolean getMarshalExtendedType() {
    return _marshalExtendedType;
  } // -- setMarshallExtendedType

  /**
   * Marshals the given Object as XML using the given writer.
   *
   * @param object The Object to marshal.
   * @param out The writer to marshal to.
   * @exception org.exolab.castor.xml.MarshalException
   * @exception org.exolab.castor.xml.ValidationException
   */
  public static void marshal(Object object, Writer out)
      throws MarshalException, ValidationException {
    try {
      staticMarshal(object, new Marshaller(out));
    } catch (IOException e) {
      throw new MarshalException(e);
    }
  } // -- marshal

  /**
   * Marshals the given Object as XML using the given DocumentHandler to send events to.
   *
   * @param object The Object to marshal.
   * @param handler The DocumentHandler to marshal to.
   * @exception org.exolab.castor.xml.MarshalException
   * @exception org.exolab.castor.xml.ValidationException
   */
  public static void marshal(Object object, DocumentHandler handler)
      throws MarshalException, ValidationException {
    staticMarshal(object, new Marshaller(handler));
  } // -- marshal

  /**
   * Marshals the given Object as XML using the given ContentHandler to send events to.
   *
   * @param object The Object to marshal.
   * @param handler The ContentHandler to marshal to.
   * @exception org.exolab.castor.xml.MarshalException
   * @exception org.exolab.castor.xml.ValidationException
   */
  public static void marshal(Object object, ContentHandler handler)
      throws MarshalException, ValidationException {
    staticMarshal(object, new Marshaller(handler));
  } // -- marshal

  /**
   * Marshals the given Object as XML using the given DOM Node to send events to.
   *
   * @param object The Object to marshal.
   * @param node The DOM Node to marshal to.
   * @exception org.exolab.castor.xml.MarshalException
   * @exception org.exolab.castor.xml.ValidationException
   */
  public static void marshal(Object object, Node node)
      throws MarshalException, ValidationException {
    staticMarshal(object, new Marshaller(node));
  } // -- marshal

  /**
   * Static helper method to marshal the given object using the Marshaller instance provided.
   *
   * @param object The Object to marshal.
   * @param marshaller The {@link Marshaller} to use for marshalling.
   * @throws MarshalException as thrown by marshal(Object)
   * @throws ValidationException as thrown by marshal(Object)
   */
  private static void staticMarshal(final Object object, final Marshaller marshaller)
      throws MarshalException, ValidationException {
    if (object == null) {
      throw new MarshalException("object must not be null");
    }

    if (LOG.isInfoEnabled()) {
      LOG.info("Marshaller called using one of the *static* "
          + " marshal(Object, *) methods. This will ignore any "
          + " mapping files as specified. Please consider switching to "
          + " using Marshaller instances and calling one of the" + " marshal(*) methods.");
    }

    marshaller.marshal(object);
  } // -- staticMarshal

  /**
   * Marshals the given Object as XML using the DocumentHandler for this Marshaller.
   *
   * @param object The Object to marshal.
   * @exception org.exolab.castor.xml.MarshalException
   * @exception org.exolab.castor.xml.ValidationException
   */
  public void marshal(Object object) throws MarshalException, ValidationException {
    if (object == null)
      throw new MarshalException("object must not be null");

    if (LOG.isDebugEnabled()) {
      LOG.debug("Marshalling " + object.getClass().getName());
    }

    if (object instanceof AnyNode) {
      try {
        AnyNode2SAX2.fireEvents((AnyNode) object, _handler, namespacesStack);
      } catch (SAXException e) {
        throw new MarshalException(e);
      }
    } else {
      validate(object);
      MarshalState mstate = new MarshalState(object, "root");
      if (_asDocument) {
        try {
          _handler.startDocument();
          // -- handle processing instructions
          for (int i = 0; i < _processingInstructions.size(); i++) {
            ProcessingInstruction pi = _processingInstructions.get(i);
            _handler.processingInstruction(pi.getTarget(), pi.getData());
          }
          marshal(object, null, _handler, mstate);
          _handler.endDocument();
        } catch (SAXException sx) {
          throw new MarshalException(sx);
        }
      } else {
        marshal(object, null, _handler, mstate);
      }
    }

  } // -- marshal

  /**
   * Marshals the given object, using the given descriptor and document handler.
   *
   * <BR/>
   * <B>Note:</B> <I> It is an error if this method is called with an AttributeDescriptor. </I>
   * 
   * @param descriptor the XMLFieldDescriptor for the given object
   * @param handler the DocumentHandler to marshal to
   * @exception org.exolab.castor.xml.MarshalException
   * @exception org.exolab.castor.xml.ValidationException during marshaling
   **/
  private void marshal(Object object, XMLFieldDescriptor descriptor, ContentHandler handler,
      final MarshalState mstate) throws MarshalException, ValidationException {


    if (object == null) {
      String err = "Marshaller#marshal: null parameter: 'object'";
      throw new MarshalException(err);
    }

    if (descriptor != null && descriptor.isTransient())
      return;

    // -- notify listener
    if (_marshalListener != null) {
      boolean toBeMarshalled = true;
      try {
        toBeMarshalled = _marshalListener.preMarshal(object);
      } catch (RuntimeException e) {
        LOG.error(
            "Invoking #preMarshal() on your custom MarshalListener instance caused the following problem:",
            e);
      }
      if (!toBeMarshalled) {
        return;
      }
    }

    // -- handle AnyNode
    if (object instanceof AnyNode) {
      try {
        AnyNode2SAX2.fireEvents((AnyNode) object, handler, namespacesStack);
      } catch (SAXException e) {
        throw new MarshalException(e);
      }
      return;
    }

    boolean containerField = false;

    if (descriptor != null && descriptor.isContainer()) {
      containerField = true;
    }

    // -- add object to stack so we don't potentially get into
    // -- an endlessloop
    if (_parents.search(object) >= 0) {
      return;
    }

    _parents.push(object);

    final boolean isNil = (object instanceof NilObject);

    Class<?> cls = null;

    if (!isNil) {
      cls = object.getClass();

      if (!_proxyInterfaces.isEmpty()) {
        boolean isProxy = false;

        Class<?>[] interfaces = cls.getInterfaces();
        for (int i = 0; i < interfaces.length; i++) {
          if (_proxyInterfaces.contains(interfaces[i].getName())) {
            isProxy = true;
          }
        }

        if (isProxy) {
          cls = cls.getSuperclass();
        }
      }
    } else {
      cls = ((NilObject) object).getClassDescriptor().getJavaClass();
    }

    boolean byteArray = false;
    if (cls.isArray())
      byteArray = (cls.getComponentType() == Byte.TYPE);

    boolean atRoot = false;
    if (descriptor == null) {
      descriptor = new XMLFieldDescriptorImpl(cls, "root", null, null);
      atRoot = true;
    }


    // -- calculate Object's name
    String name = descriptor.getXMLName();
    if (atRoot && _rootElement != null)
      name = _rootElement;

    boolean autoNameByClass = false;
    if (name == null) {
      autoNameByClass = true;
      name = cls.getName();
      // -- remove package information from name
      int idx = name.lastIndexOf('.');
      if (idx >= 0) {
        name = name.substring(idx + 1);
      }
      // -- remove capitalization
      name = getInternalContext().getXMLNaming().toXMLName(name);
    }

    // -- obtain the class descriptor
    XMLClassDescriptor classDesc = null;
    boolean saveType = false; /* flag for xsi:type */

    if (object instanceof NilObject) {
      classDesc = ((NilObject) object).getClassDescriptor();
    } else if (cls == descriptor.getFieldType()) {
      classDesc = (XMLClassDescriptor) descriptor.getClassDescriptor();
    }

    if (classDesc == null) {

      // -- check for primitive or String, we need to use
      // -- the special #isPrimitive method of this class
      // -- so that we can check for the primitive wrapper
      // -- classes
      if (isPrimitive(cls) || byteArray) {
        classDesc = STRING_CLASS_DESCRIPTOR;
        // -- check to see if we need to save the xsi:type
        // -- for this class
        Class<?> fieldType = descriptor.getFieldType();
        if (cls != fieldType) {
          while (fieldType.isArray()) {
            fieldType = fieldType.getComponentType();
          }
          saveType = (!primitiveOrWrapperEquals(cls, fieldType));
        }
      } else {
        saveType = cls.isArray();
        // -- save package information for use when searching
        // -- for MarshalInfo classes
        String className = cls.getName();
        int idx = className.lastIndexOf(".");
        String pkgName = null;
        if (idx > 0) {
          pkgName = className.substring(0, idx + 1);
          if (!_packages.contains(pkgName))
            _packages.add(pkgName);
        }

        if (_marshalExtendedType) {
          // -- Check to see if we can determine the class or
          // -- ClassDescriptor from the type specified in the
          // -- FieldHandler or from the current CDR state

          if ((cls != descriptor.getFieldType()) || atRoot) {

            saveType = true;

            boolean containsDesc = false;

            // -- if we're not at the root, check to see if we can resolve name.
            // -- if we're at the root, the name will most likely be resolvable
            // -- due to the validation step, so in most cases, if we are not
            // -- using a mapping we need the xsi:type at the root
            if (!atRoot) {
              String nsURI = descriptor.getNameSpaceURI();
              XMLClassDescriptor tmpDesc = null;
              try {
                tmpDesc = getResolver().resolveByXMLName(name, nsURI, null);
              } catch (ResolverException rx) {
                // -- exception not important as we're simply
                // -- testing to see if we can resolve during
                // -- unmarshalling
                if (LOG.isDebugEnabled()) {
                  LOG.debug("Error resolving", rx);
                }
              }

              if (tmpDesc != null) {
                Class<?> tmpType = tmpDesc.getJavaClass();
                if (tmpType == cls) {
                  containsDesc = (!tmpType.isInterface());
                }
              }
            }

            if (!containsDesc) {
              // -- check for class mapping, we don't use the
              // -- resolver directly because it will try to
              // -- load a compiled descriptor, or introspect
              // -- one
              if (atRoot) {
                if (_useXSITypeAtRoot) {
                  XMLMappingLoader ml = (XMLMappingLoader) getResolver().getMappingLoader();
                  if (ml != null) {
                    containsDesc = (ml.getDescriptor(cls.getName()) != null);
                  }
                } else {
                  // -- prevent xsi:type from appearing
                  // -- on root
                  containsDesc = true;
                }

              }

              // -- The following logic needs to be expanded to use
              // -- namespace -to- package mappings
              if ((!containsDesc) && (pkgName == null)) {
                // -- check to see if the class name is guessable
                // -- from the xml name
                classDesc = getClassDescriptor(cls);
                if (classDesc != null) {
                  String tmpName = classDesc.getXMLName();
                  if (name.equals(tmpName))
                    saveType = false;
                }
              }
            }

            if (containsDesc)
              saveType = false;

          }

          // marshal as the actual type
          if (classDesc == null)
            classDesc = getClassDescriptor(cls);

        } // -- end if (marshalExtendedType)
        else {
          // marshall as the base field type
          cls = descriptor.getFieldType();
          classDesc = getClassDescriptor(cls);
        }

        // -- If we are marshalling an array as the top
        // -- level object, or if we run into a multi
        // -- dimensional array, use the special
        // -- ArrayDescriptor
        if ((classDesc == null) && cls.isArray()) {
          classDesc = new RootArrayDescriptor(cls);
          if (atRoot) {
            containerField = (!_asDocument);
          }
        }
      } // -- end else not primitive

      if (classDesc == null) {
        // -- make sure we are allowed to marshal Object
        if ((cls == Void.class) || (cls == Object.class) || (cls == Class.class)) {

          throw new MarshalException(MarshalException.BASE_CLASS_OR_VOID_ERR);
        }
        _parents.pop();
        return;
      }
    }

    // -- handle auto-naming by class
    if (autoNameByClass) {
      if (classDesc.getXMLName() != null) {
        name = classDesc.getXMLName();
      }
    }

    // -- at this point naming should be done, update
    // -- MarshalState.xmlName if root element
    if (atRoot) {
      mstate._xmlName = name;
    }

    // ------------------------------------------------/
    // - Next few sections of code deal with xsi:type -/
    // - prevention, if necessary -/
    // ------------------------------------------------/

    // -- Allow user to prevent xsi:type
    saveType = (saveType && (!_suppressXSIType));

    // -- Suppress xsi:type for special types
    if (saveType) {
      // -- java.util.Enumeration and java.util.Date fix
      if (descriptor.getHandler() instanceof DateFieldHandler)
        saveType = false;
      else if (descriptor.getHandler() instanceof EnumFieldHandler)
        saveType = false;
      else if (isNil)
        saveType = false;
    }

    // -- Suppress 'xsi:type' attributes when Castor is able to infer
    // -- the correct type during unmarshalling
    if (saveType) {
      // When the type of the instance of the field is not the
      // type specified for the field, it might be necessary to
      // store the type explicitly (using xsi:type) to avoid
      // confusion during unmarshalling.
      //
      // However, it might be possible to use the XMLName of the
      // instance rather than the XMLName of the field. If
      // Castor could find back the type from the name of the
      // element, there is no need to add an xsi:type.
      //
      // In order to do that, there is two conditions:
      // 1. Castor should be able to find the right class to
      // instantiate form the XMLName of the instance.
      // 2. Castor should be sure than when using the XMLName of
      // the instance, there is only one field wich will match
      // that name (and that it is the current field)

      // XML Name associated with the class we are marshalling
      String xmlElementName = name;
      String xmlNamespace = descriptor.getNameSpaceURI();

      // We try to find if there is a XMLClassDescriptor associated
      // with the XML name of this class
      XMLClassDescriptor xmlElementNameClassDesc = null;
      try {
        xmlElementNameClassDesc = getResolver().resolveByXMLName(xmlElementName, null, null);
      } catch (ResolverException rx) {
        // -- exception not important as we're simply
        // -- testing to see if we can resolve during
        // -- unmarshalling
        if (LOG.isDebugEnabled()) {
          LOG.debug("Error resolving " + xmlElementName, rx);
        }
      }

      // Test if we are not dealing with a source generated vector
      if ((xmlElementName != null) && (xmlElementNameClassDesc != null)) {
        // More than one class can map to a given element name
        try {
          Iterator<ClassDescriptor> classDescriptorIter =
              getResolver().resolveAllByXMLName(xmlElementName, null, null);
          for (; classDescriptorIter.hasNext();) {
            xmlElementNameClassDesc = (XMLClassDescriptor) classDescriptorIter.next();
            if (cls == xmlElementNameClassDesc.getJavaClass()) {
              break;
            }
            // reset the classDescriptor --> none has been found
            xmlElementNameClassDesc = null;
          }
        } catch (ResolverException rx) {
          if (LOG.isDebugEnabled()) {
            LOG.debug("Error resolving " + xmlElementName, rx);
          }
          xmlElementNameClassDesc = null;
        }

        // make sure we only run into this logic if the classDescriptor
        // is coming from a mapping file.
        if (xmlElementNameClassDesc instanceof XMLClassDescriptorAdapter) {

          // Try to find a field descriptor directly in the parent object
          XMLClassDescriptor tempContaining =
              (XMLClassDescriptor) descriptor.getContainingClassDescriptor();

          // --if no containing class descriptor
          // --it means the container class could have been introspected
          // --so no need to enter the logic
          if (tempContaining != null) {
            XMLFieldDescriptor fieldDescMatch =
                tempContaining.getFieldDescriptor(xmlElementName, xmlNamespace, NodeType.Element);

            // Try to find a field descriptor by inheritance in the parent object
            InheritanceMatch[] matches = searchInheritance(xmlElementName, null, tempContaining); // TODO:
                                                                                                  // Joachim,
                                                                                                  // _cdResolver);

            if (matches.length == 1) {

              boolean foundTheRightClass = ((xmlElementNameClassDesc != null)
                  && (cls == xmlElementNameClassDesc.getJavaClass()));

              boolean oneAndOnlyOneMatchedField =
                  ((fieldDescMatch != null) || (matches[0].parentFieldDesc == descriptor));

              // Can we remove the xsi:type ?
              if (foundTheRightClass && oneAndOnlyOneMatchedField) {
                saveType = false;
                // no name swapping for now
              }
            } // lengh is one
          }
        } // the classDesc comes from a mapping file
      }
    } // --- End of "if (saveType)"


    // ------------------------/
    // - Namespace Management -/
    // ------------------------/

    // -- Set a new namespace scoping
    // -- Note: We still need to declare a new scope even if
    // -- we are suppressing most namespaces. Certain elements
    // -- like xsi:type and xsi:nil will require a namespace
    // -- declaration and cannot be suppressed.
    if (!atRoot) {
      namespacesStack.addNewNamespaceScope();
    }

    String nsPrefix = "";
    String nsURI = "";

    if (!_suppressNamespaces) {

      // -- Must be done before any attributes are processed
      // -- since attributes can be namespaced as well.

      nsPrefix = descriptor.getNameSpacePrefix();
      if (nsPrefix == null)
        nsPrefix = classDesc.getNameSpacePrefix();

      nsURI = descriptor.getNameSpaceURI();
      if (nsURI == null)
        nsURI = classDesc.getNameSpaceURI();

      if ((nsURI == null) && (nsPrefix != null)) {
        nsURI = namespacesStack.getNamespaceURI(nsPrefix);
      } else if ((nsPrefix == null) && (nsURI != null)) {
        nsPrefix = namespacesStack.getNamespacePrefix(nsURI);
      }
      // -- declare namespace at this element scope?
      if (nsURI != null) {
        String defaultNamespace = namespacesStack.getDefaultNamespaceURI();
        if ((nsPrefix == null) && (!nsURI.equals(defaultNamespace))) {
          if ((defaultNamespace == null) && atRoot) {
            nsPrefix = "";
          } else
            nsPrefix = DEFAULT_PREFIX + (++_namespaceCounter);
        }
        declareNamespace(nsPrefix, nsURI);
      } else {
        nsURI = "";
        // -- redeclare default namespace as empty
        String defaultNamespace = namespacesStack.getNamespaceURI("");
        if ((defaultNamespace != null) && (!"".equals(defaultNamespace)))
          namespacesStack.addNamespace("", "");
      }
    }



    // ---------------------/
    // - handle attributes -/
    // ---------------------/

    AttributesImpl atts = new AttributesImpl();

    // -- user defined attributes
    if (atRoot) {
      // -- declare xsi prefix if necessary
      if (_topLevelAtts.getSize() > 0) {
        namespacesStack.addNamespace(XSI_PREFIX, XSI_NAMESPACE);
      }

      for (int i = 0; i < _topLevelAtts.getSize(); i++) {
        String localName = _topLevelAtts.getName(i);
        String qName = localName;
        String ns = "";
        if (!_suppressNamespaces) {
          ns = _topLevelAtts.getNamespace(i);
          String prefix = null;
          if (StringUtils.isNotEmpty(ns)) {
            prefix = namespacesStack.getNonDefaultNamespacePrefix(ns);
          }
          if (StringUtils.isNotEmpty(prefix)) {
            qName = prefix + ':' + qName;
          }
          if (ns == null)
            ns = "";
        }
        atts.addAttribute(ns, localName, qName, CDATA, _topLevelAtts.getValue(i));
      }
    }

    // ----------------------------
    // -- process attr descriptors
    // ----------------------------

    int nestedAttCount = 0;
    XMLFieldDescriptor[] nestedAtts = null;
    XMLFieldDescriptor[] descriptors = null;
    if ((!descriptor.isReference()) && (!isNil)) {
      descriptors = classDesc.getAttributeDescriptors();
    } else {
      // references don't have attributes
      descriptors = NO_FIELD_DESCRIPTORS;
    }

    for (int i = 0; i < descriptors.length; i++) {
      XMLFieldDescriptor attributeDescriptor = descriptors[i];
      if (attributeDescriptor == null) {
        continue;
      }
      String path = attributeDescriptor.getLocationPath();
      if (StringUtils.isNotEmpty(path)) {
        // -- save for later processing
        if (nestedAtts == null) {
          nestedAtts = new XMLFieldDescriptor[descriptors.length - i];
        }
        nestedAtts[nestedAttCount] = attributeDescriptor;
        nestedAttCount++;
      } else {
        processAttribute(object, attributeDescriptor, atts);
      }
    }

    // -- handle ancestor nested attributes
    if (mstate._nestedAttCount > 0) {
      for (int i = 0; i < mstate._nestedAtts.length; i++) {
        XMLFieldDescriptor attributeDescriptor = mstate._nestedAtts[i];
        if (attributeDescriptor == null) {
          continue;
        }
        String locationPath = attributeDescriptor.getLocationPath();
        if (name.equals(locationPath)) {
          // indicate that this 'nested' attribute has been processed
          mstate._nestedAtts[i] = null;
          // decrease number of unprocessed 'nested' attributes by one
          mstate._nestedAttCount--;
          processAttribute(mstate.getOwner(), attributeDescriptor, atts);
        }
      }
    }

    // -- Look for attributes in container fields,
    // -- (also handle container in container)
    if (!isNil)
      processContainerAttributes(object, classDesc, atts);

    // -- xml:space
    String attValue = descriptor.getXMLProperty(XMLFieldDescriptor.PROPERTY_XML_SPACE);
    if (attValue != null) {
      atts.addAttribute(Namespaces.XML_NAMESPACE, SPACE_ATTR, XML_SPACE_ATTR, CDATA, attValue);
    }

    // -- xml:lang
    attValue = descriptor.getXMLProperty(XMLFieldDescriptor.PROPERTY_XML_LANG);
    if (attValue != null) {
      atts.addAttribute(Namespaces.XML_NAMESPACE, LANG_ATTR, XML_LANG_ATTR, CDATA, attValue);
    }


    // ------------------/
    // - Create element -/
    // ------------------/


    // -- save xsi:type information, if necessary

    if (saveType) {
      // -- declare XSI namespace, if necessary
      declareNamespace(XSI_PREFIX, XSI_NAMESPACE);

      // -- calculate type name, either use class name or
      // -- schema type name. If XMLClassDescriptor is introspected,
      // -- or is the default XMLClassDescriptorImpl, then
      // -- use java:classname, otherwise use XML name.
      String typeName = classDesc.getXMLName();

      // -- Check for introspection...
      boolean introspected = false;
      if (classDesc instanceof InternalXMLClassDescriptor)
        introspected = ((InternalXMLClassDescriptor) classDesc).introspected();
      else
        introspected = Introspector.introspected(classDesc);

      boolean useJavaPrefix = false;
      if ((typeName == null) || introspected) {
        typeName = JAVA_PREFIX + cls.getName();
        useJavaPrefix = true;
      } else if (classDesc instanceof RootArrayDescriptor) {
        typeName = JAVA_PREFIX + cls.getName();
        useJavaPrefix = true;
      } else {
        String dcn = classDesc.getClass().getName();
        if (dcn.equals(XMLClassDescriptorImpl.class.getName())) {
          typeName = JAVA_PREFIX + cls.getName();
          useJavaPrefix = true;
        } else {
          // -- calculate proper prefix
          String tns = classDesc.getNameSpaceURI();
          String prefix = null;
          if (StringUtils.isNotEmpty(tns)) {
            prefix = namespacesStack.getNamespacePrefix(tns);
            if (StringUtils.isNotEmpty(prefix)) {
              typeName = prefix + ':' + typeName;
            }
          }
        }
      }
      // -- save type information
      atts.addAttribute(XSI_NAMESPACE, TYPE_ATTR, XSI_TYPE, CDATA, typeName);
      if (useJavaPrefix) {
        if (namespacesStack.getNamespaceURI("java") == null) {
          // -- declare Java namespace, if necessary
          declareNamespace("java", "http://java.sun.com");
        }
      }
    }

    if (isNil && !_suppressXSIType) {
      // -- declare XSI namespace, if necessary
      declareNamespace(XSI_PREFIX, XSI_NAMESPACE);
      // -- add xsi:nil="true"
      atts.addAttribute(XSI_NAMESPACE, NIL_ATTR, XSI_NIL_ATTR, CDATA, TRUE_VALUE);
    }

    // check if the value is a QName that needs to
    // be resolved ({URI}value -> ns:value)
    // This should be done BEFORE declaring the namespaces as attributes
    // because we can declare new namespace during the QName resolution
    String valueType = descriptor.getSchemaType();
    if ((valueType != null) && (valueType.equals(QNAME_NAME))) {
      object = resolveQName(object, descriptor);
    }


    String qName = null;
    if (nsPrefix != null) {
      int len = nsPrefix.length();
      if (len > 0) {
        qName = nsPrefix + ':' + name;
      } else
        qName = name;
    } else
      qName = name;


    Object firstNonNullValue = null;
    int firstNonNullIdx = 0;

    try {


      if (!containerField) {


        // -- isNillable?
        if ((!isNil) && descriptor.isNillable()) {
          XMLFieldDescriptor desc = classDesc.getContentDescriptor();
          descriptors = classDesc.getElementDescriptors();
          int descCount = descriptors.length;
          boolean isNilContent = (descCount > 0) || (desc != null);

          // -- check content descriptor for a valid value
          if (desc != null) {
            Object value = desc.getHandler().getValue(object);
            if (value != null) {
              isNilContent = false;
              descCount = 0;
            } else if (desc.isNillable() && desc.isRequired()) {
              isNilContent = false;
              descCount = 0;
            }
          }


          for (int i = 0; i < descCount; i++) {
            desc = descriptors[i];
            if (desc == null)
              continue;
            Object value = desc.getHandler().getValue(object);
            if (value != null) {
              isNilContent = false;
              firstNonNullIdx = i;
              firstNonNullValue = value;
              break;
            } else if (desc.isNillable() && desc.isRequired()) {
              isNilContent = false;
              firstNonNullIdx = i;
              firstNonNullValue = new NilObject(classDesc, desc);
              break;
            }
          }

          if (isNilContent) {
            declareNamespace(XSI_PREFIX, XSI_NAMESPACE);
            atts.addAttribute(XSI_NAMESPACE, NIL_ATTR, XSI_NIL_ATTR, CDATA, TRUE_VALUE);
          }
        }

        // -- declare all necesssary namespaces
        namespacesStack.getCurrentNamespaceScope().sendStartEvents(handler);
        // -- Make sure qName is not null
        if (qName == null) {
          // -- hopefully this never happens, but if it does, it means
          // -- we have a bug in our naming logic
          String err = "Error in deriving name for type: " + cls.getName()
              + ", please report bug to: " + "http://castor.exolab.org.";
          throw new IllegalStateException(err);
        }


        handler.startElement(nsURI, name, qName, atts);
      }
    } catch (org.xml.sax.SAXException sx) {
      throw new MarshalException(sx);
    }


    // ---------------------------------------
    // -- process all child content, including
    // -- text nodes + daughter elements
    // ---------------------------------------

    Stack<WrapperInfo> wrappers = null;


    // ----------------------
    // -- handle text content
    // ----------------------

    if (!isNil) {

      XMLFieldDescriptor cdesc = null;
      if (!descriptor.isReference()) {
        cdesc = classDesc.getContentDescriptor();
      }
      if (cdesc != null) {
        Object obj = null;
        try {
          obj = cdesc.getHandler().getValue(object);
        } catch (IllegalStateException ise) {
          LOG.warn("Error getting value from: " + object, ise);
        }

        if (obj != null) {

          // -- <Wrapper>
          // -- handle XML path
          String path = cdesc.getLocationPath();
          String currentLoc = null;

          if (path != null) {
            _attributes.clear();
            if (wrappers == null) {
              wrappers = new SafeStack<WrapperInfo>();
            }
            try {
              while (path != null) {

                String elemName = null;
                int idx = path.indexOf('/');

                if (idx > 0) {
                  elemName = path.substring(0, idx);
                  path = path.substring(idx + 1);
                } else {
                  elemName = path;
                  path = null;
                }

                // -- save current location without namespace
                // -- information for now.
                if (currentLoc == null)
                  currentLoc = elemName;
                else
                  currentLoc = currentLoc + "/" + elemName;

                String elemQName = elemName;
                if (StringUtils.isNotEmpty(nsPrefix)) {
                  elemQName = nsPrefix + ':' + elemName;
                }
                wrappers.push(new WrapperInfo(elemName, elemQName, currentLoc));


                _attributes.clear();
                if (nestedAttCount > 0) {
                  for (int na = 0; na < nestedAtts.length; na++) {
                    if (nestedAtts[na] == null)
                      continue;
                    String tmpPath = nestedAtts[na].getLocationPath();
                    if (tmpPath.equals(currentLoc)) {
                      processAttribute(object, nestedAtts[na], _attributes);
                      nestedAtts[na] = null;
                      --nestedAttCount;
                    }
                  }
                }
                handler.startElement(nsURI, elemName, elemQName, _attributes);
              }
            } catch (SAXException sx) {
              throw new MarshalException(sx);
            }
          }
          // -- </Wrapper>

          char[] chars = null;
          Class<?> objType = obj.getClass();
          if (objType.isArray() && (objType.getComponentType() == Byte.TYPE)) {
            // -- handle base64/hexbinary content
            final String schemaType = descriptor.getSchemaType();
            if (HexDecoder.DATA_TYPE.equals(schemaType)) {
              chars = new String(HexDecoder.encode((byte[]) obj)).toCharArray();
            } else {
              chars = Base64Encoder.encode((byte[]) obj);
            }
          } else {
            // -- all other types
            String str = obj.toString();
            if (StringUtils.isNotEmpty(str)) {
              chars = str.toCharArray();
            }
          }
          if ((chars != null) && (chars.length > 0)) {
            try {
              handler.characters(chars, 0, chars.length);
            } catch (org.xml.sax.SAXException sx) {
              throw new MarshalException(sx);
            }
          }
        }
      }
      // -- element references
      else if (descriptor.isReference()) {
        Object id = getObjectID(object);
        if (id != null) {
          char[] chars = id.toString().toCharArray();
          try {
            handler.characters(chars, 0, chars.length);
          } catch (org.xml.sax.SAXException sx) {
            throw new MarshalException(sx);
          }
        }
      }
      // special case for byte[]
      else if (byteArray) {
        // -- Base64Encoding / HexBinary
        String schemaType = descriptor.getSchemaType();
        String componentType = descriptor.getComponentType();
        char[] chars = new char[0];
        if ((descriptor.isMultivalued() && HexDecoder.DATA_TYPE.equals(componentType))
            || HexDecoder.DATA_TYPE.equals(schemaType)) {
          chars = new String(HexDecoder.encode((byte[]) object)).toCharArray();
        } else {
          chars = Base64Encoder.encode((byte[]) object);
        }
        try {
          handler.characters(chars, 0, chars.length);
        } catch (org.xml.sax.SAXException sx) {
          throw new MarshalException(sx);
        }
      }
      /* special case for Strings and primitives */
      else if (isPrimitive(cls)) {

        char[] chars;
        if (cls == java.math.BigDecimal.class) {
          chars = convertBigDecimalToString(object).toCharArray();
        } else {
          chars = object.toString().toCharArray();
        }
        try {
          handler.characters(chars, 0, chars.length);
        } catch (org.xml.sax.SAXException sx) {
          throw new MarshalException(sx);
        }
      } else if (isEnum(cls)) {
        char[] chars = object.toString().toCharArray();
        try {
          handler.characters(chars, 0, chars.length);
        } catch (org.xml.sax.SAXException sx) {
          throw new MarshalException(sx);
        }

      }
    }

    // ---------------------------
    // -- handle daughter elements
    // ---------------------------

    if (isNil || descriptor.isReference()) {
      descriptors = NO_FIELD_DESCRIPTORS;
    } else {
      descriptors = classDesc.getElementDescriptors();
    }

    ++_depth;

    // -- marshal elements
    for (int i = firstNonNullIdx; i < descriptors.length; i++) {

      XMLFieldDescriptor elemDescriptor = descriptors[i];
      Object obj = null;
      boolean nil = false;

      // -- used previously cached value?
      if ((i == firstNonNullIdx) && (firstNonNullValue != null)) {
        obj = firstNonNullValue;
      } else {
        // -- obtain value from handler
        try {
          obj = elemDescriptor.getHandler().getValue(object);
        } catch (IllegalStateException ise) {
          LOG.warn("Error marshalling " + object, ise);
          continue;
        }
      }

      if (obj == null
          || (obj instanceof Enumeration && !((Enumeration<?>) obj).hasMoreElements())) {
        if (elemDescriptor.isNillable() && (elemDescriptor.isRequired())) {
          nil = true;
        } else {
          continue;
        }
      }


      // -- handle XML path
      String path = elemDescriptor.getLocationPath();
      String currentLoc = null;
      // -- Wrapper/Location cleanup
      if (wrappers != null) {
        try {
          while (!wrappers.empty()) {
            WrapperInfo wInfo = wrappers.peek();
            if (path != null) {
              if (wInfo._location.equals(path)) {
                path = null;
                break;
              } else if (path.startsWith(wInfo._location + "/")) {
                path = path.substring(wInfo._location.length() + 1);
                currentLoc = wInfo._location;
                break;
              }
            }
            handler.endElement(nsURI, wInfo._localName, wInfo._qName);
            wrappers.pop();
          }
        } catch (SAXException sx) {
          throw new MarshalException(sx);
        }
      }


      if (path != null) {
        _attributes.clear();
        if (wrappers == null) {
          wrappers = new SafeStack<WrapperInfo>();
        }
        try {
          while (path != null) {

            String elemName = null;
            int idx = path.indexOf('/');

            if (idx > 0) {
              elemName = path.substring(0, idx);
              path = path.substring(idx + 1);
            } else {
              elemName = path;
              path = null;
            }

            // -- save current location without namespace
            // -- information for now.
            if (currentLoc == null)
              currentLoc = elemName;
            else
              currentLoc = currentLoc + "/" + elemName;

            String elemQName = elemName;
            if (StringUtils.isNotEmpty(nsPrefix)) {
              elemQName = nsPrefix + ':' + elemName;
            }
            wrappers.push(new WrapperInfo(elemName, elemQName, currentLoc));


            _attributes.clear();
            if (nestedAttCount > 0) {
              for (int na = 0; na < nestedAtts.length; na++) {
                if (nestedAtts[na] == null)
                  continue;
                String tmpPath = nestedAtts[na].getLocationPath();
                if (tmpPath.equals(currentLoc)) {
                  processAttribute(object, nestedAtts[na], _attributes);
                  nestedAtts[na] = null;
                  --nestedAttCount;
                }
              }
            }
            handler.startElement(nsURI, elemName, elemQName, _attributes);
          }
        } catch (SAXException sx) {
          throw new MarshalException(sx);
        }
      }

      if (nil) {
        obj = new NilObject(classDesc, elemDescriptor);
      }

      final Class<?> type = obj.getClass();

      MarshalState myState = mstate.createMarshalState(object, name);
      myState._nestedAtts = nestedAtts;
      myState._nestedAttCount = nestedAttCount;


      // -- handle byte arrays
      if (type.isArray() && (type.getComponentType() == Byte.TYPE)) {
        marshal(obj, elemDescriptor, handler, myState);
      } else if (type.isArray() && elemDescriptor.isDerivedFromXSList()) {
        Object buffer = processXSListType(obj, elemDescriptor);
        String elemName = elemDescriptor.getXMLName();
        String elemQName = elemName;
        if (StringUtils.isNotEmpty(nsPrefix)) {
          elemQName = nsPrefix + ':' + elemName;
        }
        char[] chars = buffer.toString().toCharArray();
        try {
          handler.startElement(nsURI, elemName, elemQName, _attributes);
          handler.characters(chars, 0, chars.length);
          handler.endElement(nsURI, elemName, elemQName);
        } catch (org.xml.sax.SAXException sx) {
          throw new MarshalException(sx);
        }
      }
      // -- handle all other collection types
      else if (isCollection(type)) {
        boolean processCollection = true;
        if (_saveMapKeys) {
          MapHandler mapHandler = MapHandlers.getHandler(type);
          if (mapHandler != null) {
            processCollection = false;
            MapItem item = new MapItem();
            Enumeration<?> keys = mapHandler.keys(obj);
            while (keys.hasMoreElements()) {
              item.setKey(keys.nextElement());
              item.setValue(mapHandler.get(obj, item.getKey()));
              marshal(item, elemDescriptor, handler, myState);
            }
          }

        }
        if (processCollection) {
          CollectionHandler<?> colHandler = getCollectionHandler(type);
          Enumeration<?> enumeration = colHandler.elements(obj);
          while (enumeration.hasMoreElements()) {
            Object item = enumeration.nextElement();
            if (item != null) {
              marshal(item, elemDescriptor, handler, myState);
            }
          }
        }
      }
      // -- otherwise just marshal object as is
      else {
        marshal(obj, elemDescriptor, handler, myState);
      }

      if (nestedAttCount > 0) {
        nestedAttCount = myState._nestedAttCount;
      }

    }


    // -- Wrapper/Location cleanup for elements
    if (wrappers != null) {
      try {
        while (!wrappers.empty()) {
          WrapperInfo wInfo = wrappers.peek();
          boolean popStack = true;
          if (nestedAttCount > 0) {
            for (int na = 0; na < nestedAtts.length; na++) {
              // TODO[LL]: refactor to avoid check against null
              if (nestedAtts[na] == null)
                continue;
              String nestedAttributePath = nestedAtts[na].getLocationPath();
              if (nestedAttributePath.startsWith(wInfo._location + "/")) {
                popStack = false;
                break;
              }
            }
          }
          if (popStack) {
            handler.endElement(nsURI, wInfo._localName, wInfo._qName);
            wrappers.pop();
          } else {
            break;
          }
        }
      } catch (SAXException sx) {
        throw new MarshalException(sx);
      }
    }

    // TODO: Handling additional attributes at the end causes elements to be marshalled in the wrong
    // order when element 'text' is null, but their attribute value is not null. Can this be fixed
    // to process element attributes even when the element text value is null?

    if (wrappers != null && !wrappers.isEmpty()) {
      dealWithNestedAttributesNested(object, handler, nsPrefix, nsURI, nestedAttCount, nestedAtts,
          wrappers);
    }

    dealWithNestedAttributes(object, handler, nsPrefix, nsURI, nestedAttCount, nestedAtts,
        new SafeStack<WrapperInfo>());

    // -- finish element
    try {
      if (!containerField) {
        handler.endElement(nsURI, name, qName);
        // -- undeclare all necesssary namespaces
        namespacesStack.getCurrentNamespaceScope().sendEndEvents(handler);
      }
    } catch (org.xml.sax.SAXException sx) {
      throw new MarshalException(sx);
    }

    --_depth;
    _parents.pop();
    if (!atRoot) {
      namespacesStack.removeNamespaceScope();
    }

    // -- notify listener of post marshal
    if (_marshalListener != null) {
      try {
        _marshalListener.postMarshal(object);
      } catch (RuntimeException e) {
        LOG.error(
            "Invoking #postMarshal() on your custom MarshalListener instance caused the following problem:",
            e);
      }
    }

  }

  private void dealWithNestedAttributes(Object object, ContentHandler handler, String nsPrefix,
      String nsURI, int nestedAttCount, XMLFieldDescriptor[] nestedAtts,
      Stack<WrapperInfo> wrappers) throws MarshalException {
    // -- Handle any additional attribute locations that were
    // -- not handled when dealing with wrapper elements
    if (nestedAttCount > 0) {
      for (int i = 0; i < nestedAtts.length; i++) {
        if (nestedAtts[i] == null)
          continue;
        String path = nestedAtts[i].getLocationPath();
        String currentLoc = null;


        // -- Make sure attribute has value before continuing
        // -- We really could use a FieldHandler#hasValue()
        // -- method (since sometimes getValue() methods may
        // -- be expensive and we don't always want to call it
        // -- multiple times)
        if (nestedAtts[i].getHandler().getValue(object) == null) {
          nestedAtts[i] = null;
          --nestedAttCount;
          continue;
        }
        try {
          while (path != null) {
            int idx = path.indexOf('/');
            String elemName = null;
            if (idx > 0) {
              elemName = path.substring(0, idx);
              path = path.substring(idx + 1);
            } else {
              elemName = path;
              path = null;
            }
            if (currentLoc == null)
              currentLoc = elemName;
            else
              currentLoc = currentLoc + "/" + elemName;

            String elemQName = elemName;
            if (StringUtils.isNotEmpty(nsPrefix)) {
              elemQName = nsPrefix + ':' + elemName;
            }
            wrappers.push(new WrapperInfo(elemName, elemQName, null));

            _attributes.clear();
            if (path == null) {
              processAttribute(object, nestedAtts[i], _attributes);
              nestedAtts[i] = null;
              --nestedAttCount;
            }
            if (nestedAttCount > 0) {
              for (int na = i + 1; na < nestedAtts.length; na++) {
                if (nestedAtts[na] == null)
                  continue;
                String tmpPath = nestedAtts[na].getLocationPath();
                if (tmpPath.equals(currentLoc)) {
                  processAttribute(object, nestedAtts[na], _attributes);
                  nestedAtts[na] = null;
                  --nestedAttCount;
                }
              }
            }
            handler.startElement(nsURI, elemName, elemQName, _attributes);
          }

          while (!wrappers.empty()) {
            WrapperInfo wInfo = wrappers.pop();
            handler.endElement(nsURI, wInfo._localName, wInfo._qName);
          }
        } catch (Exception e) {
          throw new MarshalException(e);
        }
      }
    } // if (nestedAttCount > 0)
  }

  private void dealWithNestedAttributesNested(Object object, ContentHandler handler,
      String nsPrefix, String nsURI, int nestedAttCount, XMLFieldDescriptor[] nestedAtts,
      Stack<WrapperInfo> wrappers) throws MarshalException {
    // -- Handle any additional attribute locations that were
    // -- not handled when dealing with wrapper elements

    WrapperInfo wrapperInfo = wrappers.peek();
    String currentLocation = wrapperInfo._location;

    if (nestedAttCount > 0) {
      for (int i = 0; i < nestedAtts.length; i++) {
        if (nestedAtts[i] == null)
          continue;
        String nestedAttributePath = nestedAtts[i].getLocationPath();

        if (!nestedAttributePath.startsWith(currentLocation + "/")) {
          continue;
        }

        nestedAttributePath = nestedAttributePath.substring(wrapperInfo._location.length() + 1);
        String currentLoc = currentLocation;


        // -- Make sure attribute has value before continuing
        // -- We really could use a FieldHandler#hasValue()
        // -- method (since sometimes getValue() methods may
        // -- be expensive and we don't always want to call it
        // -- multiple times)
        if (nestedAtts[i].getHandler().getValue(object) == null) {
          nestedAtts[i] = null;
          --nestedAttCount;
          continue;
        }
        try {
          while (nestedAttributePath != null) {
            int idx = nestedAttributePath.indexOf('/');
            String elemName = null;
            if (idx > 0) {
              elemName = nestedAttributePath.substring(0, idx);
              nestedAttributePath = nestedAttributePath.substring(idx + 1);
            } else {
              elemName = nestedAttributePath;
              nestedAttributePath = null;
            }
            if (currentLoc == null)
              currentLoc = elemName;
            else
              currentLoc = currentLoc + "/" + elemName;

            String elemQName = elemName;
            if (StringUtils.isNotEmpty(nsPrefix)) {
              elemQName = nsPrefix + ':' + elemName;
            }
            wrappers.push(new WrapperInfo(elemName, elemQName, null));

            _attributes.clear();
            if (nestedAttributePath == null) {
              processAttribute(object, nestedAtts[i], _attributes);
              nestedAtts[i] = null;
              --nestedAttCount;
            }
            if (nestedAttCount > 0) {
              for (int na = i + 1; na < nestedAtts.length; na++) {
                if (nestedAtts[na] == null)
                  continue;
                String tmpPath = nestedAtts[na].getLocationPath();
                if (tmpPath.equals(currentLoc)) {
                  processAttribute(object, nestedAtts[na], _attributes);
                  nestedAtts[na] = null;
                  --nestedAttCount;
                }
              }
            }
            handler.startElement(nsURI, elemName, elemQName, _attributes);
          }

          while (!wrappers.empty()) {
            WrapperInfo wInfo = wrappers.pop();
            handler.endElement(nsURI, wInfo._localName, wInfo._qName);
          }
        } catch (Exception e) {
          throw new MarshalException(e);
        }
      }
    } // if (nestedAttCount > 0)
  }


  /**
   * Converts a {@link BigDecimal} instance value to its String representation. This method will
   * take into into account the Java version number, as the semantics of BigDecimal.toString() have
   * changed between Java 1.4 and Java 5.0 and above.
   * 
   * @param object The {@link BigDecimal} instance
   * @return The String representation of the {@link BigDecimal} instance
   * @throws MarshalException If invocation of BigDecimal#toPlainString() fails.
   */
  private String convertBigDecimalToString(Object object) throws MarshalException {
    String stringValue;
    float javaVersion = Float.parseFloat(System.getProperty("java.specification.version"));
    if (javaVersion >= 1.5) {
      // as of Java 5.0 and above, BigDecimal.toPlainString() should be used.
      // TODO: reconsider if we start using BigDecimal for XSTypes that can hold scientific values
      Method method;
      try {
        method = java.math.BigDecimal.class.getMethod("toPlainString", (Class[]) null);
        stringValue = (String) method.invoke(object, (Object[]) null);
      } catch (Exception e) {
        LOG.error("Problem accessing java.math.BigDecimal.toPlainString().", e);
        throw new MarshalException("Problem accessing java.math.BigDecimal.toPlainString().", e);
      }
    } else {
      // use BigDecimal.toString() with Java 1.4 and below
      stringValue = object.toString();
    }
    return stringValue;
  }

  /**
   * Retrieves the ID for the given Object
   *
   * @param object the Object to retrieve the ID for
   * @return the ID for the given Object
   **/
  private Object getObjectID(Object object) throws MarshalException {
    if (object == null)
      return null;

    Object id = null;
    XMLClassDescriptor cd = getClassDescriptor(object.getClass());
    String err = null;
    if (cd != null) {
      XMLFieldDescriptor fieldDesc = (XMLFieldDescriptor) cd.getIdentity();
      if (fieldDesc != null) {
        FieldHandler<?> fieldHandler = fieldDesc.getHandler();
        if (fieldHandler != null) {
          try {
            id = fieldHandler.getValue(object);
          } catch (IllegalStateException ise) {
            err = ise.toString();
          }
        } // fieldHandler != null
        else {
          err = "FieldHandler for Identity descriptor is null.";
        }
      } // fieldDesc != null
      else
        err = "No identity descriptor available";
    } // cd!=null
    else {
      err = "Unable to resolve ClassDescriptor.";
    }
    if (err != null) {
      String errMsg = "Unable to resolve ID for instance of class '";
      errMsg += object.getClass().getName();
      errMsg += "' due to the following error: ";
      throw new MarshalException(errMsg + err);
    }
    return id;
  } // -- getID


  /**
   * Declares the given namespace, if not already in scope
   *
   * @param nsPrefix the namespace prefix
   * @param nsURI the namespace URI to declare
   * @return true if the namespace was not in scope and was sucessfully declared, other false
   **/
  private boolean declareNamespace(String nsPrefix, String nsURI) {
    boolean declared = false;

    // -- make sure it's not already declared...
    if ((nsURI != null) && (nsURI.length() != 0)) {

      String tmpURI = namespacesStack.getNamespaceURI(nsPrefix);
      if ((tmpURI != null) && (tmpURI.equals(nsURI))) {
        return declared;
      }
      String tmpPrefix = namespacesStack.getNamespacePrefix(nsURI);
      if ((tmpPrefix == null) || (!tmpPrefix.equals(nsPrefix))) {
        namespacesStack.addNamespace(nsPrefix, nsURI);
        declared = true;
      }
    }
    return declared;
  } // -- declareNamespace

  /**
   * Sets the PrintWriter used for logging
   * 
   * @param printWriter the PrintWriter to use for logging
   **/
  public void setLogWriter(final PrintWriter printWriter) {}

  /**
   * Sets the encoding for the serializer. Note that this method cannot be called if you've passed
   * in your own DocumentHandler.
   *
   * @param encoding the encoding to set
   **/
  public void setEncoding(String encoding) {

    if (_serializer != null) {
      if (_format == null) {
        _format = getInternalContext().getOutputFormat();
      }
      _format.setEncoding(encoding);
      // -- reset output format, this needs to be done
      // -- any time a change occurs to the format.
      _serializer.setOutputFormat(_format);
      try {
        // -- Due to a Xerces Serializer bug that doesn't allow declaring
        // -- multiple prefixes to the same namespace, we use the old
        // -- DocumentHandler format and process namespaces ourselves
        _handler = new DocumentHandlerAdapter(_serializer.asDocumentHandler());
      } catch (java.io.IOException iox) {
        // -- we can ignore this exception since it shouldn't
        // -- happen. If _serializer is not null, it means
        // -- we've already called this method sucessfully
        // -- in the Marshaller() constructor
        if (LOG.isDebugEnabled()) {
          LOG.debug("Error setting encoding to " + encoding, iox);
        }
      }
    } else {
      String error = "encoding cannot be set if you've passed in " + "your own DocumentHandler";
      throw new IllegalStateException(error);
    }
  } // -- setEncoding

  /**
   * Sets the value for the xsi:noNamespaceSchemaLocation attribute. When set, this attribute will
   * appear on the root element of the marshalled document.
   *
   * @param schemaLocation the URI location of the schema to which the marshalled document is an
   *        instance of.
   **/
  public void setNoNamespaceSchemaLocation(String schemaLocation) {
    if (schemaLocation == null) {
      // -- remove if necessary
      // -- to be added later.
    } else {
      _topLevelAtts.setAttribute(XSI_NO_NAMESPACE_SCHEMA_LOCATION, schemaLocation, XSI_NAMESPACE);
    }
  } // -- setNoNamespaceSchemaLocation

  /**
   * Sets the value for the xsi:schemaLocation attribute. When set, this attribute will appear on
   * the root element of the marshalled document.
   *
   * @param schemaLocation the URI location of the schema to which the marshalled document is an
   *        instance of.
   **/
  public void setSchemaLocation(String schemaLocation) {
    if (schemaLocation == null) {
      // -- remove if necessary
      // -- to be added later.
    } else {
      _topLevelAtts.setAttribute(XSI_SCHEMA_LOCATION, schemaLocation, XSI_NAMESPACE);
    }
  } // -- setSchemaLocation

  /**
   * Sets whether or not namespaces are output. By default the Marshaller will output namespace
   * declarations and prefix elements and attributes with their respective namespace prefix. This
   * method can be used to prevent the usage of namespaces.
   *
   * @param suppressNamespaces a boolean that when true will prevent namespaces from being output.
   */
  public void setSuppressNamespaces(boolean suppressNamespaces) {
    _suppressNamespaces = suppressNamespaces;
  } // -- setSuppressNamespaces

  /**
   * Sets whether or not the xsi:type attribute should appear on the marshalled document.
   *
   * @param suppressXSIType a boolean that when true will prevent xsi:type attribute from being used
   *        in the marshalling process.
   */
  public void setSuppressXSIType(boolean suppressXSIType) {
    _suppressXSIType = suppressXSIType;
  } // -- setSuppressXSIType

  /**
   * Sets whether or not to output the xsi:type at the root element. This is usually needed when the
   * root element type cannot be determined by the element name alone. By default xsi:type will not
   * be output on the root element.
   *
   * @param useXSITypeAtRoot a boolean that when true indicates that the xsi:type should be output
   *        on the root element.
   */
  public void setUseXSITypeAtRoot(boolean useXSITypeAtRoot) {
    _useXSITypeAtRoot = useXSITypeAtRoot;
  } // -- setUseXSITypeAtRoot

  /**
   * Finds and returns an XMLClassDescriptor for the given class. If a XMLClassDescriptor could not
   * be found, this method will attempt to create one automatically using reflection.
   * 
   * @param cls the Class to get the XMLClassDescriptor for
   * @exception MarshalException when there is a problem retrieving or creating the
   *            XMLClassDescriptor for the given class
   **/
  private XMLClassDescriptor getClassDescriptor(final Class<?> cls) throws MarshalException {
    XMLClassDescriptor classDesc = null;

    try {
      if (!isPrimitive(cls))
        classDesc = (XMLClassDescriptor) getResolver().resolve(cls);
    } catch (ResolverException rx) {
      Throwable actual = rx.getCause();
      if (actual instanceof MarshalException) {
        throw (MarshalException) actual;
      }
      if (actual != null) {
        throw new MarshalException(actual);
      }
      throw new MarshalException(rx);
    }

    if (classDesc != null)
      classDesc = new InternalXMLClassDescriptor(classDesc);

    return classDesc;
  } // -- getClassDescriptor

  /**
   * Processes the attribute associated with the given attDescriptor and parent object.
   *
   * @param atts the SAX attribute list to add the attribute to
   */
  private void processAttribute(Object object, XMLFieldDescriptor attDescriptor,
      AttributesImpl atts) throws MarshalException {
    if (attDescriptor == null)
      return;

    // -- process Namespace nodes from Object Model,
    // -- if necessary.
    if (attDescriptor.getNodeType() == NodeType.Namespace) {
      if (!_suppressNamespaces) {
        Object map = attDescriptor.getHandler().getValue(object);
        MapHandler mapHandler = MapHandlers.getHandler(map);
        if (mapHandler != null) {
          Enumeration<?> keys = mapHandler.keys(map);
          while (keys.hasMoreElements()) {
            Object key = keys.nextElement();
            Object val = mapHandler.get(map, key);
            declareNamespace(key.toString(), val.toString());
          }
        }
      }
      return;
    }

    String localName = attDescriptor.getXMLName();
    String qName = localName;

    // -- handle attribute namespaces
    String namespace = "";
    if (!_suppressNamespaces) {
      namespace = attDescriptor.getNameSpaceURI();
      if (StringUtils.isNotEmpty(namespace)) {
        String prefix = attDescriptor.getNameSpacePrefix();
        if ((prefix == null) || (prefix.length() == 0))
          prefix = namespacesStack.getNonDefaultNamespacePrefix(namespace);

        if ((prefix == null) || (prefix.length() == 0)) {
          // -- automatically create namespace prefix?
          prefix = DEFAULT_PREFIX + (++_namespaceCounter);
        }
        declareNamespace(prefix, namespace);
        qName = prefix + ':' + qName;
      } else
        namespace = "";
    }

    Object value = null;

    try {
      value = attDescriptor.getHandler().getValue(object);
    } catch (IllegalStateException ise) {
      LOG.warn("Error getting value from " + object, ise);
      return;
    }

    // -- handle IDREF(S)
    if (attDescriptor.isReference() && (value != null)) {

      if (attDescriptor.isMultivalued()) {
        Enumeration<?> enumeration = null;
        if (value instanceof Enumeration) {
          enumeration = (Enumeration<?>) value;
        } else {
          CollectionHandler<?> colHandler = null;
          try {
            colHandler = CollectionHandlers.getHandler(value.getClass());
          } catch (MappingException mx) {
            throw new MarshalException(mx);
          }
          enumeration = colHandler.elements(value);
        }
        if (enumeration.hasMoreElements()) {
          StringBuilder sb = new StringBuilder();
          for (int v = 0; enumeration.hasMoreElements(); v++) {
            if (v > 0)
              sb.append(' ');
            sb.append(getObjectID(enumeration.nextElement()));
          }
          value = sb;
        } else
          value = null;
      } else {
        value = getObjectID(value);
      }
    }
    // -- handle multi-value attributes
    else if (attDescriptor.isMultivalued() && (value != null)) {
      value = processXSListType(value, attDescriptor);
    } else if (value != null) {
      // -- handle hex/base64 content
      Class<?> objType = value.getClass();
      if (objType.isArray() && (objType.getComponentType() == Byte.TYPE)) {
        value = encodeBinaryData(value, attDescriptor.getSchemaType());
      }
    }

    if (value != null) {
      // check if the value is a QName that needs to
      // be resolved ({URI}value -> ns:value).
      String valueType = attDescriptor.getSchemaType();
      if ((valueType != null) && (valueType.equals(QNAME_NAME)))
        value = resolveQName(value, attDescriptor);

      atts.addAttribute(namespace, localName, qName, CDATA, value.toString());
    }
  }

  private Object processXSListType(final Object value, XMLFieldDescriptor descriptor)
      throws MarshalException {
    Object returnValue = null;
    Enumeration<?> enumeration = null;
    if (value instanceof Enumeration) {
      enumeration = (Enumeration<?>) value;
    } else {
      CollectionHandler<?> colHandler = null;
      try {
        colHandler = CollectionHandlers.getHandler(value.getClass());
      } catch (MappingException mx) {
        throw new MarshalException(mx);
      }
      enumeration = colHandler.elements(value);
    }
    if (enumeration.hasMoreElements()) {
      StringBuilder sb = new StringBuilder();
      for (int v = 0; enumeration.hasMoreElements(); v++) {
        if (v > 0) {
          sb.append(' ');
        }
        Object collectionValue = enumeration.nextElement();
        // -- handle hex/base64 content
        Class<?> objType = collectionValue.getClass();
        if (objType.isArray() && (objType.getComponentType() == Byte.TYPE)) {
          collectionValue = encodeBinaryData(collectionValue, descriptor.getComponentType());
        }

        sb.append(collectionValue);
      }
      returnValue = sb;
    }

    return returnValue;
  }


  /**
   * Encode binary data.
   * 
   * @param valueToEncode The binary data to encode.
   * @param componentType The XML schema component type.
   * @return Encoded binary data in {@link String} form.
   */
  private Object encodeBinaryData(final Object valueToEncode, final String componentType) {
    String encodedValue;
    if (HexDecoder.DATA_TYPE.equals(componentType)) {
      encodedValue = HexDecoder.encode((byte[]) valueToEncode);
    } else {
      encodedValue = new String(Base64Encoder.encode((byte[]) valueToEncode));
    }
    return encodedValue;
  }


  /**
   * Processes the attributes for container objects
   *
   * @param target the object currently being marshalled.
   * @param classDesc the XMLClassDescriptor for the target object
   * @param atts the SAX attributes list to add attributes to
   */
  private void processContainerAttributes(Object target, XMLClassDescriptor classDesc,
      AttributesImpl atts) throws MarshalException {
    if (classDesc instanceof XMLClassDescriptorImpl) {
      if (!((XMLClassDescriptorImpl) classDesc).hasContainerFields())
        return;
    }

    XMLFieldDescriptor[] elemDescriptors = classDesc.getElementDescriptors();
    for (int i = 0; i < elemDescriptors.length; i++) {
      if (elemDescriptors[i] == null)
        continue;
      if (!elemDescriptors[i].isContainer())
        continue;
      processContainerAttributes(target, elemDescriptors[i], atts);
    }
  } // -- processContainerAttributes

  /**
   * Processes the attributes for container objects.
   *
   * @param target the object currently being marshalled.
   * @param containerFieldDesc the XMLFieldDescriptor for the containter to process
   * @param atts the SAX attributes list to add any necessary attributes to.
   * @throws MarshalException If there's a problem marshalling the attribute(s).
   */
  private void processContainerAttributes(final Object target,
      final XMLFieldDescriptor containerFieldDesc, final AttributesImpl atts)
      throws MarshalException {
    if (target.getClass().isArray()) {
      int length = Array.getLength(target);
      for (int j = 0; j < length; j++) {
        Object item = Array.get(target, j);
        if (item != null) {
          processContainerAttributes(item, containerFieldDesc, atts);
        }
      }
      return;
    } else if (target instanceof Enumeration) {
      Enumeration<?> enumeration = (Enumeration<?>) target;
      while (enumeration.hasMoreElements()) {
        Object item = enumeration.nextElement();
        if (item != null) {
          processContainerAttributes(item, containerFieldDesc, atts);
        }
      }
      return;
    }

    Object containerObject = containerFieldDesc.getHandler().getValue(target);

    if (containerObject == null) {
      return;
    }

    XMLClassDescriptor containerClassDesc =
        (XMLClassDescriptor) containerFieldDesc.getClassDescriptor();

    if (containerClassDesc == null) {
      containerClassDesc = getClassDescriptor(containerFieldDesc.getFieldType());
      if (containerClassDesc == null) {
        return;
      }
    }

    // Look for attributes
    XMLFieldDescriptor[] attrDescriptors = containerClassDesc.getAttributeDescriptors();
    for (int idx = 0; idx < attrDescriptors.length; idx++) {
      if (attrDescriptors[idx] == null) {
        continue;
      }
      if (attrDescriptors[idx].getLocationPath() == null
          || attrDescriptors[idx].getLocationPath().length() == 0) {
        processAttribute(containerObject, attrDescriptors[idx], atts);
      }
    }

    // recursively process containers
    processContainerAttributes(containerObject, containerClassDesc, atts);
  } // -- processContainerAttributes

  /**
   * Resolve a QName value ({URI}value) by declaring a namespace after having retrieved the prefix.
   */
  private Object resolveQName(Object value, XMLFieldDescriptor fieldDesc) {
    if ((value == null) || !(value instanceof String))
      return value;
    if (!(fieldDesc instanceof XMLFieldDescriptorImpl))
      return value;

    String result = (String) value;

    String nsURI = null;
    int idx = -1;
    if ((result.length() > 0) && (result.charAt(0) == '{')) {
      idx = result.indexOf('}');
      if (idx <= 0) {
        String err = "Bad QName value :'" + result + "', it should follow the pattern '{URI}value'";
        throw new IllegalArgumentException(err);
      }
      nsURI = result.substring(1, idx);
    } else
      return value;

    String prefix = ((XMLFieldDescriptorImpl) fieldDesc).getQNamePrefix();
    // no prefix provided, check if one has been previously defined
    if (prefix == null)
      prefix = namespacesStack.getNamespacePrefix(nsURI);
    // if still no prefix, use a naming algorithm (ns+counter).
    if (prefix == null)
      prefix = DEFAULT_PREFIX + (++_namespaceCounter);
    result = (prefix.length() != 0) ? prefix + ":" + result.substring(idx + 1)
        : result.substring(idx + 1);
    declareNamespace(prefix, nsURI);
    return result;
  }

  private void validate(final Object object) throws ValidationException {
    if (_validate) {
      // -- we must have a valid element before marshalling
      Validator validator = new Validator();
      ValidationContext context = new ValidationContext();
      context.setInternalContext(getInternalContext());
      // context.setConfiguration(_config);
      // context.setResolver(_cdResolver);
      validator.validate(object, context);
    }
  }

  /**
   * Returns the value of the given Castor XML-specific property.
   * 
   * @param name Qualified name of the CASTOR XML-specific property.
   * @return The current value of the given property.
   * @since 1.1.2
   */
  public String getProperty(final String name) {
    return getInternalContext().getStringProperty(name);
  }

  /**
   * Sets a custom value of a given Castor XML-specific property.
   * 
   * @param name Name of the Castor XML property
   * @param value Custom value to set.
   * @since 1.1.2
   */
  public void setProperty(final String name, final String value) {
    getInternalContext().setProperty(name, value);
    deriveProperties();
  }

  /**
   * Inner-class used for handling wrapper elements and locations.
   */
  static class WrapperInfo {
    private String _localName = null;
    private String _qName = null;
    private String _location = null;

    WrapperInfo(final String localName, final String qName, final String location) {
      _localName = localName;
      _qName = qName;
      _location = location;
    }
  }


  static class MarshalState {
    private String _xpath = null;
    private XMLFieldDescriptor[] _nestedAtts = null;
    private int _nestedAttCount = 0;
    private MarshalState _parent = null;
    private Object _owner = null;
    private String _xmlName = null;

    MarshalState(Object owner, String xmlName) {
      checkNotNull(owner, "The argument 'owner' must not be null.");
      checkNotNull(xmlName, "The argument 'xmlName' must not be null.");

      _owner = owner;
      _xmlName = xmlName;
    }


    MarshalState createMarshalState(Object owner, String xmlName) {
      MarshalState ms = new MarshalState(owner, xmlName);
      ms._parent = this;
      return ms;
    }

    String getXPath() {
      if (_xpath == null) {
        if (_parent != null) {
          _xpath = _parent.getXPath() + "/" + _xmlName;
        } else {
          _xpath = _xmlName;
        }
      }
      return _xpath;
    }

    Object getOwner() {
      return _owner;
    }

    MarshalState getParent() {
      return _parent;
    }
  }

  /**
   * A wrapper for a "Nil" object
   *
   */
  public static class NilObject {

    private XMLClassDescriptor _classDesc = null;
    private XMLFieldDescriptor _fieldDesc = null;

    NilObject(XMLClassDescriptor classDesc, XMLFieldDescriptor fieldDesc) {
      _classDesc = classDesc;
      _fieldDesc = fieldDesc;
    }

    /**
     * Returns the associated XMLClassDescriptor
     *
     * @return the XMLClassDescriptor
     */
    public XMLClassDescriptor getClassDescriptor() {
      return _classDesc;
    }

    /**
     * Returns the associated XMLFieldDescriptor
     *
     * @return the associated XMLFieldDescriptor
     */
    public XMLFieldDescriptor getFieldDescriptor() {
      return _fieldDesc;
    }
  }

  /**
   * To set the SAX {@link ContentHandler} which is used as destination at marshalling.
   * 
   * @param contentHandler the SAX {@link ContentHandler} to use as destination at marshalling
   */
  public void setContentHandler(final ContentHandler contentHandler) {
    _handler = contentHandler;
  }

  /**
   * Assigns the document handler, ignoring any possible exception.
   */
  private void setDocumentHandler() {
    try {
      // -- Due to a Xerces Serializer bug that doesn't allow declaring
      // -- multiple prefixes to the same namespace, we use the old
      // -- DocumentHandler format and process namespaces ourselves
      _handler = new DocumentHandlerAdapter(_serializer.asDocumentHandler());
    } catch (IOException iox) {
      // -- we can ignore this exception since it shouldn't
      // -- happen. If _serializer is not null, it means
      // -- we've already called this method successfully
      // -- in the Marshaller() constructor
      if (LOG.isDebugEnabled()) {
        LOG.debug("Error setting up document handler", iox);
      }
    }
  }

  /**
   * Checks if passed parameter is not null and not a empty string. In case it is, a
   * {@link IllegalArgumentException} is thrown.
   *
   * @param param the parameter to check
   * @param msg the error message to use for thrown exception
   *
   * @throws IllegalArgumentException if param is null
   */
  private static void checkNotEmpty(String param, String msg) {
    checkNotNull(param, msg);

    if (param.length() == 0) {
      throw new IllegalArgumentException(msg);
    }
  }

  /**
   * Checks if passed parameter is not null. In case it is, a {@link IllegalArgumentException} is
   * thrown.
   *
   * @param param the parameter to check
   * @param msg the error message to use for thrown exception
   *
   * @throws IllegalArgumentException if param is null
   */
  private static void checkNotNull(Object param, String msg) {

    if (param == null) {
      throw new IllegalArgumentException(msg);
    }
  }
} // -- Marshaller


