fusionlightcat fusionlightcat - 23 days ago 11
Java Question

Java: Add files to zip-file recursively but without full path

I am trying to put files from a folder inside a zip file in the following structure:

Folder structure:

myFolder
|-file1.txt
|-file2.txt
|-folder172
|-file817.txt
|-file818.txt
...

Supposed structure inside ZipFile:

file1.txt
file2.txt
folder172
|-file817.txt
|-file818.txt


This is my code:

public static void writeZip(String path) throws IOException{

FileOutputStream fos = new FileOutputStream(path+File.separator+"atest.zip");
ZipOutputStream zos = new ZipOutputStream(fos);

try {
Files.walk(Paths.get(path)).filter(Files::isRegularFile).forEach((string) -> addToZipFile(string.toString(),zos));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

zos.close();
fos.close();



}


public static void addToZipFile(String fileName, ZipOutputStream zos) throws IOException {

System.out.println("Writing '" + fileName + "' to zip file");

File file = new File(fileName);
FileInputStream fis = null;

fis = new FileInputStream(file);

ZipEntry zipEntry = new ZipEntry(fileName);

zos.putNextEntry(zipEntry);


byte[] bytes = new byte[1024];
int length;

while ((length = fis.read(bytes)) >= 0) {
zos.write(bytes, 0, length);
}
zos.closeEntry();
fis.close();

}


The problem is now, when i call
writeZip("/home/arthur/.grutil/");
, i get the following structure in the zip-file:

home
|-arthur
|-.grutil
|-file1.txt
|-file2.txt
|-folder172
|-file817.txt
|-file818.txt
...


How do i need to change my code to get the supposed structure (as described above) and not the structure with the full path '/home/arthur/.grutil/ ...'?

Answer

Whilst this can be done with the ancient ZipOutputStream I would recommend against it.

It is much more intuitive to think about a Zip archive as a compressed filesystem inside a file, than a stream of bytes. For this reason, Java provides the ZipFileSystem.

So all you need to do is open the Zip as a FileSystem and then manually copy files across.

There are a couple of gotchas:

  1. You need to only copy files, directories need to be created.
  2. The NIO API does not support operations such as relativize across different filesystems (reasons should be obvious) so this you need to do yourself.

Here are a couple of simple methods that will do exactly that:

/**
 * This creates a Zip file at the location specified by zip
 * containing the full directory tree rooted at contents
 *
 * @param zip      the zip file, this must not exist
 * @param contents the root of the directory tree to copy
 * @throws IOException, specific exceptions thrown for specific errors
 */
public static void createZip(final Path zip, final Path contents) throws IOException {
    if (Files.exists(zip)) {
        throw new FileAlreadyExistsException(zip.toString());
    }
    if (!Files.exists(contents)) {
        throw new FileNotFoundException("The location to zip must exist");
    }
    final Map<String, String> env = new HashMap<>();
    //creates a new Zip file rather than attempting to read an existing one
    env.put("create", "true");
    // locate file system by using the syntax
    // defined in java.net.JarURLConnection
    final URI uri = URI.create("jar:file:/" + zip.toString().replace("\\", "/"));
    try (final FileSystem zipFileSystem = FileSystems.newFileSystem(uri, env);
         final Stream<Path> files = Files.walk(contents)) {
        final Path root = zipFileSystem.getPath("/");
        files.forEach(file -> {
            try {
                copyToZip(root, contents, file);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        });
    }
}

/**
 * Copy a specific file/folder to the zip archive
 * If the file is a folder, create the folder. Otherwise copy the file
 *
 * @param root     the root of the zip archive
 * @param contents the root of the directory tree being copied, for relativization
 * @param file     the specific file/folder to copy
 */
private static void copyToZip(final Path root, final Path contents, final Path file) throws IOException {
    final Path to = root.resolve(contents.relativize(file).toString());
    if (Files.isDirectory(file)) {
        Files.createDirectories(to);
    } else {
        Files.copy(file, to);
    }
}
Comments