/**
 * 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.
 *
 * $Id$
 */

package org.exolab.castor.xml;

import java.io.PrintWriter;
import java.io.Reader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.StringTokenizer;

import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.castor.mapping.BindingType;
import org.castor.mapping.MappingUnmarshaller;
import org.castor.xml.BackwardCompatibilityContext;
import org.castor.xml.InternalContext;
import org.castor.xml.UnmarshalListenerAdapter;
import org.castor.xml.XMLProperties;
import org.exolab.castor.mapping.Mapping;
import org.exolab.castor.mapping.MappingException;
import org.exolab.castor.mapping.MappingLoader;
import org.exolab.castor.types.AnyNode;
import org.exolab.castor.util.ObjectFactory;
import org.exolab.castor.xml.location.FileLocation;
import org.exolab.castor.xml.util.AnyNode2SAX2;
import org.exolab.castor.xml.util.DOMEventProducer;
import org.w3c.dom.Node;
import org.xml.sax.ContentHandler;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.Parser;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

/**
 * An unmarshaller to allowing unmarshalling of XML documents to Java Objects. The Class must
 * specify the proper access methods (setters/getters) in order for instances of the Class to be
 * properly unmarshalled.
 *
 * @author <a href="mailto:kvisco-at-intalio.com">Keith Visco</a>
 * @version $Revision$ $Date: 2006-02-23 14:16:51 -0700 (Thu, 23 Feb 2006) $
 */
public class Unmarshaller {

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

  // ----------------------------/
  // - Private Member Variables -/
  // ----------------------------/

  /**
   * The Class that this Unmarshaller was created with.
   */
  private Class _class = null;

  /**
   * A boolean indicating whether or not collections (including arrays) should be cleared upon
   * initial use by Castor. False by default for backward compatibility.
   */
  private boolean _clearCollections = false;

  /**
   * A user specified IDResolver for resolving IDREFs.
   */
  private IDResolver _idResolver = null;

  /**
   * A boolean that specifies whether or not non-matched attributes should be ignored upon
   * unmarshalling.
   */
  private boolean _ignoreExtraAtts = true;

  /**
   * A boolean that specifies whether or not non-matched elements should be ignored upon
   * unmarshalling.
   */
  private boolean _ignoreExtraElements = false;

  /**
   * The instance of _class to Unmarshal into (optional)
   */
  private Object _instanceObj = null;

  /**
   * The EntityResolver used for resolving entities
   */
  EntityResolver entityResolver = null;

  /**
   * The class loader to use
   */
  private ClassLoader _loader = null;

  /**
   * A boolean to indicate that objects should be re-used where appropriate
   */
  private boolean _reuseObjects = false;


  /**
   * The unmarshaller listener that listens to unmarshalling event
   */
  private org.castor.xml.UnmarshalListener _unmarshalListener = null;

  /**
   * The flag indicating whether or not to validate during unmarshalling
   */
  private boolean _validate = false;

  /**
   * A flag indicating the unmarshaller should preserve "ignorable" whitespace. The XML instance can
   * control it's own behavior using the xml:space attribute. This sets the "default" behavior when
   * xml:space="default".
   */
  private boolean _wsPreserve = false;

  /**
   * A list of namespace To Package Mappings
   */
  private HashMap _namespaceToPackage = null;

  /**
   * An optional factory for unmarshalling objects
   */
  private ObjectFactory _objectFactory;

  /**
   * The Castor XML context to use at unmarshalling.
   */
  private InternalContext _internalContext;

  // ----------------/
  // - Constructors -/
  // ----------------/

  /**
   * An empty default constructor which: - sets the internal context to the backward compatibility
   * context - all other flags to defaults Internally the Unmarshaller(Class) constructor is called.
   */
  public Unmarshaller() {
    this((Class) null);
  }

  /**
   * A constructor which sets the root class.
   * 
   * Internally calls constructor Unmarshaller(InternalContext, Class) with an instance of
   * BackwardCompatibilityContext as context.
   * 
   * @param clazz root class for unmarshalling
   */
  public Unmarshaller(final Class clazz) {
    this(new BackwardCompatibilityContext(), clazz);
  }

  /**
   * Creates a new basic Unmarshaller.
   *
   * When using this constructor it will most likely be necessary to use a mapping file or
   * ClassDescriptorResolver So that the Unmarshaller can find the classes during the unmarshalling
   * process.
   * 
   * @param internalContext the {@link InternalContext} to use
   *
   * @throws IllegalArgumentException if internalContext is null
   */
  public Unmarshaller(final InternalContext internalContext) {
    this(internalContext, (Class) null, (ClassLoader) null);
  }

  /**
   * Creates a new Unmarshaller with the given Class.
   * 
   * @param internalContext the {@link InternalContext} to use
   * @param c the Class to create the Unmarshaller for, this may be null, if the
   *        Unmarshaller#setMapping is called to load a mapping for the root element of xml
   *        document.
   *
   * @throws IllegalArgumentException if internalContext is null
   */
  public Unmarshaller(final InternalContext internalContext, final Class c) {
    this(internalContext, c, null);
  } // -- Unmarshaller(Class)

  /**
   * Creates a new {@link Unmarshaller} with the given Class.
   *
   * @param internalContext the {@link InternalContext} to be used, for config, and such...
   * @param c the {@link Class} to create the {@link Unmarshaller} for, this may be null, if the
   *        Unmarshaller#setMapping is called to load a mapping for the root element of xml
   *        document.
   * @param loader The {@link ClassLoader} to use.
   *
   * @throws IllegalArgumentException if internalContext is null
   */
  public Unmarshaller(final InternalContext internalContext, final Class c,
      final ClassLoader loader) {
    super();

    checkNotNull(internalContext, "InternalContext must not be null.");
    setInternalContext(internalContext);

    setClass(c);
    _loader = loader;
    if ((loader == null) && (c != null)) {
      _loader = c.getClassLoader();
    }
    _internalContext.setClassLoader(_loader);
  }

  /**
   * Creates a new Unmarshaller with the given Mapping. An instance of BackwardsCompatibilityContext
   * is used as InternalContext.
   *
   * @param mapping The Mapping to use.
   * @throws MappingException in case that Unmarshaller fails to be instantiated
   */
  public Unmarshaller(final Mapping mapping) throws MappingException {
    this(new BackwardCompatibilityContext(), mapping);
  }

  /**
   * Creates a new Unmarshaller with the given Mapping.
   *
   * @param internalContext the internal context to use
   * @param mapping The Mapping to use.
   * @throws MappingException in case that Unmarshaller fails to be instantiated
   *
   * @throws IllegalArgumentException if internalContext is null
   */
  public Unmarshaller(final InternalContext internalContext, final Mapping mapping)
      throws MappingException {
    this(internalContext, null, null);
    if (mapping != null) {
      setMapping(mapping);
      this._loader = mapping.getClassLoader();
    }
  }

  /**
   * Creates a new Unmarshaller with the given Object.
   *
   * @param root the instance to unmarshal into. This may be null, if the Unmarshaller#setMapping is
   *        called to load a mapping for the root element of xml document.
   */
  public Unmarshaller(final Object root) {
    this(new BackwardCompatibilityContext(), root);
  }

  /**
   * Creates a new Unmarshaller with the given Object.
   *
   * @param internalContext the internal context to use
   * @param root the instance to unmarshal into. This may be null, if the Unmarshaller#setMapping is
   *        called to load a mapping for the root element of xml document.
   *
   * @throws IllegalArgumentException if internalContext is null
   */
  public Unmarshaller(final InternalContext internalContext, final Object root) {
    this(internalContext, null, null);
    if (root != null) {
      final Class clazz = root.getClass();
      setClass(clazz);
      _loader = clazz.getClassLoader();
    }
    _instanceObj = root;
  }

  /**
   * Adds a mapping from the given namespace URI to the given package name.
   * 
   * @param nsURI the namespace URI to map from
   * @param packageName the package name to map to
   */
  public void addNamespaceToPackageMapping(final String nsURI, final String packageName) {
    if (_namespaceToPackage == null) {
      _namespaceToPackage = new HashMap();
    }
    String iNsUri = (nsURI == null) ? "" : nsURI;
    String iPackageName = (packageName == null) ? "" : packageName;
    _namespaceToPackage.put(iNsUri, iPackageName);

  } // -- addNamespaceToPackageMapping


