FileUtils.java

/**************************************************************************
 *
 * Copyright (c) 2016-2020 Yawg project contributors.
 *
 **************************************************************************/

package com.varmateo.yawg.util;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Optional;
import java.util.regex.Pattern;

import io.vavr.control.Option;
import io.vavr.control.Try;


/**
 * Utility functions for managing files and directories.
 */
public final class FileUtils {


    /**
     * No instances of this class are to be created.
     */
    private FileUtils() {
        // Nothing to do.
    }


    /**
     * Determines the basename of the given file.
     *
     * <p>The basename is the name of the file without extension.</p>
     *
     * @param path The path of the file for which the basename will be
     * determined.
     *
     * @return The basename of the given file.
     */
    public static String basename(final Path path) {

        final String basenameWithExtension =
                Option.of(path.getFileName())
                .map(Object::toString)
                .getOrElse("");
        final int extensionIndex = basenameWithExtension.lastIndexOf('.');

        return (extensionIndex > -1)
                ? basenameWithExtension.substring(0, extensionIndex)
                : basenameWithExtension;
    }


    /**
     *
     */
    public static boolean isNameMatch(
            final Path path,
            final Pattern pattern) {

        return  Optional.ofNullable(path.getFileName())
                .map(Path::toString)
                .map(basename -> pattern.matcher(basename).matches())
                .orElse(false);
    }


    /**
     * Copies one file to a given location.
     *
     * <p>If the target file already exists, itl will be overwritten</p>
     *
     * @param source The path of the source file to be copied.
     *
     * @param target The path of the target file to be created. Its
     * parent directory is supposed to exist.
     *
     * @throws IOException If there were problems reading from the
     * source file, or writing to the target file.
     */
    public static void copy(
            final Path source,
            final Path target)
            throws IOException {

        Files.copy(
                source,
                target,
                StandardCopyOption.REPLACE_EXISTING,
                StandardCopyOption.COPY_ATTRIBUTES);
    }


    /**
     *
     */
    public static Try<Void> safeCopy(
            final Path source,
            final Path target) {

        return Try.run(() -> copy(source, target));
    }


    /**
     * Reads the contents of a file into a string.
     *
     * <p>The file is expected to be UTF-8 encoded.</p>
     *
     * @param sourcePath The file to be read.
     *
     * @return The contents of the given file as a string.
     *
     * @throws IOException if there were any problems reading the
     * file.
     */
    public static String readAsString(final Path sourcePath)
            throws IOException {

        final byte[] contentBytes = Files.readAllBytes(sourcePath);

        return new String(contentBytes, StandardCharsets.UTF_8);
    }


    /**
     * Creates a new <code>Writer</code> and passes it to the given
     * consumer.
     *
     * <p>The <code>Writer</code> passed to the consumer is a buffered
     * writer with UTF-8 pointing to the given target file. The
     * <code>Writer</code> is ensured to be closed when this method
     * returns.</p>
     *
     * @param target The file to be written.
     *
     * @param consumer The consumer that is suppoed to write into the
     * <code>Writer</code> associated with the given target file.
     *
     * @throws IOException If there were problems opening the file for
     * writing, or writing into the file.
     */
    public static void writeTo(
            final Path target,
            final ConsumerWithIoException<Writer> consumer)
            throws IOException {

        try ( Writer writer = Files.newBufferedWriter(target, StandardCharsets.UTF_8) ) {
            consumer.accept(writer);
        }
    }


    /**
     *
     */
    public static <T> Try<Void> safeWriteTo(
            final Path target,
            final ConsumerWithIoException<Writer> action) {

        try ( Writer writer = Files.newBufferedWriter(target, StandardCharsets.UTF_8) ) {
            return Try.run(() -> action.accept(writer));
        } catch ( final IOException cause ) {
            return Try.failure(cause);
        }
    }


    /**
     *
     */
    public static <T> Try<T> safeWriteWith(
            final Path target,
            final FunctionWithIoException<Writer, T> action) {

        try ( Writer writer = Files.newBufferedWriter(target, StandardCharsets.UTF_8) ) {
            return Try.of(() -> action.apply(writer));
        } catch ( final IOException cause ) {
            return Try.failure(cause);
        }
    }


    /**
     *
     */
    public static <T> T readFrom(
            final Path source,
            final FunctionWithIoException<Reader, T> transformer)
            throws IOException {

        try ( Reader reader = Files.newBufferedReader(source, StandardCharsets.UTF_8) ) {
            return transformer.apply(reader);
        }
    }


    /**
     *
     */
    public static <T> Try<T> safeReadFrom(
            final Path source,
            final FunctionWithIoException<Reader, T> transformer) {

        final Try<T> result;

        try ( Reader reader = Files.newBufferedReader(source, StandardCharsets.UTF_8) ) {
            return Try.of(() -> transformer.apply(reader));
        } catch ( final IOException cause ) {
            return Try.failure(cause);
        }
    }


    /**
     * Similar to a <code>java.util.function.Consumer</code> but
     * throws <code>IOException</code>. Intended for simplifying the
     * use of lambdas when calling the <code>newWriter(...)</code>
     * method.
     *
     * @param <T> The input type for the consumer.
     */
    @FunctionalInterface
    public interface ConsumerWithIoException<T> {

        /**
         * Performs the operation on the given argument.
         *
         * @param input The input object.
         *
         * @throws IOException For whatever reason.
         */
        void accept(T input)
                throws IOException;
    }


    /**
     *
     */
    @FunctionalInterface
    public interface SupplierWithIoException<T> {

        /**
         *
         */
        T get()
                throws IOException;
    }


    /**
     *
     */
    @FunctionalInterface
    public interface FunctionWithIoException<T, R> {

        /**
         *
         */
        R apply(T input)
                throws IOException;
    }


}