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

com.telenav.cactus.maven.tree.ProjectTreeCache 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.util.preconditions.Exceptions;
import com.telenav.cactus.git.Branches;
import com.telenav.cactus.git.GitCheckout;
import com.telenav.cactus.git.Heads;
import com.telenav.cactus.git.SubmoduleStatus;
import com.telenav.cactus.maven.model.GroupId;
import com.telenav.cactus.maven.model.Pom;
import com.telenav.cactus.scope.ProjectFamily;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import static com.telenav.cactus.scope.ProjectFamily.familyOf;

/**
 * Internal cache for the ProjectTree, so we don't re-execute expensive git
 * operations if nothing has changed.
 *
 * @author Tim Boudreau
 */
final class ProjectTreeCache
{
    final Map> infoForGroupAndArtifact = new ConcurrentHashMap<>();
    final Map> projectsByRepository = new ConcurrentHashMap<>();
    final Map checkoutForPom = new ConcurrentHashMap<>();
    final Map> branches = new HashMap<>();
    final Map dirty = new ConcurrentHashMap<>();
    final Map dirtyIgnoring = new ConcurrentHashMap<>();
    final Map allBranches = new ConcurrentHashMap<>();
    final Map> branchByGroupId = new HashMap<>();
    final Map detachedHeads = new ConcurrentHashMap<>();
    final Set nonMavenCheckouts = new HashSet<>();
    final Map remoteHeads = new HashMap<>();
    final Map> checkoutsForProjectFamily = new ConcurrentHashMap<>();
    final Set families = new HashSet<>();
    Boolean rootIsRoot;
    private final ProjectTree outer;

    ProjectTreeCache(final ProjectTree outer)
    {
        this.outer = outer;
    }

    public boolean rootIsSubmoduleRoot()
    {
        if (rootIsRoot == null)
        {
            rootIsRoot = outer.root.isSubmoduleRoot();
        }
        return rootIsRoot;
    }

    public Set allProjectFamilies()
    {
        if (families.isEmpty())
        {
            allPoms().forEach(pom ->
            {
                families.add(ProjectFamily.familyOf(pom));
            });
        }
        return families;
    }

    public Heads remoteHeads(GitCheckout checkout)
    {
        return remoteHeads.computeIfAbsent(checkout,
                GitCheckout::remoteHeads);
    }

    public Set checkoutsContainingGroupId(String groupId)
    {
        Set all = new HashSet<>();
        projectsByRepository.forEach((repo, projectSet) ->
        {
            for (Pom project : projectSet)
            {
                if (groupId.equals(project.coordinates().groupId))
                {
                    all.add(repo);
                    break;
                }
            }
        });
        return all;
    }

    public Set checkoutsInProjectFamily(Set family)
    {
        switch (family.size())
        {
            case 0:
                return new HashSet<>(allCheckouts());
            case 1:
                return checkoutsInProjectFamily(family.iterator().next());
            default:
                Set result = new HashSet<>();
                for (ProjectFamily f : family)
                {
                    result.addAll(checkoutsInProjectFamily(f));
                }
                return result;
        }
    }

    public Set checkoutsInProjectFamily(ProjectFamily family)
    {
        return checkoutsForProjectFamily.computeIfAbsent(family,
                key ->
        {
            Set all = new HashSet<>();
            projectsByRepository.forEach((repo, projectSet) ->
            {
                for (Pom project : projectSet)
                {
                    if (family.equals(ProjectFamily.fromGroupId(project
                            .groupId()
                            .text())))
                    {
                        all.add(repo);
                        break;
                    }
                }
            });
            return all;
        });
    }

    private static boolean containsParentFamilyOf(GroupId groupId,
            Set s)
    {
        for (ProjectFamily p : s)
        {
            if (p.isParentFamilyOf(groupId))
            {
                return true;
            }
        }
        return false;
    }

    public Set checkoutsInProjectFamilyOrChildProjectFamily(
            String groupId)
    {
        ProjectFamily parent = ProjectFamily.fromGroupId(groupId);
        Set all = new HashSet<>();
        projectsByRepository.forEach((repo, projectSet) ->
        {
            for (Pom p : projectSet)
            {
                if (familyOf(p).equals(parent)
                        || parent.isParentFamilyOf(p.groupId()))
                {
                    all.add(repo);
                    break;
                }
            }
        });
        return all;
    }

    public Set checkoutsInProjectFamilyOrChildProjectFamily(
            Set family)
    {
        Set all = new HashSet<>();
        projectsByRepository.forEach((repo, projectSet) ->
        {
            if (family.isEmpty())
            {
                all.add(repo);
                return;
            }
            for (Pom project : projectSet)
            {
                ProjectFamily pomFamily = ProjectFamily.familyOf(project
                        .groupId());
                if (family.contains(pomFamily) || containsParentFamilyOf(project
                        .groupId(), family))
                {
                    all.add(repo);
                    break;
                }
            }
        });
        return all;
    }

    public Set nonMavenCheckouts()
    {
        return Collections.unmodifiableSet(nonMavenCheckouts);
    }

    public boolean isDetachedHead(GitCheckout checkout)
    {
        return detachedHeads.computeIfAbsent(checkout,
                GitCheckout::isDetachedHead);
    }

    public Optional mostCommonBranchForGroupId(String groupId)
    {
        // Cache these since they are expensive to compute
        return branchByGroupId.computeIfAbsent(groupId,
                this::_mostCommonBranchForGroupId);
    }