  /**
   * Creates and initalizes an UnmarshalHandler
   * 
   * @return the new UnmarshalHandler
   **/
  public UnmarshalHandler createHandler() {

    UnmarshalHandler handler = new UnmarshalHandler(_internalContext, _class);

    handler.setClearCollections(_clearCollections);
    handler.setReuseObjects(_reuseObjects);
    handler.setValidation(_validate);
    handler.setIgnoreExtraAttributes(_ignoreExtraAtts);
    handler.setIgnoreExtraElements(_ignoreExtraElements);
    handler.setInternalContext(_internalContext);
    handler.setWhitespacePreserve(_wsPreserve);

    // If the object factory has been set, set it on the handler
    if (this._objectFactory != null) {
      handler.setObjectFactory(this._objectFactory);
    }

    // -- copy namespaceToPackageMappings
    if (_namespaceToPackage != null) {
      Iterator keys = _namespaceToPackage.keySet().iterator();
      while (keys.hasNext()) {
        String nsURI = (String) keys.next();
        String pkgName = (String) _namespaceToPackage.get(nsURI);
        handler.addNamespaceToPackageMapping(nsURI, pkgName);
      }
    }

    if (_instanceObj != null) {
      handler.setRootObject(_instanceObj);
    }
    if (_idResolver != null)
      handler.setIDResolver(_idResolver);

    if (_loader != null)
      handler.setClassLoader(_loader);

    if (_unmarshalListener != null)
      handler.setUnmarshalListener(_unmarshalListener);

    return handler;
  } // -- createHandler

  /**
   * Indicates whether or not validation should be performed during umarshalling.
   * 
   * @return True if validation is performed during umarshalling.
   */
  public boolean isValidating() {
    return _validate;
  }

  /**
   * Sets the 'expected' {@link Class} instance on the Unmarshaller.
   *
   * @param clazz the Class to create the Unmarshaller for, this may be null, if the
   *        Unmarshaller#setMapping is called to load a mapping for the root element of xml
   *        document.
   */
  public void setClass(Class clazz) {
    _class = clazz;
  } // -- setClass(Class)

  /**
   * Sets the 'expected' {@link Object} instance on the Unmarshaller, into which will be
   * unmarshalled.
   *
   * @param root the instance to unmarshal into. This may be null, if the Unmarshaller#setMapping is
   *        called to load a mapping for the root element of xml document.
   */
  public void setObject(Object root) {
    _instanceObj = root;
  } // -- setObject(Object)

  /**
   * Sets the ClassLoader to use when loading new classes. <br />
   * <b>Note:</b>This ClassLoader is used for classes loaded by the unmarshaller only. If a Mapping
   * has been set, the Mapping has it's own ClassLoader and may also need to be set propertly.
   * <br />
   *
   * @param loader the ClassLoader to use
   **/
  public void setClassLoader(ClassLoader loader) {
    this._loader = loader;
  } // -- setClassLoader


  /**
   * Sets whether or not to clear collections (including arrays) upon first use to remove default
   * values. By default, and for backward compatibility with previous versions of Castor this value
   * is false, indicating that collections are not cleared before initial use by Castor.
   *
   * @param clear the boolean value that when true indicates collections should be cleared upon
   *        first use.
   */
  public void setClearCollections(boolean clear) {
    _clearCollections = clear;
  } // -- setClearCollections

  /**
   * Custom debugging is replaced with commons-logging
   * 
   * @deprecated
   **/
  public void setDebug(boolean debug) {
    // no-op
  } // -- setDebug

  /**
   * Sets the EntityResolver to use when resolving system and public ids with respect to entites and
   * Document Type.
   * 
   * @param entityResolver the EntityResolver to use when resolving System and Public ids.
   **/
  public void setEntityResolver(EntityResolver entityResolver) {
    this.entityResolver = entityResolver;
  } // -- entityResolver

