Accessing Resources in a Location Independent Manner

Motivation

Applets, Beans, Applications, and Libraries often have resource files associated with them. These Applets, Beans, and so on, want to use these files in a way that is independent of where these Applets, et al., are located.

Existing code in JDK1.0.x uses two types of mechanisms. The first mechanism is used in applets. Applet.getCodeBase() returns a URL to the base of the code for the applet. This base can be extended with a "relative path" to point to the desired resource, which can then be loaded (for example using Applet.getAudioClip(url)). The second mechanism is used by applications. Applications use "well known locations" (such as System.getProperty("user.home") or System.getProperty("java.home")). They add "/lib/" to the location and then open that file.

Untiil now, JDK1.0.x has not had a mechanism to locate resources that are independent of the code. That is, JDK1.0.x. has not had the means to locate resources for an applet loaded from the net using multiple http connects, or for an applet loaded using JAR files, or for a Bean loaded or a Bean installed in the CLASSPATH, or for a "library" installed in the CLASSPATH, and so on. The APIs described here provide such a mechanism.

The I18N APIs use this API as a primitive operation to locate ResourceBundles. See the latest I18N documentation (when available) for details.

Resources, names, and contexts

A resource is identified by a String. This String, while possibly empty, is a /-separated sequence of substrings, each a valid Java Identifier, followed by a name of the form "<shortName>" or "<shortName>.<extension>". Both "shortName" and "extension" are composed of valid Java Letters and Numbers (section 3.8 in JLS). If the optional sequence exists, it is separated from the "shortName" by a /.

The name of a resource is independent of the Java implementation; in particular, the / is always used as a separator. However, the Java implementation controls the details of how the contents of the resource are mapped into a file, database, or other object containing the actual resource.

The interpretation of a resource name is relative to a class instance. Methods implemented by the ClassLoader do this interpretation. Since the ClassLoader knows the actual origin of a given class, the ClassLoader can locate the contents of the requested resource.

System Resources

A system resource is similar to a system class (section 20.14.5 of the JLS). A system resource is a resource that is either built-in to the system, or it is kept by the host implementation in, for example, a local file system. System resources are accessed through special methods (getSystemResourceAsName and getSystemResourceAsStream) that consult the base host implementation.

For example, in a particular implementation, locating a system resource may involve searching the entries in the CLASSPATH. Each directory, zip file, or jar file entry in the CLASSPATH is searched for the resource file, and, if found, either an InputStream, or its name, is returned. If not found, null is returned. Note that a resource may be found in a different entry in the CLASSPATH than where the class file was loaded.

Non-System Resources

The implementation of getResource on a given ClassLoader will depend on the details of the ClassLoader. For example AppletClassLoader will:

Most ClassLoaders, and AppletClassLoader in particular, will search for a resource first as a system resource, in a manner analogous to searcing for class files. This search rule permits overwriting locally any resource. Clients should choose a resource name that will be unique (using the company or package name as a prefix, for instance).

Resource Names

A common convention for the name of a resource used by a class is to use the fully qualified name of the package of the class, convert all "." to "/", and add a resource name of the form "<Name>.<ext>". To support this, and to simplify handling the details of system classes (for which getClassLoader returns null), the class Class provides two convenience methods that call the appropriate methods in ClassLoader.

URL Name of a Resource

The method getResourceAsName() returns a String that is the external representation of a URL for the resource. The URL (and its representation) is implementation-specific and may vary depending on the implementation details. Its protocol is (usually) specific to the ClassLoader loading the resource.

We have (initially?) chosen to return the external representation instead of the real URL because URL is in java.net. This may require more thought.

API Additions to Class

Specifically, the class Class methods are of the following form:


class Class {

    /**
     * Find a resource with a given name.  Will return null if no
     * resource with this name is found.  The rules for searching a
     * resources associated with a given class are implemented by the
     * ClassLoader of the class.
     *
     * @see java.lang.ClassLoader
     */
    public InputStream getResourceAsStream(String name) {
	// handle '/' conventions -- not implemented in alpha's snapshot
	if ((name != null) && !name.startsWith("/")) {
	    while (c.isArray()) {
		c = c.getComponentType();
	    }
	    String baseName = c.getName();
	    String pkgName = baseName.substring(0, baseName.lastIndexOf('.'));
	    if (pkgName != null) {
		name = pkgName.replace('.', '/')+"/"+name;
	    }
	}
	// end of '/' conventions
	ClassLoader cl = getClassLoader();
	if (cl==null) {
	    // A system class.
	    return ClassLoader.getSystemResourceAsStream(name);
	}
	return cl.getResourceAsStream(name);
    }

