Tom Rutchik
Tom Rutchik

Reputation: 1702

when using a zip file as a FileSystem the zip file is not being update

I want to manipulate a jar using the standard nio Files and Paths methods. So, Java has a way to do this by creating a zip FileSystem:

    try {
        zipFS = FileSystems.newFileSystem(zipDisk, zipFSproperties);
    } catch(Exception e) {
        System.out.println(e.getMessage());
    }

My test program uses an existing jar file as a FileSystem and it lists the entries contained in the jar. All that works great. I then copy a new file into the jar and list the entries again. And just as you would expect, the list now contains the newly added file. The problem is after the program closes, I open up the jar file that the jar filesystem is based upon and it doesn't have the new entry added to it. So that's my question! Shouldn't the jar file itself be changed when I add a new entry. I don't know of any commands I can issue the would cause the zip FileSystem to update to the actual jar file that the zip FileSystem wraps. Am I reading more into a FileSystem; are changes in the zip filesystem suppose to cause the corresponding backend zip file to be updated.

code: public class Main {

public static void main(String[] args) throws IOException {
    ZipFileSystem zipFS = new ZipFileSystem("C:\\Temp\\mylibrary\\build\\outputs\\jar\\temp\\mylibrary-debug.zip");
    Stream<Path> paths = Files.find(zipFS.zipFS.getRootDirectories().iterator().next().getRoot(),10, (path, basicFileAttributes) -> {
        return !Files.isDirectory(path);
    });
    paths.forEach( path ->
            System.out.println ("zip contains entry: " + path)
    );

    File file = new File("C:\\Temp\\mylibrary\\src\\main\\java\\com\\phinneyridge\\android\\myLib.java");
    System.out.println("copying " + file.getPath());
    Path outPath = zipFS.zipFS.getPath("myLib.java");
    Files.copy (file.toPath(), outPath, StandardCopyOption.REPLACE_EXISTING);
    paths = Files.find(zipFS.zipFS.getPath(""),10, (path, basicFileAttributes) -> {
        return !Files.isDirectory(path);
    });
    paths.forEach( path ->
            System.out.println ("zip contains entry: " + path)
    );
}

}

I added code that shows me accessing a zip file, listing the current entries it contains, adding a new entry (via file copy), and lastly listing the contents again. All of this code works correctly. What doesn't work is that the changes to the zip filesystem don't get incorporated back into the zip file when the application ends. I was surprised that the zip file didn't get updated, but I'm now under the opinion, that it's working as it is intended to work; not doing what I wanted it to do, but that's okay. I can't find any documentation that says it would update the jar file that the FileSystem object originated from. So I'm basically asking is that the correct behavior, or is there something I'm entirely missing to cause the zip FileSystem object to update the Zip file?

Here's the code when I tried Dunc suggestion: public static void main(String[] args) throws IOException {

    ZipFileSystem zipFS = new ZipFileSystem("C:\\Temp\\mylibrary\\build\\outputs\\jar\\temp\\mylibrary-debug.zip");
    try (FileSystem fs = zipFS.zipFS) {
        try (Stream<Path> paths = Files.find(zipFS.zipFS.getRootDirectories().
                iterator().next().getRoot(), 10, (path, basicFileAttributes) -> {
            return !Files.isDirectory(path);
        })) {
            paths.forEach(path ->
                    System.out.println("zip contains entry: " + path)
            );
        }

        File file = new File("C:\\Temp\\mylibrary\\src\\main\\java\\com\\phinneyridge\\android\\myLib.java");
        System.out.println("copying " + file.getPath());
        Path outPath = fs.getPath("myLib.java");
        Files.copy(file.toPath(), outPath, StandardCopyOption.REPLACE_EXISTING);

        try (Stream<Path> paths = Files.find(zipFS.zipFS.getRootDirectories().
                iterator().next().getRoot(), 10, (path, basicFileAttributes) -> {
            return !Files.isDirectory(path);
        })) {
            paths.forEach(path ->
                    System.out.println("zip contains entry: " + path)
            );
        }
    } catch (Exception e) {
        System.out.println("FileSystem Error: " + e.getClass().getName() + " - " + e.getMessage());
        e.printStackTrace();
    }
}

}

And by the way ZipFileSystem is a wrapper class around the FileSystem. I'll post that code too, incase that's where I 'm doing something wrong.

public class ZipFileSystem {
FileSystem zipFS;
Path zipFSPath;

/**
 * Constructor for a ZipFile object
 * @param zipFilePath string representing the path to the zipfile.  If the path doesn't exist,
 * the zip file will be automatically created.  If the path exist, it must be a file (not
 * a directory) and it must be a valid zip file
 */
public ZipFileSystem(String zipFilePath) {
    Map<String, String> zipFSproperties = new HashMap<>();
    /* set create to true if you want to create a new ZIP file */
    zipFSproperties.put("create", "true");
    /* specify encoding to UTF-8 */
    zipFSproperties.put("encoding", "UTF-8");
    /* Locate File on disk for creation */
    URI zipFileUri = new File(zipFilePath).toURI();
    URI zipDisk = URI.create("jar:" + zipFileUri);
    zipFSPath = Paths.get(zipFileUri);
    if (!Files.exists(zipFSPath)) {
        try {
            createEmptyZipFile(zipFSPath);
        } catch (Exception e ) {
            System.out.println(e.getMessage());
        }
    } else {
        if (Files.isDirectory(zipFSPath)) {
        } else {
            try {
                // let's open it, which will verify if it's a valid zip file
                ZipFile zipFile = new ZipFile(zipFilePath);
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }
        }
    }
    try {
        zipFS = FileSystems.newFileSystem(zipDisk, zipFSproperties);
    } catch(Exception e) {
        System.out.println(e.getMessage());
    }
    try {
        listFiles(zipFS.getPath("/"));
    } catch (IOException e) {
        e.printStackTrace();
    }
}

Upvotes: 2

Views: 1208

Answers (1)

DuncG
DuncG

Reputation: 15186

The correct way to open a zip from a Path - and create if not exists - is:

Path zip = Path.of("/Somepath/to/xyz.zip");
Map<String, String> env = Map.of(
    "create", "true"
    // other args here ...
);
try (FileSystem fs = FileSystems.newFileSystem(zip, env)) {
     // code to read/update here
}

You have not closed any files or streams properly so your changes are probably not flushed back to the file system and will keep hold of file handles which block some operations.

Use try with resources for every operation which will manage the modifications to zip filesystem as well as closing each Stream<Path> from Files.find, and check other places such as createEmptyZipFile for the same problem:

try (FileSystem fs = ... ) {

    try (Stream<Path> paths = Files.find(...) ) {
    }
    
    Files.copy( ... );

    try (Stream<Path> paths = Files.find(...) ) {
    }
}

The process cannot access the file because it is being used by another process

You have unnecessary code ZipFile zipFile = new ZipFile(zipFilePath) which tests the zip is valid and you do not call close(), so it will prevent the zip changes being written back. The check can safely be deleted (as FileSystems.newFileSystem does same) or must be wrapped in try() {} so that zipFile is closed before your edits to the zip filesystem.

Upvotes: 3

Related Questions