Recent articles:
Popular archives:
Java: A platform for platforms
Sun's reorg may seem promising to shareholders but it's also a scramble for position. The question now is whether Sun can,
or wants to, maintain its hold on Java technology. Especially with enterprise leaders like SpringSource and RedHat investing
heavily in Java's future as a platform for platforms
Also see:
Discuss: Tim Bray on 'What Sun Should Do'
I initially set out to design a data-reading character-stream filter class. Analogous to the byte-stream filter DataInputStream, this character-stream filter was intended to provide the capability to read textual data from a character stream (namely
the output of a human or the PrintWriter println() method).
Let me now, in retrospect, describe what I actually implemented.
First, I created an UndoReader class. This character-stream filter supports three special methods:
checkpoint()commit()rollback()As you read characters through the stream, you have the option to checkpoint the stream -- that is, save the stream's current state and put it into a mode such that it stores all data subsequently read
through it. From that point on, the UndoReader stores all the data you read. After any amount of reading, choosing to commit the stream will cause the stored data to be discarded, after which reading proceeds undisturbed. Alternatively, choosing
to rollback the stream will cause it to rewind and revert to reading from the position at which you asserted the checkpoint -- just as
if you hadn't yet read anything. This stream also supports a couple of related methods.
Next, I implemented the DataReader class, the character-stream filter. This class makes use of the UndoReader class and provides methods to read all the primitive Java types (readInt(), readFloat(), readBoolean(), and so on). What is special about this class is that if you attempt to read a primitive from the stream and it turns out
the stream data is incorrect -- if, for example, you attempt to read a Boolean and the next token in the stream is truthfulness -- it rolls back, un-reading any characters read during the erroneous operation, and throws an exception. The stream also supports a feature whereby it
can read data one line at a time, signaling each time the end of a line is reached (among other wonders).
The classes I developed will only work in JDK 1.1-plus. Adapting them to work as InputStreams, usable under JDK 1.0.2, should be quite easy, however.
In the interest of brevity, I'll spare you an introduction to character streams. Todd Sundsted's November 1997 How-To Java column should serve that purpose quite adequately; if you want an introduction to byte streams, check out Todd's October 1997 column. For further details of the Java stream classes, I refer you to Java Network Programming, Second Edition, which I coauthored with Michael Shoffner and Derek Hamner, and which is due out any day now. (See Resources.)
What I should perhaps explain is my justification for the UndoReader class. At the most abstract level, I want the ability to undo a series of read operations, because otherwise my DataReader class will violate the basic law of propriety: It would be improper for an erroneous attempt at reading an int to end up consuming a Boolean. Furthermore, the behavior of my class wouldn't necessarily be clearly defined in the presence of such an error: the amount
of erroneous input consumed would be implementation-dependent, and exposing implementation-dependent details of this nature
simply invites abuse.
Those readers familiar with the stream classes may then ask about the mark() and reset() methods, or indeed the PushbackReader class: Do these basic features of the stream API not already address my needs? Indeed, use of the mark() and reset() methods does allow a sequence of read operations to be undone, and the PushbackReader unread() methods can be used to the same effect. However, both of these options are bounded. Therefore you must, in each case, declare ahead of time the maximum volume of data you will un-read. In our situation, no
such limit exists for textual data: "00...01" is a valid integer, just as "00...0z" is not. I cannot, simply to avoid writing
an extra class, presume to impose arbitrary limitations on the data I will process.
Thus rationalized, we can now proceed with the code.
The UndoReader class is a character-stream filter that provides unbounded checkpoint, commit, and rollback operations and an additional undo facility.

Figure 1. Using methods checkpoint(), commit(), rollback(), and undo()
checkpoint() is used, it proceeds to store all data read through it in an internal, expanding buffercommit() is used, the stored contents of the checkpoint buffer are discarded and further reads are no longer storedrollback() is used, reading reverts to data stored in the internal buffer; when this is used up, reading proceeds as normalWe'll start by looking at our class definition: