A Graphical User Interface for Output Streams
As an example, I'm going to show a subclass of javax.swing.JTextArea that can be connected to an output stream. As data is written onto the stream, it is appended to the text area in the default character set. (This isn't ideal. Since text areas contain text, a writer would be a better source for this data. In later chapters, I'll expand on this class to use a writer instead. For now, this makes a neat example.) This subclass is shown in Example 2-4.
The actual output stream is contained in an inner class inside the JStreamedTextArea class. Each JStreamedTextArea component contains a TextAreaOutputStream object in its theOutput field. Client programmers access this object via the getOutputStream( ) method. The JStreamedTextArea class has four overloaded constructors that imitate the four constructors in the javax.swing.JTextArea class, each taking a different combination of text, rows, and columns. The first three constructors merely pass their arguments and suitable defaults to the most general fourth constructor using this( ). The fourth constructor calls the most general superclass constructor, then calls setEditable(false) to ensure that the user doesn't change the text while output is streaming into it.
Example 2-4. The JStreamedTextArea component
package com.elharo.io.ui; import javax.swing.*; import java.io.*; public class JStreamedTextArea extends JTextArea { private OutputStream theOutput = new TextAreaOutputStream( ); public JStreamedTextArea( ) { this("", 0, 0); } public JStreamedTextArea(String text) { this(text, 0, 0); } public JStreamedTextArea(int rows, int columns) { this("", rows, columns); } public JStreamedTextArea(String text, int rows, int columns) { super(text, rows, columns); setEditable(false); } public OutputStream getOutputStream( ) { return theOutput; } private class TextAreaOutputStream extends OutputStream { private boolean closed = false; public void write(int b) throws IOException { checkOpen( ); // recall that the int should really just be a byte b &= 0x000000FF; // must convert byte to a char in order to append it char c = (char) b; append(String.valueOf(c)); } private void checkOpen( ) throws IOException { if (closed) throw new IOException("Write to closed stream"); } public void write(byte[] data, int offset, int length) throws IOException { checkOpen( ); append(new String(data, offset, length)); } public void close( ) { this.closed = true; } } } |
The TextAreaOutputStream inner class is quite simple. It extends OutputStream and thus must implement the abstract method write( ). It also overrides the primary array write( ) method to provide a more efficient implementation. Finally, it overrides close( ) to make sure that no writes take place after the stream is closed.
To use this class, simply add an instance of it to a container such as an applet or a window, much as you'd add a regular text area. Next, invoke its getOutputStream( ) method to get a reference to the output stream for the area, then use the usual write( ) methods to write into the text area. Often, these steps will take place at different times in different methods.
Figure 2-1 shows a program using a JStreamedTextArea to display data downloaded from http://www.oreilly.com/. The application in this picture will be developed in Chapter 5.
Figure 2-1. The JStreamedTextArea component
I'll revisit and improve this class in future chapters using techniques I haven't discussed yet. In particular, I'll pay much more attention to the issue of character sets and encodings.