/*
 * Javolution - Java(TM) Solution for Real-Time and Embedded Systems
 * Copyright (C) 2006 - Javolution (http://javolution.org/)
 * All rights reserved.
 * 
 * Permission to use, copy, modify, and distribute this software is
 * freely granted, provided that this notice is preserved.
 */
package javolution.xml;


import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;

import java.lang.IllegalStateException;
import javolution.context.ObjectFactory;
import javolution.lang.Reusable;
import javolution.xml.stream.XMLStreamException;
import javolution.xml.stream.XMLStreamReader;
import javolution.xml.stream.XMLStreamReaderImpl;


/**
 * <p> This class restores objects which have been serialized in XML
 *     format using an {@link XMLObjectWriter}.</p>
 *     
 * <p> When the XML document is parsed, each elements are recursively
 *     processed and Java objects are created using the {@link XMLFormat}
 *     of the class as identified by the {@link XMLBinding}.</p>
 *     
 * <p> Multiple objects can be read from the same XML input.
 *     For example:[code]
 *     XMLObjectReader reader = XMLObjectReader.newInstance(inputStream);
 *     while (reader.hasNext()) {
 *         Message message = reader.read("Message", Message.class);
 *     }
 *     reader.close(); // Reader is recycled, the underlying stream is closed.
 *     [/code]</p>
 *     
 * @author  <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
 * @version 4.0, September 4, 2006
 */
public class XMLObjectReader implements Reusable {

    /**
     * Holds the associated factory.
     */
    private static final ObjectFactory FACTORY = new ObjectFactory() {

        protected Object create() {
            return new XMLObjectReader();
        } 
        
    };

    /**
     * Hold the xml element used when parsing.
     */
    private final XMLFormat.InputElement _xml = new XMLFormat.InputElement();

    /**
     * Holds reader if any.
     */
    private Reader _reader;

    /**
     * Holds input stream if any.
     */
    private InputStream _inputStream;

    /**
     * Indicates if factory produced.
     */
    private boolean _isFactoryProduced;

    /**
     * Returns a XML object reader (potentially recycled) having the specified
     * input stream as input.
     * 
     * @param in the input stream.
     */
    public static XMLObjectReader newInstance(InputStream in) throws XMLStreamException {
        XMLObjectReader reader = (XMLObjectReader) FACTORY.object();
        reader._isFactoryProduced = true;
        reader.setInput(in);
        return reader;
    }

    /**
     * Returns a XML object reader (potentially recycled) having the specified
     * input stream/encoding as input.
     * 
     * @param in the input stream.
     * @param encoding the input stream encoding
     */
    public static XMLObjectReader newInstance(InputStream in, String encoding) throws XMLStreamException {
        XMLObjectReader reader = (XMLObjectReader) FACTORY.object();
        reader._isFactoryProduced = true;
        reader.setInput(in, encoding);
        return reader;
    }

    /**
     * Returns a XML object reader (potentially recycled) having the specified
     * reader as input.
     * 
     * @param in the reader source.
     */
    public static XMLObjectReader newInstance(Reader in) throws XMLStreamException {
        XMLObjectReader reader = (XMLObjectReader) FACTORY.object();
        reader._isFactoryProduced = true;
        reader.setInput(in);
        return reader;
    }

    /**
     * Recycles the specified XMLObjectReader.
     *
     * @param that the instance to recycle.
     */
    public static void recycle(XMLObjectReader that) {
        FACTORY.recycle(that);
    }

    /**
     * Default constructor.
     */
    public XMLObjectReader() {
    }

    /**
     * Returns the stream reader being used by this reader (it can be 
     * used to set prefix, read prologs, etc).
     * 
     * @return the stream reader.
     */
    public XMLStreamReader getStreamReader() {
        return _xml._reader;
    }

    /**
     * Sets the input stream source for this XML object reader
     * (encoding retrieved from XML prolog if any).
     * 
     * @param  in the source input stream.
     * @return <code>this</code>
     * @see    XMLStreamReaderImpl#setInput(InputStream)
     */
    public XMLObjectReader setInput(InputStream in) throws XMLStreamException {
        if ((_inputStream != null) || (_reader != null))
            throw new IllegalStateException("Reader not closed or reset");
        _xml._reader.setInput(in);
        _inputStream = in;
        return this;
    }

