Class-Path Wildcards in Mustang
By mr on Feb 15, 2006
Most non-trivial Java programs depend upon external libraries in the form of jar ﬁles. Tools like Ant make it easier to cope with large collections of jar ﬁles at build time, but at runtime one often has to resort to passing a class path to the VM which explicitly names each and every jar ﬁle needed by the application. Such class paths are typically baked into a launch script or .BAT ﬁle, like so:
#! /bin/bash LIB=$(dirname $0)/../lib CP=$LIB/foo.jar:$LIB/bar.jar:$LIB/baz.jar java -classpath $CP com.xyzzy.app.Main
This is fragile from a maintenance standpoint, of course, since you have to remember to update the script every time you add or remove a jar ﬁle.
A more ﬂexible solution can be had at the cost of writing a more complex launcher that carefully ﬁgures out where all of the jar ﬁles for your program reside and constructs the required classpath. This can pretty quickly get out of hand, however, as witnessed by, e.g., the NetBeans 5.0 launch script for Unix or—worse—the equivalent C++ code for Windows.
Mustang addresses this annoying problem with the introduction of class-path wildcards. The basic idea is very simple: Instead of listing individual jar ﬁles in a directory, as in the example above, you can instead just use an asterisk to stand for all of the jar ﬁles that can be found in that directory:
java -classpath $LIB/'\*' com.xyzzy.app.Main
Note that you must quote the asterisk in order to prevent it from being interpreted by your shell and expanded into something that’s guaranteed not to work if there’s more than one jar ﬁle in the directory. When designing this feature we considered various other syntaxes that wouldn’t require quoting. In the end, however, we decided to go with the asterisk since it’s already familiar to Ant users and we ﬁgured that anyone clever enough to use class-path wildcards would also be clever enough to understand shell quotation.
So how exactly do class-path wildcards work?
Expansion of wildcards is done early, prior to the invocation of a program’s main method, rather than late, during the class-loading process itself. This simpliﬁes both the semantics and the implementation and is also the most compatible approach. Each element of the input class path containing a wildcard is replaced by the (possibly empty) sequence of elements generated by enumerating the jar ﬁles in the named directory. If the directory foo contains a.jar, b.jar, and c.jar, e.g., then the class path foo/\* is expanded into foo/a.jar:foo/b.jar:foo/c.jar, and that string will be the value of the system property java.class.path.
The order in which the jar ﬁles in a directory are enumerated in the expanded class path is not speciﬁed and may vary from platform to platform and even from moment to moment on the same machine. A well-constructed application shouldn’t depend upon any particular order; if a speciﬁc order is required then the jar ﬁles must be enumerated explicitly in the class path.
Subdirectories are not searched recursively, i.e., foo/\* only looks for jar ﬁles in foo, not in foo/bar, foo/baz, etc. There’s no equivalent to Ant’s /\*\* construct, though that could be added later on.
A wildcard only matches jar ﬁles, not class ﬁles that happen to be in the same directory. If you want to load both class ﬁles and jar ﬁles from a single directory foo then you can say foo:foo/\*, or foo/\*:foo if you want the jar ﬁles to take precedence.
Class-path wildcards work in the -classpath (equiv. -cp) command-line option to the JVM launcher and most other command-line tools, and also in the CLASSPATH environment variable. They don’t work in in the Class-Path header of a jar-ﬁle manifest, nor do they work in the bootstrap class path (but you’d never use that anyway, right?).
Finally, class-path wildcards are an implementation-speciﬁc feature. They’re supported in Sun’s Mustang implementations, and they may be available in other implementations. As a feature of command-line tools, however, they’re not part of the Platform Speciﬁcation, so there’s nothing that requires every implementation of Java SE 6 to support them.