Helping ordinary people create extraordinary websites!
GET OUR NEWSLETTER
Your Email:
 

Simplify Your Application Delivery with One-JAR

By P. Simon Tuffs
2005-04-22


Loading Resources

Loading Resources
During the development of One-JAR, findClass was the first thing I got working as a proof of concept. But when I started to deploy more complex applications I found I had to deal with loading resources as well as classes. This is where things got slippery. Casting about for a suitable method in ClassLoader to override in order to lookup resources, I picked the one with which I was most familiar, shown in Listing 3:

Listing 3. The getResourceAsStream() method

public InputStream getResourceAsStream(String name) {

URL url = getResource(name);
try {
return url != null ? url.openStream() : null;
} catch (IOException e) {
return null;
}
}


Alarm bells should have been sounding at this point: I simply couldn't understand why URLs were being used to locate the resources. So I ignored this implementation and inserted my own, shown in Listing 4:

Listing 4. One-JAR's implementation of getResourceAsStream()

public InputStream getResourceAsStream(String resource) {

byte bytes[] = null;
ByteCode bytecode = (ByteCode)byteCode.get(resource);
if (bytecode != null) {
bytes = bytecode.bytes;
}
...
if (bytes != null) {
return new ByteArrayInputStream(bytes);
}
...
return null;
}



One last hurdle
My new implementation of the getResourceAsStream() method seemed to do the trick, until I tried to One-JAR an application that loaded a resource using the URL url = object.getClass().getClassLoader().getResource() pattern; at which point things fell apart. Why? Because the URLs returned by the default implementation of ClassLoader are null, which broke the callers code.

At this point things started to get really confusing. I had to figure out what URL should be used to refer to a resource inside a JAR file in the lib/ directory. Would it be something like jar:file:main.jar!lib/a.jar!com.a.A.resource?

I tried all the possible combinations I could think of, none of which worked. The jar: syntax simply doesn't support nested JAR files, which left me facing an apparent dead-end to the whole One-JAR approach. While most applications don't seem to use ClassLoader.getResource some definitely do, and I wasn't happy with an exclusion that said "If your application uses ClassLoader.getResource() you can't use One-JAR."

And finally, a solution ...!
While I was trying to figure out the jar: syntax, I stumbled onto the mechanism by which the Java Runtime Environment maps URL prefixes to handlers. This was the clue I needed to fix the findResource problem: I would simply invent my own protocol prefix called onejar:. I could then map the new prefix to a protocol handler, which would return the byte stream for the resource, as shown in Listing 5. Note that Listing 5 represents code in two files, the JarClassLoader and a new file called com/simontuffs/onejar/Handler.java.

Listing 5. findResource and the onejar: protocol

com/simontuffs/onejar/JarClassLoader.java


protected URL findResource(String $resource) {
try {
// resolve($resource) returns the name of a resource in the
// byteCode Map if it is known to this classloader.
String resource = resolve($resource);
if (resource != null) {
// We know how to handle it.
return new URL(Handler.PROTOCOL + ":" + resource);
}
return null;
} catch (MalformedURLException mux) {
WARNING("unable to locate " + $resource + " due to " + mux);
}
return null;
}

com/simontuffs/onejar/Handler.java

package com.simontuffs.onejar;
...
public class Handler extends URLStreamHandler {
/ **
* This protocol name must match the name of the package in which this class
* lives.
*/
public static String PROTOCOL = "onejar";
protected int len = PROTOCOL.length()+1;

protected URLConnection openConnection(URL u) throws IOException {
final String resource = u.toString().substring(len);
return new URLConnection(u) {
public void connect() {
}
public InputStream getInputStream() {
// Use the Boot classloader to get the resource. There
// is only one per one-jar.
JarClassLoader cl = Boot.getClassLoader();
return cl.getByteStream(resource);
}
};
}
}


Tutorial Pages:
» Power Programming with Custom Classloaders
» Overview of One-JAR
» Problems and solutions
» Enter the JarClassLoader
» Loading Resources
» Bootstrapping the JarClassLoader
» In Conclusion
» Resources


First published by IBM DeveloperWorks


 | Bookmark
Related Tutorials:
» All about JAXP, Part 1
» Make Database Queries Without the Database
» Load List Values for Improved Efficiency
» 2 Ways To Implement Session Tracking
» A Simple Way to Read an XML File in Java
» Develop Aspect-Oriented Java Applications with Eclipse and AJDT

Advertise with Us!


Tutorials Scripts Web Hosting Developer Manuals
Resources