    /**
     * Sets the input stream source and encoding for this XML object reader.
     * 
     * @param in the input source.
     * @param encoding the associated encoding.
     * @return <code>this</code>
     * @see    XMLStreamReaderImpl#setInput(InputStream, String)
     */
    public XMLObjectReader setInput(InputStream in, String encoding)
            throws XMLStreamException {
        if ((_inputStream != null) || (_reader != null))
            throw new IllegalStateException("Reader not closed or reset");
        _xml._reader.setInput(in, encoding);
        _inputStream = in;
        return this;
    }

    /**
     * Sets the reader input source for this XML stream reader. 
     * 
     * @param  in the source reader.
     * @return <code>this</code>
     * @see    XMLStreamReaderImpl#setInput(Reader)
     */
    public XMLObjectReader setInput(Reader in) throws XMLStreamException {
        if ((_inputStream != null) || (_reader != null))
            throw new IllegalStateException("Reader not closed or reset");
        _xml._reader.setInput(in);
        _reader = in;
        return this;
    }

    /**
     * Sets the XML binding to use with this object reader.
     * 
     * @param binding the XML binding to use.
     * @return <code>this</code>
     */
    public XMLObjectReader setBinding(XMLBinding binding) {
        _xml.setBinding(binding);
        return this;
    }

    /**
     * Sets the XML reference resolver to use with this object reader
     * (the same resolver can be used accross multiple readers).
     * 
     * @param referenceResolver the XML reference resolver.
     * @return <code>this</code>
     */
    public XMLObjectReader setReferenceResolver(
            XMLReferenceResolver referenceResolver) {
        _xml.setReferenceResolver(referenceResolver);
        return this;
    }

    /**
     * Indicates if more elements can be read. This method 
     * positions the reader at the start of the
     * next element to be read (if any).
     *
     * @return <code>true</code> if more element/data to be read; 
     *         <code>false</code> otherwise.
     * @see    XMLFormat.InputElement#hasNext()
     */
    public boolean hasNext() throws XMLStreamException {
        return _xml.hasNext();
    }

    /**
     * Returns the object corresponding to the next element/data.
     *
     * @return the next nested object (can be <code>null</code>)
     * @throws XMLStreamException if <code>hasNext() == false</code>
     * @see    XMLFormat.InputElement#getNext()
     */
    public  <T>  T  read() throws XMLStreamException {
        return ( T )_xml.getNext();
    }

    /**
     * Returns the object corresponding to the next nested element only 
     * if it has the specified local name. 
     *
     * @param name the local name of the next element.
     * @return the next content object or <code>null</code> if the 
     *         local name does not match.
     * @see    XMLFormat.InputElement#get(String)
     */
    public  <T>  T  read(String name) throws XMLStreamException {
        return ( T ) _xml.get(name);
    }

    /**
     * Returns the object corresponding to the next nested element only 
     * if it has the specified local name and namespace URI.
     *
     * @param localName the local name.
     * @param uri the namespace URI.
     * @return the next content object or <code>null</code> if the 
     *         name/uri does not match.
     * @see    XMLFormat.InputElement#get(String, String)
     */
    public  <T>  T  read(String localName, String uri)
            throws XMLStreamException {
        return ( T ) _xml.get(localName, uri);
    }

    /**
     * Returns the object corresponding to the next nested element only 
     * if it has the specified local name; the actual object type is identified 
     * by the specified class parameter. 
     *      
     * @param name the name of the element to match.
     * @param cls the non-abstract class identifying the object to return.
     * @return <code>read(name, null, cls)</code>
     */
    public <T>  T read(String name, Class <T> cls)
            throws XMLStreamException {
        return _xml.get(name, cls);
    }

    /**
     * Returns the object corresponding to the next nested element only 
     * if it has the specified local name and namespace URI; the 
     * actual object type is identified by the specified class parameter. 
     *      
     * @param localName the local name.
     * @param uri the namespace URI.
     * @param cls the non-abstract class identifying the object to return.
     * @return the next content object or <code>null</code> if no match.
     */
    public <T>  T read(String localName, String uri,
            Class <T> cls) throws XMLStreamException {
        return _xml.get(localName, uri, cls);
    }

    /**
     * Closes this reader and its underlying input then {@link #reset reset}
     * this reader for potential reuse.
     */
    public void close() throws XMLStreamException {
        try {
            if (_inputStream != null) {
                _inputStream.close();
                reset();
            } else if (_reader != null) {
                _reader.close();
                reset();
            }
            if (_isFactoryProduced) {
                FACTORY.recycle(this);
            }
        } catch (IOException e) {
            throw new XMLStreamException(e);
        }
    }

    /**
     * Resets this object reader for reuse.
     */
    public void reset() {
        _xml.reset();
        _reader = null;
        _inputStream = null;
    }
}