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

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

The newest version!
package com.telenav.cactus.maven.tree;

import com.telenav.cactus.maven.log.BuildLog;
import com.telenav.cactus.maven.git.GitCheckout;
import com.telenav.cactus.maven.util.ThrowingOptional;
import com.telenav.cactus.maven.xml.PomInfo;
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;
import org.apache.maven.project.MavenProject;

/**
 * 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 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);
    }

    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 forbiddingDirty()
    {
        return new ConsistencyChecker(toString(ignoreInBranchConsistencyCheck), toString(ignoreInVersionConsistencyCheck), targetGroupId, true);
    }

    public Set> checkConsistency(MavenProject project, BuildLog log) throws Exception
    {
        log = log.child("consistency");
        ThrowingOptional treeOpt = ProjectTree.from(project.getBasedir().toPath());
        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> checkBranchConsistency(ProjectTree tree, MavenProject project, BuildLog log)
    {
        Set> branchInconsistencies = new HashSet<>();
        checkBranchConsistency(tree, project, log, branchInconsistencies);
        return branchInconsistencies;
    }

    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,
                        PomInfo::projectFolder);
                into.add(issue);
            }
        });
    }

    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, PomInfo::projectFolder));
        }
    }

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

    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 boolean isRelevantCheckout(ProjectTree tree, GitCheckout checkout)
    {
        boolean result = isRelevantCheckout(checkout);
        if (result)
        {
            if (targetGroupId != null)
            {
                boolean found = false;
                for (PomInfo pom : tree.allProjects())
                {
                    GitCheckout co = pom.checkout().get();
                    if (co.equals(checkout))
                    {
                        if (targetGroupId.equals(pom.coords.groupId))
                        {
                            found = true;
                            break;
                        }
                    }
                }
                if (!found)
                {
                    result = false;
                }
            }

        }
        return result;
    }

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

    private boolean isRelevantCheckout(GitCheckout checkout)
    {
        if (!Files.exists(checkout.checkoutRoot().resolve("pom.xml")))
        {
            return false;
        }
        if (targetGroupId != null)
        {
            PomInfo info = PomInfo.from(checkout.checkoutRoot().resolve("pom.xml")).get();
            if (!targetGroupId.equals(info.coords.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 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 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;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy