WhereAmI: Locating the installation directory of your Java application

Know what I really hate about Java? This half-assed attempt at establishing a virtual filesystem. On one hand, you have the java.io package by which you can access the native filesystem of the host OS, on the other, you are suppose to load every application resource via ClassLoader.findResource(). Got a resource, you can't reasonably bundle in a JAR file (e.g. because you have to update/modify it occasionally)? Tough luck! Java will happily tell you where the user's home directory is, it will even tell you tons of stuff, you should not be interested in at all (like who your VM vendor is - it's the job of the TCK to ensure that all virtual machine implementations are alike), but it will not tell you where the application resides on the local disk.

I can understand that this whole virtual filesystem mess was needed because of Microsoft sticking to FAT way past it's prime and because this whole applet nonsense, no one ever really used, did not even have a concept of a local disk. This, however, does nothing to the fact, that not knowing where your application is installed, is a pain in the ass.

Ok, enough of the ranting. Is there a reliable way of worming the application installation directory out of the Java Virtual Machine? The best solution, I have come up with (and that works regardless of how many jars your software comes bundled in) looks like the following:

import java.io.*;
import java.net.*;
 
public class WhereAmI {
 
  /**
   * Locate the application on the filesystem
   * @param clazz the class, containing the main() method of the application
   * @return The directory in which the application is installed.
   */
  public static File whereAmI(Class clazz) throws IOException, MalformedURLException {
    // Java Applications can come bundled in any number (including none) 
    // of JAR files, so we have to inspect each entry in the classpath whether
    // or not it leads to the class.
    String[] lst = System.getProperty("java.class.path").split(System.getProperty("path.separator"));
 
    File userdir = new File(System.getProperty("user.dir"));
    File ret = null;
 
    if (lst.length==0) {
      // If the classpath is empty, than the app must be installed relative to
      // the current directory.
      ret=userdir;
    }
    else {
      for (String s : lst) {
        File file = new File(s).getCanonicalFile();
        URL[] url = {file.toURI().toURL()};
        String name = clazz.getName().replace('.','/')+".class";
        URLClassLoader loader = new URLClassLoader(url);
        if (loader.findResource(name)!=null) {
          // We found the classpath component from which the class can be accessed
          if (file.isDirectory()) {
            // The application is installed as loose classfiles.
            ret=file;
          }
          else {
            // The application comes in JAR files.
            ret = file.getParentFile();
          }
          break;
        }
      }
    }
    return ret;
  }
}

This, of course, fails for applets, but I guess, no one has the illusions any more that applets can be developed in the same way, applications can.

Update: Figures, as soon as you decide to let the world know about your grandiose solution, you find a better one a few hours later:

import java.io.*;
import java.net.*;
public class WhereAmI {
  
  /**
   * Get the installation directory of the application
   * @param clazz the class that contains the main() method
   * @return The installation directory of the application.
   */
  public static File whereAmI(Class clazz) {
    URL url = clazz.getProtectionDomain().getCodeSource().getLocation();
    File file = null;
    try {
      file  = new File(url.toURI());
    }
    catch (URISyntaxException e) {
      // Let's trust the JDK to get it rigth.
    }
 
    if (file.isDirectory()) {
      // Application consists of loose class files
      return file;
    }
    else {
      // Application is packaged in a JAR file
      return file.getParentFile();
    }
  }
}

Thanks Sun/Oracle for burying this so deep in the API and putting the amulet of Yendor right on top of it.