Make Your Swing App Go Native, Part 2 Blog

Version 2


    Welcome back to this series on making Swing applications behave more like native applications. In th e previous article, we created a simple chat program with OS-specific menus and alerts. It sort of feels native, but we still have to start it from the command line. It would be nice if there were a double-clickable icon on the desktop to start our program.

    In this installment, we will create native executables for Mac OS X and Windows, then add another native feature: file type associations. In the third installment we will add custom icons and finishing polish.


    As we add icons and file associations, we need the process to be repeatable and automatable. Using GUI tools and wizards is fine for while you are learning, but in production, the last thing you want to do is open Photoshop and scale icons for every build. Everything we do here will be run from Ant, the popular Java build tool, so all work needs to be done by custom Ant tasks or scriptable utilities.

    If you haven't used Ant before, you should really check it out. Ant is similar to the old Unix make, in that it uses a project description file to build portions, or the entirety, of programs. Ant is different in that it uses XML files instead of plain text, and all of its functions are implemented as cross-platform Java classes instead of shell scripts that may not be present on your computer. Though it's only been around for a few years, it has quickly proven to be the tool of choice for building Java applications.

    Packaging Options

    The ideal Java application package is one that behaves just like a native app. For desktop systems, this means that the user can double click on an icon to launch the program. The icon is custom for the program, and the windows and taskbar/dock all have matching icons. This is the ideal scenario. Now, how to get there? To package up an application. we have a couple of options:

    • Double-clickable .jar files
    • Shell scripts
    • .apps and .exes

    First we could just make a double-clickable .jar. This is where the user receives a .jar file and can double click on it to start the program. The .jar has extra information in the manifest to tell the JVM where the startup class is and what other .jars need to be included in the classpath. In this case, double clicking on the .jar would be equivalent to doing a java -jar myprogram.jar command. This is a great way to keep it simple, but it doesn't feel quite right. There's no custom icon, as Figure 1 illustrates, and the application doesn't take advantage of native features. In fact, its icon and description make it look like a document, not an application. Still, it does have the ability to launch the program without a command line. It's pretty simple and shouldn't be overlooked when you are sending out betas.

    Figure 1
    Figure 1. Double clickable .jar on Mac OS X

    Our second option is using a batch file or Mac .commandscript to launch the program. This may be somewhat better, since we can probably get a custom icon and the user no longer has to know which of the many .jars you sent is the one he or she needs to double click, but it still doesn't take advantage of native hooks.

    It looks like we need something better: real .apps and.exes.

    Building a Double-Clickable OS X Application

    On the Mac, we can create a real application bundle, which gives the program access to the same features as a native application. An application bundle (commonly called is similar to a .exe under Windows in that it is a double-clickable application, but instead of being a simple binary, it is actually a folder with a .appextension. The extension is usually hidden, but a listing from the command line will show it. One of the really cool things about.apps is that they contain an entire directory structure with standard places for resources (icons and text), and XML config files for setting every conceivable option. In particular, we will set the startup classfile and display options. So as not to rehash what's been written elsewhere, including Apple's documentation, I won't explain all of the available features. Instead, we'll focus on automating it with an Ant script.

    To build an application bundle, we first need to create the directory structure. Here's part of a dist-mac target from an Ant build.xml file to do that:

    <target name="dist-mac" depends="jar"> <property name="appdir" value="${dist-mac}/${app-name}.app"/> <mkdir dir="${appdir}"/> <mkdir dir="${appdir}/Contents"/> <mkdir dir="${appdir}/Contents/MacOS"/> <mkdir dir="${appdir}/Contents/Resources"/> <mkdir dir="${appdir}/Contents/Resources/Java"/>

    This assumes that the variables ${dist-mac} and${app-name} have been previously defined. Such an arrangement lets us decide later where to put the files and makes the task more reusable.

    Next, we need to get a copy of the application stub, which is the binary code that actually starts your Java program. This stub is helpfully provided by Apple in the developer toolkit, in/System/Library/Frameworks/JavaVM.framework/ Versions/Current/Resources/MacOS/. Since we need to use the 1.3 JVM to support our JDirect calls to bounce the dock icon (see the previous article), I have copied the older one tosrc/packaging so it won't conflict with the normal dev tools.

    <copy file="${packaging}/JavaApplicationStub" todir="${appdir}/Contents/MacOS"/> <exec command="chmod 755 ${appdir}/Contents/MacOS/JavaApplicationStub"/>

    A quick note here: I've added the chmod command here because Ant will not preserve the executable bit. Unfortunately, Java has no concept of Unix file permissions, only the ability to read and write. So we use Ant's exectask to call out the shell command chmod. Hopefully a future version of Java (or a standard extension) will address this.

    Next we need to copy in the Info.plist file.

    <copy file="${packaging}/Info.plist" todir="${dist-mac}/${app-name}.app/Contents"/>

    The Info.plist file is an XML configuration file that completely defines an application. It tells OS X the name of the application, how to start it, which icons to use, which files it understands, and virtually every other config parameter you can imagine. To create it, we will start with a sample file provided by Apple and make a few changes. We will set the main class to ourStartup class and include thejoshy-common.jar support lib. Below is a portion of the file:

    <key>CFBundleIconFile</key> <string>GenericJavaApp.icns</string> <key>Java</key> <dict> <key>MainClass</key> <string>org.joshy.oreilly.swingnative.Startup</string> <key>JVMVersion</key> <string>1.3+</string> <key>ClassPath</key> <array> <string>$JAVAROOT/joshy-common.jar</string> </array> <key>Properties</key> <dict> <key></key> <string>MadChatter</string> <key></key> <string>true</string> </dict>

    Notice that in the <dict> element, we've also included the command line parameters discussed in the previous article to set the application name and use the main Mac menu bar.

    Now, back to our Ant file. Next we copy the application itself, which consists of our code .jars and any support library .jars.

    <copy file="${build}/${app-name}.jar" todir="${appdir}/Contents/Resources/Java"/> <copy todir="${appdir}/Contents/Resources/Java"> <fileset dir="lib"> <include name="*.jar"/> </fileset> </copy>

    The last thing we need to do is tell OS X that this directory isn't just a directory, but a real live application. We do this with another exec call to a command-line tool,setfile.

    <exec command="/Developer/Tools/SetFile -a B ${appdir}"/>

    Run the target and we get a double-clickable application, as seen in Figure 2:

    Figure 2
    Figure 2. Mac OS X Java application bundle

    Creating a Double-Clickable .exe for Windows

    On the Windows side, we have to use a completely different method because neither Microsoft nor Sun provides any tools for packaging Java apps. There are, however, several third-party tools to do these tasks. For this article, I will use a commercial tool,JexePack by DuckWare, that I've had great luck with in the past. It costs $100 for commercial use, but you can freely download a fully functional trial version.

    First we will call jexepack from our Ant file, which is a simple exec call:

    <target name="exe" depends="assemble"> <exec dir="." executable="${bin}/jexepack.exe"> <arg value="${packaging}/jexepack.ini"/> </exec> </target>

    We've stored the command-line options in an .ini file so that we can modify them easily and keep our Ant file from being cluttered. It also makes version control easier, since it's a separate text file.

    The jexepack.ini file looks like this:

    # turn on verbose output /v # make it a windowed application /w # set the class with the main() method in it /main:org.joshy.oreilly.swingnative.Startup # the output exe filename /out:dist\MadChatter.exe # include the class files, images, support .exes, and html files /dir:classes /r *.class /dir:lib /r *.jar /jar:joshy-common.jar

    This text file will create an executable in the distdirectory, setting it to be a windowed application (hiding thestdout console window), sets the main class file, and includes all of the relative classes. Note that we have to specify the .jars twice, once to make sure they are included in the final.exe (the /r *.jar part), and then once to make sure they are in the classpath and available to the application (the /jar:joshy-common.jar part). Unfortunately, the names of the .jars and class files have to be hardcoded in this text file. Hopefully DuckWare will make Ant tasks in a future version, or we could use custom tasks to edit the file in place. We won't need to change it very often, so this is okay.

    Run the exe task to produce the double-clickable.exe shown in figure 3.

    Figure 3
    Figure 3. Windows native Java executable

    Creating File Type Associations for OS X

    Now that we have icons out of the way, let's work on something a little more daring. Our chat application might want to save log files. When a user clicks on the log file, we would like our application to launch and open the file. This is typical behavior for native applications, so we should do it too. Not an easy task, but we've got some help.

    Let's say our log file ends with .mchat (which means "MadChatter log file") — we want to tell the OS to find our application and launch it when any .mchat file is clicked. That means our application needs to receive an event saying it should open the file named filename.mchat.

    On the Mac, we can use Apple's ApplicationListenerinterface to pick up the open event. The XP library introduced in the previous article handles most of this for us, but the code below shows what we need to do. It subclasses the ApplicationAdapter, which provides no-op implementations of all of the methods inApplicationListener, and overrides thehandleOpenFile() method to do our work.

    Application app = new Application(); getApplication().addApplicationListener(new ApplicationAdapter() { public void handleOpenFile(ApplicationEvent evt) { System.out.println("opening"); ChatPanel panel = Startup.newWindow(); String file = evt.getFilename(); // u is a utility class included in the XP lib String text = u.file_to_string(file); panel.textarea.setText(text); } });

    On the packaging side, we need to tell OS X about our app's desire to get certain files. Back in our Info.plist file, we add a few lines to do this:

    <key>CFBundleDocumentTypes</key> <array> <dict> <key>CFBundleTypeExtensions</key> <array> <string>mchat</string> </array> <key>CFBundleTypeName</key> <string>MadChatter Log File</string> <key>CFBundleTypeRole</key> <string>Viewer</string> </dict> </array>

    All file-type configuration is stored inside of theCFBundleDocumentTypes key. It's an array because we could have more than one document type associated with our application. Each document definition has a list of extensions, the name of the type of file, and the role. The name is the description that will be shown in the Finder. The role specifies what our application can do with it, which usually means whether we can edit or just view the files. This controls the listings in open/save dialog boxes and other desktop behavior that doesn't matter as much to us. The CFBundleDocumentTypes key can also be used to assign creator codes, set icons, and other nifty things. For more information, see Apple's Cocoa documentation on document types.

    Figure 4 shows that the Mac Finder understands the association of .mchat files to the Mad Chatter application. Double-clicking the file will now tell our application to load it.

    Figure 4
    Figure 4. OS X filetype associations

    Filetypes For Windows

    Windows doesn't have a clean text-file way of setting filetype associations. Instead, we will have to muck with the registry, which isn't the easiest thing to do. There are no standard APIs to help, but there are some options. We will start with an open source JNI library called JNIRegistry, created by Tim Endres and ICE Engineering. Unfortunately, it's not terribly well documented, and again, Windows registry hacking isn't easy for programmers used to the very open and structured Java world. Being a Java programmer, I had no idea where to start. After a few minutes of Googling, however, I did find some code in thejtorrent CVS tree on Source Forge that uses JNIRegistry to do exactly the same thing we want to do: associate our program with a file extension.

    The Windows registry is essentially a tree of nested keys. To view, edit, or delete keys you have to find them and then manipulate them. The keys we want all start in the top level key ofHKCR. This is the HKEY_CLASSES_ROOTportion of the registry database, which contains "file extensions associations, CLSIDs file and object types." (More information can be found here.)

    String topLevelKey = "HKCR"; RegistryKey topKey = Registry.getTopLevelKey(topLevelKey);

    Now we want to create a new key to hold our new values:

    RegistryKey localKey = topKey.createSubKey(key, value, RegistryKey.ACCESS_WRITE);

    The value itself has to be held in another structure, theRegStringValue:

    RegStringValue val = new RegStringValue(localKey, valueName, value);

    Now we can set the value and save the key:

    localKey.setValue(val); localKey.flushKey();

    I've wrapped all of this up in a static function calledsetKey, which takes a key, value name, and value. The key is the actual name of the key we want to set. The value name specifies which value in this key we want to set. In our case, we use "" for the default value name. The actual value is the string we are setting. (It is also possible to put numbers and binary data into the registry, but that is beyond the scope of this article.) A full list of the available Windows keys can be found here.

    Now that we have the ability to set a key, what keys do we want to set? First we need to tell Windows that the file extension.mchat is associated with our application:


    Then we need to set the description of MadChatterfiles:

    setKey("MadChatter","","MadChatter logfile");

    Now Windows recognizes .mchat files, but it doesn't know what to do with them. Let's tell it we can open them and what to open them with:

    String command = exeDir() + "\\MadChatter.exe \"%1\""; setKey("MadChatter\\shell","","open") setKey("MadChatter\\shell\\open\command","",command)

    The first setKey() says we know how to open files and the second gives the command-line program that will do it. The command is three parts: the location of our .exe, the.exe itself, and the place where the path of the requested file will be stored. The first part, exeDir(), is a utility function that retrieves the jexepack.exeproperty. JexePack, our Windows packaging program, stores the directory of our .exe here. If jexepack.exe is missing, then exeDir() will return the current working directory instead.

    I've wrapped all of this code into a single static utility function, RegTest.setAppString(), which takes the filename extension, the command string, the name of the program, and the description of the filetype. It will be called in the main function at program startup, which ensures that the keys are always set to the right values.

    public static final String EXTENSION = "mchat"; [...] public void main(String[] args) { [...] // if on windows then run the registry code if(!xp.isMac()) { String command = exeDir() + "\\MadChatter.exe \"%1\""; RegTest.setAppString("."+Startup.EXTENSION,command, "madchatter","MadChatter logfile"); }

    That's all we need to tell Windows about our app without using an installer. This strategy has a limitation, however. If the user ever moves our program, the filetype association will be broken until our program is run again. This is really a limitation of the Windows filesystem, because unlike the Mac's HFS, there is no path-independent way of finding a file on the hard drive. Usually, however, people just put the application in one place and never move it again, so we should be pretty safe.

    To run it, we have to load up the JNIRegistry libraries. We can add the .jar file (registry.jar) to our build.xmleasily enough, but dealing with the .dll is trickier. It turns out that native libraries are loaded via the system path, which we'd rather not mess with. However, the system path already includes the current directory, so if we just put the .dlls there we are all set. So we copy the .dll from the lib dir to the working dir in our build file.

    <target name="run" depends="compile"> <copy file="lib/ICE_JNIRegistry.dll" todir="."/> <java fork="true" classpath= "${classes};lib/joshy-common.jar;lib/registry.jar" classname= "org.joshy.oreilly.swingnative.Startup"/> </target>

    Now this is fine for testing from the command line, but what about our generated .exe? Well, JexePack has already thought of that. As long as we put the .dll and extra .jars in the build set and add the /jni option, JexePack will take care of the rest. So we add few more lines to ourjexepack.ini file:

    /jar:registry.jar /jni ICE_JNIRegistry.dll

    Run ant win-dist and we are ready to go. File Explorer recognizes our application type and will load it when the user double clicks on .mchat files, as illustrated by Figure 5:

    Figure 5
    Figure 5. Windows filetype associations


    Thank you for joining me for the second part of my series on making Swing applications feel native. Last time, we worked on the menus and added alerts. This time, we bundled our program into real executables and added filetype associations. Mad Chatter is starting to feel like a real application now. I hope you will join me for the final article, where we will create custom icons, a splash screen, and a few more bits of polish to make The Mad Chatter really shine.

    Editor's Note: Downloadable sample code for this article and others in the series will be provided at the conclusion of the series. You can also participate in the Mad Chatter project, part of's Java Desktopcommunity.