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

io.helidon.build.maven.enforcer.FileFinder Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2021, 2023 Oracle and/or its affiliates.
 *
 * 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 io.helidon.build.maven.enforcer;

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import io.helidon.build.common.logging.Log;

import static io.helidon.build.maven.enforcer.GitIgnore.create;

/**
 * Configuration of discovery of files to check.
 */
public class FileFinder {
    private static final DateTimeFormatter YEAR_FORMATTER = DateTimeFormatter.ofPattern("yyyy");

    private final String currentYear = YEAR_FORMATTER.format(ZonedDateTime.now());
    private final Path repositoryRoot;
    private final boolean useGit;
    private final boolean honorGitIgnore;

    private FileFinder(Builder builder) {
        useGit = builder.useGit;
        honorGitIgnore = builder.honorGitIgnore;
        repositoryRoot = builder.repositoryRoot;
    }

    /**
     * Builder to set up file config.
     *
     * @return a new builder
     */
    public static Builder builder() {
        return new Builder();
    }

    /**
     * Get files to check based on this file config.
     *
     * @param basePath path to use
     * @return found files
     */
    public FoundFiles findFiles(Path basePath) {
        // find repository root if not configured
        Path gitRepoDir = (repositoryRoot == null ? GitCommands.repositoryRoot(basePath) : repositoryRoot);

        List excludes = new ArrayList<>();
        if (honorGitIgnore) {
            excludes.add(create(gitRepoDir));
        }

        Set foundFiles;
        Set locallyModified;

        // Tracks list of files that have been renamed but not yet committed. This
        // ensures we can reliably ignore the old file name. See issue 661
        List renamed = new ArrayList<>();

        if (useGit) {
            locallyModified = GitCommands.locallyModified(gitRepoDir, basePath, currentYear, renamed);
            foundFiles = new HashSet<>(locallyModified);
            foundFiles.addAll(GitCommands.gitTracked(gitRepoDir, basePath));
        } else {
            foundFiles = findAllFiles(gitRepoDir, basePath);
            locallyModified = foundFiles;
        }

        List fileRequests = foundFiles.stream()
                .filter(file -> isValid(file, excludes, renamed))
                .collect(Collectors.toList());

        Set filteredLocallyModified = exclude(excludes, locallyModified);
        fileRequests.sort(FileRequest::compareTo);

        return FoundFiles.create(gitRepoDir, fileRequests, filteredLocallyModified, useGit);
    }

    @Override
    public String toString() {
        return "FileConfig{"
                + "repositoryRoot=" + repositoryRoot
                + ", useGit=" + useGit
                + ", honorGitIgnore=" + honorGitIgnore
                + '}';
    }

    private Set findAllFiles(Path gitRepoDir, Path basePath) {
        Set result = new HashSet<>();

        try {
            Files.walkFileTree(basePath, new SimpleFileVisitor<>() {
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
                    result.add(FileRequest.create(gitRepoDir, gitRepoDir.relativize(file).toString(), lastModifiedYear(file)));
                    return FileVisitResult.CONTINUE;
                }
            });
        } catch (IOException e) {
            throw new EnforcerException("Failed to list files", e);
        }

        return result;
    }

    private String lastModifiedYear(Path file) {
        try {
            return YEAR_FORMATTER.format(ZonedDateTime.ofInstant(Files.getLastModifiedTime(file).toInstant(), ZoneId.of("GMT")));
        } catch (IOException e) {
            throw new EnforcerException("Failed to parse last modified year from local file: " + file, e);
        }
    }

    private Set exclude(List excludes, Set locallyModified) {
        return locallyModified.stream()
                .filter(it -> {
                    for (FileMatcher exclude : excludes) {
                        if (exclude.matches(it)) {
                            return false;
                        }
                    }
                    return true;
                })
                .map(FileRequest::relativePath)
                .collect(Collectors.toSet());
    }

    private boolean isValid(FileRequest file, List excludes, List renamedPaths) {

        // file may have been deleted from GIT (or locally)
        if (!Files.exists(file.path())) {
            Log.debug("File " + file.relativePath() + " does not exist, ignoring.");
            return false;
        }

        for (FileMatcher exclude : excludes) {
            if (exclude.matches(file)) {
                Log.debug("Excluding " + file.relativePath());
                return false;
            }
        }

        if (renamedPaths.contains(file.path())) {
            Log.debug("File " + file.relativePath() + " has been renamed, ignoring.");
            return false;
        }

        return true;
    }

    /**
     * {@code FileConfig} builder static inner class.
     */
    public static final class Builder {
        private Path repositoryRoot;
        private boolean useGit = true;
        private boolean honorGitIgnore = true;

        private Builder() {
        }

        /**
         * Sets the {@code useGit} and returns a reference to this Builder so that the methods can be chained together.
         * @param useGit the {@code useGit} to set
         * @return a reference to this Builder
         */
        public Builder useGit(boolean useGit) {
            this.useGit = useGit;
            return this;
        }

        /**
         * Sets the {@code honorGitIgnore} and returns a reference to this Builder so that the methods can be chained together.
         * @param honorGitIgnore the {@code honorGitIgnore} to set
         * @return a reference to this Builder
         */
        public Builder honorGitIgnore(boolean honorGitIgnore) {
            this.honorGitIgnore = honorGitIgnore;
            return this;
        }

        /**
         * Sets the root of the repository, if it cannot be determined using git.
         *
         * @param repositoryRoot root of repo
         * @return updated builder
         */
        public Builder repositoryRoot(Path repositoryRoot) {
            this.repositoryRoot = repositoryRoot;
            return this;
        }

        /**
         * Returns a {@code FileConfig} built from the parameters previously set.
         *
         * @return a {@code FileConfig} built with parameters of this {@code FileConfig.Builder}
         */
        public FileFinder build() {
            return new FileFinder(this);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy