20100215

Java and getResource()

I'm posting this so that somebody else might find it useful, and I found my answer here, just for reference.

When designing Java code that is to run both as jar-applet and as an application, and you need to load some file (image, text, whatever) that lives with your code (e.g. from the same jar file, or when you're debugging with Eclipse) it's necessary to use getResource() while ensuring that your resource lies in the classpath.

However, ensuring that the resource is in your classpath is not enough - by any means.

Here's a simple example where getResource() does not work as expected. if I want to read in a text file stored in "resource/text/Hello.txt" and if "resource" is in my classpath, then I should use something like


import java.io.*;
import java.net.*;


public class Hello {
public static void main(String[] args) {
URL url = getClass().getResource( "text/Hello.txt" );
System.out.println( System.getProperty( "java.class.path" ) );
try {
BufferedInputStream bis = new BufferedInputStream( url.openStream() );
byte[] buffer = new byte[16];
int br = -1;
br = bis.read( buffer );
while( br != -1 ) {
System.out.println( (new String( buffer )).trim() );
br = bis.read( buffer );
}
} catch( Exception e ) {
e.printStackTrace();
}
}
}

Run the above example with

java -cp resource Hello

However, this just fails miserably when the Hello class is not part of the default package. Just try inserting "package some.random.pkg;" at the top of Hello.java and see what happens. The reasoning for this is slightly backwards if you ask me. The ClassLoader that Hello.class references does not have the visibility to load eternal resources, and I'm not sure if it has the ability to load a resource in it's own package for that matter. What you'll see is that url is always null when you use getClass().getResource() when Hello is in a non-default package.

According to Class.getResource() API, this shouldn't actually happen, since it's only specified to look in the classpath.

The only way that I have found to actually load a resource is to use the following:


package some.random.pkg;
import java.io.*;
import java.net.*;

public class Hello {
public static void main(String[] args) {
URL url = Thread.currentThread().getContextClassLoader().getResource( "Hello.txt" );
System.out.println( System.getProperty( "java.class.path" ) );
try {
BufferedInputStream bis = new BufferedInputStream( url.openStream() );
byte[] buffer = new byte[16];
int br = -1;
br = bis.read( buffer );
while( br != -1 ) {
System.out.println( (new String( buffer )).trim() );
br = bis.read( buffer );
}
} catch( Exception e ) {
e.printStackTrace();
}
}
}

Run the above example with

java -cp .:resource some.random.pkg.Hello


So, I'm not exactly sure what the point of the ClassLoader visibility braindamage is, but it seems fairly pointless to me. At least there's a workaround.

1 comment:

Christopher Friedt said...

Also, for anyone trying to load an image using this method, via ImageIO, make sure that you use ImageIO.setUseCache(false) otherwise, the plugin will attempt to buffer the image to a file and it will fail.