|
The Java Specialists' Newsletter
Issue 037 2001-12-13
Category:
Software Engineering
Java version: Checking that your classpath is validby Sydney Redelinghuys
Welcome to the 37th edition of "The Java(tm) Specialists'
Newsletter", sent to 2103 Java experts in 62 countries. This
week, my friend Sydney Redelinghuys
has stepped in and provided the ideas for this
newsletter, as well as most of the writing. Sydney was the brain
behind the SoftHashMap newsletter that also made it onto DevX
.
I consider Sydney to be one of the best Java programmer that I know
and I marvel at the designs Sydney comes up with, I don't know
anyone with such a knack for cool designs. Sydney found a copy
of the Design Patterns book in the home economics section
of his university (I will refrain from making comments regarding
his university based on that last comment ;-))).
Enough of my jabbering, let's see what Sydney, the one-legged
Java Guru has to say ...
Would you like to really understand Java concurrency? Join us for an
in-depth study of how threading works in Java. During the course,
you will learn how to write correct and fast multi-threaded Java code.
Please
click here if you would like to learn more. Checking Your Classpath Validity
The classpath - like most programmers, I really struggled with it
in the beginning. I first cut my teeth on Java in middle of
1996 when I was a student doing some vacation work, as part of an
experiment of using JDK 1.0 to solve the world's problems. As
you might imagine, I caused more problems than I solved. I
must've been the most underpaid Java programmer of all time - in
today's South African currency terms, I only got US$ 272 for 2
months work (and that was with a 100% bonus)! Enough reminiscing,
back to class paths. It is not that class paths are difficult to
understand, it is just that it is too easy to get them wrong and
not easy enough to locate errors, especially if you are used to
case-insensitive NT paths. The classpath problem is so part of
the Java culture that even The Onion
reports on it! [HK: A must-read magazine for TJSN subscribers]
Even after I got more experienced it still seemed to eek its way
into builds. In those days (before Ant and what not) we used to
write huge batch files to check that all the required jars were
available, I believe the company in question still does it ;)
[HK: Actually, we had a chap who had an MCSE in WinNT 3.51 who
was a real whizz at batch files. This, perhaps, was to our
disadvantage.]
These days I am involved with Websphere, a product aimed at the
Java professional who thinks he knows how Java works. This
product will make even the seasoned Java expert quiver at his
knees when he gets a MarshallException at run-time
due to linkage errors often caused by an incorrect classpath.
Note: in Websphere the classpath can be specified on at least
three levels (container, server and node levels).
My first attempt at solving the marshall exception was to add
another dynamic layer like any true geek developer. [HK: Sydney
is truly the master of adding dynamic layers. That first Java
program that he wrote for US$ 272 over 2 months took US$ 13500
of real professional Java programmers' time to debug.]
Java does tend to make it too easy to do this with reflection,
dynamic class loaders, dynamic proxies, soft references and all
other kinds of weird & wonderful constructs this newsletter
likes to comment on.
So, my first solution was to load the class files dynamically
(via Class.forName(...)) and then to simply try
catching the ClassNotFoundException.
try {
Class my_class = Class.forName("ClassName");
} catch(ClassNotFoundException cnfe) {
cnfe.printStackTrace();
}
Of course this worked, so now I could see the exception other
application servers probably would have provided me with
originally.
This really bugged me. I had to write compilers at varsity and I
always thought I was doing the world a favour by doing compile
time type checking etc. It surely is better to find a bug at
compile time than at run-time, especially if your build-deploy
cycle spirals into an affair that can take hours. [HK: With the
batch files that we had originally the build cycle took so long
that we could only do a weekly build!]
[SR: I think the most time consuming part of that process was
actually Source Safe]
Ant (or other similar build-tools) could solve some of these
problems, unfortunately some of our components have to be
deployed manually due to our IDE keeping information hidden.
[HK: Isn't your IDE written in Java - perhaps you could use
reflection to find the information dynamically and ... ]
[SR: No such luck it is actually written in SmallTalk.]
So I asked myself: "Why does Sun keep the classpath hidden at
runtime?" I decided to start digging in the jdk api's.
To load classes, the default class loader needs access to the
classpath. The default class loader is very easy to get hold of,
via the java.lang.ClassLoader.getSystemClassLoader
static method. Unfortunately, the interface of
java.lang.ClassLoader does not contain any methods
that appear to be useful to us.
Just before I decided to start digging in the Sun source code, I
noticed that java.security.SecureClassLoader extended
java.lang.ClassLoader and
java.net.URLClassLoader extended
java.security.SecureClassLoader. By doing a quick
experiment, I discovered that the System class loader (in the SUN
VM) is indeed an instance of java.net.URLClassLoader. [HK: *evil
grin*]
At this point I got all excited. Why? Because URLClassLoader
contains a method getURLs() which returns the current classpath.
So by writing the following small class, I can validate the
classpath (i.e. make sure all the jars exist).
import java.net.*;
import java.io.*;
public class ClassPathInfo {
private final ClassLoader classLoader;
public ClassPathInfo(ClassLoader classLoader) {
this.classLoader = classLoader;
}
public ClassPathInfo() {
this(ClassLoader.getSystemClassLoader());
}
/**
* validates classpath, throws an LinkageError if invalid
* classpath was specified
*/
public void validateClassPath() {
try {
URL[] urls = ((URLClassLoader)classLoader).getURLs();
for(int i=0; i<urls.length; i++) {
try {
urls[i].openStream();
} catch(IllegalArgumentException iae) {
throw new LinkageError(
"malformed class path url:\n "+urls[i]);
} catch(IOException ioe) {
throw new LinkageError(
"invalid class path url:\n "+urls[i]);
}
}
} catch(ClassCastException e) {
throw new IllegalArgumentException(
"The current VM's System classloader is not a "
+ "subclass of java.net.URLClassLoader");
}
}
public static void main(String[] args) {
ClassPathInfo info = new ClassPathInfo();
info.validateClassPath();
}
}
Now it is possible to add batch or script files to replace java
and javac. I was planning to leave this as an exercise for the
reader but Heinz would have none of that [HK: Incidentally,
someone sent me the "exercise to the reader" parts of the
CircularArrayList,
if any of you are interested]. It turned out to be more
difficult than it initially seemed, as it is tricky to make sure
that ClassPathInfo is in the classpath.
Firstly if you are a Unix user I shall leave this as an exercise
for the reader. [HK: awww, come-on Syd] If you are using
Windows you need to copy the following three batch files with
ClassPathInfo.class to somewhere on your PATH. You
also need to change the STRICTHOME enviroment variable in
checkcp.bat to the directory these files were copied into.
checkcp.bat
@echo off
rem Set STRICTHOME to the directory you're deploying this file to.
set STRICTHOME=C:\strictjava
if ""=="%3" (
set STRICTCP="%CLASSPATH%;%STRICTHOME%"
) else (
if -classpath==%1 (
set STRICTCP="%2;%STRICTHOME%"
)else (
echo If more than three parameters are specified,
echo the first has to be -classpath.
set ERRORLEVEL=1
goto end
)
)
java -classpath %STRICTCP% ClassPathInfo
set STRICTHOME=
set STRICTCP=
:end
strictjavac.bat
@echo off
call checkcp %1 %2 %3
if not %errorlevel%==0 goto END
javac %1 %2 %3 %4 %5 %6 %7 %8 %9
:END
strictjava.bat
@echo off
call checkcp %1 %2 %3
if not %errorlevel%==0 goto END
java %1 %2 %3 %4 %5 %6 %7 %8 %9
:END
Please note that if you supply the classpath to these batch files,
you will have to put it in quotes (i.e.
strictjava -classpath "c:\sub\jar1.jar;c:\sub2\jar2.jar" MyClass).
Regards
Sydney Redelinghuys (aka hopalong)
Software Engineering Articles
Related Java Course
Discuss at The Java Specialist Club
|