Matheus Moreira
Matheus Moreira

Reputation: 17020

JarInputStream: getNextJarEntry always returns null

I have an I18n helper class that can find out the available Locales by looking at the name of the files inside the application's Jar.

private static void addLocalesFromJar(List<Locale> locales) throws IOException {
    ProtectionDomain domain = I18n.class.getProtectionDomain();
    CodeSource src = domain.getCodeSource();
    URL url = src.getLocation();
    JarInputStream jar = new JarInputStream(url.openStream());
    while (true) {
        JarEntry entry = jar.getNextJarEntry();
        if (entry == null) {
            break;
        }
        String name = entry.getName();
        // ...
    }
}

Currently, this isn't working - jar.getNextJarEntry() seems to always return null. I have no idea why that's happening, all I know is that url is set to rsrc:./. I have never seen that protocol, and couldn't find anything about it.

Curiously, this works:

class Main {
    public static void main(String[] args) {
        URL url = Main.class.getProtectionDomain().getCodeSource().getLocation();
        JarInputStream jar = new JarInputStream(url.openStream());
        while (true) {
            JarEntry entry = jar.getNextJarEntry();
            if (entry == null) {
                break;
            }
            System.out.println(entry.getName());
        }
    }
}

In this version, even though there is practically no difference between them, the url is correctly set to the path of the Jar file.

Why doesn't the first version work, and what is breaking it?

UPDATE:

The working example really only works if I don't use Eclipse to export it. It worked just fine in NetBeans, but in the Eclipse version the URL got set to rsrc:./ too.

Since I exported it with Package required libraries into generated JAR library handling, Eclipse put its jarinjarloader in my Jar so I can have all dependencies inside it. It works fine with the other settings, but is there any way to make this work independently of them?


Another question

At the moment, that class is part of my application, but I plan to put it in a separate library. In that case, how can I make sure it will work with separate Jars?

Upvotes: 1

Views: 2737

Answers (4)

Dan Berindei
Dan Berindei

Reputation: 7194

Eclipse's jarinjarloader loads everything using the system classloader and it never knows what jar file it was loaded from. That's why you can't get the jar URL for a rsrc: url.

I suggest storing the list of locales in a file in each application jar, e.g. META-INF/locales. Then you can use ClassLoader.getResources("META-INF/locales") to get the list of all the files with that name in the classpath and combine them to obtain the full list of locales.

Upvotes: 1

Jesse Barnum
Jesse Barnum

Reputation: 6816

The problem is the jarinjarloader ClassLoader that is being used by Eclipse. Apparently it is using its own custom rsrc: URL scheme to point to jar files stored inside the main jar file. This scheme is not understood by your URL stream handler factory, so the openStream() method returns null which causes the problem that you're seeing.

This answers the second part of your question about separate jars - not only will this work, it's the only way that it will work. You need to change your main application to use separate jars instead of bundling them all up inside the main jar. If you're building a web application, copy them into the WEB-INF/lib directory and you're fine. If you're building a desktop application, add a relative path reference in the META-INF/MANIFEST.MF to the other jars, and they will automatically be included as part of the classpath when you run the main jar.

Upvotes: 2

bestsss
bestsss

Reputation: 12056

The code may or may not result into the jar file where I18n resides. Also getProtectionDomain can be null. It depends how the classloader is implemented.

ProtectionDomain domain = I18n.class.getProtectionDomain();
CodeSource src = domain.getCodeSource();
URL url = src.getLocation();

about the rsrc:./ protocol, the classloader is free to use whatever URL they please (or name it for that matter)

try this out, you might get lucky :)

URL url = getClass().getResource(getClass().getSimpleName()+".class");
java.net.JarURLConnection conn = (java.net.JarURLConnection) url.openConnection();
Enumeration<JarEntry> e = conn.getJarFile().entries();
...

and good luck!

Upvotes: 1

RedSoxFan
RedSoxFan

Reputation: 634

I use System.getProperty("java.class.path") for getting the location of the jar. I do not know if that makes a difference. I have not explored the ProtectDomain path so I cannot help you there, sorry. As for multiple jars, just iterate through those jar file also.

Upvotes: 0

Related Questions