HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavaMinor

Extracting ZIP, JAR and EPUB files

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
jarzipfilesepubextractingand

Problem

I wrote a utility class that is used to extract the contents of a ZIP file to a destination folder. This is a very small class, but it has several practical applications: it can not only be used to extract a ZIP file, it can also be used to extract JAR and EPUB files.

UnZipper.java

```
package reader.utils;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
* This is a utility class that provides methods for extraction of data from
* ZIP (and GZIP) files whilst making use of the {@linkplain java.util.zip}
* package. All the public methods in this class are static and have the same
* name: {@code unzip()}. Sample usage is as below:
*
* ...
* String src = "(FULL path of ZIP file)";
* String dst = "(FULL path of destination folder)";
* try {
* UnZipper.unzip(src, dst);
* } catch(IOException e) {
* e.printStackTrace();
* }
* ...
*
*
* NOTE:
* If the specified output directory is present, it will replace the
* existing one and create a new one.
* This class can also be used to extract JAR and EPUB files.
*
*
* @author Subhomoy Haldar
* @version 1.0
*/
public final class UnZipper {

/**
* Demonstration.
*
* @param args The command-line arguments.
*/
public static void main(String[] args) {
if (args.length != 2) {
System.out.println("Sample usage:\n" +
"java UnZipper (full source file path) " +
"(full destination folder path");
return;
}
String src = args[0];
String dst = args[1];
try {
UnZipper.unzip(src, dst, Charset.forName("UTF-8"));
} catch (IOException e) {
e.printStackTrace();
}
}

/**
* The maximum number of bytes read p

Solution

Don't try to delete the destination

Before unzipping,
this snippet seems to try to delete the destination directory if exists:

if (destination.exists()) {
            destination.delete();
        }
        destination.mkdir();


But I don't think this works as you would expect.
It will only delete the directory if it's not empty.
But then it doesn't make sense to delete and then recreate.

Note that unzipping tools normally don't delete the destination by default.
It would be dangerous.

I suggest to change to this:

if (!destination.exists()) {
            destination.mkdir();
        }


Don't form paths from strings if you don't have to

Instead of this:

String filePath = destination + File.separator + entry.getName();
File file = new File(filePath);


It's simpler to use the 2-param constructor of File:

File file = new File(destination, entry.getName());


Bugs

Both branches of this if condition will fail in some cases:

if (entry.isDirectory()) {
        file.mkdir();  // type 1
    } else {
        File parent = file.getParentFile();
        if (!parent.exists()) {
            parent.mkdir();  // type 2
        }
        extractFile(zipIn, file);
    }


Type 1: fails for a ZIP file whose first entry is a nested directory (unless the parent directories were previously created).
For example if you create a ZIP file like this:

zip -r /tmp/test.zip some/nested/path/


Type 2: fails for a ZIP file whose first entry is a file in a nested directory (unless the parent directories were previously created).

For example if you create a ZIP file like this:

zip -r /tmp/test.zip some/nested/path/file.txt


You can fix both of these by changing the file.mkdir() to file.mkdirs().

Suggested implementation

With the suggestion in the last point,
there's no more need to create the destination directory in advance,
it will be created as needed.
Which has the other advantage that if the zip is empty,
a destination directory won't be unnecessarily created.

public static void unzip(File source, File destination, Charset charset) throws IOException {
    try (ZipInputStream zipIn = new ZipInputStream(new FileInputStream(source), charset)) {
        ZipEntry entry = zipIn.getNextEntry();
        while (entry != null) {
            File file = new File(destination, entry.getName());
            if (entry.isDirectory()) {
                file.mkdirs();
            } else {
                File parent = file.getParentFile();
                if (!parent.exists()) {
                    parent.mkdirs();
                }
                extractFile(zipIn, file);
            }
            zipIn.closeEntry();
            entry = zipIn.getNextEntry();
        }
    }
}

Code Snippets

if (destination.exists()) {
            destination.delete();
        }
        destination.mkdir();
if (!destination.exists()) {
            destination.mkdir();
        }
String filePath = destination + File.separator + entry.getName();
File file = new File(filePath);
File file = new File(destination, entry.getName());
if (entry.isDirectory()) {
        file.mkdir();  // type 1
    } else {
        File parent = file.getParentFile();
        if (!parent.exists()) {
            parent.mkdir();  // type 2
        }
        extractFile(zipIn, file);
    }

Context

StackExchange Code Review Q#90966, answer score: 5

Revisions (0)

No revisions yet.