Wednesday, 9 April 2008

Java: finding the application directory

EDIT 2009/05/28: It has been pointed out to me that a far easier way to do all this is using this method:

    URL url = Location.class.getProtectionDomain()
        .getCodeSource().getLocation();

...which makes everything below here pointless. You live and learn!


If you are writing a rich client application, you may want to find the place where the system owner has dumped your files. You might want to search the application directory for a configuration file, load plugins, etc. In a C application, you can get the launch command from the first entry in the argument array:

#include <stdio.h>

int main(int argc, char **argv)
{
  if(argc>0) {
    printf ("%s\n", argv[0]);
  }
  return (0);
}


This gives something that can be parsed to get the directory:
..\tempc\printLoc.exe
Java doesn't support such a mechanism directly, though your application launcher could be designed to pass this information as an argument.

Finding the Application Directory in Java

There is a pure Java trick that can be used to locate the application. You can use the ClassLoader.

  /**
   * Returns the URL of a given class.
   @param c    a non-null class
   @return    the URL for that class
   */
  public static URL getUrlOfClass(Class c) {
    if(c==null) {
      throw new NullPointerException();
    }
    String className = c.getName();
    String resourceName = className.replace('.''/'".class";
    ClassLoader classLoader = c.getClassLoader();
    if(classLoader==null) {
      classLoader = ClassLoader.getSystemClassLoader();
    }
    URL url = classLoader.getResource(resourceName);
    return url;
  }


The above code might return the following URLs:
jar:file:/C:/dev/IBM-jre/jre-j91.4.2/lib/core.jar!/java/lang/String.class
file:/C:/Documents%20and%20Settings/User/workspace-web/Locator/bin/test/LocateTest.class
It should be obvious that these can be parsed to get java.io.File objects.

    String szUrl = url.toString();
    if(szUrl.startsWith("jar:file:")) {
      try {
        szUrl = szUrl.substring("jar:".length(), szUrl.lastIndexOf("!"));
        URI uri = new URI(szUrl);
        return new File(uri);
      catch(URISyntaxException e) {
        throw new IOException(e.toString());
      }
    else if(szUrl.startsWith("file:")) {
      try {
        szUrl = szUrl.substring(0, szUrl.length() - resourceName.length());
        URI uri = new URI(szUrl);
        return new File(uri);
      catch(URISyntaxException e) {
        throw new IOException(e.toString());
      }
    }


Caveats

J(2)EE containers support complex ClassLoader higherarchies. If a library was in the container and a web application, the class location that is returned might become unpredictable or configuration dependent. Some environments, like the Eclipse platform, do not rely on filesystem-based URIs and will not return useful values. It is possible for libraries to be loaded remotely via HTTP or some other network protocol (see URLClassLoader).

This technique works best when it is kept 'close' to the development of the application launcher. It should not be relied on in general APIs.

I don't have a *nix machine to hand, but the code should work across platforms.

Full Listing for Locate.java

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;

/**@author McDowell*/
public class Locate {
  
  /**
   * Returns the URL of a given class.
   @param c    a non-null class
   @return    the URL for that class
   */
  public static URL getUrlOfClass(Class c) {
    if(c==null) {
      throw new NullPointerException();
    }
    String className = c.getName();
    String resourceName = className.replace('.''/'".class";
    ClassLoader classLoader = c.getClassLoader();
    if(classLoader==null) {
      classLoader = ClassLoader.getSystemClassLoader();
    }
    URL url = classLoader.getResource(resourceName);
    return url;
  }
  
  /**
   * Finds the location of a given class file on the file system.
   * Throws an IOException if the class cannot be found.
   <br>
   * If the class is in an archive (JAR, ZIP), then the returned object
   * will point to the archive file.
   <br>
   * If the class is in a directory, the base directory will be returned
   * with the package directory removed.
   <br>
   * The <code>File.isDirectory()</code> method can be used to
   * determine which is the case.
   <br>
   @param c    a given class
   @return    a File object
   @throws IOException
   */
  public static File getClassLocation(Class cthrows IOException, FileNotFoundException {
    if(c==null) {
      throw new NullPointerException();
    }
    
    String className = c.getName();
    String resourceName = className.replace('.''/'".class";
    ClassLoader classLoader = c.getClassLoader();
    if(classLoader==null) {
      classLoader = ClassLoader.getSystemClassLoader();
    }
    URL url = classLoader.getResource(resourceName);
    
    String szUrl = url.toString();
    if(szUrl.startsWith("jar:file:")) {
      try {
        szUrl = szUrl.substring("jar:".length(), szUrl.lastIndexOf("!"));
        URI uri = new URI(szUrl);
        return new File(uri);
      catch(URISyntaxException e) {
        throw new IOException(e.toString());
      }
    else if(szUrl.startsWith("file:")) {
      try {
        szUrl = szUrl.substring(0, szUrl.length() - resourceName.length());
        URI uri = new URI(szUrl);
        return new File(uri);
      catch(URISyntaxException e) {
        throw new IOException(e.toString());
      }
    }
    
    throw new FileNotFoundException(szUrl);
  }
  
}


JUnit Test Case

import java.io.File;
import java.io.IOException;
import java.net.URL;

import junit.framework.TestCase;

public class LocateTest extends TestCase {

  public void testGetUrlOfString() {
    URL url = Locate.getUrlOfClass(String.class);
    System.out.println(url);
    assertNotNull(url);
  }
  
  public void testGetUrlOfThis() {
    URL url = Locate.getUrlOfClass(this.getClass());
    System.out.println(url);
    assertNotNull(url);
  }
  
  public void testGetStringLocation() {
    try {
      File file = Locate.getClassLocation(String.class);
      System.out.println(file);
      assertTrue(file.exists());
      assertTrue(file.isFile());
    catch(IOException e) {
      e.printStackTrace();
      fail(e.toString());
    }
  }

  public void testGetThisLocation() {
    //assumes you're running the class from an un-jarred
    //test environment
    try {
      File file = Locate.getClassLocation(this.getClass());
      System.out.println(file);
      assertTrue(file.exists());
      assertTrue(file.isDirectory());
    catch(IOException e) {
      e.printStackTrace();
      fail(e.toString());
    }
  }
  
}

5 comments:

  1. Excellent piece of code and does the job - unlike a lot of other snippets on the net. I've tested it on Windows XP, Vista and Mac OSX with Java 1.5 and 1.6.

    Thanks

    ReplyDelete
  2. Thanks for the feedback, particularly regarding OS X. Since I posted this, I did test it on a couple of Linux distros too.

    ReplyDelete
  3. Re:
    URL url = Location.class.getProtectionDomain()
    .getCodeSource().getLocation();

    What is Location ? Not known by Java!

    ReplyDelete
  4. "Location" is the class you want to find; substitute in one of your own types.

    If you are calling the code in an instance, use: this.getClass().getProtectionDomain().getCodeSource().getLocation();

    ReplyDelete
  5. Well, in my case getClass().getProtectionDomain().getCodeSource().getLocation() returned "rsrc:./". Not too useful.

    ReplyDelete

All comments are moderated