//==============================================================================
// StackedReader.java
//==============================================================================

package tribble.io;

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

import java.lang.NullPointerException;


/*******************************************************************************
* Push-down stack of input reader streams.
* Manages a stack of <tt>Reader</tt> objects.
*
* <p>
* This is useful for implementing readers for source files that contain nested
* inclusions of other source files (a la the <tt>#include</tt> directive of
* C and C++).
*
* <p>
* Each file inclusion is achieved by pushing the included file as a new reader
* onto the reader stack.  Subsequent input is then read from the reader on the
* top of the stack.  Once the end of the input of that reader is reached, the
* reader is automatically closed and popped from the stack, and subsequent input
* is read from the previously pushed reader that is now the new top of the
* stack.  Once all of the reader streams have been popped, the end of the
* entire stream is reached and no more input is available.
*
* <!-- ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ -->
* <dl>
* <dt> <b>Source code:</b> </dt>
*  <dd>
*   <a href="../../../src/java/tribble/io/StackedReader.java"
*    >http://david.tribble.com/src/java/tribble/io/StackedReader.java</a>
*  </dd>
* <dt> <b>Documentation:</b> </dt>
*  <dd>
*   <a href="StackedReader.html"
*    >http://david.tribble.com/docs/tribble/io/StackedReader.html</a>
*  </dd>
* </dl>
*
* <!-- ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ -->
* @version	$Revision: 1.2 $ $Date: 2008/09/27 18:04:14 $
* @since	2008-09-27
* @author	David R. Tribble (david&#64;tribble.com)
*	<p>
*	Copyright ©2008 by David R. Tribble, all rights reserved.<br/>
*	Permission is granted to any person or entity except those designated by
*	by the United States Department of State as a terrorist, or terrorist
*	government or agency, to use and distribute this source code provided
*	that the original copyright notice remains present and unaltered.
*/

