A Console Terminal for JARs Blog

Version 2



    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
       TheOutStrmAdapter Class
       TheInStrmAdapter Class
       Jar-Stdio-Terminal Application Lifecycle
       Initial Setup
       Fetching Configuration Data
       Application Launch
    The User Guide
    Mustang and thejava.io.Console Class

    If you are bleary-eyed from a night vainly spent trying to makeSystem.out.println() work in a JAR and want answers now, skip to the User Guidesection 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 newjava.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.outand 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, andstderr 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.Systemwith three public static members calledin, out, and err that reference the standard I/O streams. The public staticdeclaration 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, butSystem.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 viaSystem.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 toSystem.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 ofjava.io.PrintStream, which is the type of theSystem.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 theOutputStream argument passed toPrintStream'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-friendlyPrintStream.

    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(), orprintf()), the PrintStream object converts the argument into text (at "2") and passes it as a stream of bytes to the hidden OutputStreamobject. 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." OutputStreamis 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 ofOutputStream 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 invokingSystem.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 toSystem.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.

    The Design of Jar-Stdio-Terminal

    The class diagram in Figure 3 illustrates the overall structure of Jar-Stdio-Terminal. There are three custom classes:StdioTerm, InStrmAdapter, andOutStrmAdapter. OutStrmAdapter is a subclass of OutputStream, and its purpose is to divert output and error data away from their original (JVM-determined) destinations, and into Jar-Stdio-Terminal's synthetic console (described below). The InStrmAdapter is a subclass ofInputStream, and its purpose, likewise, is to shuntSystem.in's supply chain away from the original source and connect it into the synthetic console. Observe thatStdioTerm uses two instances ofOutputStream (for System.out andSystem.err). The only difference between the twoOutStrmAdapter instances is the color used for rendering the output (green and orange, respectively).

    Class diagram
    Figure 3. Class diagram

    The StdioTerm class is the owner of all of these resources, and also owns a JFrame and aJTextPane. The JTextPane is contained within the JFrame, and they together constitute thesynthetic console window. The synthetic console is not a console in a true operating-system sense. However, functionally and visually it is a more than adequate substitute. All of the code for one-time activities--setup, application launch, shutdown--is in theStdioTerm class. This class also implementsActionListener and KeyListener to support a couple of buttons and the keyboard.

    This article does not contain all of Jar-Stdio-Terminal's code, since much of it is typical Swing programming. However, all of the code that gives Jar-Stdio-Terminal its character is shown and explained here. All of the source code is, however, available at the a-jar-stdio-terminal project page.

    The OutStrmAdapterClass

    OutStrmAdapter is a custom subclass ofjava.io.OutputStream. Instances of this class are passed to the constructor of java.io.PrintStream to create instances that will handle the supported application'sSystem.out and System.err. This subclass implements the two flavors of write() to send the application's output (or error) stream to Jar-Stdio-Terminal's synthetic console window.

    package net.java.dev.a_jar_stdio_terminal; import java.io.*; import javax.swing.text.Document; import javax.swing.text.SimpleAttributeSet; public class OutStrmAdapter extends OutputStream { public OutStrmAdapter(StdioTerm owner, SimpleAttributeSet textAttr) { this.owner = owner; theDocument = owner.theDocument; this.textAttr = textAttr; } public void write(byte b[], int off, int len) throws IOException { try { theDocument.insertString( theDocument.getLength(), new String(b, off, len), textAttr); } catch (Exception e) {/* Not expected */} owner.configureDisplay(); } public void write(int c) throws IOException { tempChar[0] = (byte)c; try { theDocument.insertString( theDocument.getLength(), new String(tempChar), textAttr); } catch (Exception e) {/* Not expected */} owner.configureDisplay(); } private byte tempChar[] = new byte[1]; private Document theDocument; private SimpleAttributeSet textAttr; private StdioTerm owner; }

    The two instances of this class used will differ only in the color they use to render text: green and orange forSystem.out and System.err, respectively. The instances are created in the init() method (not shown in this article, but available with the project source), and the desired text attribute is passed as a parameter to the constructor there.

    TheInStrmAdapter Class

    InStrmAdapter is a custom subclass ofjava.io.InputStream, needed to function as the supported application's System.in. It implements two flavors of read() to satisfy the application's input needs with characters harvested by a KeyListenerattached to the synthetic console window. The instance used is created in the init() method, and the desired text attribute (white foreground) is passed to the constructor there.

    package net.java.dev.a_jar_stdio_terminal; import java.io.*; import javax.swing.text.Document; import javax.swing.text.SimpleAttributeSet; public class InStrmAdapter extends InputStream { public InStrmAdapter(StdioTerm owner, SimpleAttributeSet textAttr) { this.owner = owner; theDocument = owner.theDocument; this.textAttr = textAttr; } public int read() throws IOException { if (owner.inputEofSeen) return -1; if (owner.inputBuffer.isEmpty()) owner.getUserInput(); return owner.inputBuffer.remove(0); } public int read(byte b[], int off, int len) throws IOException { if (owner.inputEofSeen) return -1; if (owner.inputBuffer.isEmpty()) owner.getUserInput(); int dataLength = Math.min(len, owner.inputBuffer.size()); for (int i = 0; i < dataLength; ++i) b[off + i] = owner.inputBuffer.remove(0); return dataLength; } private byte tempChar[] = new byte[1]; private Document theDocument; private SimpleAttributeSet textAttr; private StdioTerm owner; }

    InStrmAdapter takes characters off of the end of aVector which is used as a queue between itself and the main class StdioTerm, which implementsKeyListener. This is arguably inefficient, but rugged and simple. It uses the services of a helper method,getUserInput(), to manage synchronization and block type-ahead.

    Jar-Stdio-Terminal Application Lifecycle

    When you double-click a JAR that contains an application using Jar-Stdio-Terminal, much happens before the real application is activated. All of these actions are kicked off byStdioTerm.main(), whose code is shown below:

    public static void main(String args[]) { attach(null); getStdioNames(); theFrame.setTitle(stdioTerminalTitle); if (stdioClassName == null) return; try { Class stdioClass = Class.forName( stdioClassName); Method main = stdioClass.getDeclaredMethod( "main", new Class[] {java.lang.String[].class}); main.invoke(null, new Object[] {new String[]{}}); detach(); } catch (Exception e) { System.err.printf("Cant use \"%s\"\n", stdioClassName); if (e instanceof InvocationTargetException) { e.getCause().printStackTrace(System.err); } else System.err.println(e); } }

    StdioTerm.main() must run before the supported application, in order to change the run-time environment before be application starts. This also enables Jar-Stdio-Terminal to be run without changing the application itself. To getStdioTerm.main() to run first, theMain-Class entry in the manifest file must point tonet.java.dev.a_jar_stdio_terminal.StdioTerm.

    Initial Setup

    The method attach(), listed below, is called first. The StdioTerm instance is created here. TheStdioTerm constructor then completes all environment setup actions, including creating instances of JFrameand JTextPane and the three objects to be used forSystem.in, System.out, andSystem.err. attach() then switches the values assigned to System.in, System.out, and System.err.

    public static void attach(String title) { if (me == null) { me = new StdioTerm(title == null ? stdioTerminalTitle : title); oldOut = System.out; oldErr = System.err; oldIn = System.in; System.setIn(in); System.setOut(out); System.setErr(err); } }

    The public static declaration ofSystem.in, System.out, andSystem.err make the effect of executingattach() applicable system-wide. So even the supported application will use these values of System.in,System.out, and System.err.

    There should never be more than one console window, so, although this is not the classic singleton pattern, the code protects against the possibility of this error.

    Fetching Configuration Data

    After the initial setup actions have been completed and a functional console window has been deployed,StdioTerm.main() needs to fetch the configuration information. This is done by calling getStdioNames()shown below:

    private static void getStdioNames() { InputStream nameStream = me.getClass(). getResourceAsStream("StdioConfig.txt"); if (nameStream == null) { System.err.println("No StdioConfig.txt"); return; } byte nameBytes[] = null; try { nameBytes = new byte[nameStream.available()]; nameStream.read(nameBytes); } catch (Throwable t) { System.err.println("JarStdioTerminal. getStdioNames(): " + t); return; } String names = new String(nameBytes); int end = names.indexOf('\n'); if (end == -1) { stdioClassName = names.trim(); } else { stdioClassName = names.substring(0, end).trim(); if (names.length() > end + 1) stdioTerminalTitle = names.substring(end + 1).trim(); } }

    Jar-Stdio-Terminal needs the name of the supported application'sMain-Class, and the title of the console terminal's window. These are expected to be in the fileStdioConfig.txt in the same directory as Jar-Stdio-Terminal's class files. Observe that the console window title is optional--the default value is JAR-STDIO Terminal.

    Application Launch

    If getStdioNames() fails to find configuration data, either because StdioConfig.txt was not found or it did not contain data in the required format,StdioTerm.main() returns, leaving the console window intact with the error messages. However, if at least the name of the application's main class was found,StdioTerm.main() uses classic techniques based on Java reflection to activate the application's main().

    StdioTerm.main() therefore remains on the activation stack as long as the supported application is running. Control returns from Method.invoke() only if the supported application's main() returns, which never happens if the application's code calls System.exit(). If control does return from Method.invoke(), cleanup is performed by calling detach():

    public static void detach() { if (me != null) { System.setOut(oldOut); System.setErr(oldErr); System.setIn(oldIn); me = null; theFrame.dispose(); } }

    detach() cleans up by restoring the original values of System.in, System.out, andSystem.err. It then removes the terminal window by calling dispose() on the JFrame.

    The User Guide

    1. Download the distribution: JarStdioTerminal.zip, fromthe a-jar-stdio-terminal project.
    2. Un-jar the distribution file,JarStdioTerminal.jar, into the root of the directory structure containing all of the .class and resource files of your application. This should create the directorynet/java/dev/a_jar_stdio_terminal with the following four files: StdioTerm.class, OutStrmAdapter.class,InStrmAdapter.class, and StdioConfig.txt.
    3. In the manifest file used by the jar command, use the following line as the declaration of the main class:
      Main-Class: net.java.dev.a_jar_stdio_terminal.StdioTerm

      This is required to set up the standard I/O terminal before the application class starts up. StdioTerm will then start up the application class as described below.

    4. Edit the filenet/java/dev/a_jar_stdio_terminal/StdioConfig.txt, and replace the information in it with two lines as follows:
      • The fully qualified name of the application class, the one that would have been specified as the main class if Jar-Stdio-Terminal was not being used.
      • The desired title for the terminal's frame
      The first line is mandatory, while the second line is optional. Note that the distribution should already contain a file of this name in the correct directory, since it also serves as a demo.
    5. Use the jar -cmf ... command as usual to wrap up all the files into one JAR file and you are ready to fly.

    Mustang and the java.io.Console Class

    Early information about Java's next release tells us that code like the following will be possible:

    Console cons; char[] passwd; if ((cons = System.console()) != null && (passwd = cons.readPassword("[%s]", "Password:")) != null) { ... java.util.Arrays.fill(passwd, ' '); }

    The new java.io.Console class has a bunch of convenience functions that make it easier to read from and write toConsole objects. But the only really new feature is the readPassword() method. ThereadPassword()s (there are two flavors) hide what the user is typing in, a feature that has been in demand for a long time. Also new is the System.console() method. It is interesting that it institutionalizes the fact that a console may not always be available. So we may presume thatSystem.console() will in fact return nullin a JAR activated by a double-click. Jar-Stdio-Terminal could possibly be used in some of those situations.


    The Jar-Stdio-Terminal project is a solution for projects with console I/O problems. The current version only addresses JAR files activated by a double-click, but the approach could be applied to other situations as well. The console user community also has a number of other needs such as VT100 simulation,curses, kbhit(), etc., and Jar-Stdio-Terminal could serve as a vehicle to deliver them.