001: /*
002: * xtc - The eXTensible Compiler
003: * Copyright (C) 2004 Robert Grimm
004: *
005: * This program is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU General Public License
007: * as published by the Free Software Foundation; either version 2
008: * of the License, or (at your option) any later version.
009: *
010: * This program is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013: * GNU General Public License for more details.
014: *
015: * You should have received a copy of the GNU General Public License
016: * along with this program; if not, write to the Free Software
017: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
018: */
019: package xtc.util;
020:
021: import java.io.BufferedReader;
022: import java.io.FileReader;
023: import java.io.IOException;
024: import java.io.Reader;
025:
026: import java.util.EventListener;
027: import java.util.LinkedList;
028:
029: /**
030: * Implementation of a nested reader. A nested reader combines
031: * several streams into a single stream. It starts by reading from a
032: * main stream. Additional streams are added through the {@link
033: * #insert(Reader)} and {@link
034: * #insert(Reader,NestedReader.EOFListener)} methods and are consumed
035: * completely before returning to read from the previous stream. Note
036: * that inserted streams are automatically closed after having being
037: * consumed. Further note that closing a nested reader closes all
038: * streams currently associated with that nested reader.
039: *
040: * @author Robert Grimm
041: * @version $Revision: 1.1 $
042: */
043: public class NestedReader extends Reader {
044:
045: /**
046: * Event listener to provide notification that the most recently
047: * inserted character stream has reached its end.
048: *
049: * @see #insert(Reader,NestedReader.EOFListener)
050: */
051: public static interface EOFListener extends EventListener {
052:
053: /** Signal that all characters have been consumed. */
054: public void consumed();
055:
056: }
057:
058: /** Flag for whether this nested reader has been closed. */
059: protected boolean closed;
060:
061: /** The current character stream. */
062: protected Reader reader;
063:
064: /** The corresponding end-of-file listener. */
065: protected EOFListener listener;
066:
067: /**
068: * The stack of readers, with the most recently added stream at the
069: * front.
070: */
071: protected LinkedList readerStack;
072:
073: /**
074: * The stack of listeners, with the most recently added listener at
075: * the front.
076: */
077: protected LinkedList listenerStack;
078:
079: /**
080: * Create a new nested reader.
081: *
082: * @param in The main stream.
083: */
084: public NestedReader(Reader in) {
085: closed = false;
086: reader = in;
087: listener = null;
088: readerStack = new LinkedList();
089: listenerStack = new LinkedList();
090: }
091:
092: /**
093: * Open the specified file. The implementation of this method
094: * simply creates a new file reader with the specified file name.
095: *
096: * @param file The file name.
097: * @return The corresponding character stream.
098: * @throws IOException Signals an I/O error.
099: */
100: public Reader open(String file) throws IOException {
101: return new BufferedReader(new FileReader(file));
102: }
103:
104: /**
105: * Insert the specified character stream. After reading all
106: * characters from the specified stream, this nested reader silently
107: * returns to reading characters from the current stream.
108: *
109: * @param in The stream.
110: * @throws IOException Signals an I/O error.
111: */
112: public void insert(Reader in) throws IOException {
113: insert(in, null);
114: }
115:
116: /**
117: * Insert the specified character stream. After reading all
118: * characters from the specified stream, but before returning to
119: * read characters from the current stream, this nested reader
120: * {@link NestedReader.EOFListener#consumed() notifies} the
121: * specified listener.
122: *
123: * @param in The stream.
124: * @param eof The listener to be notified when the specified stream
125: * has been consumed.
126: * @throws IOException Signals an I/O error.
127: */
128: public void insert(Reader in, EOFListener eof) throws IOException {
129: synchronized (lock) {
130: if (closed) {
131: throw new IOException("Nested reader closed");
132: }
133: readerStack.addFirst(reader);
134: listenerStack.addFirst(listener);
135: reader = in;
136: listener = eof;
137: }
138: }
139:
140: /**
141: * Restore the previous character stream. This method must be
142: * called while holding the {@link #lock}.
143: *
144: * @throws IOException Signals an I/O error.
145: */
146: private void restore() throws IOException {
147: // Notify the listener and close the current stream.
148: if (null != listener) {
149: listener.consumed();
150: }
151: reader.close();
152:
153: // Actually restore the previous stream.
154: reader = (Reader) readerStack.removeFirst();
155: listener = (EOFListener) listenerStack.removeFirst();
156: }
157:
158: public int read() throws IOException {
159: synchronized (lock) {
160: do {
161: int result = reader.read();
162:
163: // Return on a character or the end-of-file for the main stream.
164: if ((-1 != result) || readerStack.isEmpty()) {
165: return result;
166: }
167:
168: // Restore the previous stream.
169: restore();
170:
171: // Try again.
172: } while (true);
173: }
174: }
175:
176: public int read(char[] cbuf, int off, int len) throws IOException {
177: synchronized (lock) {
178: do {
179: int result = reader.read(cbuf, off, len);
180:
181: // Return on characters or the end-of-file for the main stream.
182: if ((-1 != result) || readerStack.isEmpty()) {
183: return result;
184: }
185:
186: // Restore the previous stream.
187: restore();
188:
189: // Try again.
190: } while (true);
191: }
192: }
193:
194: public void close() throws IOException {
195: synchronized (lock) {
196: if (closed) {
197: return;
198: } else {
199: closed = true;
200: }
201:
202: IOException error = null;
203:
204: try {
205: reader.close();
206: } catch (IOException x) {
207: error = x;
208: }
209:
210: while (!readerStack.isEmpty()) {
211: reader = (Reader) readerStack.removeFirst();
212: try {
213: reader.close();
214: } catch (IOException x) {
215: error = x;
216: }
217: listenerStack.removeFirst();
218: }
219:
220: if (null != error) {
221: throw error;
222: }
223: }
224: }
225:
226: }
|