The java.io.FilterReader
and
java.io.FilterWriter
classes are abstract classes
that read characters and filter them in some way before passing the
text along. You can imagine a FilterReader
that
converts all characters to uppercase.
public abstract class FilterReader extends Reader public abstract class FilterWriter extends Writer
Although FilterReader
and
FilterWriter
are modeled after
java.io.FilterInputStream
and
java.io.FilterOutputStream
, they are much less
commonly used than those classes. There are no concrete subclasses of
FilterWriter
in the java
packages and only one concrete subclass of
FilterReader
(PushbackReader
discussed later). These classes exist so you can write your own
filters.
FilterReader
has a single constructor, which is
protected:
protected FilterReader(Reader in)
The in
argument is the Reader
to which this filter is chained. This reference is stored in a
protected field called in
from which text for this
filter is read and is null
after the filter has
been closed.
protected Reader in
Since FilterReader
is an abstract class, only
subclasses may be instantiated. Therefore, it doesn’t matter
that the constructor is protected, since it may only be invoked from
subclass constructors.
FilterReader
provides the usual collection of
read()
, skip()
,
ready()
, markSupported()
,
mark()
, reset()
, and
close()
methods:
public int read() throws IOException public int read(char[] text, int offset, int length) throws IOException public long skip(long n) throws IOException public boolean ready() throws IOException public boolean markSupported() public void mark(int readAheadLimit) throws IOException public void reset() throws IOException public void close() throws IOException
These all simply invoke the equivalent method in the
in
field with the same arguments. For example:
public long skip(long n) throws IOException { return in.skip(n); }
Java source code is written in pure ASCII with Unicode characters
written as a u
followed by the
four-hexadecimal-digit equivalent of the Unicode character. As an
example, I’ll write a FilterReader
subclass
that reads a u
-escaped file and converts it to
pure Unicode. This is a much trickier problem than it first appears.
First, there’s not a fixed ratio between the number of bytes
and number of characters. Most of the time one byte is one character,
but some of the time five bytes are one character. The second
difficulty is ensuring that u09EF
is recognized
as Unicode escape, while \u09EF
is not. In other
words, only a u
preceded by an odd number of
slashes is a valid Unicode escape. A u
preceded by
an even number of slashes should be passed along unchanged. Example 15.7 shows a solution.
Example 15-7. SourceReader
package com.macfaq.io; import java.io.*; public class SourceReader extends FilterReader { public SourceReader(InputStream in) { this(new InputStreamReader(in)); } public SourceReader(Reader in) { super(new BufferedReader(in)); } private int backslashParity = 1; public int read() throws IOException { int c = in.read(); if (c != '') return c; backslashParity *= -1; // If there are an even number of backslashes, // this is not a Unicode escape. if (backslashParity == 1) return c; // Mark is supported because I used // a BufferedReader in the constructor. in.mark(1); int next = in.read(); if (next != 'u' ) { // This is not a Unicode escape in.reset(); return c; } // Read next 4 hex digits. // If the next four chars do not make a valid hex digit // this is not a valid .java file and you need a compiler error. StringBuffer sb = new StringBuffer(); sb.append((char) in.read()); sb.append((char) in.read()); sb.append((char) in.read()); sb.append((char) in.read()); String hex = sb.toString(); try { return Integer.valueOf(hex, 16).intValue(); } catch (NumberFormatException e) { throw new IOException("Bad Unicode escape"); } } public int read(char[] text, int offset, int length) throws IOException { int numRead = 0; for (int i = offset; i < offset+length; i++) { int temp = this.read(); if (temp == -1) break; text[i] = (char) temp; numRead++; } return numRead; } public long skip(long n) throws IOException { char[] c = new char[(int) n]; int numSkipped = this.read(c); return numSkipped; } }
The
FilterWriter
class has a single constructor and no
other unique methods:
protected FilterWriter(Writer out)
The out
argument is the writer to which this
filter is chained. This reference is stored in a protected field
called out
, to which text sent through this filter
is written.
protected Writer out
Since FilterWriter
is an abstract class, only
subclasses may be instantiated. Therefore, it doesn’t matter
that the constructor is protected, since it may only be invoked from
subclass constructors anyway. FilterWriter
provides the usual collection of write()
,
close()
, and flush()
methods:
public void write(int c) throws IOException public void write(char[] text, int offset, int length) throws IOException public void write(String s, int offset, int length) throws IOException public void flush() throws IOException public void close() throws IOException
These all simply invoke the equivalent method in the
out
field with the same arguments. For example:
public void close() throws IOException { out.close(); }
In general, each subclass will have to override at least the three
write()
methods to perform the filtering.
There are no subclasses of FilterWriter
in the
core API. Example 15.8,
SourceWriter
, is an example of a
FilterWriter
that converts Unicode text to
u
-escaped ASCII. The big question is what to do
if the input text contains an unescaped backslash. The easiest and
most robust solution is to replace it with u005C
,
the Unicode escape for the backslash itself.
Example 15-8. SourceWriter
package com.macfaq.io; import java.io.*; public class SourceWriter extends FilterWriter { public SourceWriter(Writer out) { super(out); } public void write(char[] text, int offset, int length) throws IOException { for (int i = offset; i < offset+length; i++) { this.write(text[i]); } } public void write(String s, int offset, int length) throws IOException { for (int i = offset; i < offset+length; i++) { this.write(s.charAt(i)); } } public void write(int c) throws IOException { // We have to escape the backslashes below. if (c == '') out.write("\u005C"); else if (c < 128) out.write(c); else { String s = Integer.toHexString(c); // Pad with leading zeroes if necessary. if (c < 256) s = "00" + s; else if (c < 4096) s = "0" + s; out.write("\u"); out.write(s); } } }
The
PushbackReader
class is a filter that provides a
pushback buffer around a given reader. This allows a program to
“unread” the last character it read. It’s similar
to PushbackInputStream
, discussed in Chapter 6, but instead of pushing back bytes, it pushes
back char
s. Both PushbackReader
and BufferedReader
use buffers, but only
PushbackReader
allows unreading and only
BufferedReader
allows marking and resetting. The
difference is that pushing back characters allows you to unread
characters after the fact. Marking and resetting requires you to mark
in advance the location you want to reset to.
PushbackReader
has two constructors, both of which
take an underlying reader as an argument. The first uses a
one-character pushback buffer; the second sets the pushback buffer to
a specified size:
public PushbackReader(Reader in) public PushbackReader(Reader in, int size)
The PushbackReader
class has the usual collection
of read()
methods. These methods first try to read
the requested characters from the pushback buffer and only read from
the underlying reader if the pushback buffer is empty or has too few
characters.
public int read() throws IOException public int read(char[] text, int offset, int length) throws IOException
PushbackReader
also has
ready()
, markSupported()
, and
close()
methods:
public boolean ready() throws IOException public boolean markSupported() public void close() throws IOException
The ready()
and close()
methods
merely invoke the ready()
and
close()
methods of the underlying reader. The
markSupported()
method returns
false
; pushback readers do not support marking and
resetting.
Three unread()
methods push back specific
characters. The first pushes back the character c
;
the second pushes back the text
array; the third
pushes back the sub-array of text
beginning at
offset
and continuing for
length
char
s.
public void unread(int c) throws IOException public void unread(char[] text) throws IOException public void unread(char[] text, int offset, int length) throws IOException
The unread characters aren’t necessarily the same as the
characters that were read. It would be nicer if the
PushbackReader
itself kept track of which
characters had been read, and all you had to tell it was how many
characters to unread. However, this approach does give the client
programmer the option of inserting tags or other data as the stream
is read. The number of characters you can push back onto the stream
is limited by the size of the buffer set in the constructor. Attempts
to unread more characters than can fit in the buffer cause an
IOException
to be thrown. An
IOException
is also thrown if you try to unread a
closed reader; once a PushbackReader
has been
closed, it can be neither read nor unread.
18.189.31.26