com.cloudbees.clickstack.util.Files2.java Source code

Java tutorial

Introduction

Here is the source code for com.cloudbees.clickstack.util.Files2.java

Source

/*
 * Copyright 2010-2013, the original author or authors
 *
 * 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.cloudbees.clickstack.util;

import com.cloudbees.clickstack.util.exception.RuntimeIOException;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.apache.commons.compress.utils.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.*;

/**
 * @author <a href="mailto:cleclerc@cloudbees.com">Cyrille Le Clerc</a>
 */
public class Files2 {
    final static Set<PosixFilePermission> PERMISSION_R = Collections
            .unmodifiableSet(PosixFilePermissions.fromString("rw-r-----")); // grant 'w' to owner
    final static Set<PosixFilePermission> PERMISSION_RX = Collections
            .unmodifiableSet(PosixFilePermissions.fromString("rwxr-x---")); // grant 'w' to owner
    final static Set<PosixFilePermission> PERMISSION_RW = Collections
            .unmodifiableSet(PosixFilePermissions.fromString("rw-rw----"));
    final static Set<PosixFilePermission> PERMISSION_RWX = Collections
            .unmodifiableSet(PosixFilePermissions.fromString("rwxrwx---"));
    final static Set<PosixFilePermission> PERMISSION_750 = Collections
            .unmodifiableSet(PosixFilePermissions.fromString("rwxr-x---"));
    final static Set<PosixFilePermission> PERMISSION_770 = Collections
            .unmodifiableSet(PosixFilePermissions.fromString("rwxrwx---"));
    final static Set<PosixFilePermission> PERMISSION_640 = Collections
            .unmodifiableSet(PosixFilePermissions.fromString("rw-r-----"));
    private static final Logger logger = LoggerFactory.getLogger(Files2.class);

