ZipFiles.java [src/csip/utils] Revision:   Date:
/*
 * $Id: 2.8+7 ZipFiles.java 7dfec32a5e62 2023-11-27 od $
 *
 * This file is part of the Cloud Services Integration Platform (CSIP),
 * a Model-as-a-Service framework, API and application suite.
 *
 * 2012-2022, Olaf David and others, OMSLab, Colorado State University.
 *
 * OMSLab licenses this file to you under the MIT license.
 * See the LICENSE file in the project root for more information.
 */
package csip.utils;

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.file.attribute.FileTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.Deflater;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
import org.apache.commons.compress.archivers.ArchiveException;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOCase;
import org.apache.commons.io.filefilter.WildcardFileFilter;

/**
 * Basic (g)zip/(g)unzip operations.
 *
 * @author od
 */
public class ZipFiles {

  private static final int BUF_SIZE = 4096;

  private static final String ZIP_EXT = ".zip";
  private static final String GZIP_EXT = ".gz";


  /**
   * gzip a file. a new file with the added extension '.gz' will be created.
   *
   * @param file The file to gzip
   * @return the gzipped file
   * @throws IOException if it fails
   */
  public static File gzip(File file) throws IOException {
    File out = new File(file.toString() + GZIP_EXT);
    try (InputStream is = new BufferedInputStream(new FileInputStream(file)); GZIPOutputStream gzos = new GZIPOutputStream(new FileOutputStream(out), BUF_SIZE)) {
      byte[] buf = new byte[BUF_SIZE];
      int l;
      while ((l = is.read(buf)) != -1) {
        gzos.write(buf, 0, l);
      }
    }
    return out;
  }


  /**
   * gunzip a file.
   *
   * @param gzFile the file to gunzip, assumes a filename that has the .gz
   * extension added.
   * @return the gunzipped file
   * @throws IOException if unzipping fails
   */
  public static File gunzip(File gzFile) throws IOException {
    String fname = gzFile.getName();
    if (!fname.endsWith(GZIP_EXT))
      throw new IllegalArgumentException("Illegal file name: " + gzFile);

    File nf = new File(gzFile.getParentFile(), fname.substring(0, fname.length() - 3));
    try (InputStream is = new GZIPInputStream(new FileInputStream(gzFile), BUF_SIZE); OutputStream fos = new BufferedOutputStream(new FileOutputStream(nf))) {
      byte[] buf = new byte[BUF_SIZE];
      int l;
      while ((l = is.read(buf)) != -1) {
        fos.write(buf, 0, l);
      }
    }
    return nf;
  }


  /**
   * Zip a directory.
   *
   * @param dir the directory to zip
   * @return the zipped directory.
   * @throws IOException if the operation fails
   */
  public static File zip(File dir) throws IOException {
    return zip(dir, new File(dir.getParentFile(), dir.getName() + ZIP_EXT));
  }


