//============================================================================== // tribble/io/CharReader.java //============================================================================== package tribble.io; // System imports import java.io.IOException; import java.io.Reader; import java.lang.IllegalArgumentException; import java.lang.NullPointerException; import java.lang.Object; import java.lang.String; // Local imports // (None) /******************************************************************************* * Generic character input stream. * *

* This is a generic implementation of an input stream that is capable of * reading a single character at a time. * * *

* This character stream handles various combinations of newline termination * characters (LF, CR, and CR/LF pairs), returning a single newline character * ('\n') for any given combination. * *

* This also handles formfeed (FF) and null (NUL) characters, treating them as * spaces (SP). * *

* Tab (HT) characters are replaced with one or more spaces, to align on tabbed * columns. By default, tabs are replaced with the appropriate number of spaces * so as to align on 8-character columns, but this can be changed to a different * width or disabled altogether. * * @version $Revision: 1.1 $ $Date: 2003/02/25 21:59:04 $ * @since 2003-02-25 * @author * David R. Tribble, * david@tribble.com. *
* Copyright * ©2003 by David R. Tribble, all rights reserved. *
* Permission is granted to freely use and distribute this source code * provided that the original copyright and authorship notices remain * intact. */ public class CharReader extends java.io.Reader { // Identification /** Revision information. */ static final String REV = "@(#)tribble/io/CharReader.java $Revision: 1.1 $ $Date: 2003/02/25 21:59:04 $\n"; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Public constants /** End of file character code. */ public static final int EOF = -1; /** Newline (end of line) character code. */ public static final int NEWLINE = '\n'; /** Default tab width (8). */ public static final int DFL_TABSIZE = 8; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Static methods // (None) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Protected variables /** Character input stream. */ protected Reader m_in; /** Pushed-back (unread) character. */ protected int m_ungetc = EOF; /** Column position (of the last character read). */ protected int m_colNo = 0; /** Tab (HT) column width. */ protected int m_tabSz = DFL_TABSIZE; /** Pending tab (HT) character replacement. */ protected int m_tabSp; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Public constructors /*************************************************************************** * Constructor. * * @param in * The input stream from which to read characters. * * @since 1.1, 2003-02-24 */ public CharReader(Reader in) { // Establish the input stream setInput(in); } /*************************************************************************** * Constructor. * * @param in * The input stream from which to read characters. * * @param lock * An object that this input character stream will use to synchronize * critical sections. * * @since 1.1, 2003-02-25 */ public CharReader(Reader in, Object lock) { super(lock); // Establish the input stream setInput(in); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Public methods /*************************************************************************** * Close this input stream. * *

* Disassociates the underlying input and output streams from this input * stream. * * @throws IOException * Thrown if an I/O error occurs. * * @since 1.1, 2003-02-25 */ public void close() throws IOException //overrides java.io.Reader { synchronized (lock) { Reader in; // Disassociate the input and output streams // from this character stream in = m_in; m_in = null; // Close the input stream if (in != null) { synchronized (in) { in.close(); // May throw } } } } /*************************************************************************** * Read the next character from this input stream. * *

* This character stream handles various combinations of newline termination * characters (LF, CR, and CR/LF pairs), returning a single newline * character ('\n') for any given combination. * *

* This stream also handles formfeed (FF) and null (NUL) characters, * replacing them with spaces (SP). * *

* Tab (HT) characters are replaced by one or more space (SP) characters, so * that the character following the tab is aligned on a tab-sized column. * Tab character replacement can be disabled by specifying a tab size of * zero (see {@link #setTabSize setTabSize()}). * *

* This method may block until a character has been read. * * @return * The next character code read from the input stream, or -1 if there are no * more characters to read (i.e., the end of the input stream was reached). * * @throws IOException * Thrown if an I/O (read) error occurs. * * @since 1.1, 2003-02-25 */ public int read() throws IOException //overrides java.io.Reader { // Check for an open input stream if (m_in == null) throw new IOException("Input stream is closed"); // Read the next input character from the input stream if (lock != null) { synchronized (lock) { return (readChar()); } } else return (readChar()); } /*************************************************************************** * Read characters from this input stream into a portion of an array. * *

* Characters are read from the input stream up to (and including) the first * newline character read or until len characters are read into * array cbuf, whichever comes first. * *

* See {@link #read()} for more details about the way characters are read * from this input stream. * *

* This method may block until enough characters have been read. * * @param cbuf * Character array to read characters into. * * @param off * Index of the first character within cbuf to read. * * @param len * Maximum number of characters to read into cbuf. * * @return * The number of characters actually read into array cbuf (which * will be no more than len), or -1 if there are no more characters * available to read (i.e., the end of the input stream was reached). * * @throws IOException * Thrown if an I/O (read) error occurs. * * @since 1.1, 2003-02-25 */ public int read(char cbuf[], int off, int len) throws IOException //overrides java.io.Reader { Object mutex; int nChars; // Check for an open input stream if (m_in == null) throw new IOException("Input stream is closed"); // Read characters from the input stream mutex = (lock != null ? lock : this); synchronized (mutex) { nChars = 0; while (len > 0) { int ch; // Read the next input character ch = readChar(); // Check for end of input if (ch == EOF) { if (nChars == 0) return (EOF); break; } // Add the character to the array cbuf[off++] = (char) ch; nChars++; len--; // Check for end of line (newline) if (ch == NEWLINE) break; } } // Done return (nChars); } /*************************************************************************** * Determine whether the underlying input stream supports position marks or * not. * * @return * True if the underlying input stream supports the {@link #mark} operation, * otherwise false. * * @throws IOException * Thrown if an I/O error occurs. * * @since 1.1, 2003-02-25 */ public boolean markSupported() //overrides java.io.Reader { // Check for an open input stream if (m_in == null) throw new NullPointerException("Input stream is closed"); // Determine if mark() is supported return (m_in.markSupported()); } /*************************************************************************** * Mark the current position in this input stream. * * @param limit * Minimum number number of characters that can be read ahead without losing * the mark. * * @throws IOException * Thrown if the underlying input stream does not support this operation, or * if some other I/O error occurs. * * @since 1.1, 2003-02-25 */ public void mark(int limit) throws IOException //overrides java.io.Reader { // Check for an open input stream if (m_in == null) throw new IOException("Input stream is closed"); // Set a mark m_in.mark(limit); } /*************************************************************************** * Reset the current position of this input stream to the last mark. * If no mark has been set, the stream is repositioned appropriately, such as * to its beginning. * * @throws IOException * Thrown if the underlying input stream does not support this operation, or * if the previous mark has been invalidated, or if some other I/O error * occurs. * * @since 1.1, 2003-02-25 */ public void reset() throws IOException //overrides java.io.Reader { // Check for an open input stream if (m_in == null) throw new IOException("Input stream is closed"); // Reset the stream m_in.reset(); } /*************************************************************************** * Skip characters in this input stream. * This method may block until enough characters have been skipped. * * @param n * The number of characters to skip in this input stream. * * @return * The number of characters actually skipped (which may be less than * n). * * @throws IOException * Thrown if an I/O (read) error occurs. * * @throws IllegalArgumentException (unchecked) * Thrown if n is negative. * * @since 1.1, 2003-02-25 */ public long skip(long n) throws IOException //overrides java.io.Reader { // Check for an open input stream if (m_in == null) throw new IOException("Input stream is closed"); // Skip some characters return (m_in.skip(n)); } /*************************************************************************** * Determine whether characters are ready to be read from this input stream * or not. * * @return * True if the next read() operation is guaranteed not to block for * input, otherwise false. Note that a return of false does not guarantee * that the next read() will block. * * @throws IOException * Thrown if an I/O (read) error occurs. * * @since 1.1, 2003-02-25 */ public boolean ready() throws IOException //overrides java.io.Reader { // Check for an open input stream if (m_in == null) throw new IOException("Input stream is closed"); // Determine if any input characters are ready to be read if (m_ungetc != EOF) return (true); else if (m_tabSp > 0) return (true); else return (m_in.ready()); } /*************************************************************************** * Establish the tab (HT) column width. * *

* Setting a tab width of zero disables the replacing of tab (HT) characters * with spaces, i.e., tab characters are read as is. The default tab width * is 8 (see {@link #DFL_TABSIZE}). * * @param n * The tab column width. * * @since 1.1, 2003-02-24 */ public void setTabSize(int n) { m_tabSz = n; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Protected methods /*************************************************************************** * Finalization. * Closes the underlying input stream. * * @since 1.1, 2003-02-24 */ protected synchronized void finalize() { try { // Close and disassociate the input stream close(); } catch (IOException ex) { // Ignored } } /*************************************************************************** * Establish the input stream from which to read characters. * * @param in * The underlying input stream from which to read characters. * * @since 1.1, 2003-02-25 */ protected void setInput(Reader in) { // Establish the input stream m_in = in; } /*************************************************************************** * Read the next character from this input stream. * *

* This character stream handles various combinations of newline termination * characters (LF, CR, and CR/LF pairs), returning a single newline * character ('\n') for any given combination. * *

* This stream also handles formfeed (FF) and null (NUL) characters, * replacing them with spaces (SP). * *

* Tab (HT) characters are replaced by one or more space (SP) characters, so * that the character following the tab is aligned on a tab-sized column. * Tab character replacement can be disabled by specifying a tab size of * zero (see {@link #setTabSize setTabSize()}). * *

* This method may block until a character has been read. * *

* Note that this method is not synchronized, so any public member method * that invokes it should itself be synchronized. * * @return * The next character code read from the input stream, or -1 if there are no * more characters to read (i.e., the end of the input stream was reached). * * @throws IOException * Thrown if an I/O (read) error occurs. * * @since 1.1, 2003-02-25 */ protected int readChar() throws IOException { int ch; // Check for a previously pushed-back character if (m_ungetc != EOF) { // Use the pushed-back character ch = m_ungetc; m_ungetc = EOF; } else if (m_tabSp > 0) { // Continue replacing a tab (HT) with spaces (SP) m_tabSp--; ch = ' '; m_colNo++; } else { // Read the next character from the input stream ch = m_in.read(); // May throw m_colNo++; } // Handle control characters if (ch < ' ') { if (ch == '\n') { // End of line: LF ch = NEWLINE; m_colNo = 0; } else if (ch == '\r') { // End of line: CR, check for CR/LF pair ch = m_in.read(); // May throw if (ch != '\n') m_ungetc = ch; ch = NEWLINE; m_colNo = 0; } else if (ch == '\t' && m_tabSz > 0) { // Tab (HT), replace with spaces (SP) m_tabSp = 8 - m_colNo%8; ch = ' '; } else if (ch == '\f') { // Formfeed (FF), treat as a space (SP) ch = ' '; } else if (ch == '\0') { // Null (NUL) character, treat as a space (SP) ch = ' '; } // else leave character as is } // Done return (ch); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Private constructors /*************************************************************************** * Default constructor. * Do not use this constructor. * * @since 1.1, 2003-02-25 */ private CharReader() { // Do nothing } } // End CharReader.java