//============================================================================== // 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 Reader objects. * *
* This is useful for implementing readers for source files that contain nested * inclusions of other source files (a la the #include directive of * C and C++). * *
* 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. * * *
* Copyright ©2008 by David R. Tribble, all rights reserved.
* 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:
*
* new StackedReader(null, null, false);* *
* 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: *
* new StackedReader(in, null, false);* * @since 1.1, 2008-09-26 */ public StackedReader(Reader in) { this(in, null, false); } /*************************************************************************** * Constructor. * Equivalent to: *
* new StackedReader(in, null, eof);* * @since 1.1, 2008-09-26 */ public StackedReader(Reader in, boolean eof) { this(in, null, eof); } /*************************************************************************** * Constructor. * Equivalent to: *
* new StackedReader(in, lock, false);* * @since 1.1, 2008-09-27 */ public StackedReader(Reader in, Object lock) { this(in, lock, false); } /*************************************************************************** * Constructor. * * @param in * Reader input stream. *
* 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 in 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. * *
* 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. * *
* 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 eof parameter was true when the input stream was created, * the special code {@link #END_OF_FILE} is returned; if eof * 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. * *
* Note that this method is not synchronized.
*
* @param cbuf
* Character buffer to read into.
*
*
* @param off
* Index of the first character in cbuf to read.
*
*
* @param len
* Number of characters to read into cbuf
*
* @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 eof parameter was true when the input stream was created,
* cbuf 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 eof 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 not
* 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;
}
/***************************************************************************
* This operation is not supported.
*
* @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");
}
/***************************************************************************
* This operation is not supported.
*
* @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