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

com.telenav.cactus.maven.tree.ConsistencyChecker Maven / Gradle / Ivy

There is a newer version: 1.5.49
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// © 2011-2022 Telenav, Inc.
//
// 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
//
// https://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.telenav.cactus.maven.tree;

import com.mastfrog.function.optional.ThrowingOptional;
import com.telenav.cactus.git.GitCheckout;
import com.telenav.cactus.maven.log.BuildLog;
import com.telenav.cactus.maven.model.Pom;
import org.apache.maven.project.MavenProject;

import java.nio.file.Files;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

/**
 * Checks a number of dimensions of consistency in the project, and can detect
 * if there are only a few outliers (in which case - if, say, all projects but
 * one are on the same branch and that branch exists)
 *
 * @author Tim Boudreau
 */
public class ConsistencyChecker
{
    /**
     * Partition key for on-a-branch state of a repository. Git submodules, on
     * checkout, put you in detached head state, which may not be where you want
     * to stay.
     */
    public static final String ON_BRANCH = "on-branch";

    /**
     * Partition key for detached-head state of a repository. Git submodules, on
     * checkout, put you in detached head state, which may not be where you want
     * to stay.
     */
    public static final String DETACHED = "detached";

    /**
     * Partition key for clean state of a repository - no local modifications.
     */
    public static final String CLEAN = "clean";

    /**
     * Partition key for dirty state of a repository - has local modifications.
     */
    public static final String DIRTY = "dirty";

    private final Set ignoreInBranchConsistencyCheck;

    private final Set ignoreInVersionConsistencyCheck;

    private final String targetGroupId;

    private final boolean forbidDirty;

    public ConsistencyChecker(
            String ignoreInBranchConsistencyCheckCommaOrSpaceDelimitedList,
            String ignoreInVersionConsistencyCheckCommaOrSpaceDelimitedList,
            String targetGroupId,
            boolean forbidDirty)
    {
        this.ignoreInBranchConsistencyCheck = splitToSet(
                ignoreInBranchConsistencyCheckCommaOrSpaceDelimitedList);
        this.ignoreInVersionConsistencyCheck = splitToSet(
                ignoreInVersionConsistencyCheckCommaOrSpaceDelimitedList);
        this.targetGroupId = targetGroupId;
        this.forbidDirty = forbidDirty;
    }

    public ConsistencyChecker()
    {
        this(null, null, null, false);
    }

    public Set> checkBranchConsistency(ProjectTree tree,
            MavenProject project, BuildLog log)
    {
        Set> branchInconsistencies = new HashSet<>();
        checkBranchConsistency(tree, project, log, branchInconsistencies);
        return branchInconsistencies;
    }

    public Set> checkConsistency(MavenProject project,
            BuildLog log) throws Exception
    {
        log = log.child("consistency");
        ThrowingOptional treeOpt = ProjectTree.from(project
                .getBasedir().toPath());
        return checkConsistency(project, log, treeOpt);
    }

    public Set> checkConsistency(MavenProject project,
            BuildLog log, ThrowingOptional treeOpt) throws Exception
    {
        if (!treeOpt.isPresent())
        {
            log.child("checkConsistency").error(
                    "Could not find a project tree for " + project.getBasedir());
            return Collections.emptySet();
        }
        Set> result = new LinkedHashSet<>();
        checkBranchConsistency(treeOpt.get(), project, log.child("branch"),
                result);
        checkVersionConsistency(treeOpt.get(), project, log.child("versions"),
                result);
        checkDirtyAndDetached(treeOpt.get(), project, log.child(DIRTY), result,
                forbidDirty);

        return result;
    }

    public Set> checkDetached(ProjectTree tree,
            MavenProject project,
            BuildLog log)
    {
        Set> result = new HashSet<>();
        checkDirtyAndDetached(tree, project, log, result, false);
        return result;
    }

    public ConsistencyChecker forbiddingDirty()
    {
        return new ConsistencyChecker(toString(ignoreInBranchConsistencyCheck),
                toString(ignoreInVersionConsistencyCheck), targetGroupId, true);
    }

    public ConsistencyChecker onlyCheckingGroupId(String targetGroupId)
    {
        if (this.targetGroupId != null)
        {
            throw new IllegalStateException("Target group id already set to "
                    + this.targetGroupId + " - cannot set to " + targetGroupId);
        }
        return new ConsistencyChecker(toString(ignoreInBranchConsistencyCheck),
                toString(ignoreInVersionConsistencyCheck), targetGroupId,
                forbidDirty);
    }

    public ConsistencyChecker withIgnoreBranchConsistencySuffixes(
            String commaOrSpaceDelimitedList)
    {
        if (!ignoreInBranchConsistencyCheck.isEmpty())
        {
            throw new IllegalStateException(
                    "Ignore in branches check is already set to "
                    + ignoreInVersionConsistencyCheck + " - cannot set to "
                    + commaOrSpaceDelimitedList);
        }
        return new ConsistencyChecker(commaOrSpaceDelimitedList,
                toString(ignoreInVersionConsistencyCheck), targetGroupId,
                forbidDirty);
    }

    public ConsistencyChecker withIgnoreVersionConsistencySuffixes(
            String commaOrSpaceDelimitedList)
    {
        if (!ignoreInVersionConsistencyCheck.isEmpty())
        {
            throw new IllegalStateException(
                    "Ignore in versions check is already set to "
                    + ignoreInVersionConsistencyCheck + " - cannot set to "
                    + commaOrSpaceDelimitedList);
        }
        return new ConsistencyChecker(toString(ignoreInBranchConsistencyCheck),
                commaOrSpaceDelimitedList, targetGroupId, forbidDirty);
    }