  /**
   * Zips a dir into a file.
   *
   * @param dir the dir to zip
   * @param zipFile the target zipfile
   * @return the zipped file
   * @throws IOException if something goes wrong
   */
  public static File zip(File dir, File zipFile) throws IOException {
    try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) {
      zos.setLevel(Deflater.BEST_COMPRESSION);
      zip(dir, dir, zos, new byte[BUF_SIZE]);
    }
    return zipFile;
  }


  /**
   * Zip a collection of files.
   *
   * @param zipFile the target zip file
   * @param files the files to add
   * @return the zip file
   * @throws IOException if something goes wrong
   */
  public static File zip(File zipFile, Collection<File> files) throws IOException {
    return zip(zipFile, files.toArray(new Object[0]));
  }


  /**
   * Zip an array of files.
   *
   * @param zipFile the target zip file
   * @param files the files to put into the zip file, can be String, File or
   * File[]
   * @return the zip file
   * @throws IOException if something goes wrong
   */
  public static File zip(File zipFile, Object... files) throws IOException {
    try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) {
      zos.setLevel(Deflater.BEST_COMPRESSION);
      byte[] buf = new byte[BUF_SIZE];
      for (Object file : files) {
        File[] expfiles;

        if (file instanceof File) {
          File f = (File) file;
          expfiles = resolve(f);
        } else if (file instanceof File[]) {
          expfiles = (File[]) file;
        } else if (file instanceof String) {
          File f = new File(file.toString());
          expfiles = resolve(f);
        } else {
          throw new IllegalArgumentException("file: " + file.toString());
        }

        for (File expfile : expfiles) {
          try (InputStream is = new BufferedInputStream(new FileInputStream(expfile))) {
            ZipEntry ze = new ZipEntry(expfile.getName());
            ze.setLastModifiedTime(FileTime.from(expfile.lastModified(),
                TimeUnit.MILLISECONDS));
            zos.putNextEntry(ze);
            int len;
            while ((len = is.read(buf)) != -1) {
              zos.write(buf, 0, len);
            }
            zos.closeEntry();
          }
        }
      }
    }
    return zipFile;
  }


  private static void zip(File dir, File base, ZipOutputStream zos, byte[] buf) throws IOException {
    for (File file : dir.listFiles()) {
      if (file.isDirectory()) {
        zip(file, base, zos, buf);
      } else {
        try (InputStream is = new BufferedInputStream(new FileInputStream(file))) {
          ZipEntry ze = new ZipEntry(file.getPath().substring(base.getPath().length() + 1));
          ze.setLastModifiedTime(FileTime.from(file.lastModified(),
              TimeUnit.MILLISECONDS));
          zos.putNextEntry(ze);
          int l;
          while ((l = is.read(buf)) != -1) {
            zos.write(buf, 0, l);
          }
          zos.closeEntry();
        }
      }
    }
  }


  /**
   * Unzip a file.
   *
   * @param zip the file to unzip.
   * @return the directory
   * @throws IOException if operation fails
   */
  public static File unzip(File zip) throws IOException {
    return unzip(zip, zip.getParentFile());
  }
  

  /**
   * Unzip a file.
   *
   * @param zip the file to unzip
   * @param dir the directory.
   * @return the directory
   * @throws IOException if unzipping fails
   */
  public static File unzip(File zip, File dir) throws IOException {
    return unzip(zip, dir.getCanonicalPath() + "/");
  }


  public static File unzip(File zip, String dir) throws IOException {
    byte[] buf = new byte[BUF_SIZE];
    try (ZipFile archive = new ZipFile(zip)) {
      Enumeration<? extends ZipEntry> e = archive.entries();

      if (!dir.endsWith("/"))
        throw new IOException("target directory not ending with '/'.");

      while (e.hasMoreElements()) {
        ZipEntry entry = e.nextElement();
        File file = new File(dir, entry.getName());

        if (!file.getCanonicalPath().startsWith(dir))
          throw new IOException("Entry is outside of the target directory.");

//        System.out.println(file.getCanonicalPath());
        if (entry.isDirectory()) {
          file.mkdirs();
          continue;
        }

        if (!file.getParentFile().exists())
          file.getParentFile().mkdirs();

        try (InputStream is = archive.getInputStream(entry); OutputStream out = new BufferedOutputStream(new FileOutputStream(file))) {
          int l;
          while ((l = is.read(buf)) != -1) {
            out.write(buf, 0, l);
          }
        }
        if (entry.getLastModifiedTime() != null)
          file.setLastModified(entry.getLastModifiedTime().toMillis());
      }
    }
    return new File(dir);
  }


  static boolean isWildcardPattern(String p) {
    return p.contains("*") || p.contains("?");
  }


  static File[] resolve(File f) {
    if (isWildcardPattern(f.getName())) {
      FilenameFilter ff = new WildcardFileFilter(f.getName(), IOCase.INSENSITIVE);
      return f.getParentFile().listFiles(ff);
    } else {
      return new File[]{f};
    }
  }

}