    public String getResourceAsName(String name) {
	// handle '/' conventions -- not implemented in alpha's snapshot
	if ((name != null) && !name.startsWith("/")) {
	    while (c.isArray()) {
		c = c.getComponentType();
	    }
	    String baseName = c.getName();
	    String pkgName = baseName.substring(0, baseName.lastIndexOf('.'));
	    if (pkgName != null) {
		name = pkgName.replace('.', '/')+"/"+name;
	    }
	}
	// end of '/' conventions
	ClassLoader cl = getClassLoader();
	if (cl==null) {
	    // A system class.
	    return ClassLoader.getSystemResourceAsName(name);
	}
	return cl.getResourceAsName(name);
    }


Note that it is possible, albeit somewhat uncommon, to have two classes in two diffent packages sharing the same resource.

API Additions to ClassLoader

We provide two sets of methods to access a resource. One set returns an InputStream on the resource. The other set returns a String with the textual representation of a URL. The methods that return an InputStream are somewhat easier to use and will satisfy many needs, while the methods that return names of URLs provide access to more complex information, such as an Image and an AudioClip.

Resources are managed through ClassLoaders in a manner analogous to classes. A ClassLoader controls how to map the name of a resource to its content. ClassLoader also provides methods for accessing system resources, analogous to the system classes (and which have no ClassLoader in JDK1.0.x and JDK1.1). Class Class provides some convenience methods that delegate functionality to the appropriate ClassLoader methods.

Many Java programs will access these methods indirectly through the I18N APIs. Others will access it through methods in class Class. A few will directly invoke the ClassLoader methods.


class ClassLoader {

    /**
     * Find a resource with a given name.  Will return null if no
     * resource with this name is found. 
     * The resource name may be any system resource (e.g. follows CLASSPATH order)
     */
    public static final InputStream getSystemResourceAsStream(String name) {
	...
    }

    /**
     * Find a resource with a given name.  Will return null if no
     * resource with this name is found. 
     * The resource name may be any system resource (e.g. follows CLASSPATH order)
     */
    public static final String getSystemResourceAsName(String name) {
	...
    }

    /**
     * Locate a resource relative to the specific class loader used
     */

    public InputStream getResourceAsStream(String name) {
	// REMIND -- Need to settle error conditions.
	return null;
    }

    public String getResourceAsName(String name) {
	// REMIND -- Need to settle error conditions.
	return null;
    }

    }

Client code

Below are two examples of client code. The first example uses "absolute resource" names and traditional mechanisms to get a class Class object:


package pkg;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;

class Test {

    private static final String absName = "/pkg/mumble.baf";

    public static void test1() {
	Class c=null;
	try {
	    c = Class.forName("pkg.Test");
	} catch (Exception ex) {
	    // This should not happen.
	}
	InputStream s = c.getResourceAsStream(absName);
	// do something with it.
    }

    public void test2() {
	InputStream s = this.getClass().getResourceAsStream(absName);
	// do something with it.
    }


The second example uses "relative resource" names and the new mechanism, available from the compiler through the -experimental flag, to get a class Class object:


package pkg;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;

class Test {

    private static final String relName = "mumble.baf";

    public static void test1() {
	InputStream s = Test.class.getResourceAsStream(relName);
	// do something with it.
    }

    public void test2() {
	InputStream s = Test.class.getResourceAsStream(relName);
	// do something with it.
    }


Issues & Known Bugs

There are a few issues related to this topic. Most issues and bugs have been resolved. However, we may change the return type of getResourceAsName to return a URL.

JDK1.1.alpha does not implement the rules for resource names starting with '/'.

JDK1.1.alpha does not support names (getResourceAsName()) of system resources.


Eduardo Pelegrí-Llopart (pelegri@eng.sun.com). Last update November 29th, 1994.