    private static Set splitToSet(String what)
    {
        if (what == null || what.isBlank())
        {
            return Collections.emptySet();
        }
        Set result = new HashSet<>();
        for (String item : what.split("[,\\s]+"))
        {
            if (item.isEmpty())
            {
                continue;
            }
            result.add(item.trim());
        }
        return result;
    }

    private static String toString(Set what)
    {
        if (what.isEmpty())
        {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        for (String s : what)
        {
            if (sb.length() > 0)
            {
                sb.append(',');
            }
            sb.append(s);
        }
        return sb.toString();
    }

    private void checkBranchConsistency(ProjectTree tree, MavenProject project,
            BuildLog log, Set> into)
    {
        log.info("Checking consistency of branches" + (targetGroupId == null
                                                       ? ""
                                                       : " for projects with the group id " + targetGroupId));
        Map>> found
                = tree.projectsByBranchByGroupId(
                        this::isVersionRequiredToBeConsistent);

        found.forEach((groupId, pomInfosForBranch) ->
        {
            if (pomInfosForBranch.size() > 1)
            {
                Inconsistency issue = new Inconsistency<>(
                        pomInfosForBranch, Inconsistency.Kind.BRANCH,
                        Pom::projectFolder);
                into.add(issue);
            }
        });
    }

    private void checkDirtyAndDetached(ProjectTree tree, MavenProject project,
            BuildLog log, Set> into,
            boolean forbidDirty)
    {
        log.info("Checking for detached-head checkouts" + (targetGroupId == null
                                                           ? ""
                                                           : " for projects with the group id " + targetGroupId));
        log.info("Checking for dirty checkouts" + (targetGroupId == null
                                                   ? ""
                                                   : " for projects with the group id " + targetGroupId));
        Map> dirtyNotDirty = new HashMap<>();
        Map> detachedNotDetached = new HashMap<>();
        for (GitCheckout checkout : tree.allCheckouts())
        {
            if (isRelevantCheckout(tree, checkout))
            {
                if (forbidDirty)
                {
                    String cleanDirtyKey = tree.isDirty(checkout)
                                           ? DIRTY
                                           : CLEAN;
                    Set checkouts = dirtyNotDirty.computeIfAbsent(
                            cleanDirtyKey, k -> new TreeSet<>());
                    checkouts.add(checkout);
                }

                String detachedKey = checkout.isDetachedHead()
                                     ? DETACHED
                                     : ON_BRANCH;
                Set detachedCheckouts = detachedNotDetached
                        .computeIfAbsent(
                                detachedKey, k -> new TreeSet<>());
                detachedCheckouts.add(checkout);
            }
        }
        if (dirtyNotDirty.containsKey(DIRTY))
        {
            into.add(new Inconsistency<>(dirtyNotDirty,
                    Inconsistency.Kind.CONTAINS_MODIFIED_SOURCES,
                    GitCheckout::checkoutRoot));
        }
        if (detachedNotDetached.containsKey("detached"))
        {
            into.add(new Inconsistency<>(detachedNotDetached,
                    Inconsistency.Kind.NOT_ON_A_BRANCH,
                    GitCheckout::checkoutRoot));
        }
    }

    private void checkVersionConsistency(ProjectTree tree, MavenProject project,
            BuildLog log, Set> into)
    {
        log.info("Checking consistency of versions" + (targetGroupId == null
                                                       ? ""
                                                       : " for projects with the group id " + targetGroupId));
        Map> projectsByVersion = tree.projectsByVersion(
                this::isVersionRequiredToBeConsistent);
        if (projectsByVersion.size() > 1)
        {
            into.add(new Inconsistency<>(projectsByVersion,
                    Inconsistency.Kind.VERSION, Pom::projectFolder));
        }
    }

    private boolean isRelevantCheckout(ProjectTree tree, GitCheckout checkout)
    {
        boolean result = isRelevantCheckout(checkout);
        if (result)
        {
            if (targetGroupId != null)
            {
                boolean found = false;
                for (Pom pom : tree.allProjects())
                {
                    GitCheckout co = GitCheckout.checkout(pom.path()).get();
                    if (co.equals(checkout))
                    {
                        if (pom.groupId().is(targetGroupId))
                        {
                            found = true;
                            break;
                        }
                    }
                }
                if (!found)
                {
                    result = false;
                }
            }
        }
        return result;
    }

    private boolean isRelevantCheckout(GitCheckout checkout)
    {
        if (!Files.exists(checkout.checkoutRoot().resolve("pom.xml")))
        {
            return false;
        }
        if (targetGroupId != null)
        {
            Pom info = Pom.from(checkout.checkoutRoot().resolve("pom.xml"))
                    .get();
            if (!targetGroupId.equals(info.groupId()))
            {
                return false;
            }
        }
        Set names = new HashSet<>();
        names.add(checkout.checkoutRoot().getFileName().toString());
        names.addAll(checkout.remoteProjectNames());
        for (String name : names)
        {
            for (String suffix : ignoreInBranchConsistencyCheck)
            {
                if (name.endsWith(suffix) || name.equals(suffix))
                {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean isVersionRequiredToBeConsistent(Pom info)
    {
        if (targetGroupId != null && !targetGroupId.equals(info.groupId().text()))
        {
            return false;
        }
        return ignoreInVersionConsistencyCheck.contains(info.artifactId().text());
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy