Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

java

classpath

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?

like image 489
Tavian Barnes Avatar asked Nov 20 '13 19:11

Tavian Barnes


People also ask

How do I know if a file is in classpath?

In this example we shall show you how to find a file in the classpath. To find a file in the classpath we have created a method, File findFileOnClassPath(final String fileName) that reads a fileName and returns the File.

Is resource folder in classpath?

Usually, the files in the resources folder will copy to the root of the classpath.

What are classpath resources?

Classpath in Java is not only used to load . class files, but also can be used to load resources e.g. properties files, images, icons, thumbnails, or any binary content. Java provides API to read these resources as InputStream or URL.


2 Answers

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 URLClassLoaders 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!

like image 193
Adrian Petrescu Avatar answered Sep 20 '22 21:09

Adrian Petrescu


There is no safe and generic way to detect this. When you use ClassLoader.getResource(), the ClassLoader can return practically anything in the URL, in principle even something you have never seen before if the ClassLoader implements its own URL scheme (and protocol).

Your only option is to analyze the URL returned by getResource(), the protocol should hint at what it is (e.g. "file://"). But beware, depending on environment it may return things you did not plan for.

But to just access a resource, you don't care where it comes from (you may care if you're debugging a configuration issue, but your code should not care).

In general you should not make assumptions about the returned InputStream's capabilities, i.e. do not rely on it supporting mark/reset etc. The only safe operation would be simply reading the Stream. If an IOException occurs during read it indicates a problem with access to the resource (network connection lost etc.).

EDIT: getResource() should IMO only return resources (e.g. files or zip file entries), but never directories (since they are not resources). However I wouldn't count on every possible ClassLoader to do so, and I'm not sure what the correct behavior is (if its even specified somewhere).

like image 41
Durandal Avatar answered Sep 17 '22 21:09

Durandal