org.nuiton.util.ZipUtil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of nuiton-utils Show documentation
Show all versions of nuiton-utils Show documentation
Library of usefull classes to be used in any project.
/*
* #%L
* Nuiton Utils
* %%
* Copyright (C) 2004 - 2010 CodeLutin
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* .
* #L%
*/
package org.nuiton.util;
import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
/**
* Opérations sur des fichiers Zip. Compression et décompression avec ou
* sans filtres, scan des fichiers créés ou écrasés lors de la décompression...
*
* Created: 24 août 2006 10:13:35
*
* @author Benjamin Poussin - [email protected]
*
*/
public class ZipUtil {
/** Class logger. */
private static final Log log = LogFactory.getLog(ZipUtil.class);
/** Taille du buffer pour les lectures/écritures. */
private static final int BUFFER_SIZE = 8 * 1024;
/** Le séparateur de fichier en local. */
private static final String LOCAL_SEP = File.separator;
private static final String LOCAL_SEP_PATTERN = "\\".equals(LOCAL_SEP) ?
LOCAL_SEP + LOCAL_SEP : LOCAL_SEP;
/** Le séparateur zip. */
private static final String ZIP_SEP = "/";
private static final String ZIP_SEP_PATTERN = "/";
/** Accept all file pattern. */
protected static FileFilter ALL_FILE_FILTER = new FileFilter() {
public boolean accept(File pathname) {
return true;
}
};
/**
* Uncompress zipped file in targetDir.
*
* @param file the zip source file
* @param targetDir the destination directory
* @return return last entry name
* @throws IOException if any problem while uncompressing
*/
public static String uncompress(File file, File targetDir) throws IOException {
String result;
result = uncompressAndRename(file, targetDir, null, null);
return result;
}
/**
* Uncompress zipped stream in targetDir.
*
*
* @param stream the zip source stream, stream is closed before return
* @param targetDir the destination directory
* @return return last entry name
* @throws IOException if any problem while uncompressing
* @since 2.6.6
*/
public static String uncompress(InputStream stream, File targetDir) throws IOException {
String result = uncompressAndRename(stream, targetDir, null, null);
return result;
}
/**
* Uncompress zipped file in targetDir, and rename uncompressed file if
* necessary. If renameFrom or renameTo is null no renaming is done
*
* file in zip use / to separate directory and not begin with /
* each directory ended with /
*
* @param file the zip source file
* @param targetDir the destination directory
* @param renameFrom pattern to permit rename file before uncompress it
* @param renameTo new name for file if renameFrom is applicable to it
* you can use $1, $2, ... if you have '(' ')' in renameFrom
* @return return last entry name
* @throws IOException if any problem while uncompressing
*/
public static String uncompressAndRename(File file,
File targetDir,
String renameFrom,
String renameTo) throws IOException {
return uncompressAndRename(new FileInputStream(file), targetDir, renameFrom, renameTo);
}
/**
* Uncompress zipped stream in targetDir, and rename uncompressed file if
* necessary. If renameFrom or renameTo is null no renaming is done
*
* file in zip use / to separate directory and not begin with /
* each directory ended with /
*
* @param stream the zip source stream, stream is closed before return
* @param targetDir the destination directory
* @param renameFrom pattern to permit rename file before uncompress it
* @param renameTo new name for file if renameFrom is applicable to it
* you can use $1, $2, ... if you have '(' ')' in renameFrom
* @return return last entry name
* @throws IOException if any problem while uncompressing
* @since 2.6.6
*/
public static String uncompressAndRename(InputStream stream,
File targetDir,
String renameFrom,
String renameTo) throws IOException {
String result = "";
ZipInputStream in = new ZipInputStream(new BufferedInputStream(stream));
try {
ZipEntry entry;
while ((entry = in.getNextEntry()) != null) {
String name = entry.getName();
if (renameFrom != null && renameTo != null) {
name = name.replaceAll(renameFrom, renameTo);
if (log.isDebugEnabled()) {
log.debug("rename " + entry.getName() + " → " + name);
}
}
result = name;
File target = new File(targetDir, name);
if (entry.isDirectory()) {
FileUtil.createDirectoryIfNecessary(target);
} else {
FileUtil.createDirectoryIfNecessary(target.getParentFile());
OutputStream out = new BufferedOutputStream(new FileOutputStream(target));
try {
byte[] buffer = new byte[BUFFER_SIZE];
int len;
while ((len = in.read(buffer, 0, BUFFER_SIZE)) != -1) {
out.write(buffer, 0, len);
}
} finally {
out.close();
}
}
}
} finally {
in.close();
}
return result;
}
/**
* Compress 'includes' files in zipFile. If file in includes is directory
* only the directory is put in zipFile, not the file contained in directory
*
* @param zipFile the destination zip file
* @param root for all file in includes that is in this directory, then we
* remove this directory in zip entry name (aka -C for tar), can be null;
* @param includes the files to include in zip
* @throws IOException if any problem while compressing
*/
public static void compressFiles(File zipFile,
File root,
Collection includes) throws
IOException {
compressFiles(zipFile, root, includes, false);
}
/**
* Compress 'includes' files in zipFile. If file in includes is directory
* only the directory is put in zipFile, not the file contained in directory
*
* @param zipFile the destination zip file
* @param root for all file in includes that is in this directory, then we
* remove this directory in zip entry name (aka -C for tar), can be null;
* @param includes the files to include in zip
* @param createMD5 also create a MD5 file (zip name + .md5). MD5 file is created after zip.
* @throws IOException if any problem while compressing
*/
public static void compressFiles(File zipFile,
File root,
Collection includes,
boolean createMD5) throws IOException {
OutputStream oStream = new FileOutputStream(zipFile);
// if md5 creation flag
if (createMD5) {
oStream = new MD5OutputStream(oStream);
}
try {
ZipOutputStream zipOStream = new ZipOutputStream(oStream);
for (File file : includes) {
String entryName = toZipEntryName(root, file);
// Création d'une nouvelle entrée dans le zip
ZipEntry entry = new ZipEntry(entryName);
entry.setTime(file.lastModified());
zipOStream.putNextEntry(entry);
if (file.isFile() && file.canRead()) {
byte[] readBuffer = new byte[BUFFER_SIZE];
int bytesIn;
BufferedInputStream bis = new BufferedInputStream(
new FileInputStream(file), BUFFER_SIZE);
try {
while ((bytesIn =
bis.read(readBuffer, 0, BUFFER_SIZE)) != -1) {
zipOStream.write(readBuffer, 0, bytesIn);
}
} finally {
bis.close();
}
}
zipOStream.closeEntry();
}
zipOStream.close();
// if md5 creation flag
if (createMD5) {
String md5hash = StringUtil.asHex(((MD5OutputStream) oStream).hash());
File md5File = new File(zipFile.getAbsoluteFile() + ".md5");
FileUtils.write(md5File, md5hash);
}
} finally {
oStream.close();
}
}
/**
* If fileOrDirectory is directory Compress recursively all file in this
* directory, else if is just file compress one file.
*
* Entry result name in zip start at fileOrDirectory.
* example: if we compress /etc/apache, entry will be apache/http.conf, ...
*
* @param zipFile the target zip file
* @param fileOrDirectory the file or directory to compress
* @throws IOException if any problem while compressing
*/
public static void compress(File zipFile,
File fileOrDirectory) throws IOException {
compress(zipFile, fileOrDirectory, null, false);
}
/**
* If fileOrDirectory is directory Compress recursively all file in this
* directory, else if is just file compress one file.
*
* Entry result name in zip start at fileOrDirectory.
* example: if we compress /etc/apache, entry will be apache/http.conf, ...
*
* @param zipFile the target zip file
* @param fileOrDirectory the file or directory to compress
* @param filter used to accept file, if null, all file is accepted
* @throws IOException if any problem while compressing
*/
public static void compress(File zipFile,
File fileOrDirectory,
FileFilter filter) throws IOException {
compress(zipFile, fileOrDirectory, filter, false);
}
/**
* If fileOrDirectory is directory Compress recursively all file in this
* directory, else if is just file compress one file.
*
* Entry result name in zip start at fileOrDirectory.
* example: if we compress /etc/apache, entry will be apache/http.conf, ...
*
* @param zipFile the target zip file
* @param fileOrDirectory the file or directory to compress
* @param filter used to accept file, if null, all file is accepted
* @param createMD5 also create a MD5 file (zip name + .md5). MD5 file is created after zip.
* @throws IOException if any problem while compressing
*/
public static void compress(File zipFile,
File fileOrDirectory,
FileFilter filter,
boolean createMD5) throws IOException {
if (filter == null) {
filter = ALL_FILE_FILTER;
}
List files = new ArrayList();
if (fileOrDirectory.isDirectory()) {
files = FileUtil.getFilteredElements(fileOrDirectory, filter, true);
} else if (filter.accept(fileOrDirectory)) {
files.add(fileOrDirectory);
}
compressFiles(zipFile, fileOrDirectory.getParentFile(), files,
createMD5);
}
/**
* supprime le root du fichier
* Converti les '\' en '/' car les zip entry utilise des '/'
* ajoute un '/' a la fin pour les repertoires
* supprime le premier '/' si la chaine commence par un '/'
*
* @param root the root directory
* @param file the file to treate
* @return the zip entry name corresponding to the given {@code file}
* from {@code root} dir.
*/
private static String toZipEntryName(File root, File file) {
String result = file.getPath();
if (root != null) {
String rootPath = root.getPath();
if (result.startsWith(rootPath)) {
result = result.substring(rootPath.length());
}
}
result = result.replace('\\', '/');
if (file.isDirectory()) {
result += '/';
}
while (result.startsWith("/")) {
result = result.substring(1);
}
return result;
}
/**
* Scan a zipFile, and fill two lists of relative paths corresponding of
* zip entries.
* First list contains all entries to be added while a uncompress operation
* on the destination directory {@code targetDir}.
* Second list contains all entries to be overwritten while a uncompress
* operation on the destination directory {@code targetDir}.
*
* If {@code targetDir} is {@code null} we don't fill {@code existingFiles} list.
*
* @param zipFile location of the zip to scanZip
* @param targetDir location of destination for a uncompress operation.
* If {@code null} we don't test to
* find overwritten files.
* @param newFiles list of files to be added while a uncompress
* @param existingFiles list of files to be overwritten while a uncompress
* if the {@code targetDir},
* (only use if {@code targetDir} is not
* {@code null})
* @param excludeFilter used to exclude some files
* @param renameFrom {@link #uncompressAndRename(File, File, String, String)}
* @param renameTo {@link #uncompressAndRename(File, File, String, String)}
* @throws IOException if any exception while dealing with zipfile
*/
public static void scan(File zipFile,
File targetDir,
List newFiles,
List existingFiles,
FileFilter excludeFilter,
String renameFrom,
String renameTo) throws IOException {
ZipFile zip = null;
try {
zip = new ZipFile(zipFile);
boolean findExisting = targetDir != null && targetDir.exists();
boolean filter = findExisting && excludeFilter != null;
boolean rename = renameFrom != null && renameTo != null;
Enumeration extends ZipEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
String entryName = entries.nextElement().getName();
if (rename) {
entryName = entryName.replaceAll(renameFrom, renameTo);
}
String name = convertToLocalEntryName(entryName);
if (findExisting || filter) {
File file = new File(targetDir, name);
if (filter && excludeFilter.accept(file)) continue;
if (file.exists()) {
existingFiles.add(name);
continue;
}
}
newFiles.add(name);
}
} finally {
if (zip != null) {
zip.close();
}
}
}
@SuppressWarnings({"unchecked"})
public static List[] scanAndExplodeZip(File source,
File root,
FileFilter excludeFilter)
throws IOException {
List overwrittenFiles = new ArrayList();
List newFiles = new ArrayList();
// obtain list of relative paths (to add or overwrite)
scan(source, root, newFiles, overwrittenFiles, excludeFilter, null,
null);
return new List[]{newFiles, overwrittenFiles};
}
/**
* uncompress zipped file in targetDir.
*
* If {@code toTreate} if not null nor empty, we use it to filter
* entries to uncompress : it contains a list of relative local path of
* files to uncompress.
* Otherwise just delegate to {@link ZipUtil#uncompress(File, File)}.
*
* @param file location of zip file
* @param targetDir destination directory
* @param toTreate list of relative local path of entries to treate
* @param renameFrom {@link #uncompressAndRename(File, File, String, String)}
* @param renameTo {@link #uncompressAndRename(File, File, String, String)}
* @return return last entry name
* @throws IOException if nay exception while operation
*/
public static String uncompress(File file,
File targetDir,
List toTreate,
String renameFrom,
String renameTo) throws IOException {
if (toTreate == null || toTreate.isEmpty()) {
return uncompressAndRename(file, targetDir, renameFrom, renameTo);
}
boolean rename = renameFrom != null && renameTo != null;
String result = "";
ZipEntry entry;
ZipInputStream in = new ZipInputStream(new FileInputStream(file));
try {
while ((entry = in.getNextEntry()) != null) {
String name = entry.getName();
if (rename) {
result = convertToLocalEntryName(name.replaceAll(renameFrom,
renameTo));
} else {
result = convertToLocalEntryName(name);
}
if (log.isDebugEnabled()) {
log.debug("open [" + name + "] : " + result);
}
if (!toTreate.contains(result)) {
continue;
}
if (log.isDebugEnabled()) {
log.debug("copy [" + name + "] : " + result);
}
File target = new File(targetDir, result);
if (entry.isDirectory()) {
FileUtil.createDirectoryIfNecessary(target);
} else {
FileUtil.createDirectoryIfNecessary(target.getParentFile());
OutputStream out = new BufferedOutputStream(
new FileOutputStream(target));
try {
byte[] buffer = new byte[BUFFER_SIZE];
int len;
while ((len = in.read(buffer, 0, BUFFER_SIZE)) != -1) {
out.write(buffer, 0, len);
}
} finally {
out.close();
}
}
}
} finally {
in.close();
}
return result;
}
/**
* Unzip compressed archive and keep non excluded patterns.
*
* @param file archive file
* @param targetDir destination file
* @param excludes excludes pattern (pattern must match complete entry name including root folder)
* @throws IOException FIXME
*/
public static void uncompressFiltred(File file,
File targetDir,
String... excludes) throws IOException {
ZipFile zipFile = new ZipFile(file);
Enumeration extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
String name = entry.getName();
// add continue to break loop
boolean excludeEntry = false;
if (excludes != null) {
for (String exclude : excludes) {
if (name.matches(exclude)) {
excludeEntry = true;
}
}
}
if (!excludeEntry) {
File target = new File(targetDir, name);
if (entry.isDirectory()) {
FileUtil.createDirectoryIfNecessary(target);
} else {
// get inputstream only here
FileUtil.createDirectoryIfNecessary(target.getParentFile());
InputStream in = zipFile.getInputStream(entry);
try {
OutputStream out = new BufferedOutputStream(
new FileOutputStream(target));
try {
byte[] buffer = new byte[8 * 1024];
int len;
while ((len = in.read(buffer, 0, 8 * 1024)) != -1) {
out.write(buffer, 0, len);
}
} finally {
out.close();
}
} finally {
in.close();
}
}
}
}
}
/**
* Tests if the given file is a zip file.
*
* @param file the file to test
* @return {@code true} if the file is a valid zip file,
* {@code false} otherwise.
* @since 2.4.9
*/
public static boolean isZipFile(File file) {
boolean result = false;
try {
ZipFile zipFile = new ZipFile(file);
zipFile.close();
result = true;
} catch (IOException e) {
// silent test
}
return result;
}
protected static String convertToLocalEntryName(String txt) {
String s = txt.replaceAll(ZIP_SEP_PATTERN, LOCAL_SEP_PATTERN);
if (s.endsWith(ZIP_SEP)) {
s = s.substring(0, s.length() - 1);
}
return s;
}
}