  /**
   * Sets the IDResolver to use when resolving IDREFs for which no associated element may exist in
   * XML document.
   *
   * @param idResolver the IDResolver to use when resolving IDREFs for which no associated element
   *        may exist in the XML document.
   **/
  public void setIDResolver(IDResolver idResolver) {
    _idResolver = idResolver;
  } // -- idResolver

  /**
   * Sets whether or not attributes that do not match a specific field should simply be ignored or
   * reported as an error. By default, extra attributes are ignored.
   *
   * @param ignoreExtraAtts a boolean that when true will allow non-matched attributes to simply be
   *        ignored.
   */
  public void setIgnoreExtraAttributes(boolean ignoreExtraAtts) {
    _ignoreExtraAtts = ignoreExtraAtts;
  } // -- setIgnoreExtraAttributes

  /**
   * Sets whether or not elements that do not match a specific field should simply be ignored or
   * reported as an error. By default, extra elements are flagged as an error.
   *
   * @param ignoreExtraElements a boolean that when true will allow non-matched elements to simply
   *        be ignored.
   */
  public void setIgnoreExtraElements(boolean ignoreExtraElements) {
    _ignoreExtraElements = ignoreExtraElements;
  } // -- setIgnoreExtraElements

  /**
   * Logging is replaced with commons-logging.
   * 
   * @param printWriter the PrintWriter to use for logging
   * @deprecated
   **/
  public void setLogWriter(PrintWriter printWriter) {
    // no-op
  } // -- setLogWriter

