Monday, November 24, 2008

Command Line GUI Java Apps

Over the years I have written a number of GUI-based Java apps, first using AWT and later using Swing. Often at some later point I wanted to invoke the app from the command line to make it do something in batch mode with no user interaction. Below is the approach I now use when writing a GUI app in order to avoid having to modify the app later to make it work in batch mode. (The descriptions are a synthesis of some techniques I have used, and names are representative.) I have used this approach with both Java and Scala.

Implementation

The first rule is relatively common advice: separate the model from the view and controller (the MVC paradigm). Usually this is pretty easy, and once you get into the habit of doing this it goes a long way towards making it possible to use the application in a non-interactive mode. But there were always a few places where I wanted to output a message, or ask for confirmation, or do some other kind of I/O that would tie my code to the view.

My solution to this problem is to create an IBasicUi interface to handle I/O, an IResources interface to handle resources, an Application class that implements those interfaces for normal graphical use, and a BatchApplication class that implements those interfaces for non-interactive batch use. At runtime, based on the startup options, I instantiate an instance of either Application or BatchApplication as my Application object.

The Application object is responsible for three things:
  1. Console output such as errors, warnings, questions and info.
  2. Console input such as confirmations or file names.
  3. Initializing Resources (resource bundles).
The GUI Application object uses popup boxes for console output and dialog boxes for input. The batch Application object just prints to System.out and reads input from System.in.

The Application object contains most of the methods that behave differently between GUI and batch use, although occasionally I will use a non-object-oriented approach by putting some of that in other classes and implementing an isBatch() method in the Application object to allow the other classes to change their behavior.

I also use the Application object to store other application-wide state, such as command line options that affect the behavior of the application.

Usage

I would typically pass a pointer to the Application object in to the constructors of my other objects. Alternatively, I sometimes would define an Application class and declare a static variable which I would set to the Application object on initialization, after which I could retrieve it with a static getter method. Finally, if for some reason neither of those two approaches was acceptable, I might put the Application object into a thread local variable and retrieve it from there when needed.

I suppose using today's lingo the act of passing in an Application object that could be either an Application or a BatchApplication would be referred to as dependency injection.

An additional benefit I got from having every object use the Application object for input, output and resources was that it made unit testing much simpler. I could create a TestApplication class in which the input and output methods were tied to my testing data files, and the resource methods used a small resource file with resources specific to the test being run.

Another Detail

By default when you start a Java application the JVM initializes the graphical environment. If you are running your batch command from your windowing system, this is not a problem; but if you are running it from a true batch script, where there is no windowing system available, this is not good. To prevent Java from initializing the graphical environment, you have to tell it that you are running in a headless environment. You do this by including the JVM option -Djava.awt.headless=true when starting your Java application. The app can tell if this command line option has been set by calling GraphicsEnvironment.isHeadless().

No comments: