/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.commons.lang;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Collection;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
/**
* Utils for ZIP.
*
* @author Eugene Voevodin
* @author Sergii Kabashniuk
*/
public class ZipUtils {
private static final int BUF_SIZE = 4096;
public static void zipDir(String parentPath, File dir, File zip, FilenameFilter filter) throws IOException {
if (!dir.isDirectory()) {
throw new IllegalArgumentException("Not a directory.");
}
if (!dir.getAbsolutePath().startsWith(parentPath)) {
throw new IllegalArgumentException("Invalid parent directory path " + parentPath);
}
if (filter == null) {
filter = IoUtil.ANY_FILTER;
}
try (ZipOutputStream zipOut = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zip)))) {
zipOut.setLevel(0); // TODO: move in parameters of method
addDirectoryRecursively(zipOut, parentPath, dir, filter);
zipOut.finish();
}
}
/**
* Create an output ZIP stream and add each file to that stream. Directory files are added recursively. The contents
* of the zip are written to the given file.
*
* @param zip
* The file to write the zip contents to.
* @param files
* The files to add to the zip stream.
* @throws IOException
*/
public static void zipFiles(File zip, File... files) throws IOException {
try (BufferedOutputStream bufferedOut = new BufferedOutputStream(new FileOutputStream(zip))) {
zipFiles(bufferedOut, files);
}
}
/**
* Create an output ZIP stream and add each file to that stream. Directory files are added recursively. The contents
* of the zip are written to the given stream.
*
* @param output
* The stream to write the zip contents to.
* @param files
* The files to add to the zip stream.
* @throws IOException
*/
public static void zipFiles(OutputStream output, File... files) throws IOException {
try (ZipOutputStream zipOut = new ZipOutputStream(output)) {
for (File f : files) {
if (f.isDirectory()) {
addDirectoryEntry(zipOut, f.getName());
final String parentPath = f.getParentFile().getAbsolutePath();
addDirectoryRecursively(zipOut, parentPath, f, IoUtil.ANY_FILTER);
} else if (f.isFile()) {
addFileEntry(zipOut, f.getName(), f);
}
}
}
}
private static void addDirectoryRecursively(ZipOutputStream zipOut, String parentPath, File dir, FilenameFilter filter)
throws IOException {
final int parentPathLength = parentPath.length() + 1;
final LinkedList<File> q = new LinkedList<>();
q.add(dir);
while (!q.isEmpty()) {
final File current = q.pop();
final File[] list = current.listFiles();
if (list != null) {
for (File f : list) {
if (filter.accept(current, f.getName())) {
final String entryName = f.getAbsolutePath().substring(parentPathLength).replace('\\', '/');
if (f.isDirectory()) {
addDirectoryEntry(zipOut, entryName);
q.push(f);
} else if (f.isFile()) {
addFileEntry(zipOut, entryName, f);
}
}
}
}
}
}
private static void addDirectoryEntry(ZipOutputStream zipOut, String entryName) throws IOException {
final ZipEntry zipEntry = new ZipEntry(entryName.endsWith("/") ? entryName : (entryName + '/'));
zipOut.putNextEntry(zipEntry);
zipOut.closeEntry();
}
private static void addFileEntry(ZipOutputStream zipOut, String entryName, File file) throws IOException {
final ZipEntry zipEntryEntry = new ZipEntry(entryName);
zipOut.putNextEntry(zipEntryEntry);
try (InputStream in = new BufferedInputStream(new FileInputStream(file))) {
final byte[] buf = new byte[BUF_SIZE];
int r;
while ((r = in.read(buf)) != -1) {
zipOut.write(buf, 0, r);
}
}
zipOut.closeEntry();
}
public static Collection<String> listEntries(File zip) throws IOException {
try (InputStream in = new FileInputStream(zip)) {
return listEntries(in);
}
}
public static Collection<String> listEntries(InputStream in) throws IOException {
final List<String> list = new LinkedList<>();
final ZipInputStream zipIn = new ZipInputStream(in);
ZipEntry zipEntry;
while ((zipEntry = zipIn.getNextEntry()) != null) {
if (!zipEntry.isDirectory()) {
list.add(zipEntry.getName());
}
zipIn.closeEntry();
}
return list;
}
public static void unzip(File zip, File targetDir) throws IOException {
try (InputStream in = new FileInputStream(zip)) {
unzip(in, targetDir);
}
}
public static void unzip(InputStream in, File targetDir) throws IOException {
final ZipInputStream zipIn = new ZipInputStream(in);
final byte[] b = new byte[BUF_SIZE];
ZipEntry zipEntry;
while ((zipEntry = zipIn.getNextEntry()) != null) {
final File file = new File(targetDir, zipEntry.getName());
if (!zipEntry.isDirectory()) {
final File parent = file.getParentFile();
if (!parent.exists()) {
if (!parent.mkdirs()) {
throw new IOException("Unable to create parent folder " + parent.getAbsolutePath());
}
}
try (FileOutputStream fos = new FileOutputStream(file)) {
int r;
while ((r = zipIn.read(b)) != -1) {
fos.write(b, 0, r);
}
}
} else {
if (!file.exists()) {
if (!file.mkdirs()) {
throw new IOException("Unable to create folder " + file.getAbsolutePath());
}
}
}
zipIn.closeEntry();
}
}
/**
* Provides streams to all resources matching {@code filter} criteria inside the archive.
*
* @param zip
* zip file to get resources from
* @param filter
* the search criteria
* @throws IOException
*/
public static void getResources(ZipFile zip, Pattern filter, Consumer<InputStream> consumer) throws IOException {
Enumeration<? extends ZipEntry> zipEntries = zip.entries();
while (zipEntries.hasMoreElements()) {
ZipEntry zipEntry = zipEntries.nextElement();
final String name = zipEntry.getName();
if (filter.matcher(name).matches()) {
try (InputStream in = zip.getInputStream(zipEntry)) {
consumer.accept(in);
}
}
}
}
/**
* Checks is specified file is zip file or not. Zip file <a href="http://en.wikipedia.org/wiki/Zip_(file_format)#File_headers">headers
* description</a>.
*/
public static boolean isZipFile(File file) throws IOException {
if (file.isDirectory()) {
return false;
}
// NOTE: little-indian bytes order!
final byte[] bytes = new byte[4];
try (FileInputStream fIn = new FileInputStream(file)) {
if (fIn.read(bytes) != bytes.length) {
return false;
}
}
ByteBuffer zipFileHeaderSignature = ByteBuffer.wrap(bytes);
zipFileHeaderSignature.order(ByteOrder.LITTLE_ENDIAN);
return 0x04034b50 == zipFileHeaderSignature.getInt();
}
private ZipUtils() {
}
}