2

I am having issue with writting a binary file to simple ZIP archive using Java 7's FileSystem and Files API.

The problem occur while performing write operation, which throws no exception at all, the file is not written to the ZIP archive, but is available in runtime (Files.exists(backup) returns true and it's possible to read the file using Files.readAllBytes(backup) ).

When program is closed and relaunched, the file is not available anymore.

Snippet

This method should create backup of any path, no matter who is the FileSystem provider, 'fails' just on paths inside ZIP archives.

/**
 * Creates backup of path provided by 'file' parameter.
 *
 * @param file input file requiring backup creation
 * @return backup file
 * @throws java.io.IOException thrown in case of unsuccessful backup attempt
 */
public static Path createBackup(Path file) throws IOException {

    FileSystem fileSystem = file.getFileSystem();
    Path backup = fileSystem.getPath(file.toString() + ".BAK");

    return Files.write(backup, Files.readAllBytes(file));

}

public static void main(String... args) {
   try {

      Path f = FileSystems.newFileSystem(Paths.get("a.zip"), null).getPath("file.bin");

      Path backup = createBackup(f);

      System.out.println(Files.exists(backup)); // prints "true"
      System.out.println(new String(Files.readAllBytes(backup))); // prints its bytes
      System.out.println(backup.toString()); // prints "file.bin.BAK"
   } catch (IOException ex) {
      System.err.println(ex);
   }

}

But the file does not physically exists in the ZIP.

EDIT: I've managed to make it work, but there is a problem. Code below closes the file system, but writes properly. There is need to "refresh"/"reopen" filesystem somehow.

public static Path createBackup(Path file) throws IOException {

   try(FileSystem fileSystem = file.getFileSystem()) {

      Path backup = fileSystem.getPath(file.toString() + ".BAK");
      return Files.write(backup, Files.readAllBytes(file));

   }
}

When original method is keept and file system is closed manually after everything is done, it removes the zip file and keeps something like zipfstmp***.tmpand throws:

java.nio.file.FileAlreadyExistsException: zipfstmp2666831581340533856.tmp -> a.zip

When the tmp file is renamed to "a.zip", its a valid modified archive.

glf4k
  • 429
  • 4
  • 9
  • try showing the correct compile-able `main` code - you are saying that *throws no exception at all* but the code in its current state does not compile due to many uncaught exceptions – Scary Wombat Aug 08 '17 at 01:47
  • Edited. Just surrounded the main code with try-catch. – glf4k Aug 08 '17 at 01:51
  • Have you tried catching a general exception to see if it throws an unexpected type of exception? Obviously do not do this permanently. – Andrew S Aug 08 '17 at 02:03
  • @AndrewS - The operations throw just IOException, otherwise it would fall on uncaught exception, but the build completed successfully somehow. – glf4k Aug 08 '17 at 02:11
  • Ok then catch both and see. – Andrew S Aug 08 '17 at 02:14
  • @AndrewS same result, when I attempt to write backup again (in the same runtime) it throws FileAlreadyExistsException. If you create a simple zip with one file in it you can check it. – glf4k Aug 08 '17 at 02:16

2 Answers2

3

You should close using the try-with-resource statement within the caller, which created the file system. There is no need to deal with file systems in the createBackup method at all.

public static Path createBackup(Path file) throws IOException {
    Path backup = file.resolveSibling(file.getFileName().toString()+".BAK");
    return Files.copy(file, backup, StandardCopyOption.REPLACE_EXISTING);
}
public static void main(String... args) {
    try(FileSystem fs = FileSystems.newFileSystem(Paths.get("a.zip"), null)) {
       Path f = fs.getPath("file.bin");
       Path backup = createBackup(f);

       System.out.println(Files.exists(backup)); // prints "true"
       System.out.println(new String(Files.readAllBytes(backup))); // prints its bytes
       System.out.println(backup.toString()); // prints "file.bin.BAK"
    } catch (IOException ex) {
       System.err.println(ex);
    }
}
Holger
  • 285,553
  • 42
  • 434
  • 765
  • Thanks for answer. This is how I did it in second example, but the problem is that I need to work with the original file (object instance) after backup was created (outside main), so it throws ClosedFileSystemException. – glf4k Aug 09 '17 at 15:57
  • Then, the caller (outside main) should be responsible for creating and closing the file system. Otherwise, there’s no other solution than closing and reopening… – Holger Aug 09 '17 at 16:23
  • It works, but man now I know why people are beginning to dislike Java.. That try line is rather compact and a little obtuse (though not your fault). – Andrew S Aug 11 '17 at 21:43
  • @Holger this has bitten me once again (I stopped loosing count of how many times I fail with `FileSystem`). Suppose such a case: a method that reads a certain `URI` using `FileSystem` and return a `Path`; later some callers of the method use this path to do something. Since I am using try with resource when accessing `FileSystem` - I almost always get `ClosedException` and I keep forgetting I must return the `FileSystem` to the callers also, so that they close it. This rant is actually more of a question: would there be a cleaner way to handle this? ... thank you – Eugene Jan 30 '19 at 09:20
  • @Eugene when you return a `Path`, you’re also returning a `FileSystem`, as every `Path` has a `getFileSystem()` method. The actual problem is to tell whether that file system has be closed after use or not. There’s no way around returning an instance of a dedicated type which knows whether the file system has to be closed. It may have a `Path getPath()` method and implement `Closeable`, but do nothing on `close()` if this particular file system should not be closed (like the default file system). Then, the caller doesn’t need to think about it and may always use a `try(…) { … }`. – Holger Jan 30 '19 at 10:36
  • @Holger thank you for the comment, it makes a lot more sense now and of course I will re-write this a bit since you made me understand where my mistake was – Eugene Jan 30 '19 at 13:21
0

Below is a simple example where all the logic is in the main method.

The key to solving this is in using a URI to state explicitly what kind of File System provider to use. You may wonder why jar and not zip. In Java a jar files are in fact zip files but with a different file extension. So java can use the same mechanisms to access zip files.

I tried this with binary files and text files and it seems to work on my Win 10 machine. Just change the source directory of the zip file (///e:/a.zip) to point to the location of your zip file. For me the file.bin.BAK was written to the root of eclipse project directory (you might want to change that also for deployment).

import java.io.IOException;
import java.net.URI;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.HashMap;
import java.util.Map;

public class Test {

    public static void main(String... args) {

            //Create URI to define the source as a Zip file.
            //I've used jar because this is essentially a zip file.
            Map<String, String> env = new HashMap<>(); 
            env.put("create", "true"); 
            URI uri = URI.create("jar:file:///e:/a.zip");

            try{

                //Build paths to source file in zip  and externally.
                Path pathInZipfile = FileSystems.newFileSystem(uri, env).getPath("/file.bin");  
                Path extPath  = Paths.get("file.bin.BAK");


                //If file does not exist create it.
                if (! Files.exists(extPath))
                    Files.createFile(extPath);


                //Copy the files over.
                 Files.copy( pathInZipfile, extPath,
                            StandardCopyOption.REPLACE_EXISTING ); 

            }//end try
            catch (IOException ex){
                ex.printStackTrace();
            }//enc catch

    }//end main
}//end class
Andrew S
  • 2,847
  • 3
  • 33
  • 50