    /**
     * Delete given {@code dir} and its content ({@code rm -rf}).
     *
     * @param dir
     * @throws RuntimeIOException
     */
    public static void deleteDirectory(@Nonnull Path dir) throws RuntimeIOException {
        try {
            Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {

                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {

                    logger.trace("Delete file: {} ...", file);
                    Files.delete(file);
                    return FileVisitResult.CONTINUE;
                }

                @Override
                public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {

                    if (exc == null) {
                        logger.trace("Delete dir: {} ...", dir);
                        Files.delete(dir);
                        return FileVisitResult.CONTINUE;
                    } else {
                        throw exc;
                    }
                }

            });
        } catch (IOException e) {
            throw new RuntimeIOException("Exception deleting '" + dir + "'", e);
        }
    }

    public static void chmodReadOnly(@Nonnull Path path) throws RuntimeIOException {

        SimpleFileVisitor<Path> setReadOnlyFileVisitor = new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                if (Files.isDirectory(file)) {
                    throw new IllegalStateException("no dir expected here");
                } else {
                    Files.setPosixFilePermissions(file, PERMISSION_R);
                }
                return super.visitFile(file, attrs);
            }

            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                Files.setPosixFilePermissions(dir, PERMISSION_RX);
                return super.preVisitDirectory(dir, attrs);
            }
        };
        try {
            Files.walkFileTree(path, setReadOnlyFileVisitor);
        } catch (IOException e) {
            throw new RuntimeIOException("Exception changing permissions to readonly for " + path, e);
        }
    }

    public static void chmodSetReadOnly(@Nonnull Path path) throws RuntimeIOException {
        chmodOverwritePermissions(path, PERMISSION_R, PERMISSION_RX);
    }

    public static void chmodAddReadExecute(@Nonnull Path path) throws RuntimeIOException {
        chmodAddPermissions(path, PERMISSION_RX, PERMISSION_RX);
    }

    public static void chmodAddReadWrite(@Nonnull Path path) throws RuntimeIOException {
        chmodAddPermissions(path, PERMISSION_RW, PERMISSION_RWX);
    }

    /**
     * @deprecated use {@link #chmodAddReadExecute(java.nio.file.Path)}
     */
    @Deprecated
    public static void chmodReadExecute(Path path) throws RuntimeIOException {
        chmodAddReadExecute(path);
    }

    /**
     * @deprecated use {@link #chmodAddReadWrite(java.nio.file.Path)}
     */
    @Deprecated
    public static void chmodReadWrite(Path path) throws RuntimeIOException {
        chmodAddReadWrite(path);
    }

    /**
     * Returns a zip file system
     *
     * @param zipFile to construct the file system from
     * @param create  true if the zip file should be created
     * @return a zip file system
     * @throws java.io.IOException
     */
    private static FileSystem createZipFileSystem(Path zipFile, boolean create) throws IOException {
        // convert the filename to a URI
        final URI uri = URI.create("jar:file:" + zipFile.toUri().getPath());

        final Map<String, String> env = new HashMap<>();
        if (create) {
            env.put("create", "true");
        }
        return FileSystems.newFileSystem(uri, env);
    }

    /**
     * Unzips the specified zip file to the specified destination directory.
     * Replaces any files in the destination, if they already exist.
     *
     * @param zipFilename the name of the zip file to extract
     * @param destDirname the directory to unzip to
     * @throws RuntimeIOException
     */
    public static void unzip(String zipFilename, String destDirname) throws RuntimeIOException {

        Path zipFile = Paths.get(zipFilename);
        Path destDir = Paths.get(destDirname);
        unzip(zipFile, destDir);

    }

    /**
     * Copy the given {@code zipSubFilePath} of the given {@code zipFile} into the {@code destDir} if this sub file exists.
     *
     * @param zipFile        the source zip file (e.g. {@code path/to/app.war}
     * @param zipSubFilePath sub file path in the zip file (e.g. {@code /WEB-INF/web.xml}
     * @param destDir
     * @throws RuntimeIOException
     */
    @Nullable
    public static Path unzipSubFileIfExists(@Nonnull Path zipFile, @Nonnull String zipSubFilePath,
            @Nonnull final Path destDir) throws RuntimeIOException {
        return unzipSubFileIfExists(zipFile, zipSubFilePath, destDir, false);
    }

    /**
     * Copy the given {@code zipSubFilePath} of the given {@code zipFile} into the {@code destDir} if this sub file exists.
     *
     * @param zipFile        the source zip file (e.g. {@code path/to/app.war}
     * @param zipSubFilePath sub file path in the zip file (e.g. {@code /WEB-INF/web.xml}
     * @param destDir
     * @param trimSourcePath indicates if the source path should be trimmed creating the dest file (e.g. should {@code WEB-INF/web.xml} be copied as
     *                       {@code dest-dir/WEB-INF/web.xml} or as {@code dest-dir/web.xml})
     * @throws RuntimeIOException
     */
    @Nullable
    public static Path unzipSubFileIfExists(@Nonnull Path zipFile, @Nonnull String zipSubFilePath,
            @Nonnull final Path destDir, boolean trimSourcePath) throws RuntimeIOException {
        try {
            //if the destination doesn't exist, create it
            if (Files.notExists(destDir)) {
                logger.trace("Create dir: {}", destDir);
                Files.createDirectories(destDir);
            }

            try (FileSystem zipFileSystem = createZipFileSystem(zipFile, false)) {
                final Path root = zipFileSystem.getPath("/");

                Path subFile = root.resolve(zipSubFilePath);
                if (Files.exists(subFile)) {
                    // make file path relative
                    Path destFile;
                    if (trimSourcePath) {
                        destFile = destDir.resolve(subFile.getFileName().toString());
                    } else {
                        if (Strings2.beginWith(zipSubFilePath, "/")) {
                            destFile = destDir.resolve("." + zipSubFilePath);
                        } else {
                            destFile = destDir.resolve(zipSubFilePath);
                        }
                    }
                    // create parent dirs if needed
                    Files.createDirectories(destFile.getParent());
                    // copy
                    return Files.copy(subFile, destFile, StandardCopyOption.REPLACE_EXISTING,
                            StandardCopyOption.COPY_ATTRIBUTES);
                } else {
                    return null;
                }
            }
        } catch (IOException e) {
            throw new RuntimeIOException("Exception expanding " + zipFile + ":" + zipSubFilePath + " to " + destDir,
                    e);
        }
    }

    /**
     * Copy every file of given {@code zipFile} beginning with given {@code zipSubPath} to {@code destDir}
     *
     * @param zipFile
     * @param zipSubPath must start with a "/"
     * @param destDir
     * @throws RuntimeIOException
     */
    public static void unzipSubDirectoryIfExists(@Nonnull Path zipFile, @Nonnull String zipSubPath,
            @Nonnull final Path destDir) throws RuntimeIOException {
        Preconditions.checkArgument(zipSubPath.startsWith("/"), "zipSubPath '%s' must start with a '/'",
                zipSubPath);
        try {
            //if the destination doesn't exist, create it
            if (Files.notExists(destDir)) {
                logger.trace("Create dir: {}", destDir);
                Files.createDirectories(destDir);
            }

            try (FileSystem zipFileSystem = createZipFileSystem(zipFile, false)) {
                final Path root = zipFileSystem.getPath(zipSubPath);
                if (Files.notExists(root)) {
                    logger.trace("Zip sub path {} does not exist in {}", zipSubPath, zipFile);
                    return;
                }

                //walk the zip file tree and copy files to the destination
                Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
                    @Override
                    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                        try {
                            final Path destFile = Paths.get(destDir.toString(), root.relativize(file).toString());
                            logger.trace("Extract file {} to {} as {}", file, destDir, destFile);
                            Files.copy(file, destFile, StandardCopyOption.REPLACE_EXISTING,
                                    StandardCopyOption.COPY_ATTRIBUTES);
                        } catch (IOException | RuntimeException e) {
                            logger.warn("Exception copying file '" + file + "' with root '" + root
                                    + "' to destDir '" + destDir + "', ignore file", e);
                        }
                        return FileVisitResult.CONTINUE;
                    }

                    @Override
                    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
                            throws IOException {
                        dir.relativize(root).toString();
                        final Path dirToCreate = Paths.get(destDir.toString(), root.relativize(dir).toString());

                        if (Files.notExists(dirToCreate)) {
                            logger.trace("Create dir {}", dirToCreate);
                            Files.createDirectory(dirToCreate);
                        }
                        return FileVisitResult.CONTINUE;
                    }
                });
            }
        } catch (IOException e) {
            throw new RuntimeIOException("Exception expanding " + zipFile + ":" + zipSubPath + " to " + destDir, e);
        }
    }

    public static void addFileToZip(@Nonnull Path sourceStream, @Nonnull Path destZipFile, @Nonnull String destFile,
            CopyOption... copyOptions) {
        try (FileSystem zipFileSystem = createZipFileSystem(destZipFile, false)) {
            final Path root = zipFileSystem.getPath("/");
            Path destFilePath = root.resolve(destFile);
            Files.createDirectories(destFilePath.getParent());
            logger.debug("Copy {} to {}, destFile: {}", sourceStream, destFile, destFilePath);

            Files.copy(sourceStream, destFilePath, copyOptions);
        } catch (IOException e) {
            throw new RuntimeIOException("Exception adding file " + sourceStream + " to " + destZipFile, e);
        }
    }

    public static void addFileToZip(@Nonnull InputStream sourceFile, @Nonnull Path destZipFile,
            @Nonnull String destFile, CopyOption... copyOptions) {
        try (FileSystem zipFileSystem = createZipFileSystem(destZipFile, false)) {
            final Path root = zipFileSystem.getPath("/");
            Path destFilePath = root.resolve(destFile);
            Files.createDirectories(destFilePath.getParent());
            logger.debug("Copy {} to {}, destFile: {}", sourceFile, destFile, destFilePath);

            Files.copy(sourceFile, destFilePath, copyOptions);
        } catch (IOException e) {
            throw new RuntimeIOException("Exception adding file " + sourceFile + " to " + destZipFile, e);
        }
    }

    public static void unzip(@Nonnull Path zipFile, @Nonnull final Path destDir) throws RuntimeIOException {
        try {
            //if the destination doesn't exist, create it
            if (Files.notExists(destDir)) {
                logger.trace("Create dir: {}", destDir);
                Files.createDirectories(destDir);
            }

            try (FileSystem zipFileSystem = createZipFileSystem(zipFile, false)) {
                final Path root = zipFileSystem.getPath("/");

                //walk the zip file tree and copy files to the destination
                Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
                    @Override
                    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                        try {
                            final Path destFile = Paths.get(destDir.toString(), file.toString());
                            logger.trace("Extract file {} to {}", file, destDir);
                            Files.copy(file, destFile, StandardCopyOption.REPLACE_EXISTING);
                        } catch (IOException | RuntimeException e) {
                            logger.warn("Exception copying file '" + file + "' to '" + destDir + "', ignore file",
                                    e);
                        }
                        return FileVisitResult.CONTINUE;
                    }

                    @Override
                    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
                            throws IOException {
                        final Path dirToCreate = Paths.get(destDir.toString(), dir.toString());

                        if (Files.notExists(dirToCreate)) {
                            logger.trace("Create dir {}", dirToCreate);
                            try {
                                Files.createDirectory(dirToCreate);
                            } catch (IOException e) {
                                logger.warn("Exception creating directory '" + dirToCreate + "' for '" + dir
                                        + "', ignore dir subtree", e);
                                return FileVisitResult.SKIP_SUBTREE;
                            }
                        }
                        return FileVisitResult.CONTINUE;
                    }
                });
            }
        } catch (IOException e) {
            throw new RuntimeIOException("Exception expanding " + zipFile + " to " + destDir, e);
        }
    }

    /**
     * Uncompress the specified tgz file to the specified destination directory.
     * Replaces any files in the destination, if they already exist.
     *
     * @param tgzFilename the name of the zip file to extract
     * @param destDirname the directory to unzip to
     * @throws RuntimeIOException
     */
    public static void untgz(String tgzFilename, String destDirname) throws RuntimeIOException {

        Path tgzFile = Paths.get(tgzFilename);
        Path destDir = Paths.get(destDirname);
        untgz(tgzFile, destDir);

    }

    /**
     * TODO recopy file permissions
     * <p/>
     * Uncompress the specified tgz file to the specified destination directory.
     * Replaces any files in the destination, if they already exist.
     *
     * @param tgzFile the name of the zip file to extract
     * @param destDir the directory to unzip to
     * @throws RuntimeIOException
     */
    public static void untgz(@Nonnull Path tgzFile, @Nonnull Path destDir) throws RuntimeIOException {
        try {
            //if the destination doesn't exist, create it
            if (Files.notExists(destDir)) {
                logger.trace("Create dir: {}", destDir);
                Files.createDirectories(destDir);
            }

            TarArchiveInputStream in = new TarArchiveInputStream(
                    new GzipCompressorInputStream(Files.newInputStream(tgzFile)));

            TarArchiveEntry entry;
            while ((entry = in.getNextTarEntry()) != null) {
                if (entry.isDirectory()) {
                    Path dir = destDir.resolve(entry.getName());
                    logger.trace("Create dir {}", dir);
                    Files.createDirectories(dir);
                } else {
                    Path file = destDir.resolve(entry.getName());
                    logger.trace("Create file {}: {} bytes", file, entry.getSize());
                    OutputStream out = Files.newOutputStream(file);
                    IOUtils.copy(in, out);
                    out.close();
                }
            }

            in.close();
        } catch (IOException e) {
            throw new RuntimeIOException("Exception expanding " + tgzFile + " to " + destDir, e);
        }
    }

    /**
     * For debugging purpose. Dump the tree view of the dir in {@code stderr}
     *
     * @param path
     * @throws IOException
     */
    public static void dump(@Nonnull Path path) throws RuntimeIOException {
        System.err.println("## DUMP FOLDER TREE ##");
        dump(path, 0);
    }

    private static void dump(@Nonnull Path path, int depth) throws RuntimeIOException {
        try {
            depth++;
            String icon = Files.isDirectory(path) ? " + " : " |- ";
            System.out.println(Strings.repeat(" ", depth) + icon + path.getFileName() + "\t"
                    + PosixFilePermissions.toString(Files.getPosixFilePermissions(path)));

            if (Files.isDirectory(path)) {
                DirectoryStream<Path> children = Files.newDirectoryStream(path);
                for (Path child : children) {
                    dump(child, depth);
                }
            }
        } catch (IOException e) {
            throw new RuntimeIOException("Exception dumping " + path, e);
        }
    }

    /**
     * Copy content for {@code srcDir} to {@code destDir}
     *
     * @param srcDir
     * @param destDir
     * @throws RuntimeIOException
     */
    public static void copyDirectoryContent(@Nonnull final Path srcDir, @Nonnull final Path destDir)
            throws RuntimeIOException {
        logger.trace("Copy from {} to {}", srcDir, destDir);

        FileVisitor<Path> copyDirVisitor = new SimpleFileVisitor<Path>() {

            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                Path targetPath = destDir.resolve(srcDir.relativize(dir));
                if (!Files.exists(targetPath)) {
                    Files.createDirectory(targetPath);
                }
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                Files.copy(file, destDir.resolve(srcDir.relativize(file)), StandardCopyOption.REPLACE_EXISTING);
                return FileVisitResult.CONTINUE;
            }
        };
        try {
            Files.walkFileTree(srcDir, copyDirVisitor);
        } catch (IOException e) {
            throw new RuntimeIOException("Exception copying content of dir " + srcDir + " to " + destDir, e);
        }
    }

    /**
     * Copy given {@code srcFile} to given {@code destDir}.
     *
     * @param srcFile
     * @param destDir destination directory, must exist
     * @return
     * @throws RuntimeIOException
     */
    @Nonnull
    public static Path copyToDirectory(@Nonnull Path srcFile, @Nonnull Path destDir) throws RuntimeIOException {
        Preconditions.checkArgument(Files.exists(srcFile), "Src %s not found");
        Preconditions.checkArgument(!Files.isDirectory(srcFile), "Src %s is a directory");
        Preconditions.checkArgument(Files.exists(destDir), "Dest %s not found");
        Preconditions.checkArgument(Files.isDirectory(destDir), "Dest %s is not a directory");
        try {
            return Files.copy(srcFile, destDir.resolve(srcFile.getFileName()));
        } catch (IOException e) {
            throw new RuntimeIOException("Exception copying " + srcFile.getFileName() + " to " + srcFile, e);
        }
    }

    @Nonnull
    public static Path copyArtifactToDirectory(@Nonnull Path sourceDir, @Nonnull String artifactId,
            @Nonnull Path dest) throws RuntimeIOException {
        Path source = findArtifact(sourceDir, artifactId);
        try {
            return Files.copy(source, dest.resolve(source.getFileName()));
        } catch (IOException e) {
            throw new RuntimeIOException("Exception copying " + source.getFileName() + " to " + sourceDir, e);
        }
    }

    /**
     * Find jar file with name beginning with given {@code artifactId} in given {@code srcDir}.
     *
     * @param srcDir
     * @param artifactId
     * @return
     * @throws RuntimeIOException
     * @see #findArtifact(java.nio.file.Path, String, String)
     */
    @Nonnull
    public static Path findArtifact(@Nonnull Path srcDir, @Nonnull String artifactId)
            throws RuntimeIOException, IllegalStateException {
        return findArtifact(srcDir, artifactId, "jar");
    }

    /**
     * @deprecated use {@link #findUniqueDirectoryBeginningWith(java.nio.file.Path, String)}
     */
    @Deprecated
    @Nonnull
    public static Path findUniqueFolderBeginningWith(@Nonnull Path source, @Nullable final String pattern)
            throws RuntimeIOException, IllegalStateException {
        return findUniqueDirectoryBeginningWith(source, pattern);
    }

    /**
     * @param srcDir
     * @param pattern
     * @return
     * @throws RuntimeIOException
     * @throws IllegalStateException More or less than 1 child dir found
     */
    @Nonnull
    public static Path findUniqueDirectoryBeginningWith(@Nonnull Path srcDir, @Nonnull final String pattern)
            throws RuntimeIOException, IllegalStateException {
        Preconditions.checkArgument(Files.isDirectory(srcDir), "Source %s is not a directory",
                srcDir.toAbsolutePath());

        DirectoryStream.Filter<Path> filter = new DirectoryStream.Filter<Path>() {
            @Override
            public boolean accept(Path entry) throws IOException {
                String fileName = entry.getFileName().toString();
                if (pattern == null) {
                    return true;
                } else if (fileName.startsWith(pattern)) {
                    return true;
                } else {
                    return false;
                }
            }
        };

        try (DirectoryStream<Path> paths = Files.newDirectoryStream(srcDir, filter)) {
            try {
                return Iterables.getOnlyElement(paths);
            } catch (NoSuchElementException e) {
                throw new IllegalStateException("Directory beginning with '" + pattern + "' not found in path: "
                        + srcDir + ", absolutePath: " + srcDir.toAbsolutePath());
            } catch (IllegalArgumentException e) {
                throw new IllegalStateException(
                        "More than 1 directory beginning with '" + pattern + "' found in path: " + srcDir
                                + ", absolutePath: " + srcDir.toAbsolutePath() + " -> " + paths);
            }
        } catch (IOException e) {
            throw new RuntimeIOException(
                    "Exception finding unique child directory beginning with " + filter + "in " + srcDir);
        }
    }

    /**
     * @param srcDir
     * @return
     * @throws RuntimeIOException
     * @throws IllegalStateException More or less than 1 child dir found
     */
    @Nonnull
    public static Path findUniqueChildDirectory(@Nonnull Path srcDir)
            throws RuntimeIOException, IllegalStateException {
        Preconditions.checkArgument(Files.isDirectory(srcDir), "Source %s is not a directory",
                srcDir.toAbsolutePath());

        try (DirectoryStream<Path> paths = Files.newDirectoryStream(srcDir)) {
            try {
                return Iterables.getOnlyElement(paths);
            } catch (NoSuchElementException e) {
                throw new IllegalStateException(
                        "No child directory found in : " + srcDir + ", absolutePath: " + srcDir.toAbsolutePath());
            } catch (IllegalArgumentException e) {
                throw new IllegalStateException("More than 1 child directory found in path: " + srcDir
                        + ", absolutePath: " + srcDir.toAbsolutePath() + " -> " + paths);
            }
        } catch (IOException e) {
            throw new RuntimeIOException("Exception finding unique child directory in " + srcDir);
        }
    }

    /**
     * Find a file matching {@code $artifactId*$type} in the given {@code srcDir}.
     *
     * @param srcDir
     * @param artifactId
     * @param type
     * @return
     * @throws IllegalStateException More or less than 1 matching artifact found
     * @throws RuntimeIOException
     */
    @Nonnull
    public static Path findArtifact(@Nonnull Path srcDir, @Nonnull final String artifactId,
            @Nonnull final String type) throws RuntimeIOException, IllegalStateException {
        Preconditions.checkArgument(Files.isDirectory(srcDir), "Source %s is not a directory",
                srcDir.toAbsolutePath());
        DirectoryStream.Filter<Path> filter = new DirectoryStream.Filter<Path>() {
            @Override
            public boolean accept(Path entry) throws IOException {
                String fileName = entry.getFileName().toString();
                if (fileName.startsWith(artifactId) && fileName.endsWith("." + type)) {
                    return true;
                } else {
                    return false;
                }
            }
        };
        try (DirectoryStream<Path> paths = Files.newDirectoryStream(srcDir, filter)) {
            try {
                return Iterables.getOnlyElement(paths);
            } catch (NoSuchElementException e) {
                throw new IllegalStateException("Artifact '" + artifactId + ":" + type + "' not found in path: "
                        + srcDir + ", absolutePath: " + srcDir.toAbsolutePath());
            } catch (IllegalArgumentException e) {
                throw new IllegalStateException(
                        "More than 1 version of artifact '" + artifactId + ":" + type + "' found in path: " + srcDir
                                + ", absolutePath: " + srcDir.toAbsolutePath() + " -> " + paths);
            }
        } catch (IOException e) {
            throw new RuntimeIOException("Exception finding artifact " + artifactId + "@" + type + " in " + srcDir);
        }
    }

    /**
     * Update file and dir permissions.
     *
     * @param path
     * @param filePermissions
     * @param dirPermissions
     * @throws RuntimeIOException
     */
    private static void chmodOverwritePermissions(@Nonnull Path path,
            @Nonnull final Set<PosixFilePermission> filePermissions,
            @Nonnull final Set<PosixFilePermission> dirPermissions) throws RuntimeIOException {
        if (!Files.exists(path)) {
            throw new IllegalArgumentException("Given path " + path + " does not exist");
        }

        SimpleFileVisitor<Path> setReadOnlyFileVisitor = new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                if (Files.isDirectory(file)) {
                    throw new IllegalStateException("No dir expected here: " + file);
                } else {
                    Files.setPosixFilePermissions(file, filePermissions);
                }
                return super.visitFile(file, attrs);
            }

            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                Files.setPosixFilePermissions(dir, dirPermissions);
                return super.preVisitDirectory(dir, attrs);
            }
        };
        try {
            Files.walkFileTree(path, setReadOnlyFileVisitor);
        } catch (IOException e) {
            throw new RuntimeIOException("Exception setting permissions file permissions to " + filePermissions
                    + " and folder permissions to " + dirPermissions + " on " + path, e);
        }
    }

    /**
     * Update file and dir permissions.
     *
     * @param path
     * @param filePermissions
     * @param dirPermissions
     * @throws RuntimeIOException
     */
    private static void chmodAddPermissions(@Nonnull Path path,
            @Nonnull final Set<PosixFilePermission> filePermissions,
            @Nonnull final Set<PosixFilePermission> dirPermissions) throws RuntimeIOException {
        if (!Files.exists(path)) {
            throw new IllegalArgumentException("Given path " + path + " does not exist");
        }

        SimpleFileVisitor<Path> setReadOnlyFileVisitor = new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                if (Files.isDirectory(file)) {
                    throw new IllegalStateException("No dir expected here: " + file);
                } else {
                    Set<PosixFilePermission> existingPermissions = Files.getPosixFilePermissions(file);
                    Files.setPosixFilePermissions(file, Sets.union(existingPermissions, filePermissions));
                }
                return super.visitFile(file, attrs);
            }

            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                Set<PosixFilePermission> existingPermissions = Files.getPosixFilePermissions(dir);
                Files.setPosixFilePermissions(dir, Sets.union(existingPermissions, dirPermissions));
                return super.preVisitDirectory(dir, attrs);
            }
        };
        try {
            Files.walkFileTree(path, setReadOnlyFileVisitor);
        } catch (IOException e) {
            throw new RuntimeIOException("Exception setting permissions file permissions to " + filePermissions
                    + " and folder permissions to " + dirPermissions + " on " + path, e);
        }
    }
}