Is there a way to tell if a classpath resource is a file or a directory?

classpath, java

Solution

This is a bit of a mess due to some unspecified behaviour for the protocol handlers involved in loading these resources. In this particular situation, there are two: `sun.net.www.protocol.file.Handler` and `sun.net.www.protocol.jar.Handler`, and they each handle the directory case a bit differently. Based on some experiments, here's what they each do:

sun.net.www.protocol.file.Handler:

What this `Handler` does is open a `FileURLConnection`, which does exactly what you discovered it did when confronted with a directory. You can check if it's a directory just with:

if (resource.getProtocol().equals("file")) {
    return new File(resource.getPath()).isDirectory();
}

sun.net.www.protocol.jar.Handler:

- This `Handler`, on the other hand, opens a `JarURLConnection` which eventually makes its way to a `ZipCoder`. If you take a look at that code, you'll notice something interesting: `jzentry` will come back `null` from the native JNI call because the JAR zip file does not, in fact, contain a file called `com/google`, and so it returns null to the stream that wraps it.

However, there is a solution. Although the `ZipCoder` won't find `com/google`, it will find `com/google/` (this is how most ZIP interfaces work, for some reason). In that case, the `jzentry` will be found, and it'll just return a null byte.

So, cutting through all these random implementation-specific behaviours, you can probably figure out if it's a directory by first trying to access the resource with a trailing `/` (which is what `URLClassLoader`s expect for directories anyway). If `ClassLoader.getResource()` returns non-null, then it's a directory. If it doesn't, try without the trailing slash. If it returns non-null, it's a file. If it still returns null, then it's not even an existing resource.

Kinda hacky, but I don't think there's anything better. I hope this helps!

Problem

For example, this snippet throws a NullPointerException(!) on the `stream.read()` line, assuming the com.google package exists in a JAR somewhere (Guava, for example). ``` ClassLoader classLoader = getClass().getClassLoader(); URL resource = classLoader.getResource("com/google"); InputStream stream = resource.openStream(); System.out.println(stream.toString()); // Fine -- stream is not null stream.read(); // NPE inside FilterInputStream.read()! ``` If `com/google` is swapped with a package that's in the file system rather than a JAR, then the snippet doesn't crash at all. In fact, it seems to read the files in that directory, separated by newlines, though I can't imagine that behaviour is specified anywhere. Is there a way test if the resource path "com/google" points to a "normal" resource file or to a directory?

Original source