Here’s an example of how you might write a filter stream — here, a FilterReader. This one filters in such a way that it causes Java-style comments to be stripped from the stream as it passes through the filter:
package inputOutput;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.PushbackReader;
import java.io.Reader;
/**
* This class filters out Java-style comments.
* It replaces "/*" comments with a single space,
* and "//" comments with a newline.
*
* NOTE: The algorithm used here is not perfect;
* It should ignore cases of "/*" and "//" within literal
* strings. Perfecting the algorithm is left as an exercise
* for the reader (pun intended!).
*
* @author B. J. Higgs
*/
public class CommentFilterReader extends PushbackReader
{
public CommentFilterReader(Reader in)
{
super(in);
}
/**
* Read a single character.
* This method will block until a character is available,
* an I/O error occurs, or the end of the stream is reached.
*
* @return The character read, as an integer in the range
* 0 to 16383 (0x00-0xffff),
* or -1 if the end of the stream has been reached
*/
public int read()
throws IOException
{
int c = readNextNonCommentChar();
return c;
}
/**
* Reads the input, and returns the next non-comment character.
* It detects the start and end of both kinds of Java comments,
* and skips over comment characters as it detects them.
*
* @return The character read, as an integer in the range
* 0 to 16383 (0x00-0xffff),
* or -1 if the end of the stream has been reached
*/
private int readNextNonCommentChar()
throws IOException
{
int c = super.read();
if (c == '/')
{
// Possible start of comment, so check for '*' or '/'
int lookAheadChar = super.read();
switch (lookAheadChar)
{
case '*':
// We've found a '/*' comment, so silently
// consume characters until we see a '*/'
while (true)
{
c = super.read();
if (c == -1)
break; // End of stream
if (c == '*')
{
lookAheadChar = super.read();
if (lookAheadChar == '/')
{
c = ' '; // Replace comment with space
break; // End of comment
}
}
}
break;
case '/':
// We've found a '//' comment, so silently
// consume characters until we see a newline
while (true)
{
c = super.read();
if (c == -1)
break; // End of stream
if (c == '\n')
{
// Newline ends comment --
// return the newline
break;
}
}
break;
default:
// Not a command, so push back the look
// ahead char so it gets read on the next read.
unread(lookAheadChar);
break;
}
}
// No matter how we get here,
// c will contain the character to return.
return c;
}
/**
* Read characters into a portion of an array.
* This method will block until some input is available,
* an I/O error occurs, or the end of the stream is reached.
*
* @param cbuf - Destination buffer
* @param off - Offset at which to start storing characters
* @param len - Maximum number of characters to read
* @return The number of characters read,
* or -1 if the end of the stream has been reached
*/
public int read(char[] cbuf, int off, int len)
throws IOException
{
int count = 0;
// For loop takes into account the available size of the
// buffer as well as the maximum number of characters to copy.
for (int avail = cbuf.length - off;
len > 0 && avail > 0;
len--, avail--)
{
int c = readNextNonCommentChar();
if (c == -1)
{
// If this read didn't read any characters,
// return end of stream; otherwise, return
// the actual number read. (The next call will
// return end of stream.)
if (count == 0)
count = -1;
break;
}
// Move character into buffer and keep count and
// offset updated.
cbuf[off++] = (char)c;
count++;
}
return count;
}
/**
* Skip non-comment characters.
* This method will block until some characters are available,
* an I/O error occurs, or the end of the stream is reached.
*
* @param n The number of characters to skip
* @return The number of characters actually skipped
*/
public long skip(long n)
throws IOException
{
long count = 0;
for (count = 0; count < n; count++)
{
int c = readNextNonCommentChar();
if (c == -1)
break;
}
return count;
}
/**
* Main entry point, for testing.
* Expects one or two arguments:
* inputFileName -- the name of the file to read
* outputFileName -- the name of the file to write
* the results
* (If only one argument is supplied, the output is to System.out)
*
*/
public static void main(String[] args)
{
try
{
BufferedReader reader = new BufferedReader(
new CommentFilterReader(
new FileReader(
args[0])));
PrintWriter writer = null;
if (args.length > 1)
{
writer = new PrintWriter(
new BufferedWriter(
new FileWriter(args[1])));
}
else
{
writer = new PrintWriter(
new OutputStreamWriter(
System.out));
}
while (true)
{
String line = reader.readLine();
if (line == null)
break;
writer.println(line);
}
reader.close();
writer.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
Note that I extended this class from PushbackReader, because I needed the ability to do a one-character read-ahead to determine whether a ‘/‘ character was the start of a comment or not.
When this program is applied to its own source, it outputs the following:
public class CommentFilterReader extends PushbackReader
{
public CommentFilterReader(Reader in)
{
super(in);
}
public int read()
throws IOException
{
int c = readNextNonCommentChar();
return c;
}
private int readNextNonCommentChar()
throws IOException
{
int c = super.read();
if (c == '/')
{
int lookAheadChar = super.read();
switch (lookAheadChar)
{
case '*':
while (true)
{
c = super.read();
if (c == -1)
break;
if (c == '*')
{
lookAheadChar = super.read();
if (lookAheadChar == '/')
{
c = ' ';
break;
}
}
}
break;
case '/':
while (true)
{
c = super.read();
if (c == -1)
break;
if (c == '\n')
{
break;
}
}
break;
default:
unread(lookAheadChar);
break;
}
}
return c;
}
public int read(char[] cbuf, int off, int len)
throws IOException
{
int count = 0;
for (int avail = cbuf.length - off;
len > 0 && avail > 0;
len--, avail--)
{
int c = readNextNonCommentChar();
if (c == -1)
{
if (count == 0)
count = -1;
break;
}
cbuf[off++] = (char)c;
count++;
}
return count;
}
public long skip(long n)
throws IOException
{
long count = 0;
for (count = 0; count < n; count++)
{
int c = readNextNonCommentChar();
if (c == -1)
break;
}
return count;
}
public static void main(String[] args)
{
try
{
BufferedReader reader = new BufferedReader(
new CommentFilterReader(
new FileReader(
args[0])));
PrintWriter writer = null;
if (args.length > 1)
{
writer = new PrintWriter(
new BufferedWriter(
new FileWriter(args[1])));
}
else
{
writer = new PrintWriter(
new OutputStreamWriter(
System.out));
}
while (true)
{
String line = reader.readLine();
if (line == null)
break;
writer.println(line);
}
reader.close();
writer.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
