content: package alien4cloud.utils;
  
  import java.io.*;
  import java.nio.charset.Charset;
  import java.nio.file.*;
  import java.nio.file.FileSystem;
  import java.nio.file.attribute.BasicFileAttributes;
  import java.security.DigestInputStream;
  import java.security.MessageDigest;
  import java.security.NoSuchAlgorithmException;
  import java.util.EnumSet;
  import java.util.List;
  import java.util.regex.Pattern;
  import java.util.zip.ZipEntry;
  import java.util.zip.ZipOutputStream;
  
  import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
  import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
  import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
  
  import com.google.common.base.Charsets;
  import com.google.common.collect.Lists;
  import com.google.common.io.BaseEncoding;
  import com.google.common.io.ByteStreams;
  import com.google.common.io.Closeables;
  
  import lombok.SneakyThrows;
  import lombok.extern.slf4j.Slf4j;
  
  @Slf4j
  public final class FileUtil {
      /**
       * Utility class should have private constructor.
       */
      private FileUtil() {
      }
  
      /**
       * Check if the file matching the given path is a zip file or not.
       * 
       * @param path The patch to check.
       */
      public static boolean isZipFile(Path path) {
          File f = path.toFile();
          if (f.isDirectory() || f.length() < 4) {
              return false;
          }
  
          try (DataInputStream inputStream = new DataInputStream(new FileInputStream(f))) {
              return inputStream.readInt() == 0x504b0304;
          } catch (FileNotFoundException e) {
              return false;
          } catch (IOException e) {
              return false;
          }
      }
  
      protected static void putZipEntry(ZipOutputStream zipOutputStream, ZipEntry zipEntry, Path file) throws IOException {
          zipOutputStream.putNextEntry(zipEntry);
          InputStream input = new BufferedInputStream(Files.newInputStream(file));
          try {
              ByteStreams.copy(input, zipOutputStream);
              zipOutputStream.closeEntry();
          } finally {
              input.close();
          }
      }
  
      protected static void putTarEntry(TarArchiveOutputStream tarOutputStream, TarArchiveEntry tarEntry, Path file) throws IOException {
          tarEntry.setSize(Files.size(file));
          tarOutputStream.putArchiveEntry(tarEntry);
          InputStream input = new BufferedInputStream(Files.newInputStream(file));
          try {
              ByteStreams.copy(input, tarOutputStream);
              tarOutputStream.closeArchiveEntry();
          } finally {
              input.close();
          }
      }
  
      public static String getChildEntryRelativePath(Path base, Path child, boolean convertToLinuxPath) {
          String path = base.toUri().relativize(child.toUri()).getPath();
          if (convertToLinuxPath && !"/".equals(base.getFileSystem().getSeparator())) {
              return path.replace(base.getFileSystem().getSeparator(), "/");
          } else {
              return path;
          }
      }
  
      /**
       * Recursively zip file and directory
       *
       * @param inputPath file path can be directory
       * @param outputPath where to put the zip
       * @throws IOException when IO error happened
       */
      public static void zip(Path inputPath, Path outputPath) throws IOException {
          if (!Files.exists(inputPath)) {
              throw new FileNotFoundException("File not found " + inputPath);
          }
          touch(outputPath);
          ZipOutputStream zipOutputStream = new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(outputPath)));
          try {
              if (!Files.isDirectory(inputPath)) {
                  putZipEntry(zipOutputStream, new ZipEntry(inputPath.getFileName().toString()), inputPath);
              } else {
                  Files.walkFileTree(inputPath, new ZipDirWalker(inputPath, zipOutputStream));
              }
              zipOutputStream.flush();
          } catch (IOException e) {
             log.error ("zip error", e);
             throw e;
          } finally {
              Closeables.close(zipOutputStream, true);
          }
      }
  
      /**
       * Recursively tar file
       *
       * @param inputPath file path can be directory
       * @param outputPath where to put the archived file
       * @param childrenOnly if inputPath is directory and if childrenOnly is true, the archive will contain all of its children, else the archive contains unique
       *            entry which is the inputPath itself
       * @param gZipped compress with gzip algorithm
       */
      public static void tar(Path inputPath, Path outputPath, boolean gZipped, boolean childrenOnly) throws IOException {
          if (!Files.exists(inputPath)) {
              throw new FileNotFoundException("File not found " + inputPath);
          }
          touch(outputPath);
          OutputStream outputStream = new BufferedOutputStream(Files.newOutputStream(outputPath));
          if (gZipped) {
              outputStream = new GzipCompressorOutputStream(outputStream);
          }
          TarArchiveOutputStream tarArchiveOutputStream = new TarArchiveOutputStream(outputStream);
          tarArchiveOutputStream.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
          try {
              if (!Files.isDirectory(inputPath)) {
                  putTarEntry(tarArchiveOutputStream, new TarArchiveEntry(inputPath.getFileName().toString()), inputPath);
              } else {
                  Path sourcePath = inputPath;
                  if (!childrenOnly) {
                      // In order to have the dossier as the root entry
                      sourcePath = inputPath.getParent();
                  }
                  Files.walkFileTree(inputPath, new TarDirWalker(sourcePath, tarArchiveOutputStream));
              }
              tarArchiveOutputStream.flush();
          } finally {
              Closeables.close(tarArchiveOutputStream, true);
          }
      }
  
      /**
       * Unzip a zip file to a destination folder.
       *
       * @param zipFile The zip file to unzip.
       * @param destination The destination folder in which to save the file.
       * @throws IOException In case something fails.
       */
      public static void unzip(final Path zipFile, final Path destination) throws IOException {
          try (FileSystem zipFS = FileSystems.newFileSystem(zipFile, (ClassLoader)null)) {
              final Path root = zipFS.getPath("/");
              copy(root, destination, StandardCopyOption.REPLACE_EXISTING);
          }
      }
  
      public static String relativizePath(Path root, Path child) {
          String childPath = child.toAbsolutePath().toString();
          String rootPath = root.toAbsolutePath().toString();
          if (childPath.equals(rootPath)) {
              return "";
          }
          int indexOfRootInChild = childPath.indexOf(rootPath);
          if (indexOfRootInChild != 0) {
              throw new IllegalArgumentException("Child path " + childPath + "is not beginning with root path " + rootPath);
          }
          String relativizedPath = childPath.substring(rootPath.length(), childPath.length());
          while (relativizedPath.startsWith(root.getFileSystem().getSeparator())) {
              relativizedPath = relativizedPath.substring(1);
          }
          return relativizedPath;
      }
  
      public static void copy(final Path source, final Path destination, final CopyOption... options) throws IOException {
          copy(source, destination, false, options);
      }
  
      public static void copy(final Path source, final Path destination, final boolean ignoreExisting, final CopyOption... options) throws IOException {
          if (Files.notExists(destination)) {
              Files.createDirectories(destination);
          }
  
          Files.walkFileTree(source, new SimpleFileVisitor<Path>() {
              @Override
              public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                  String fileRelativePath = relativizePath(source, file);
                  Path destFile = destination.resolve(fileRelativePath);
                  if (ignoreExisting && destFile.toFile().exists()) {
                      return FileVisitResult.CONTINUE;
                  }
                  Files.copy(file, destFile, options);
                  return FileVisitResult.CONTINUE;
              }
  
              @Override
              public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                  String dirRelativePath = relativizePath(source, dir);
                  Path destDir = destination.resolve(dirRelativePath);
                  if (!(ignoreExisting && destDir.toFile().exists())) {
                      Files.createDirectories(destDir);
                  }
                  return FileVisitResult.CONTINUE;
              }
          });
      }
  
      private static class EraserWalker extends SimpleFileVisitor<Path> {
          private Path[] keepPath;
  
          private EraserWalker(Path... keepPath) {
              this.keepPath = keepPath;
          }
  
          private boolean isKeepPath(Path path) {
              for (Path keep : keepPath) {
                  if (path.equals(keep)) {
                      return true;
                  }
              }
              return false;
          }
  
          @Override
          public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
              if (isKeepPath(dir)) {
                  return FileVisitResult.SKIP_SUBTREE;
              }
              return super.preVisitDirectory(dir, attrs);
          }
  
          @Override
          public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
              if (!isKeepPath(file)) {
                  file.toFile().delete();
              }
              return FileVisitResult.CONTINUE;
          }
  
          @Override
          public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
              if (exc == null) {
                  if (!isKeepPath(dir)) {
                      dir.toFile().delete();
                  }
                  return FileVisitResult.CONTINUE;
              }
              throw exc;
          }
      }
  
      /**
       * Recursively delete file and directory but the specified paths
       *
       * @param deletePath file path can be directory
       * @param keepPath paths not to be deleted.
       * @throws IOException when IO error happened
       */
      public static void delete(Path deletePath, Path... keepPath) throws IOException {
          if (!Files.isDirectory(deletePath)) {
              deletePath.toFile().delete();
              return;
          }
          Files.walkFileTree(deletePath, new EraserWalker(keepPath));
      }
  
      /**
       * Read all files bytes and create a string.
       *
       * @param path The file's path.
       * @param charset The charset to use to convert the bytes to string.
       * @return A string from the file content.
       * @throws IOException In case the file cannot be read.
       */
      public static String readTextFile(Path path, Charset charset) throws IOException {
          return new String(Files.readAllBytes(path), charset);
      }
  
      /**
       * Read all files bytes and create a string using UTF_8 charset.
       *
       * @param path The file's path.
       * @return A string from the file content.
       * @throws IOException In case the file cannot be read.
       */
      public static String readTextFile(Path path) throws IOException {
          return readTextFile(path, Charsets.UTF_8);
      }
  
      /**
       * Create a directory from path if it does not exist
       *
       * @param directoryPath
       * @throws IOException
       */
      public static Path createDirectoryIfNotExists(String directoryPath) throws IOException {
          Path tempPath = Paths.get(directoryPath);
          if (!Files.exists(tempPath)) {
              log.info("Temp directory for uploaded file do not exist, trying to create [" + directoryPath + "]");
              Files.createDirectories(tempPath);
          }
          return tempPath;
      }
  
      /**
       * Create an empty file at the given path
       *
       * @param path to create file
       * @throws IOException
       */
      public static boolean touch(Path path) throws IOException {
          Path parentDir = path.getParent();
          if (!Files.exists(parentDir)) {
              Files.createDirectories(parentDir);
              return true;
          }
          if (!Files.exists(path)) {
              Files.createFile(path);
              return true;
          }
          return false;
      }
  
      /**
       * Recursively list all files of which name is matching the pattern
       * 
       * @param directory the start point
       * @param matcher the regex expression to match files
       * @return list of files of which name is matching the pattern
       * @throws IOException
       */
      public static List<Path> listFiles(Path directory, String matcher) throws IOException {
          return listFiles(directory, matcher, Integer.MAX_VALUE);
      }
  
      /**
       * List all files of which name is matching the pattern
       *
       * @param directory the start point
       * @param matcher the regex expression to match files
       * @param depth depth of recursion
       * @return list of files of which name is matching the pattern
       * @throws IOException
       */
      public static List<Path> listFiles(Path directory, String matcher, int depth) throws IOException {
          final Pattern pattern = Pattern.compile(matcher);
          final List<Path> files = Lists.newArrayList();
          Files.walkFileTree(directory, EnumSet.noneOf(FileVisitOption.class), depth, new SimpleFileVisitor<Path>() {
              @Override
              public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                  if (pattern.matcher(file.toString()).matches()) {
                      files.add(file);
                  }
                  return super.visitFile(file, attrs);
              }
          });
          return files;
      }
  
      /**
       * Computes a SHA-1 checksum on a single file.
       * 
       * @param path The path of the file for which to compute the SHA-1 hash.
       * @return The SHA-1 hash string.
       */
      @SneakyThrows({ Exception.class })
      public static String getSHA1Checksum(Path path) {
          if (!Files.exists(path)) {
              throw new FileNotFoundException("File not found in hash processor" + path);
          }
          MessageDigest digest = MessageDigest.getInstance("SHA1");
          addFileToDigest(digest, path);
          return BaseEncoding.base16().encode(digest.digest());
          //return DatatypeConverter.printHexBinary(digest.digest());
      }
  
      /**
       * Computes a SHA-1 checksum on a directory. The checksum ignores hidden files.
       *
       * @param rootPath The root path for which to compute SHA-1 on every sub files and folders.
       * @return The SHA-1 hash string.
       */
      @SneakyThrows({ IOException.class })
      public static String deepSHA1(Path rootPath) {
          if (isZipFile(rootPath)) {
              try (FileSystem csarFS = FileSystems.newFileSystem(rootPath, (ClassLoader)null)) {
                  Path innerZipPath = csarFS.getPath(FileSystems.getDefault().getSeparator());
                  return computeDirectoryHash(innerZipPath);
              }
          } else if (Files.isRegularFile(rootPath)) {
              return getSHA1Checksum(rootPath);
          } else if (Files.isDirectory(rootPath)) {
              return computeDirectoryHash(rootPath);
          }
          throw new FileNotFoundException("Unable to compute hash for file " + rootPath);
      }
  
      @SneakyThrows({ IOException.class, NoSuchAlgorithmException.class })
      private static String computeDirectoryHash(Path rootPath) {
          MessageDigest digest = MessageDigest.getInstance("SHA1");
          Files.walk(rootPath).filter(FileUtil::isNotHidden).filter(Files::isRegularFile).forEach(path -> addFileToDigest(digest, path));
          return BaseEncoding.base16().encode(digest.digest());
          //return DatatypeConverter.printHexBinary(digest.digest());
  
      }
  
      @SneakyThrows({ IOException.class })
      private static void addFileToDigest(MessageDigest digest, Path path) {
          try (InputStream digestInputStream = new DigestInputStream(new BufferedInputStream(Files.newInputStream(path)), digest)) {
              while (digestInputStream.read() != -1) {
              }
          }
      }
  
      @SneakyThrows({ IOException.class })
      private static boolean isNotHidden(Path path) {
          return !Files.isHidden(path);
      }
  }
  