Filtered Readers and Writers

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.

The FilterReader Class

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

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);
    }
  }
}

PushbackReader

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

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.189.31.26