Reading Resources from JAR Files
JAR files aren't just for classes. Any other resource a program needs can be stored there as well: sounds, pictures, text files, property lists, and more. They can be read using the same input streams and output streams we've been talking about since Chapter 1. The difference is that instead of getting those streams using constructors connected to the filesystem, you ask a ClassLoader to find them for you.
For example, my XOM class library (http://www.xom.nu) needs a file called characters.dat to operate. This file is a 64K lookup table of bit flags XOM consults to test whether various characters are XML name characters, XML name start characters, and so forth. The file contains just raw binary data, not a Java class, a serialized object file, or anything else fancy like that.
This lookup table could be distributed as a separate file along with XOM's JAR archive, but then I'd have to worry about it getting moved from where XOM expected to find it. More likely, the xom.jar archive would get copied somewhere else but someone would forget to copy characters.dat at the same time. Instead, I bundle it along with the rest of XOM's classes in the nu/xom directory, along with all the classes in the nu.xom package. This way, when I load the lookup table, I'm reasonably certain it'll still be there.
Loading characters.dat requires first finding a ClassLoader. The simplest way is to ask the class that needs the data for the loader that loaded it. For example, in XOM's case, this is the Verifier class:
ClassLoader loader = Verifier.class.getClassLoader( );
Most of the time that's all you need. However, if that ClassLoader doesn't find the resource, you can ask the current thread for its loader instead:
loader = Thread.currentThread().getContextClassLoader( );
Once you have a ClassLoader in hand, the geTResourceAsStream( ) method will give you an input stream that reads from that JAR entry:
public InputStream getResourceAsStream(String path)
Pass this method the path to the resource inside the JAR archive starting from the root of the JAR. For example, XOM looks for nu/xom/characters.dat. This requests the resource named characters.dat located in the xom subdirectory of the nu directory at the top level of the JAR archive.
More accurately, it looks for one such resource found in some JAR file or top-level directory somewhere in the classpath. Theoretically, there could be more than one. The loader looks through all the classpath entries until it finds one that matches the path it's looking for. It then returns an InputStream from which you can read the resource's data. Like other input streams, you don't need to worry excessively about where the stream comes from. You read this stream just like you'd read a stream from a file or a network connection. In XOM's case, the InputStream is first chained to a DataInputStream that stores the entire file in a byte array for later random access:
InputStream raw = loader.getResourceAsStream("nu/xom/characters.dat"); DataInputStream in = new DataInputStream(raw); byte[] flags = new byte[65536]; in.readFully(flags);
You could do other things with this stream, of course; that's just what XOM happens to need to do.
For robustness, a little error checking never hurts. After all, someone could have unzipped the JAR file and moved the pieces around, or built her own copy of XOM without using my Ant build.xml file and left out characters.dat. It's unlikely, but it has been known to happen, so XOM tests for it. If there is a problem, there's not a lot XOM can do to fix it, so it throws a RuntimeException and gives up:
DataInputStream in = null; try { InputStream raw = loader.getResourceAsStream("nu/xom/characters.dat"); if (raw == null) { throw new RuntimeException("Broken XOM installation: " + "could not load nu/xom/characters.dat"); } in = new DataInputStream(raw); flags = new byte[65536]; in.readFully(flags); } catch (IOException ex) { throw new RuntimeException("Broken XOM installation: " + "could not load nu/xom/characters.dat"); } finally { try { if (in != null) in.close( ); } catch (IOException ex) { // no big deal } }
Several other methods in ClassLoader also find data (as opposed to code) resources stored in the classpath. The getresource( ) method returns a URL for the resource (or null if the loader can't find the resource) instead of an InputStream:
public URL getResource(String name)
You can then open a stream from that URL using the usual methods of the URL class:
URL resource = loader.getResource("nu/xom/characters.dat"); InputStream in = resource.openStream( );
Normally, I prefer to use getresourceAsStream( ) and skip the intermediate URL. However, this can be useful if you need to repeatedly access the same resource, since you can create many different streams from the same URL.
If you want all copies of a resource in the classpath, rather than just the first one, getresources( ) returns an Enumeration containing URL objects for each matching resource:
public Enumeration getResources(String name)
Three static methods, getSystemResource( ), getSystemResources( ), and getSystemResourceAsStream( ), behave the same, except that they always search using the system ClassLoader:
public static InputStream getSystemResourceAsStream(String path) public static URL getSystemResource(String name) public static Enumeration getSystemResources(String name)
These methods are occasionally useful in a multiclassloader environment such as a servlet container.