patternjavaMinor
Extracting ZIP, JAR and EPUB files
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
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:
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:
Don't form paths from strings if you don't have to
Instead of this:
It's simpler to use the 2-param constructor of
Bugs
Both branches of this
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:
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:
You can fix both of these by changing the
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.
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.txtYou 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.