Search code examples
javaresourceszipmacos-catalina

Java in Mac OS X Catalina - Zip file not found in Resources, while the file is found when run from Eclipse


I have a compressed file in the Resources folder which is decompressed in the folder where the program is run for the first time. If the program starts from Eclipse, the file is found and decompressed without problems. When I export the program in a jar file, and run the program with:

java -jar JRS2020-31.jar

the output is:

java.lang.RuntimeException: Error unzipping file initialData/compressed.zip
    at InterpreteSQL.Main.unzip(Main.java:111)
    at InterpreteSQL.Main.CreateInitialDirectoryIfNotFound(Main.java:77)
    at InterpreteSQL.Main.main(Main.java:60)
Caused by: java.io.FileNotFoundException: file:/Users/XXX/JRS2020-31.jar!/initialData/compressed.zip (No such file or directory)
    at java.util.zip.ZipFile.open(Native Method)
    at java.util.zip.ZipFile.<init>(ZipFile.java:219)
    at java.util.zip.ZipFile.<init>(ZipFile.java:149)
    at java.util.zip.ZipFile.<init>(ZipFile.java:120)
    at InterpreteSQL.Main.unzip(Main.java:88)
    ... 2 more

Note that other files in resources (help html files) are opened regularly in the program.

This is the code that opens the file:

public static void unzip(String zipFilePath, String unzipDir) throws Exception {
    try{
        ZipFile zipFile = new ZipFile(Main.class.getClassLoader().getResource(zipFilePath).getFile());
        Enumeration<? extends ZipEntry> entries = zipFile.entries();
            while(entries.hasMoreElements()){
...

and it is called with:

unzip("initialData/compressed.zip", "JRS_directory");

Note that the program is run in the home folder, and that it can create directories and files.

Any idea about the problem? Thank you very much.


Solution

  • You'reusing getResources wrong, in two ways.

    1. You're asking the resource to turn itself into a file for no reason. In your eclipse run, the resource IS a file. However, in a jar run, it's not (it is an entry in a jar, which is not a file). Don't call .getFile() on these resources; the whole point of the abstraction is that it might not be a file at all. Could be an entry in a jar. Could be imported via the network. All you know is: Load this data from where-ever you're finding the class files.

    Don't use ZipFile; use ZipInputStream which has very similar API. Then use getResourceAsStream.

    1. The proper form is Main.class.getResource(). Avoid the classloader intermediate. It is both a pointless method call, and in rare cases will break (in certain contexts, a class's classloader is null, causing a nullpointerexception). Note that this form means the string you pass in is relative to the class location. If you don't want that, add a leading slash.

    While you're at it, these are resources that must be closed, so let's use the try-with-resources construct to ensure this is done properly even in the face of exceptions.

    Putting this together:

    try (InputStream raw = Main.class.getResourceAsStream("/" + zipFilePath);
         ZipInputStream zip = new ZipInputStream(raw)) {
    
        for (ZipEntry entry; (entry = zip.getNextEntry()) != null; ) {
            // do something with entry here
        }
    }