  /**
   * Sets the Mapping to use during unmarshalling. If the Mapping has a ClassLoader it will be used
   * during unmarshalling.
   *
   * @param mapping Mapping to use during unmarshalling.
   * @see #setResolver
   */
  public void setMapping(final Mapping mapping) throws MappingException {
    if (_loader == null) {
      _loader = mapping.getClassLoader();
    }

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

  /**
   * Sets a boolean that when true indicates that objects contained within the object model should
   * be re-used where appropriate. This is only valid when unmarshalling to an existing object.
   *
   * @param reuse the boolean indicating whether or not to re-use existing objects in the object
   *        model.
   **/
  public void setReuseObjects(boolean reuse) {
    _reuseObjects = reuse;
  } // -- setReuseObjects

  /**
   * Sets an optional {@link org.exolab.castor.xml.UnmarshalListener} to receive pre and post
   * unmarshal notification for each Object in the tree. An UnmarshalListener is often used to allow
   * objects to appropriately initialize themselves by taking application specific behavior as they
   * are unloaded. 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.<br/>
   * The deprecated listener set with this method will be wrapped by an adapter.
   *
   * @param listener the {@link org.exolab.castor.xml.UnmarshalListener} to set.
   * @deprecated replaced by {@link org.castor.xml.UnmarshalListener}
   */
  public void setUnmarshalListener(org.exolab.castor.xml.UnmarshalListener listener) {
    if (listener == null) {
      _unmarshalListener = null;
    } else {
      UnmarshalListenerAdapter adapter = new UnmarshalListenerAdapter();
      adapter.setOldListener(listener);
      _unmarshalListener = adapter;
    }
  }

  /**
   * Sets an optional {@link org.castor.xml.UnmarshalListener} to receive pre and post unmarshal
   * notification for each Object in the tree. An UnmarshalListener is often used to allow objects
   * to appropriately initialize themselves by taking application specific behavior as they are
   * unloaded. 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 {@link org.castor.xml.UnmarshalListener} to set.
   */
  public void setUnmarshalListener(org.castor.xml.UnmarshalListener listener) {
    _unmarshalListener = listener;
  }

  /**
   * Sets the flag for validation.
   * 
   * @param validate A boolean to indicate whether or not validation should be done during
   *        umarshalling. <br/>
   *        By default validation will be performed.
   */
  public void setValidation(boolean validate) {
    _validate = validate;
  } // -- setValidation

  /**
   * Sets the top-level whitespace (xml:space) to either preserving or non preserving. The XML
   * document can override this value using xml:space on specific elements.This sets the "default"
   * behavior when xml:space="default".
   *
   * @param preserve a boolean that when true enables whitespace preserving by default.
   */
  public void setWhitespacePreserve(boolean preserve) {
    _wsPreserve = preserve;
  } // -- setWhitespacePreserve

  /**
   * Unmarshals Objects of this Unmarshaller's Class type. The Class must specify the proper access
   * methods (setters/getters) in order for instances of the Class to be properly unmarshalled.
   * 
   * @param reader the Reader to read the XML from
   * @exception MarshalException when there is an error during the unmarshalling process
   * @exception ValidationException when there is a validation error
   **/
  public Object unmarshal(Reader reader) throws MarshalException, ValidationException {
    return unmarshal(new InputSource(reader));
  } // -- unmarshal(Reader reader)

  /**
   * Unmarshals Objects of this Unmarshaller's Class type. The Class must specify the proper access
   * methods (setters/getters) in order for instances of the Class to be properly unmarshalled.
   * 
   * @param eventProducer the EventProducer which produces the SAX events
   * @exception MarshalException when there is an error during the unmarshalling process
   * @exception ValidationException when there is a validation error
   * @deprecated please use @see #unmarshal(SAX2EventProducer) instead.
   **/
  public Object unmarshal(EventProducer eventProducer)
      throws MarshalException, ValidationException {
    UnmarshalHandler handler = createHandler();
    eventProducer.setDocumentHandler(handler);
    try {
      eventProducer.start();
    } catch (org.xml.sax.SAXException sx) {
      convertSAXExceptionToMarshalException(handler, sx);
    }
    return handler.getObject();

  } // -- unmarshal(EventProducer)

  /**
   * Unmarshals Objects of this Unmarshaller's Class type. The Class must specify the proper access
   * methods (setters/getters) in order for instances of the Class to be properly unmarshalled.
   * 
   * @param eventProducer the SAX2EventProducer instance which produces the SAX 2 events
   * @exception MarshalException when there is an error during the unmarshalling process
   * @exception ValidationException when there is a validation error
   * @since 1.0M3
   **/
  public Object unmarshal(SAX2EventProducer eventProducer)
      throws MarshalException, ValidationException {
    UnmarshalHandler handler = createHandler();
    eventProducer.setContentHandler(handler);
    try {
      eventProducer.start();
    } catch (org.xml.sax.SAXException sx) {
      convertSAXExceptionToMarshalException(handler, sx);
    }
    return handler.getObject();

  } // -- unmarshal(SAX2EventProducer)

  /**
   * Unmarshals objects of this {@link Unmarshaller}'s Class type from an {@link AnyNode} instance.
   * 
   * The Class must specify the proper access methods (setters/getters) in order for instances of
   * the Class to be properly unmarshalled.
   * 
   * @param anyNode {@link AnyNode} instance to be unmarshalled from
   * @exception MarshalException when there is an error during the unmarshalling process
   * @return The {@link Object} instance that is a result of unmarshalling.
   **/
  public Object unmarshal(final AnyNode anyNode) throws MarshalException {
    UnmarshalHandler handler = createHandler();
    try {
      AnyNode2SAX2.fireEvents(anyNode, handler);
    } catch (SAXException sex) {
      convertSAXExceptionToMarshalException(handler, sex);
    }
    return handler.getObject();
  }

  /**
   * Unmarshals Objects of this Unmarshaller's Class type. The Class must specify the proper access
   * methods (setters/getters) in order for instances of the Class to be properly unmarshalled.
   * 
   * @param source the InputSource to read the XML from
   * @exception MarshalException when there is an error during the unmarshalling process
   * @exception ValidationException when there is a validation error
   **/
  public Object unmarshal(InputSource source) throws MarshalException, ValidationException {
    XMLReader reader = null;
    Parser parser = null;

    // -- First try XMLReader
    try {
      reader = _internalContext.getXMLReader();
      if (entityResolver != null) {
        reader.setEntityResolver(entityResolver);
      }
    } catch (RuntimeException rx) {
      LOG.debug("Unable to create SAX XMLReader, attempting SAX Parser.");
    }

    if (reader == null) {
      parser = _internalContext.getParser();
      if (parser == null)
        throw new MarshalException("Unable to create SAX Parser.");
      if (entityResolver != null)
        parser.setEntityResolver(entityResolver);
    }


    UnmarshalHandler handler = createHandler();


    try {
      if (reader != null) {
        reader.setContentHandler(handler);
        reader.setErrorHandler(handler);
        reader.parse(source);
      } else {
        parser.setDocumentHandler(handler);
        parser.setErrorHandler(handler);
        parser.parse(source);
      }
    } catch (java.io.IOException ioe) {
      throw new MarshalException(ioe);
    } catch (org.xml.sax.SAXException sx) {
      convertSAXExceptionToMarshalException(handler, sx);
    }

    return handler.getObject();
  } // -- unmarshal(InputSource)


  /**
   * Unmarshals Objects of this Unmarshaller's Class type. The Class must specify the proper access
   * methods (setters/getters) in order for instances of the Class to be properly unmarshalled.
   * 
   * @param node the DOM node to read the XML from
   * @exception MarshalException when there is an error during the unmarshalling process
   * @exception ValidationException when there is a validation error
   **/
  public Object unmarshal(Node node) throws MarshalException, ValidationException {
    return unmarshal(new DOMEventProducer(node));
  } // -- unmarshal(EventProducer)

  /**
   * Unmarshals objects of this {@link Unmarshaller}'s class type. The class must specify the proper
   * access methods (setters/getters) in order for instances of the class to be properly
   * unmarshalled.
   * 
   * @param eventReader the StaX {@link XMLEventReader} to read XML from.
   * @exception MarshalException indicates a general problem during the unmarshalling process.
   * @throws ValidationException indicates a problem related to validation.
   * 
   * @since 1.3.2
   **/
  public Object unmarshal(XMLEventReader eventReader) throws MarshalException, ValidationException {
    return unmarshal(BaseSax2EventFromStaxProducer.createSax2EventFromStax(eventReader));
  }

  /**
   * Unmarshals objects of this {@link Unmarshaller}'s class type. The class must specify the proper
   * access methods (setters/getters) in order for instances of the class to be properly
   * unmarshalled.
   * 
   * @param streamReader the STaX {@link XMLStreamReader} to read XML from.
   * @exception MarshalException indicates a general problem during the unmarshalling process.
   * @throws ValidationException indicates a problem related to validation.
   * 
   * @since 1.3.2
   **/
  public Object unmarshal(XMLStreamReader streamReader)
      throws MarshalException, ValidationException {
    return unmarshal(BaseSax2EventFromStaxProducer.createSax2EventFromStax(streamReader));
  }

  /**
   * Unmarshals objects of this {@link Unmarshaller}'s class type. <br/>
   * The class must specify the proper access methods (setters/getters) in order for instances of
   * the class to be properly unmarshalled. </br/>
   * 
   * @param eventProducer the {@link SAX2EventAndErrorProducer} instance which produces the SAX 2
   *        events and handles SAX 2 errors.
   * @exception MarshalException indiactes a general error during the unmarshalling process.
   * @exception ValidationException indicates a validation error.
   * @since 1.3.2
   **/
  public Object unmarshal(SAX2EventAndErrorProducer eventProducer)
      throws MarshalException, ValidationException {
    UnmarshalHandler handler = createHandler();
    eventProducer.setContentHandler(handler);
    eventProducer.setErrorHandler(handler);
    try {
      eventProducer.start();
    } catch (org.xml.sax.SAXException sx) {
      convertSAXExceptionToMarshalException(handler, sx);
    }
    return handler.getObject();

  }

  /**
   * Unmarshals ths given {@link Source} instance. Currently this method will support fallowing
   * classes {@link DOMSource}, {@link SAXSource} and {@link StreamSource}.
   *
   * @param source the source to unmarshal
   *
   * @return the unmarshalled object instance
   *
   * @throws IllegalArgumentException if the given source is null or it is unsupported
   * @throws MarshalException indiactes a general error during the unmarshalling process.
   * @throws ValidationException indicates a validation error.
   */
  public Object unmarshal(Source source) throws MarshalException, ValidationException {
    checkNotNull(source, "The given 'javax.xml.transform.Source' instance is null.");

    if (source instanceof DOMSource) {
      DOMSource domSource = (DOMSource) source;
      if (domSource.getNode() != null) {
        return unmarshal(domSource.getNode());
      }
    } else if (source instanceof SAXSource) {
      SAXSource saxSource = (SAXSource) source;

      if (saxSource.getInputSource() != null) {
        // TODO should the XMLReader from the SAXSource should be used instead ?
        return unmarshal(saxSource.getInputSource());
      }
    } else if (source instanceof StreamSource) {
      StreamSource streamSource = (StreamSource) source;

      if (streamSource.getInputStream() != null) {
        return unmarshal(new InputSource(streamSource.getInputStream()));
      } else if (streamSource.getReader() != null) {
        return unmarshal(streamSource.getReader());
      }
    }

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

  /**
   * Converts a SAXException to a (localised) MarshalException.
   * 
   * @param handler The {@link UnmarshalHandler} required to obtain DocumentLocator instance.
   * @param sex The {@link SAXException} instance
   * @throws MarshalException The {@link MarshalException} instance derived from the SAX exception.
   */
  private void convertSAXExceptionToMarshalException(UnmarshalHandler handler, SAXException sex)
      throws MarshalException {
    Exception except = sex.getException();
    if (except == null) {
      except = sex;
    }
    MarshalException marshalEx = new MarshalException(except);
    if (handler.getDocumentLocator() != null) {
      FileLocation location = new FileLocation();
      location.setFilename(handler.getDocumentLocator().getSystemId());
      location.setLineNumber(handler.getDocumentLocator().getLineNumber());
      location.setColumnNumber(handler.getDocumentLocator().getColumnNumber());
      marshalEx.setLocation(location);
    }
    throw marshalEx;
  }

  // -------------------------/
  // - Public Static Methods -/
  // -------------------------/
  /**
   * Returns a ContentHandler for the given UnmarshalHandler
   *
   * @return the ContentHandler
   */
  public static ContentHandler getContentHandler(UnmarshalHandler handler) throws SAXException {
    return handler;
  } // -- getContentHandler

  /**
   * Unmarshals Objects of the given Class type. The Class must specify the proper access methods
   * (setters/getters) in order for instances of the Class to be properly unmarshalled.
   *
   * <p>
   * <b>Note:</b>This is a *static* method, any mapping files set on a particular Unmarshaller
   * instance, and any changes made via setters will be unavailable to this method.
   * </p>
   *
   * @param c the Class to create a new instance of
   * @param reader the Reader to read the XML from
   * @exception MarshalException when there is an error during the unmarshalling process
   * @exception ValidationException when there is a validation error
   **/
  public static Object unmarshal(Class c, Reader reader)
      throws MarshalException, ValidationException {
    Unmarshaller unmarshaller = createUnmarshaller(c);
    return unmarshaller.unmarshal(reader);
  } // -- void unmarshal(Writer)

  /**
   * Helper method for static #unmarshal methods to create an {@link Unmarshaller} instance.
   * 
   * @param clazz The root class to be used during unmarshalling.
   * @return An {@link Unmarshaller} instance.
   */
  private static Unmarshaller createUnmarshaller(final Class clazz) {
    XMLContext xmlContext = new XMLContext();
    Unmarshaller unmarshaller = xmlContext.createUnmarshaller();
    unmarshaller.setClass(clazz);

    // TODO: Should this be at level INFO?
    if (LOG.isDebugEnabled()) {
      LOG.debug("*static* unmarshal method called, this will ignore any "
          + "mapping files or changes made to an Unmarshaller instance.");
    }

    // -- for backward compatibility with Castor versions
    // -- prior to version 0.9.5.3
    unmarshaller.setWhitespacePreserve(true);

    return unmarshaller;
  }

  /**
   * Unmarshals Objects of the given Class type. The Class must specify the proper access methods
   * (setters/getters) in order for instances of the Class to be properly unmarshalled.
   *
   * <p>
   * <b>Note:</b>This is a *static* method, any mapping files set on a particular Unmarshaller
   * instance, and any changes made via setters will be unavailable to this method.
   * </p>
   *
   * @param c the Class to create a new instance of
   * @param source the InputSource to read the XML from
   * @exception MarshalException when there is an error during the unmarshalling process
   * @exception ValidationException when there is a validation error
   */
  public static Object unmarshal(Class c, InputSource source)
      throws MarshalException, ValidationException {
    Unmarshaller unmarshaller = createUnmarshaller(c);

    return unmarshaller.unmarshal(source);
  } // -- void unmarshal(Writer)

  /**
   * Unmarshals Objects of the given Class type. The Class must specify the proper access methods
   * (setters/getters) in order for instances of the Class to be properly unmarshalled.
   *
   * <p>
   * <b>Note:</b>This is a *static* method, any mapping files set on a particular Unmarshaller
   * instance, and any changes made via setters will be unavailable to this method.
   * </p>
   *
   * @param c The Class to create a new instance of.
   * @param node The DOM Node to read the XML from.
   * @exception MarshalException When there is an error during the unmarshalling process.
   * @exception ValidationException When there is a validation error.
   */
  public static Object unmarshal(Class c, Node node) throws MarshalException, ValidationException {
    Unmarshaller unmarshaller = createUnmarshaller(c);

    return unmarshaller.unmarshal(node);
  } // -- void unmarshal(Writer)

  /**
   * Set an object factory for the unmarshaller. This factory will be used to construct the objects
   * being unmarshalled.
   * 
   * @param objectFactory Factory used for constructing objects during unmarshalling.
   */
  public void setObjectFactory(final ObjectFactory objectFactory) {
    this._objectFactory = objectFactory;
  } // -- setObjectFactory

  /**
   * 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) {
    Object propertyValue = _internalContext.getProperty(name);
    if ((propertyValue != null) && !(propertyValue instanceof String)) {
      String message = "Requested property: " + name + " is not of type String, but: "
          + propertyValue.getClass() + " throwing IllegalStateException.";
      LOG.warn(message);
      throw new IllegalStateException(message);
    }
    return (String) propertyValue;
  }

  /**
   * 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) {
    _internalContext.setProperty(name, value);
  }

  /**
   * To set the internal XML Context to be used.
   * 
   * @param internalContext the context to be used
   */
  public void setInternalContext(final InternalContext internalContext) {
    _internalContext = internalContext;
    deriveProperties();
  }

  /**
   * Derive class-level properties from {@link XMLProperties} as defined {@link InternalContext}.
   * This method will be called after a new {@link InternalContext} has been set.
   * 
   * @link #setInternalContext(InternalContext)
   */
  private void deriveProperties() {
    _validate = _internalContext.marshallingValidation();
    _ignoreExtraElements = (!_internalContext.strictElements());

    // -- process namespace to package mappings
    String mappings = _internalContext.getStringProperty(XMLProperties.NAMESPACE_PACKAGE_MAPPINGS);
    if (mappings != null && mappings.length() > 0) {
      StringTokenizer tokens = new StringTokenizer(mappings, ",");
      while (tokens.hasMoreTokens()) {
        String token = tokens.nextToken();
        int sepIdx = token.indexOf('=');
        if (sepIdx < 0) {
          continue;
        }
        String ns = token.substring(0, sepIdx).trim();
        String javaPackage = token.substring(sepIdx + 1).trim();
        addNamespaceToPackageMapping(ns, javaPackage);
      }
    }
  }

  /**
   * To get the internal XML Context that is in use.
   * 
   * @return the {@link InternalContext} in use
   */
  public InternalContext getInternalContext() {
    return _internalContext;
  }

  /**
   * Sets the XMLClassDescriptorResolver to use during unmarshalling
   * 
   * @param xmlClassDescriptorResolver the XMLClassDescriptorResolver to use
   * @see #setMapping <BR />
   *      <B>Note:</B> This method will nullify any Mapping currently being used by this
   *      Unmarshaller
   */
  public void setResolver(XMLClassDescriptorResolver xmlClassDescriptorResolver) {
    _internalContext.setResolver(xmlClassDescriptorResolver);
  }

  /**
   * 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);
    }
  }
} // -- Unmarshaller

