Chapter 4
The acm.io Package
The survey of problems presented in Chapter 3 notes that the problem most often cited by those attempting to teach Java to novices is the lack of a simple input mechanism, either through a traditional text-stream interface or a more modern dialog-based approach. Although this problemidentified as A1 in the taxonomyis partly addressed by the introduction of the Scanner class in Java 5.0 and the JOptionPane class in Swing, the Task Force concluded that it would be worth introducing a package that offered an integrated approach to user input that would provide both traditional and dialog-based input options in a simple, consistent way. In the Java Task Force release, this capability is provided by the acm.io package, which contains two public classes:
- An IOConsole class that supports traditional text-based interaction within the standard Java window-system hierarchy.
- An IODialog class that offers similar functionality to the JOptionPane class in a significantly simplified form.
In addition to these classes, the acm.io package defines an IOModel interface that specifies the input and output operations common to IOConsole and IODialog. The sections that follow describe the two public classes in more detail.
4.1 Console-based I/O
One of the most common strategies used by Java textbook authors to address the problem of user input is to create an IOConsole class that provides interactive text-based I/O within the framework of the standard Java graphical APIs. As examples, the Java textbooks published by Holt Software Associates [Hume00] and the web-based textbook written by David Eck [Eck02] define their own classes that provide this functionality. Similar packages not linked to textbooks have also been proposed, including those developed by Ron Poet [Poet00] and Eric Roberts [Roberts04d].
While the large number of existing implementations clearly indicate that an IOConsole class enjoys a certain popularity in the community, its inclusion in the JTF packages has generated a certain amount of controversy. The principal objection to having such a class is that the underlying computational paradigm is not representative of the event-driven, interactive user interfaces for which the object-oriented paradigm is so well suited. In the papers surveyed for the problem taxonomy presented in Chapter 3, several authors argue that Java programs should avoid such traditional modes of interaction and instead make use of dialogs that fit more closely with modern paradigms of interactive programming. After extensive discussion, the Java Task Force decided that the acm.io package should support both console- and dialog-based I/O, but only if the two models could implement a single interface, which would allow easy transition from one paradigm to the other. Our reasons for retaining the console-based model include:
- The console-based model has significant utility in its own right. The IOConsole class has many uses beyond its obvious role as a framework for traditional procedural programs. Interpreters, for example, can use it to provide a read-eval-print mechanism, which serves as a powerful interactive tool.
- Including both paradigms makes it easier to support automated testing. It is often harder to design test programs for an interactive environment, particularly if such tests are automated. Consider, for example, traditional stream-based programs written for the Unix/Linux domain (or, equivalently, Mac OS X). It is usually easy to test such programs by using redirection to supply an input script and an output log. That style of testing is typically unavailable under an interactive, dialog-based paradigm. By including compatible console- and dialog-based I/O facilities, however, the ACM packages support such testing strategies under either input model.
- The existence of a console mechanism enables teachers without object-oriented programming experience to teach Java more effectively. Many computer science teachers, particularly at the secondary school level, have little experience with Java and event-driven, interactive programs, but considerable familiarity with programs that use console-based interaction. The Task Force concluded that it would be valuable to offer teachers a mechanism that falls within their domain of expertise, at least as an interim measure until object-oriented programming skills are more widely distributed.
As a simple illustration, the code shown in Figure 4-1 shows the Java code necessary to create a Swing application that adds two numbers using the IOConsole class. The following screen snapshot shows how this program appears when running on a Macintosh under Mac OS X:

Figure 4-1. Swing application to add two numbers
/*
* File: Add2Application.java
* --------------------------
* This program adds two numbers and prints their sum. This version
* runs as a Java application without using the acm.program package.
*/
import acm.io.*;
import java.awt.*;
import javax.swing.*;
public class Add2Application {
public static void main(String[] argv) {
JFrame frame = new JFrame("Add2Application");
IOConsole console = new IOConsole();
frame.getContentPane().add(BorderLayout.CENTER, console);
frame.setSize(500, 300);
frame.show();
console.println("This program adds two numbers.");
int n1 = console.readInt("Enter n1: ");
int n2 = console.readInt("Enter n2: ");
int total = n1 + n2;
console.println("The total is " + total + ".");
}
} |
The code in Figure 4-1 can be simplified substantially by using the ConsoleProgram class described in Chapter 6. The intent of this example is to illustrate the use of IOConsole as a standalone tool.
The public methods in the IOConsole class are shown in Figure 4-2. The only new methods in the class added since the first release are the versions of readInt and readDouble that include a range specification. These methods were added in response to the following comment on the JTF web forum by Alyce Brady:
I was wondering . . . whether the Task Force also considered (and possibly rejected) the possibility of straightforward range checking for int and double types? This is easy to support if both the lower- and upper-bounds are requiredjust provide an additional method for each that takes the two bounds as parameters. This is much cleaner than requiring the students to do range-checking within a loop. Of course, they should eventually be able to do this, but to provide range-checking would seem to be consistent with the type-checking that is already being done. [Brady05]
Given that IOConsole derives its inspiration from the familiar paradigm of text-based, synchronous interaction, the design of the class is relatively straightforward. Even though the underlying paradigm is familiar, there are nonetheless several important features of the IOConsole class that are worth highlighting:
Figure 4-2. Public methods in the IOConsole class
Constructor | |
IOConsole()
Creates a new console object, which is a lightweight component capable of text I/O. |
|
Output methods | |
void print(any value)
Writes the value to the console with no terminating newline. |
void println(any value)
Writes the value to the console followed by a newline. |
void println()
Returns the cursor on the console to the beginning of the next line. |
void showErrorMessage(String msg)
Displays an error message on the console. |
|
Input methods | |
String readLine() or readLine(String prompt)
Reads and returns a line of text from the console without the terminating newline. |
int readInt() or readInt(int min, int max) or
readInt(String prompt) or readInt(String prompt, int min, int max)
Reads and returns an int value, with optional specification of a prompt and limits. |
double readDouble() or readDouble(double min, double max) or
readDouble(String prompt) or
readDouble(String prompt, double min, double max)
Reads and returns a double value, with optional specification of a prompt and limits. |
boolean readBoolean() or readBoolean(String prompt)
Reads and returns a boolean value (true or false) from the console. |
boolean readBoolean(String prompt, String trueLabel, String falseLabel)
Reads a boolean value by matching the user input against the specified labels. |
|
Additional methods (most of which are unlikely to be used by novices) | |
void setFont(Font font) or void setFont(String str)
Sets the overall font for the console, which may also be specified as a string. |
void setInputStyle(int style) and int getInputStyle()
Sets (or retrieves) the font style for user input, which can be different from the output style. |
void setInputColor(Color color) and int getInputStyle()
Sets (or retrieves) the font color for user input, which can be different from the output color. |
void setErrorStyle(int style) and int getErrorStyle()
Sets (or retrieves) the font style for error messages. |
void setErrorColor(Color color) and int getErrorStyle()
Sets (or retrieves) the font color for error messages. |
void setExceptionOnError(boolean flag)
Sets the error-handling mode: false means retry on error, true means raise an exception. |
boolean getExceptionOnError()
Returns the error-handling mode, as defined in setExceptionOnError. |
PrintWriter getWriter()
Returns a writer that can be used to write to the console (analogous to System.out). |
BufferedReader getReader()
Returns a reader that can be used to read from the console (analogous to System.in). |
void setInputScript(BufferedReader rd)
Sets the console so that it reads its input from the specified reader rather than the user. |
BufferedReader getInputScript()
Returns the current input script from which the console is reading. |
void clear()
Clears the console screen. |
void save(Writer wr)
Saves the contents of the console to the specified writer. |
void print(PrintJob pj)
Prints the contents of the console to the printer as specified by pj. |
|
|
- The console mechanism supported by the package is part of the component hierarchy. One of the common areas of confusion in discussing this proposal was that the IOConsole class is part of the window system hierarchy and not simply a layered structure on top of the standard I/O streams System.in and System.out. Making the console facility part of the standard windowing system increases the flexibility of the package and makes it usable in both the application and applet worlds. In many browsers, the standard I/O streams are not normally displayed, which makes them very difficult for students to use.
- The console facility makes it possible to differentiate user input and error messages from program output. The IOConsole class makes it possible for students to tell the difference between user input and program output. The methods setInputColor and setInputStyle set these properties for user input, while setErrorColor and setErrorStyle do the same for error messages. Output text is displayed in the style and color given by the font and foreground settings and can therefore be manipulated by calling setFont and setForeground. By default, user input is shown in blue, and error messages appear in red. One of the principal advantages of making these distinctions is that the pattern of user interaction is obvious when the program is displayed on a classroom projection screen.
- The package includes support for menu bars that support printing and saving the contents of the console, along with standard editing operations. When an IOConsole object acquires the keyboard focus, it looks to see whether a menu bar has been installed in the frame and, if not, creates a menu bar with standard File and Edit menus. In either case, the menu bar is set to direct its actions to that console. This feature is described in more detail in section 6.6.
- The input and output streams for a console are available as readers and writers. The IOConsole class defines the methods getReader and getWriter, which return a BufferedReader and a PrintWriter, respectively. These objects can therefore serve as character-stream versions of the System.in and System.out objects, which rely on the less portable byte-stream protocol. Moreover, the IOConsole class defines a special constant SYSTEM_CONSOLE, which reads and writes to the standard System.in and System.out streams, thereby providing access to these streams using the more modern discipline of readers and writers as opposed to byte streams.
4.2 Dialog-based I/O
The IOConsole classuseful though it isdoes not in any sense constitute a complete solution to the user-input problem. Most instructors are looking for some way to support dialog-based I/O that is simple enough for students to use. As noted in the discussion of this problem in Chapter 3, several mechanisms have been proposed to address this problem, including the Java Power Tools project developed at Northeastern [Raab00, Rasala00] and the simpleIO
package developed by Ursula Wolz and Elliot Koffman [Wolz99].
The standard Java APIs, of course, include many methods for constructing user dialogs. In fact, since Swing was introduced as part of JDK 1.2, the Java APIs have included the JOptionPane class that makes it possible to put up simple dialogs to request basic data types from the users. Many instructors have used JOptionPane successfully, and it appears to be the most common paradigm for this style of user interaction.
Unfortunately, the JOptionPane mechanism has several weaknesses that make it problematic for novice users. Of these deficiencies, the most serious are the following:
- The API is complex. The JOptionPane class includes seven different variants of the constructor, 37 public fields, and 61 public methods. When printed, the online documentation runs 50 pages. While students clearly dont need to understand the entire structure, the danger of information overload is significant.
- The typical usage paradigm involves static methods. The JOptionPane class adopts a curious mix of the object-based and procedural paradigms. While the displayed dialogs are objects of class JOptionPane, the methods that create those dialogs and request input from them are static methods in the JOptionPane class. As a result, students are easily confused about the paradigm. Instead of creating an object and then sending it a message requesting user input, the student must instead make static calls on the class.
- Many of the more useful interaction styles require students to use arrays. Any JOptionPane dialog that provides options outside the default set must supply those options as an array of objects. At the beginning of an introductory programming course, students have not yet learned about arrays, but it is precisely during this period that dialog-based input would be most useful.
The IODialog class in the acm.io package is designed to solve each of these problems. The customary paradigm is to create an IODialog object and then invoke methods on that object to display information, request input, or report errors. As with JOptionPane (which is used internally in the implementation as long as Swing is available), these three styles of use generate slightly different dialog formats, as shown in Figure 4-4.
Figure 4-4. The three styles of IODialog interactors
The code that produces this series of dialogs looks like this:
|
IODialog dialog = new IODialog();
dialog.showMessage("Process complete");
int n = dialog.readInt("Enter an integer: ");
dialog.showErrorMessage("Illegal operation");
|
|
The public methods in the IODialog class are shown in Figure 4-5. As the small number of entries in the table makes clear, the IODialog class is vastly simpler than the JOptionPane facility and should prove much easier for students to use.
Figure 4-5. Public methods in the IODialog class
Constructor | |
IODialog()
Creates a new IODialog object that supports dialog-based interaction. |
|
Output methods | |
void print(any value)
Adds the string to a message that will be displayed when the line is complete. |
void println(any value)
Displays the value in a dialog box. |
void println()
Displays any text in the dialog box so far. |
void showErrorMessage(String msg)
Displays an error message in a dialog box. |
|
Input methods | |
String readLine() or readLine(String prompt)
Pops up a dialog asking the user to enter a line of text (returned with no terminating newline). |
int readInt() or readInt(int min, int max) or
readInt(String prompt) or readInt(String prompt, int min, int max)
Pops up a dialog asking the user to enter an int, with optional prompt and limits. |
double readDouble() or readDouble(double min, double max) or
readDouble(String prompt) or
readDouble(String prompt, double min, double max)
Pops up a dialog asking the user to enter a double, with optional prompt and limits. |
int readInt() or readInt(String prompt)
Pops up a dialog asking the user to enter an integer value. |
double readDouble() or readDouble(String prompt)
Pops up a dialog asking the user to enter a double-precision value. |
boolean readBoolean() or readBoolean(String prompt)
Pops up a dialog asking the user to select a true/false value. |
boolean readBoolean(String prompt, String trueLabel, String falseLabel)
Pops up a dialog asking the user to choose one of the specified labels. |
|
Additional methods (most of which are unlikely to be used by novices) | |
void setExceptionOnError(boolean flag)
Sets the error-handling mode: false means retry on error, true means raise an exception. |
boolean getExceptionOnError()
Returns the error-handling mode, as defined in setExceptionOnError. |
void setAllowCancel(boolean flag)
Sets the cancel mode: true adds a Cancel button that throws an exception. |
boolean getAllowCancel()
Returns the error-handling mode, as defined in setAllowCancel. |
|
|
One of the design goals for the acm.io package was to have the IOConsole and IODialog classes implement the same interface so that students could easily switch back and forth between the two modes. In the acm.io design, that common behavior is specified by the IOModel interface, which includes the input and output methods common to the two classes. The existence of this interface makes it possible for code to remain insensitive to the interaction model. Consider, for example, the following method definition:
|
void showArray(IOModel io, int[] array) {
io.print("[");
for (int i = 0; i < array.length; i++) {
if (i > 0) io.print(", ");
io.print(array[i]);
}
io.println("]");
}
|
|
The important idea to notice here is that the first argument to showArray can be either an IOConsole or an IODialog object; since both implement the IOModel interface, the implementation doesnt care.
Designing IODialog so that it provides a reasonable implementation of IOModel turns out to be harder than it looks. The input side is easy, because both consoles and dialogs wait for the user to supply a single input value. The output side is more difficult, largely because a dialog is not a stream device. Having each call to print display a separate dialog box is not a good choice, particularly for methods like showArray that assemble the output in pieces. Displaying each individual value and comma in a separate dialog box would destroy the conceptual integrity of the output and render the entire mechanism unusable. A more satisfactory approach is to have print buffer the output inside the dialog until a call to println arrives to signal the end of the line. This interpretation has the effect, therefore, of allowing
|
int array = {2, 3, 5, 7, 11};
showDialog(new IODialog(), array);
|
|
to generate a single dialog box, as follows:

Similar care must be applied if calls to print are followed by an input operation before a println call occurs. In such situations, the buffered output is added at the beginning of the prompt string. This treatment provides a sensible interpretation for code in which the programmer explicitly prints a prompt rather than including it as a parameter to the input call.
Another difficult decision in the design of both IODialog and IOConsole was the question of how to handle illegal input data. For novices, the best strategy is for the input method simply to display an error message and request new input, either by bringing up another dialog or by repeating the prompt on the console. For more sophisticated users, however, it is more appropriate to allow the program to gain control at this point. By default, both the IODialog and IOConsole classes adopt the novice-friendly approach and request new input. The more advanced programmer, however, can change this behavior by calling setExceptionOnError(true), in which case the input methods throw an ErrorException on invalid input.
A related issue arises in defining reasonable semantics for the Cancel button, which is part of the typical input dialog mechanism available with JOptionPane. In this case, it is not appropriate to have the program bring up a new dialog after the user clicks the Cancel button, which is presumably exactly what the user did not want. To avoid this conceptual problem, the IODialog mechanism does not display Cancel buttons as part of the dialog unless the client has made it clear that special handling of dialog cancellation is desired. If the client calls setAllowCancel(true), the input methods display a Cancel button that throws a CancelledException when it is clicked.