All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.nuiton.util.ZipUtil Maven / Gradle / Ivy

There is a newer version: 3.1
Show newest version
/*
 * #%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 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 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; } }




  • © 2015 - 2024 Weber Informatics LLC | Privacy Policy