    private Optional _mostCommonBranchForGroupId(String groupId)
    {
        // Collect the number of times a branch name is used in a checkout
        // we have
        Map branchNameCounts = new HashMap<>();
        Set seen = new HashSet<>();
        // Count each checkout exactly once, if it is on a branch
        checkoutForPom.forEach((pom, checkout) ->
        {
            // Filter out any irrelevant or already examined checkouts
            if (seen.contains(checkout) || !pom.groupId().is(groupId))
            {
                return;
            }
            // If we are on a branch, collect its name and add to the number
            // of times it has been seen
            branchFor(checkout)
                    .ifPresent(branch ->
                    {
                        seen.add(checkout);
                        branchNameCounts.compute(branch,
                                (b, old) ->
                        {
                            if (old == null)
                            {
                                return 1;
                            }
                            return old + 1;
                        });
                    });
        });
        // If we found nothing, we're done
        if (branchNameCounts.isEmpty())
        {
            return Optional.empty();
        }
        // Reverse sort the map entries by the count
        List> entries = new ArrayList<>(
                branchNameCounts.entrySet());
        Collections.sort(entries,
                (a, b) ->
        {
            return b.getValue().compareTo(a.getValue());
        });
        // And take the greatest
        return Optional.of(entries.get(0).getKey());
    }

    public Set projectsWithin(GitCheckout checkout)
    {
        Set infos = projectsByRepository.get(checkout);
        return infos == null
               ? Collections.emptySet()
               : infos;
    }

    public Branches branches(GitCheckout checkout)
    {
        return allBranches.computeIfAbsent(checkout,
                GitCheckout::branches);
    }

    public Optional checkoutFor(Pom info)
    {
        return Optional.ofNullable(checkoutForPom.get(info));
    }

    public boolean isDirty(GitCheckout checkout)
    {
        return dirty.computeIfAbsent(checkout,
                GitCheckout::isDirty);
    }

    public boolean isDirtyIgnoringSubmoduleCommits(GitCheckout checkout)
    {
        return dirtyIgnoring.computeIfAbsent(checkout,
                GitCheckout::isDirtyIgnoringModifiedSubmodules);
    }

    public Set allCheckouts()
    {
        return Collections.unmodifiableSet(projectsByRepository.keySet());
    }

    public Optional branchFor(GitCheckout checkout)
    {
        return branches.computeIfAbsent(checkout,
                GitCheckout::branch);
    }

    public Map projectFolders()
    {
        Map infos = new HashMap<>();
        allPoms()
                .forEach(pom -> infos.put(pom.path().getParent(), pom));
        return infos;
    }

    public Set allPoms()
    {
        Set set = new HashSet<>();
        projectsByRepository.forEach((repo, infos) -> set.addAll(infos));
        return set;
    }

    Optional project(String groupId, String artifactId)
    {
        Map map = infoForGroupAndArtifact.get(groupId);
        if (map == null)
        {
            return Optional.empty();
        }
        return Optional.ofNullable(map.get(artifactId));
    }

    void clear()
    {
        infoForGroupAndArtifact.clear();
        projectsByRepository.clear();
        checkoutForPom.clear();
        dirtyIgnoring.clear();
        branches.clear();
        dirty.clear();
        allBranches.clear();
        branchByGroupId.clear();
        nonMavenCheckouts.clear();
        detachedHeads.clear();
        checkoutsForProjectFamily.clear();
        remoteHeads.clear();
        families.clear();
        rootIsRoot = null;
    }

    synchronized void populate()
    {
        try
        {
            outer.root.allPomFilesInSubtreeParallel(this::cacheOnePomFile);
            outer.root.submodules().ifPresent(statii ->
            {
                for (SubmoduleStatus stat : statii)
                {
                    stat.checkout()
                            .filter(GitCheckout::noPomInRoot)
                            .ifPresent(nonMavenCheckouts::add);
                }
            });
        }
        catch (IOException ex)
        {
            Exceptions.chuck(ex);
        }
    }
    private final Map repoInternTable = new ConcurrentHashMap<>();

    private GitCheckout intern(GitCheckout co)
    {
        GitCheckout result = repoInternTable.putIfAbsent(co, co);
        if (result == null)
        {
            result = co;
        }
        return result;
    }

    private void cacheOnePomFile(Path path)
    {
        //            System.out.println(
        //                    "C1 " + Thread.currentThread().getName() + "\t" + path
        //                    .getParent().getFileName());
        Pom.from(path)
                .ifPresent(info ->
                {
                    Map subcache = infoForGroupAndArtifact
                            .computeIfAbsent(info.groupId()
                                    .text(),
                                    id -> new ConcurrentHashMap<>());
                    subcache.put(info.coordinates().artifactId.text(), info);
                    GitCheckout.checkout(info.path())
                            .ifPresent(co ->
                            {
                                co = intern(co);
                                Set poms = projectsByRepository
                                        .computeIfAbsent(co,
                                                c -> new HashSet<>());
                                poms.add(info);
                                checkoutForPom.put(info, co);
                            });
                });
    }

    Void invalidateBranches(GitCheckout co)
    {
        this.allBranches.remove(co);
        this.dirtyIgnoring.remove(co);
        this.dirty.remove(co);
        this.detachedHeads.remove(co);
        this.branches.remove(co);
        return null;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy