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, and OutStrmAdapter. 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 of InputStream, and its purpose, likewise, is to shunt System.in's supply chain away from the original source and connect it into the synthetic console. Observe that StdioTerm uses two instances of OutputStream (for System.out and System.err). The only difference between the two OutStrmAdapter instances is the color used for rendering the output (green and orange, respectively).

Figure 3. Class diagram
The StdioTerm class is the owner of all of these resources, and also owns a JFrame and a JTextPane. The JTextPane is contained within the JFrame, and they together constitute the synthetic 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 the StdioTerm class. This class also implements ActionListener 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 OutStrmAdapter Class
OutStrmAdapter is a custom subclass of java.io.OutputStream. Instances of this class are passed to the constructor of java.io.PrintStream to create instances that will handle the supported application's System.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 for System.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.
The InStrmAdapter Class
InStrmAdapter is a custom subclass of java.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 KeyListener attached 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 a Vector which is used as a queue between itself and the main class StdioTerm, which implements KeyListener. 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 by StdioTerm.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 get StdioTerm.main() to run first, the Main-Class entry in the manifest file must point to net.java.dev.a_jar_stdio_terminal.StdioTerm.
Initial Setup
The method attach(), listed below, is called first. The StdioTerm instance is created here. The StdioTerm constructor then completes all environment setup actions, including creating instances of JFrame and JTextPane and the three objects to be used for System.in, System.out, and System.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 of System.in, System.out, and System.err make the effect of executing attach() 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's Main-Class, and the title of the console terminal's window. These are expected to be in the file StdioConfig.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, and System.err. It then removes the terminal window by calling dispose() on the JFrame.
The User Guide
- Download the distribution: JarStdioTerminal.zip, from the a-jar-stdio-terminal project.
- 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 directory net/java/dev/a_jar_stdio_terminal with the following four files: StdioTerm.class, OutStrmAdapter.class, InStrmAdapter.class, and StdioConfig.txt.
- 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.
- Edit the file net/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.
- 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 to Console objects. But the only really new feature is the readPassword() method. The readPassword()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 that System.console() will in fact return null in a JAR activated by a double-click. Jar-Stdio-Terminal could possibly be used in some of those situations.
Conclusion
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.
Resources
Sanjay Dasgupta has been using Java since 1996 (after many years with many other languages). He works as an independent consultant, and lives in Kolkata, India.