public class StackedReader
    extends java.io.Reader
{
    static final String		REV =
        "@(#)tribble/io/StackedReader.java $Revision: 1.2 $ $Date: 2008/09/27 18:04:14 $\n";


// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Constants

    /** Special code indicating end of the input stream (-1). */
    public static final int	END_OF_INPUT =	-1;

    /** Special code indicating end-of-file (-2). */
    public static final int	END_OF_FILE =	-2;


// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Variables

    /** Current input stream. */
    private Reader		m_in;

    /** Previous (pushed) input stream. */
    private StackedReader	m_prev;

    /** Don't discard end-of-file, return as {@link #END_OF_FILE} code. */
    private boolean		m_sendEOF;


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

    /***************************************************************************
    * Constructor.
    * Equivalent to:
    * <pre>
    *    new StackedReader(null, null, false);</pre>
    *
    * <p>
    * The internal stack of reader streams is empty until method
    * {@link #pushInput pushInput()} is called.
    *
    * @since	1.1, 2008-09-26
    */

    public StackedReader()
    {
        this(null, null, false);
    }


    /***************************************************************************
    * Constructor.
    * Equivalent to:
    * <pre>
    *    new StackedReader(in, null, false);</pre>
    *
    * @since	1.1, 2008-09-26
    */

    public StackedReader(Reader in)
    {
        this(in, null, false);
    }


    /***************************************************************************
    * Constructor.
    * Equivalent to:
    * <pre>
    *    new StackedReader(in, null, eof);</pre>
    *
    * @since	1.1, 2008-09-26
    */

    public StackedReader(Reader in, boolean eof)
    {
        this(in, null, eof);
    }


    /***************************************************************************
    * Constructor.
    * Equivalent to:
    * <pre>
    *    new StackedReader(in, lock, false);</pre>
    *
    * @since	1.1, 2008-09-27
    */

    public StackedReader(Reader in, Object lock)
    {
        this(in, lock, false);
    }


    /***************************************************************************
    * Constructor.
    *
    * @param	in
    * Reader input stream.
    * <br/><br/>
    *
    * @param	lock
    * An object used to synchronize critical operations on the reader.
    * This can be null, in which case critical sections will synchronize on the
    * reader object itself.
    * <br/><br/>
    *
    * @param	eof
    * If true, the end of each reader stream is indicated by a special
    * {@link #END_OF_FILE} character code.  If false, the end of each stream is
    * not indicated in any way, but is simply discarded.
    *
    * @throws	NullPointerException (unchecked)
    * Thrown if <tt>in</tt> is null.
    *
    * @since	1.1, 2008-09-27
    */

    public StackedReader(Reader in, Object lock, boolean eof)
    {
        // Sanity check
        if (in == null)
            throw new NullPointerException("Null reader");

        // Initialize
        this.lock = (lock != null ? lock : this);
        this.m_in = in;
        this.m_sendEOF = eof;
    }


// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Methods

    /***************************************************************************
    * Retrieve the current input stream from the stack of readers.
    *
    * @return
    * The current input stream being read from, or null if there is none.
    *
    * @since	1.1, 2008-09-26
    */

    public Reader getInput()
    {
        return m_in;
    }


    /***************************************************************************
    * Insert (push) an input stream onto the stack of readers.
    * This saves (pushes) the current input stream, suspends reading from it,
    * and establishes a new input stream as the current input reader.
    * Subsequent reads will read characters from the new input stream.
    *
    * <p>
    * Once the end of the current input stream is reached, it will be closed,
    * and the previously saved input stream is restored (popped) from the
    * internal stack, and reading resumes from it.
    *
    * @param	in
    * Input stream.
    *
    * @throws	NullPointerException (unchecked)
    * Thrown if <tt>in</tt> is null.
    *
    * @since	1.1, 2008-09-26
    */

    public void pushInput(Reader in)
    {
        // Sanity check
        if (in == null)
            throw new NullPointerException("Null reader");

        // Push the current input stream, if there is one
        synchronized (this.lock)
        {
            if (m_in != null)
                m_prev = new StackedReader(m_in, m_sendEOF);

            m_in = in;
        }
    }


    /***************************************************************************
    * Close the current input stream and remove (pop) it from the stack of
    * readers, restoring the previously saved (pushed) stream.
    *
    * <p>
    * Under normal operating conditions, this method never needs to be called by
    * client classes.
    *
    * @throws	IOException
    * Thrown if an error occurs while attemping to close the input stream.
    *
    * @since	1.1, 2008-09-26
    */

    public void popInput()
        throws IOException
    {
        IOException	err = null;

        synchronized (this.lock)
        {
            // Close the current (top) input stream on the stack
            try
            {
                if (m_in != null)
                    m_in.close();
            }
            catch (IOException ex)
            {
                err = ex;
            }
            finally
            {
                m_in = null;
            }

            // Restore the previously saved/pushed input stream from the stack
            if (m_prev != null)
            {
                StackedReader	prev;

                prev = m_prev;
                m_prev = prev.m_prev;
                m_in =   prev.m_in;
                prev.m_prev = null;
                prev.m_in =   null;
            }
        }

        // Done
        if (err != null)
            throw err;
    }


    /***************************************************************************
    * Close the input stream and all of the currently saved (pushed) input
    * streams in the stack.
    * The input streams are closed in the reverse order in which they were
    * pushed onto the stack.
    *
    * @throws	IOException
    * Thrown if an error occurs while attemping to close any of the input
    * streams.  Only the first exception thrown is re-thrown by this method, any
    * others are ignored.
    *
    * @since	1.1, 2008-09-26
    */

    public void close()
        throws IOException
        //overrides java.io.Reader
    {
        StackedReader	ent;
        IOException	err = null;

        synchronized (this.lock)
        {
            // Sanity check
            if (m_in == null)
                return;

            // Close all the stacked input streams, in reverse pushed order
            ent = this;
            while (ent != null)
            {
                StackedReader	next;

                try
                {
                    ent.m_in.close();
                }
                catch (IOException ex)
                {
                    if (err == null)
                        err = ex;
                }
                finally
                {
                    ent.m_in = null;
                }

                // Pop the next pushed reader from the stack
                next = ent.m_prev;
                ent.m_prev = null;
                ent = next;
            }
        }

        // Done
        if (err != null)
            throw err;
    }


    /***************************************************************************
    * Determine if the input stream is closed.
    *
    * @return
    * True if the end of the input reader stack has been reached, or if there
    * are no more input streams to read from.
    *
    * @since	1.1, 2008-09-26
    */

    public boolean isClosed()
    {
        return (m_in == null);
    }


    /***************************************************************************
    * Read a character from the current input stream.
    *
    * <p>
    * Note that this method is not synchronized.
    *
    * @return
    * A Unicode character code in the range [0x0000,0xFFFF], or
    * {@link #END_OF_INPUT} if the end of the entire input stream has been
    * reached.
    * If the end of the current reader in the stack of readers is reached, and
    * the <tt>eof</tt> parameter was true when the input stream was created,
    * the special code {@link #END_OF_FILE} is returned; if <tt>eof</tt>
    * was false, the reader stream is closed and a character is read from the
    * previously saved (pushed) input stream, and no indication that the
    * end of an individual input stream was reached is returned.
    *
    * @throws	IOException
    * Thrown if an I/O (read) error occurs.
    *
    * @since	1.1, 2008-09-26
    */

    public int read()
        throws IOException
        //overrides java.io.Reader
    {
        // Check for an input stream
        if (m_in == null)
            return END_OF_INPUT;

        // Read the next input character
        while (m_in != null)
        {
            int		ch;

            ch = m_in.read();
            if (ch < 0)
            {
                popInput();
                if (m_sendEOF)
                    return END_OF_FILE;
            }
        }

        // End of the entire input stack has been reached
        return END_OF_INPUT;
    }


    /***************************************************************************
    * Read characters from the current input stream.
    *
    * <p>
    * Note that this method is not synchronized.
    *
    * @param	cbuf
    * Character buffer to read into.
    * <br/><br/>
    *
    * @param	off
    * Index of the first character in <tt>cbuf</tt> to read.
    * <br/><br/>
    *
    * @param	len
    * Number of characters to read into <tt>cbuf</tt>
    *
    * @return
    * The number of characters read from the input stream stack, or
    * {@link #END_OF_INPUT} if the end of the entire input stream has been
    * reached and there are no more characters to read.
    * If the end of the current reader in the stack of readers is reached, and
    * the <tt>eof</tt> parameter was true when the input stream was created,
    * <tt>cbuf</tt> is filled with the characters read up to the end of the
    * stream, and the next read will return the special code
    * {@link #END_OF_FILE}; subsequent reads after that will read from the
    * previously saved (pushed) input stream.
    * If <tt>eof</tt> was false, the file is closed and characters are then read
    * from the previously saved (pushed) input stream, and no indication that
    * the end of an individual input stream was reached is returned.
    *
    * @throws	IOException
    * Thrown if an I/O (read) error occurs.
    *
    * @since	1.1, 2008-09-26
    */

    public int read(char[] cbuf, int off, int len)
        throws IOException
        //overrides java.io.Reader
    {
        // Check for an input stream
        if (m_in == null)
            return END_OF_INPUT;

        // Read the next input characters
        while (len > 0  &&  m_in != null)
        {
            int		rLen;

            // Read some characters from the current input stream
            rLen = m_in.read(cbuf, off, len);
            if (rLen < 0)
            {
                // End of the current input stream reached, pop it
                popInput();
                if (m_sendEOF)
                    return END_OF_FILE;
            }
            else
                return rLen;
        }

        // End of the input stack reached
        return END_OF_INPUT;
    }


    /***************************************************************************
    * Determine if the stream is ready to be read.
    *
    * @return
    * True if the next call to {@link #read read()} is guaranteed not to block
    * for input, otherwise false.  Note that returning false does <i>not</i>
    * guarantee that the next read will block.
    *
    * @throws	IOException
    * Thrown if an I/O (read) error occurs.
    *
    * @since	1.1, 2008-09-26
    */

    public boolean ready()
        throws IOException
        //overrides java.io.Reader
    {
        synchronized (this.lock)
        {
            // Sanity check
            if (m_in == null)
                throw new IOException("Reader is closed");

            // Check the current input stream
            return m_in.ready();
        }
    }


    /***************************************************************************
    * Determine whether the stream supports the {@link #mark mark()} operation
    * (which it does not).
    *
    * @return
    * False, always.
    *
    * @since	1.1, 2008-09-26
    */

    public boolean markSupported()
        //overrides java.io.Reader
    {
        return false;
    }


    /***************************************************************************
    * <i>This operation is not supported</i>.
    *
    * @throws	IOException
    * Always thrown.
    *
    * @since	1.1, 2008-09-26
    */

    public void mark(int readAheadLimit)
        throws IOException
        //overrides java.io.Reader
    {
        throw new IOException("Operation not supported");
    }


    /***************************************************************************
    * <i>This operation is not supported</i>.
    *
    * @throws	IOException
    * Always thrown.
    *
    * @since	1.1, 2008-09-26
    */

    public void reset()
        throws IOException
        //overrides java.io.Reader
    {
        throw new IOException("Operation not supported");
    }
}

// End StackedReader.java
