The Source for Java Technology Collaboration
User: Password:



   

A Console Terminal for JARs A Console Terminal for JARs

by Sanjay Dasgupta
01/31/2006

Contents
Of Tyrannosaurus-Rex and Standard I/O
   Classic Standard I/O
   Back to the Future: Standard I/O in Java
Under the Hood of System.out
The Design of Jar-Stdio-Terminal
   The OutStrmAdapter Class
   The InStrmAdapter Class
   Jar-Stdio-Terminal Application Lifecycle
   Initial Setup
   Fetching Configuration Data
   Application Launch
The User Guide
Mustang and the java.io.Console Class
Conclusion
Resources

If you are bleary-eyed from a night vainly spent trying to make System.out.println() work in a JAR and want answers now, skip to the User Guide section of this article. But even if you never have to make a JAR spring to life with a double-click, read on to understand why, and how, System.out and the other standard I/O files do different things in different environments, and what Mustang's new java.io.Console class brings to the table.

A JAR is a great way of packaging and deploying a Java SE project. But there is a problem with console input/output--the standard I/O files (System.out, System.err, and System.in) do not work when a JAR is activated by a double-click. System.out and System.err are simple, intuitive, and frequently used for problem reporting, and their loss can be quite inconvenient.

This article describes an open source project, a-jar-stdio-terminal, that provides console capability to such JARs. It does not require any change to the existing application code, and can, therefore, even be retrofitted onto existing JARs to magically restore lost console capabilities.

Of Tyrannosaurus-Rex and Standard I/O

Many of you have probably never touched, except perhaps in a museum, anything like the machine in Figure 1, a teletype. In the early days of UNIX and C, computers were big, expensive, and often housed in basements--almost always far away from users, who had to access them from teletypes connected via serial lines that carried only ASCII text. Input to the computer was hammered out on a clunky keyboard, while the computer's output was noisily printed out on a roll of paper.

A Teletype Unit
Figure 1. A teletype unit

What kept the users happy, despite the equipment, was a universal interaction model based on something called standard input/output, as described in Kernighan and Pike's book The UNIX Programming Environment. This model used three unidirectional logical links called stdin (standard input), stdout (standard output), and stderr (standard error) for the exchange of information between program and user.

Classic Standard I/O

The terms stdin, stdout, and stderr are actually the names of predefined global variables that can be used in any C program to read input from the user's keyboard, and print results to his or her screen, like this:

float num, sqrRoot;
int i;
for (i = 0; i < 5; ++i) {
   /* get number from stdin into num */
   fscanf(stdin, "%f", &num);
   if (f >= 0)
      fprintf(stdout, "sqrt(%f) is %f \n", 
            num, sqrt(num));
   else
      fprintf(stderr, "Cant get sqrt! (%f) \n", 
            num);
}

Standard I/O routes the data to and from the desired end point, which could be a specific terminal, file, or other device. There are also mechanisms that allow the program itself or an external shell program to change the path of the data at run time. We ignore these details for now, but will discuss equivalent Java functionality used in the design of Jar-Stdio-Terminal.

Back to the Future: Standard I/O in Java

There are no global variables in Java, so the designers chose the next best option. They equipped java.lang.System with three public static members called in, out, and err that reference the standard I/O streams. The public static declaration makes them visible from anywhere in a Java program, and the Java runtime ensures that they refer to usable I/O streams before user code starts running (not always, obviously, but read on anyway). So here is how you would write the same code in Java:

float num, sqrRoot;
int i;
for (i = 0; i < 5; ++i) {
   num = getInt(System.in);
   if (f >= 0)
      System.out.printf("sqrt(%f) is %f \n", 
            num, sqrt(num));
   else
      System.err.printf("Cant get sqrt! (%f) \n", 
            num);
}

Java programs almost never use System.in, but System.out and System.err are common. Wasteful effort, you may think, given that there is no knowing which shore the bottle with the message will wash up on, and whether the message will ever be found and be read. But let's try to get to the bottom of the mystery. Why do messages sent via System.out and System.err sometimes get lost in transit?

In the following section we explore the data path from (and to) a program through Java's standard I/O infrastructure. Everything that is said about System.out applies to System.err too. Although System.in sends data in the opposite direction, the design must be based on equivalent principles.

Under the Hood of System.out

The Java Platform API specification is usually a good place to begin any Java research, and here is the opening line of the description of java.io.PrintStream, which is the type of the System.out variable:

A PrintStream adds functionality to another output stream, namely the ability to print representations of various data values conveniently

Another output stream refers to the OutputStream argument passed to PrintStream's constructors. This is the adapter pattern (c.f., the GoF's book Design Patterns) at work. OutputStream performs the real job of sending output to a storage, display, or network device. But its interface is too low-level for mainstream Java developers, and so must be adapted into a more user-friendly PrintStream.

The sequence diagram in Figure 2 captures what we've found so far. When the user code executes (at "1") any of the print methods (print(), println(), or printf()), the PrintStream object converts the argument into text (at "2") and passes it as a stream of bytes to the hidden OutputStream object. The OutputStream (at "3") then proceeds to deliver the bytes to the appropriate device.

Data path through System.out
Figure 2. Data path through System.out

But the trail seems to go cold at "3." OutputStream is an abstract class, and does not specify what it does with the data passed to it. But isn't this is a clue to why we see different behaviors in different environments? The disposal of the data must be implemented differently by the various subclasses of OutputStream used in these environments.

Closer examination of java.lang.System reveals more clues. The object assigned to System.out is not wired in, but can be substituted with another object by invoking System.setOut(OutputStream). This means that disposal of data passed to System.out's print...() methods can be customized as follows:

  1. Subclass OutputStream, implementing the required behavior in its write() methods.
  2. Create an instance of PrintStream, passing an instance of the OutputStream subclass created in step 1 to PrintStream's constructor.
  3. Pass the PrintStream instance created in step 2 to System.setOut().

The procedure described above does not affect the user code, since it only sees the PrintStream interface exposed by System.out, but allows the disposal of the formatted data to be customized.

Pages: 1, 2, 3

Next Page » 

View all java.net Articles.

 Feed java.net RSS Feeds