Filter OutputStream and Filterlnput Stream

Byte array, file, and piped streams pass bytes unchanged to their destinations. Java also supports filter streams that buffer, compress/uncompress, encrypt/decrypt, or otherwise manipulate an input stream's byte sequence before it reaches its destination.

A filter output stream takes the data passed to its write() methods (the input stream), filters it, and writes the filtered data to an underlying output stream, which might be another filter output stream or a destination output stream such as a file output stream.

Filter output streams are created from subclasses of the concrete FilterOutputStream class, an OutputStream subclass. FilterOutputStream declares a single FilterOutputStream(OutputStream out) constructor that creates a filter output stream built on top of out, the underlying output stream.

NOTE: FilterOutputStream's constructor was originally declared protected because it does not appear to make sense to instantiate FilterOutputStream. However, this constructor's access was later changed to public for reasons unknown to me.

Listing 10-12 reveals that it is easy to subclass FilterOutputStream. At minimum, you declare a constructor that passes its OutputStream argument to FilterOutputStream's constructor and override FilterOutputStream's write(int) method.

Listing 10-12. Scrambling a stream of bytes import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream;

public class ScrambledOutputStream extends FilterOutputStream {

private int[] map;

public ScrambledOutputStream(OutputStream out, int[] map) {

super(out);

throw new NullPointerException("map is null"); if (map.length != 256)

throw new IllegalArgumentException("map.length != 256"); this.map = map;

@Override public void write(int b) throws IOException

Listing 10-12 presents a ScrambledOutputStream class that performs trivial encryption on its input stream by scrambling the input stream's bytes via a remapping operation. This constructor takes a pair of arguments:

■ out identifies the output stream on which to write the scrambled bytes.

■ map identifies an array of 256 byte integer values to which input stream bytes map.

The constructor first passes its out argument to the FilterOutputStream parent via a super(out); call. It then verifies its map argument's integrity (map must be nonnull and have a length of 256—a byte stream offers exactly 256 bytes to map) before saving map.

The write(int) method is trivial: it calls the underlying output stream's write(int) method with the byte to which argument b maps. FilterOutputStream declares out to be protected (for performance), which is why I can directly access this field.

NOTE: It is only essential to override write(int) because FilterOutputStream's other two write() methods are implemented via this method.

Listing 10-13 presents the source code to a Scramble application, which lets us experiment with ScrambledOutputStream by scrambling a source file's bytes and writing these scrambled bytes to a destination file.

Listing 10-13. Scrambling a file's bytes import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException;

import java.util.Random;

public class Scramble {

public static void main(String[] args) {

System.err.println("usage: java Scramble srcpath destpath"); return;

FileInputStream fis = null; ScrambledOutputStream sos = null;

fis = new FileInputStream(args[0]); FileOutputStream fos = new FileOutputStream(args[1]); sos = new ScrambledOutputStream(fos, makeMap());

catch (IOException ioe) {

ioe.printStackTrace();

finally {

catch (IOException ioe) {

ioe.printStackTrace();

catch (IOException ioe) {

ioe.printStackTrace();

map[i] = i; // Shuffle map. Random r = new Random(0);

int n = r.nextInt(map.length); int temp = map[i]; map[i] = map[n]; map[n] = temp;

return map;

Scramble's main() method first verifies the number of command-line arguments: the first argument identifies the source path of the file with unscrambled content; the second argument identifies the destination path of the file that stores scrambled content.

Assuming that two command-line arguments have been specified, main() instantiates FileInputStream, creating a file input stream that is connected to the file identified by args[0].

Continuing, main() instantiates FileOutputStream, creating a file output stream that is connected to the file identified by args[1]. It then instantiates ScrambledOutputStream, passing the FileOutputStream instance to ScrambledOutputStream's constructor.

NOTE: When a stream instance is passed to another stream class's constructor, we say that the two streams are chained together. For example, the scrambled output stream is chained to the file output stream.

main() now enters a loop, reading bytes from the file input stream and writing them to the scrambled output stream by calling ScrambledOutputStream's write(int) method. This loop continues until FileInputStream's read() method returns -1 (end of file).

The finally clause closes the file input stream and scrambled output stream by calling their close() methods. It does not call the file output stream's close() method because FilterOutputStream automatically calls the underlying output stream's close() method.

The makeMap() method is responsible for creating the map array that is passed to ScrambledOutputStream's constructor. The idea is to populate the array with all 256 byte integer values, storing them in random order.

NOTE: I pass 0 as the seed argument when creating the Random object in order to return a predictable sequence of random numbers. I need to use the same sequence of random numbers when creating the complementary map array in the Unscramble application, which I will present shortly. Unscrambling will not work without the same sequence.

Suppose you have a simple 15-byte file named hello.txt that contains "Hello, World!' (followed by a carriage return and a line feed). If you execute java Scramble hello.txt hello.out on an XP platform, you will observe Figure 10-3's scrambled output.

C:Sprj\deu\ljfad\cl0\code\CUSTFI~1>type hello.out =gMMilîIplfln •_

jj hello.out - Notepad

File Edit Format View Help

Figure 10-3. Different fonts yield different-looking scrambled output.

A filter input stream takes the data obtained from its underlying input stream, which might be another filter input stream or a source input stream such as a file input stream, filters it, and makes this data available via its read() methods (the output stream).

Filter input streams are created from subclasses of the concrete FilterInputStream class, an InputStream subclass. FilterInputStream declares a single FilterInputStream(InputStream in) constructor that creates a filter input stream built on top of in, the underlying input stream.

Listing 10-14 reveals that it is easy to subclass FilterInputStream. At minimum, declare a constructor that passes its InputStream argument to FilterInputStream's constructor and override FilterInputStream's read() and read(byte[], int, int) methods.

Listing 10-14. Unscrambling a stream of bytes import java.io.FilterInputStream; import java.io.InputStream; import java.io.IOException;

public class ScrambledInputStream extends FilterInputStream

private int[] map;

public ScrambledInputStream(InputStream in, int[] map) {

super(in);

throw new NullPointerException("map is null"); if (map.length != 256)

throw new IllegalArgumentException("map.length != 256"); this.map = map;

@Override public int read() throws IOException

@Override public int read(byte[] b, int off, int len) throws IOException

int nBytes = in.read(b, off, len); if (nBytes <= 0) return nBytes; for (int i = 0; i < nBytes; i++) b[off+i] = (byte) map[off+i]; return nBytes;

Listing 10-14 presents a ScrambledInputStream class that performs trivial decryption on its underlying input stream by unscrambling the underlying input stream's scrambled bytes via a remapping operation.

The read() method first reads the scrambled byte from its underlying input stream. If the returned value is -1 (end of file), this value is returned to its caller. Otherwise, the byte is mapped to its unscrambled value, which is returned.

The read(byte[], int, int) method is similar to read(), but stores bytes read from the underlying input stream in a byte array, taking an offset into this array and a length (number of bytes to read) into account.

Once again, -1 might be returned from the underlying read() method call. If so, this value must be returned. Otherwise, each byte in the array is mapped to its unscrambled value, and the number of bytes read is returned.

NOTE: It is only essential to override read() and read(byte[], int, int) because FilterInputStream's read(byte[]) method is implemented via the latter method.

Listing 10-15 presents the source code to an Unscramble application, which lets us experiment with ScrambledlnputStream by unscrambling a source file's bytes and writing these unscrambled bytes to a destination file.

Listing 10-15. Unscrambling a file's bytes import java.io.FilelnputStream; import java.io.FileOutputStream; import java.io.IOException;

import java.util.Random;

public class Unscramble {

public static void main(String[] args) {

System.err.println("usage: java Unscramble srcpath destpath"); return;

ScrambledInputStream sis = null; FileOutputStream fos = null;

FilelnputStream fis = new FileInputStream(args[0]); sis = new ScrambledInputStream(fis, makeMap()); fos = new FileOutputStream(args[l]); int b;

catch (IOException ioe) {

ioe.printStackTrace();

finally {

catch (IOException ioe) {

ioe.printStackTrace();

catch (IOException ioe) {

ioe.printStackTrace();

map[i] = i; // Shuffle map. Random r = new Random(o);

int n = r.nextlnt(map.length); int temp = map[i]; map[i] = map[n]; map[n] = temp;

Unscramble's main() method first verifies the number of command-line arguments: the first argument identifies the source path of the file with scrambled content; the second argument identifies the destination path of the file that stores unscrambled content.

Assuming that two command-line arguments have been specified, main() instantiates FilelnputStream, creating a file input stream that is connected to the file identified by args[o].

Continuing, main() instantiates ScrambledlnputStream, passing the FilelnputStream instance to ScrambledlnputStream's constructor. It then instantiates FileOutputStream, creating a file output stream that is connected to the file identified by args[l].

NOTE: When a stream instance is passed to another stream class's constructor, we say that the two streams are chained together. For example, the scrambled input stream is chained to the file input stream.

main() now enters a loop, reading bytes from the scrambled input stream and writing them to the file output stream. This loop continues until ScrambledlnputStream's read() method returns -1 (end of file).

The finally clause closes the scrambled input stream and file output stream by calling their close() methods. It does not call the file input stream's close() method because FilterOutputStream automatically calls the underlying input stream's close() method.

The makeMap() method is responsible for creating the map array that is passed to ScrambledlnputStream's constructor. The idea is to duplicate Listing 10-13's map array and then invert it so that unscrambling can be performed.

Continuing from the previous hello.txt/hello.out example, execute java Unscramble hello.out hello.bak and you will see the same unscrambled content in hello.bak that is present in hello.txt.

NOTE: For an additional example of a filter output stream and its complementary filter input stream, check out the "Extending Java Streams to Support Bit Streams" article (http://www.drdobbs.com/i84410423) on the Dr. Dobb's website. This article introduces BitStreamOutputStream and BitStreamlnputStream classes that are useful for outputting and inputting bit streams. The article then demonstrates these classes in a Java implementation of the Lempel-Zif-Welch (LZW) data compression and decompression algorithm. (Click the Next Page >> link at the bottom of the article page to access the listings.)

Was this article helpful?

0 0

Post a comment