//============================================================================== // 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. * * *

*
Source code:
*
* http://david.tribble.com/src/java/tribble/io/StackedReader.java *
*
Documentation:
*
* http://david.tribble.com/docs/tribble/io/StackedReader.html *
*
* * * @version $Revision: 1.2 $ $Date: 2008/09/27 18:04:14 $ * @since 2008-09-27 * @author David R. Tribble (david@tribble.com) *

* 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. *

* * @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. *

* * @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 in 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. * *

* 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