/*
* xtc - The eXTensible Compiler
* Copyright (C) 2004, 2006 Robert Grimm
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*/
package xtc.util;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.EventListener;
import java.util.LinkedList;
/**
* Implementation of a nested reader. A nested reader combines
* several streams into a single stream. It starts by reading from a
* main stream. Additional streams are added through the {@link
* #insert(Reader)} and {@link
* #insert(Reader,NestedReader.EOFListener)} methods and are consumed
* completely before returning to read from the previous stream. Note
* that inserted streams are automatically closed after having being
* consumed. Further note that closing a nested reader closes all
* streams currently associated with that nested reader.
*
* @author Robert Grimm
* @version $Revision: 1.5 $
*/
public class NestedReader extends Reader {
/**
* Event listener to provide notification a stream has reached its
* end.
*
* @see #insert(Reader,NestedReader.EOFListener)
*/
public static interface EOFListener extends EventListener {
/** Signal that all characters have been consumed. */
public void consumed();
}
/** Flag for whether this nested reader has been closed. */
protected boolean closed;
/** The current character stream. */
protected Reader reader;
/** The corresponding end-of-file listener. */
protected EOFListener listener;
/**
* The stack of readers, with the most recently added stream at the
* front.
*/
protected LinkedList<Reader> readerStack;
/**
* The stack of listeners, with the most recently added listener at
* the front.
*/
protected LinkedList<EOFListener> listenerStack;
/**
* Create a new nested reader.
*
* @param in The main stream.
*/
public NestedReader(Reader in) {
closed = false;
reader = in;
listener = null;
readerStack = new LinkedList<Reader>();
listenerStack = new LinkedList<EOFListener>();
}
/**
* Open the specified file. The implementation of this method
* simply creates a new file reader with the specified file name.
*
* @param file The file name.
* @return The corresponding character stream.
* @throws IOException Signals an I/O error.
*/
public Reader open(String file) throws IOException {
return new BufferedReader(new FileReader(file));
}
/**
* Insert the specified character stream. After reading all
* characters from the specified stream, this nested reader silently
* returns to reading characters from the current stream.
*
* @param in The stream.
* @throws IOException Signals an I/O error.
*/
public void insert(Reader in) throws IOException {
insert(in, null);
}
/**
* Insert the specified character stream. After reading all
* characters from the specified stream, but before returning to
* read characters from the current stream, this nested reader
* {@link NestedReader.EOFListener#consumed() notifies} the
* specified listener.
*
* @param in The stream.
* @param eof The listener to be notified when the specified stream
* has been consumed.
* @throws IOException Signals an I/O error.
*/
public void insert(Reader in, EOFListener eof) throws IOException {
synchronized (lock) {
if (closed) {
throw new IOException("Nested reader closed");
}
readerStack.addFirst(reader);
listenerStack.addFirst(listener);
reader = in;
listener = eof;
}
}
/**
* Restore the previous character stream. This method must be
* called while holding the {@link #lock}.
*
* @throws IOException Signals an I/O error.
*/
private void restore() throws IOException {
// Notify the listener and close the current stream.
if (null != listener) {
listener.consumed();
}
reader.close();
// Actually restore the previous stream.
reader = readerStack.removeFirst();
listener = listenerStack.removeFirst();
}
public int read() throws IOException {
synchronized (lock) {
do {
int result = reader.read();
// Return on a character or the end-of-file for the main stream.
if ((-1 != result) || readerStack.isEmpty()) {
return result;
}
// Restore the previous stream.
restore();
// Try again.
} while (true);
}
}
public int read(char[] cbuf, int off, int len) throws IOException {
synchronized (lock) {
do {
int result = reader.read(cbuf, off, len);
// Return on characters or the end-of-file for the main stream.
if ((-1 != result) || readerStack.isEmpty()) {
return result;
}
// Restore the previous stream.
restore();
// Try again.
} while (true);
}
}
public void close() throws IOException {
synchronized (lock) {
if (closed) {
return;
} else {
closed = true;
}
IOException error = null;
try {
reader.close();
} catch (IOException x) {
error = x;
}
while (! readerStack.isEmpty()) {
reader = readerStack.removeFirst();
try {
reader.close();
} catch (IOException x) {
error = x;
}
listenerStack.removeFirst();
}
if (null != error) {
throw error;
}
}
}
}