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

com.igormaznitsa.mvnjlink.utils.ArchUtils Maven / Gradle / Ivy

Go to download

There is a newer version: 1.2.4
Show newest version
/*
 * Copyright 2019 Igor Maznitsa.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.igormaznitsa.mvnjlink.utils;

import com.igormaznitsa.meta.annotation.MustNotContainNull;
import org.apache.commons.compress.archivers.ArchiveEntry;
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.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;
import org.apache.maven.plugin.logging.Log;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.GZIPInputStream;

import static com.igormaznitsa.meta.common.utils.Assertions.assertNotNull;
import static com.igormaznitsa.mvnjlink.utils.SystemUtils.closeCloseable;
import static java.nio.file.Files.*;
import static java.nio.file.Paths.get;
import static java.util.Locale.ENGLISH;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Stream.of;
import static org.apache.commons.io.FilenameUtils.normalize;
import static org.apache.commons.io.IOUtils.copy;

public final class ArchUtils {

  private static final ArchiveStreamFactory ARCHIVE_STREAM_FACTORY = new ArchiveStreamFactory();

  private ArchUtils() {

  }

  /**
   * Find shortes directory path in archive
   *
   * @param archiveFile
   * @return
   * @throws IOException
   */
  @Nullable
  public static final String findShortestDirectory(@Nonnull final Path archiveFile) throws IOException {
    final String lcArchiveFileName = assertNotNull(archiveFile.getFileName()).toString().toLowerCase(ENGLISH);

    final ArchEntryGetter entryGetter;

    final ZipFile zipFile;
    final ArchiveInputStream archiveInputStream;

    if (lcArchiveFileName.endsWith(".zip")) {
      zipFile = new ZipFile(archiveFile.toFile());
      archiveInputStream = null;

      entryGetter = new ArchEntryGetter() {
        private final Enumeration iterator = zipFile.getEntries();

        @Nullable
        @Override
        public ArchiveEntry getNextEntry() throws IOException {
          ArchiveEntry result = null;
          if (this.iterator.hasMoreElements()) {
            result = this.iterator.nextElement();
          }
          return result;
        }
      };
    } else {
      zipFile = null;
      try {
        if (lcArchiveFileName.endsWith(".tar.gz")) {
          archiveInputStream = new TarArchiveInputStream(new GZIPInputStream(new BufferedInputStream(newInputStream(archiveFile))));

          entryGetter = new ArchEntryGetter() {
            @Nullable
            @Override
            public ArchiveEntry getNextEntry() throws IOException {
              final TarArchiveInputStream tarInputStream = (TarArchiveInputStream) archiveInputStream;
              return tarInputStream.getNextTarEntry();
            }
          };

        } else {
          archiveInputStream = ARCHIVE_STREAM_FACTORY.createArchiveInputStream(new BufferedInputStream(newInputStream(archiveFile)));

          entryGetter = new ArchEntryGetter() {
            @Nullable
            @Override
            public ArchiveEntry getNextEntry() throws IOException {
              return archiveInputStream.getNextEntry();
            }
          };
        }

      } catch (ArchiveException ex) {
        throw new IOException("Can't recognize or read archive file : " + archiveFile, ex);
      } catch (CantReadArchiveEntryException ex) {
        throw new IOException("Can't read entry from archive file : " + archiveFile, ex);
      }
    }

    String result = null;
    while (!Thread.currentThread().isInterrupted()) {
      final ArchiveEntry entry = entryGetter.getNextEntry();
      if (entry == null) {
        break;
      }
      String path = entry.getName();
      boolean dotRootPrefix = false;
      if (path.startsWith("./")) {
        path = path.substring(2);
        dotRootPrefix = true;
      }
      final int separator = path.indexOf('/');
      if (separator >= 0) {
        result = path.substring(0, separator);
        if (dotRootPrefix) {
          result = "./" + result;
        }
      }
    }

    closeCloseable(archiveInputStream, null);
    closeCloseable(zipFile, null);

    return result;
  }

  /**
   * Unpack whole archive or some its folders into a folder.
   *
   * @param logger            maven logger for logging, must not be null
   * @param tryMakeExecutable true if to make bin files executable ones
   * @param archiveFile       the archive to be unpacked
   * @param destinationFolder the destination folder for unpacking
   * @param foldersToUnpack   folders which content should be extracted
   * @return number of extracted files
   * @throws IOException it will be thrown for error in unpack process
   */
  public static int unpackArchiveFile(@Nonnull final Log logger, final boolean tryMakeExecutable, @Nonnull final Path archiveFile, @Nonnull final Path destinationFolder,
                                      @Nonnull @MustNotContainNull final String... foldersToUnpack) throws IOException {
    final String lcArchiveFileName = assertNotNull(archiveFile.getFileName()).toString().toLowerCase(ENGLISH);

    final ArchEntryGetter entryGetter;

    final ZipFile zipFile;
    final ArchiveInputStream archiveInputStream;

    if (lcArchiveFileName.endsWith(".zip")) {
      logger.debug("Detected ZIP archive");
      zipFile = new ZipFile(archiveFile.toFile());
      archiveInputStream = null;

      entryGetter = new ArchEntryGetter() {
        private final Enumeration iterator = zipFile.getEntries();

        @Nullable
        @Override
        public ArchiveEntry getNextEntry() throws IOException {
          ArchiveEntry result = null;
          if (this.iterator.hasMoreElements()) {
            result = this.iterator.nextElement();
          }
          return result;
        }
      };
    } else {
      zipFile = null;
      try {
        if (lcArchiveFileName.endsWith(".tar.gz")) {
          logger.debug("Detected TAR.GZ archive");

          archiveInputStream = new TarArchiveInputStream(new GZIPInputStream(new BufferedInputStream(newInputStream(archiveFile))));

          entryGetter = new ArchEntryGetter() {
            @Nullable
            @Override
            public ArchiveEntry getNextEntry() throws IOException {
              final TarArchiveInputStream tarInputStream = (TarArchiveInputStream) archiveInputStream;
              return tarInputStream.getNextTarEntry();
            }
          };

        } else {
          logger.debug("Detected OTHER archive");
          archiveInputStream = ARCHIVE_STREAM_FACTORY.createArchiveInputStream(new BufferedInputStream(newInputStream(archiveFile)));
          logger.debug("Created archive stream : " + archiveInputStream.getClass().getName());

          entryGetter = new ArchEntryGetter() {
            @Nullable
            @Override
            public ArchiveEntry getNextEntry() throws IOException {
              return archiveInputStream.getNextEntry();
            }
          };
        }

      } catch (ArchiveException ex) {
        throw new IOException("Can't recognize or read archive file : " + archiveFile, ex);
      } catch (CantReadArchiveEntryException ex) {
        throw new IOException("Can't read entry from archive file : " + archiveFile, ex);
      }
    }

    try {
      final List normalizedFolders = of(foldersToUnpack).map(x -> normalize(x, true) + '/').collect(toList());

      int unpackedFilesCounter = 0;

      while (!Thread.currentThread().isInterrupted()) {
        final ArchiveEntry entry = entryGetter.getNextEntry();
        if (entry == null) {
          break;
        }
        logger.debug("Unpacking entry: " + entry.getName());

        final String normalizedPath = normalize(entry.getName(), true);

        if (normalizedFolders.isEmpty() || normalizedFolders.stream().anyMatch(normalizedPath::startsWith)) {
          final String normalizedFolder = normalizedFolders.stream().filter(normalizedPath::startsWith).findFirst().orElse("");
          final Path targetFile = get(destinationFolder.toString(), normalizedPath.substring(normalizedFolder.length()));

          if (entry.isDirectory()) {
            logger.debug("Folder : " + normalizedPath);
            if (!exists(targetFile)) {
              createDirectories(targetFile);
            }
          } else {
            final Path parent = targetFile.getParent();

            if (parent != null && !isDirectory(parent)) {
              createDirectories(parent);
            }

            try (final OutputStream fos = newOutputStream(targetFile)) {
              if (zipFile != null) {
                logger.debug("Unpacking ZIP entry : " + normalizedPath);

                try (final InputStream zipEntryInStream = zipFile.getInputStream((ZipArchiveEntry) entry)) {
                  if (copy(zipEntryInStream, fos) != entry.getSize()) {
                    throw new IOException("Can't unpack file, illegal unpacked length : " + entry.getName());
                  }
                }
              } else {
                logger.debug("Unpacking archive entry : " + normalizedPath);

                if (!archiveInputStream.canReadEntryData(entry)) {
                  throw new IOException("Can't read archive entry data : " + normalizedPath);
                }
                if (copy(archiveInputStream, fos) != entry.getSize()) {
                  throw new IOException("Can't unpack file, illegal unpacked length : " + entry.getName());
                }
              }
            }

            if (tryMakeExecutable) {
              final String name = assertNotNull(targetFile.getFileName()).toString().toLowerCase(ENGLISH);
              if (Files.size(targetFile) > 0 && (name.endsWith(".bat")
                  || name.endsWith(".cmd")
                  || name.endsWith(".exe")
                  || name.endsWith(".sh")
                  || !name.contains("."))) {
                if (!targetFile.toFile().setExecutable(true, true)) {
                  logger.warn("Can't make executable : " + targetFile);
                }
              }
            }

            unpackedFilesCounter++;
          }
        } else {
          logger.debug("Archive entry " + normalizedPath + " ignored");
        }
      }
      return unpackedFilesCounter;
    } finally {
      closeCloseable(zipFile, logger);
      closeCloseable(archiveInputStream, logger);
    }
  }

  private interface ArchEntryGetter {

    @Nullable
    ArchiveEntry getNextEntry() throws IOException;
  }

  public static final class CantReadArchiveEntryException extends RuntimeException {
    public CantReadArchiveEntryException(@Nullable final Throwable cause) {
      super("Can't read archive entry for exception", cause);
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy