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

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

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

import com.telenav.cactus.maven.git.Branches;
import com.telenav.cactus.maven.git.GitCheckout;
import com.telenav.cactus.maven.git.Heads;
import com.telenav.cactus.maven.util.ThrowingOptional;
import com.telenav.cactus.maven.xml.PomInfo;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
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.TreeMap;
import java.util.TreeSet;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.apache.maven.project.MavenProject;

/**
 *
 * @author Tim Boudreau
 */
public class ProjectTree
{

    private final GitCheckout root;
    private volatile boolean upToDate;
    private final Cache cache = new Cache();

    ProjectTree(GitCheckout root)
    {
        this.root = root;
    }

    public GitCheckout root()
    {
        return root;
    }

    public static ThrowingOptional from(MavenProject project)
    {
        return from(project.getBasedir().toPath());
    }

    public static ThrowingOptional from(Path fileOrFolder)
    {
        return ThrowingOptional.from(GitCheckout.repository(fileOrFolder))
                .flatMapThrowing(repo -> repo.submoduleRoot())
                .map(ProjectTree::new);
    }

    public void invalidateCache()
    {
        if (upToDate)
        {
            synchronized (this)
            {
                upToDate = false;
                cache.clear();
            }
        }
    }

    private synchronized  T withCache(Function func)
    {
        if (!upToDate)
        {
            cache.populate();
        }
        return func.apply(cache);
    }

    public Optional findProject(String groupId, String artifactId)
    {
        return withCache(c ->
        {
            return c.project(groupId, artifactId);
        });
    }

    public boolean areVersionsConsistent()
    {
        return allVersions().size() <= 1;
    }

    public Set allBranches(Predicate pred)
    {

        return allCheckouts().stream()
                // filter to the checkouts we want
                .filter(pred)
                // map to the branch, which may not be present
                .map(co -> co.branch().orElse(""))
                // prune those that are not on a branch
                .filter(s -> !s.isEmpty())
                .collect(Collectors.toCollection(HashSet::new));
    }

    public Set allBranches()
    {
        Set branches = new HashSet<>();
        this.allCheckouts().forEach(checkout ->
        {
            checkout.branch().ifPresent(branches::add);
        });
        return branches;
    }

    public Set allVersions()
    {
        Set result = new HashSet<>();
        allProjects().forEach(pom ->
        {
            result.add(pom.coords.version);
        });
        return result;
    }

    public Set allVersions(Predicate test)
    {
        Set result = new HashSet<>();
        allProjects().forEach(pom ->
        {
            if (test.test(pom))
            {
                result.add(pom.coords.version);
            }
        });
        return result;
    }

    public Set allProjects()
    {
        return withCache(Cache::allPoms);
    }

    public Set projectsForGroupId(String groupId)
    {
        Set result = new TreeSet<>();
        allProjects().forEach(project ->
        {
            if (groupId.equals(project.coords.groupId))
            {
                result.add(project);
            }
        });
        return result;
    }

    public Map> branchesByGroupId()
    {
        return withCache(c ->
        {
            Map> result = new TreeMap<>();
            c.allPoms().forEach(pom ->
            {
                pom.checkout().ifPresent(checkout ->
                {
                    Set branches = result.computeIfAbsent(pom.coords.groupId, g -> new TreeSet<>());

                });
            });
            return result;
        });
    }

    public Map>> projectsByBranchByGroupId(Predicate filter)
    {
        return withCache(c ->
        {
            Map>> result = new TreeMap<>();
            for (PomInfo pom : c.allPoms())
            {
                if (!filter.test(pom))
                {
                    continue;
                }
                Map> infosByBranch = result.computeIfAbsent(
                        pom.coords.groupId, id -> new TreeMap<>());
                pom.checkout().ifPresent(checkout ->
                {
                    c.branchFor(checkout).ifPresent(branch ->
                    {
                        Set set = infosByBranch.computeIfAbsent(branch,
                                b -> new TreeSet<>());
                        set.add(pom);
                    });
                });
            }
            return result;
        });
    }

    public Map>> projectsByGroupIdAndVersion()
    {
        Map>> result = new TreeMap<>();
        projectsByGroupId().forEach((gid, poms) ->
        {
            Map> subMap = result.computeIfAbsent(gid, g -> new TreeMap<>());
            for (PomInfo info : poms)
            {
                Set pomSet = subMap.computeIfAbsent(info.coords.version, v -> new TreeSet<>());
                pomSet.add(info);
            }
        });
        return result;
    }

    public Set groupIdsIn(GitCheckout checkout)
    {
        return withCache(c ->
        {
            return c.projectsWithin(checkout).stream().map(info -> info.coords.groupId)
                    .collect(Collectors.toCollection(HashSet::new));
        });
    }

    public Map> projectsByGroupId()
    {
        Map> result = new TreeMap<>();
        allProjects().forEach(pom ->
        {
            Set set = result.computeIfAbsent(pom.coords.groupId, x -> new TreeSet<>());
            set.add(pom);
        });
        return result;
    }

    public Set allProjectFolders()
    {
        return withCache(c ->
        {
            Set result = new HashSet<>();
            c.allPoms().forEach(pom -> result.add(pom.projectFolder()));
            return result;
        });
    }

    public Map> projectsByVersion(Predicate filter)
    {
        return withCache(c ->
        {
            Map> result = new TreeMap<>();
            c.allPoms().forEach(pom ->
            {
                if (filter.test(pom))
                {
                    Set infos = result.computeIfAbsent(pom.coords.version, v -> new TreeSet<>());
                    infos.add(pom);
                }
            });
            return result;
        });
    }

    public Optional projectOf(Path file)
    {
        withCache(c ->
        {
            List paths = new ArrayList<>();
            Map candidateItems = new HashMap<>();
            c.projectFolders().forEach((dir, pomInfo) ->
            {
                if (file.startsWith(dir))
                {
                    candidateItems.put(dir, pomInfo);
                    paths.add(dir);
                }
            });
            if (paths.isEmpty())
            {
                return Optional.empty();
            }
            Collections.sort(paths, (a, b) ->
            {
                // reverse sort
                return Integer.compare(b.getNameCount(), a.getNameCount());
            });
            return Optional.of(candidateItems.get(paths.get(0)));
        });
        return null;
    }

    public GitCheckout checkoutFor(PomInfo info)
    {
        return withCache(c -> c.checkoutForPom.get(info));
    }

    public Set allCheckouts()
    {
        return withCache(Cache::allCheckouts);
    }

    public Optional branchFor(GitCheckout checkout)
    {
        return withCache(c -> c.branchFor(checkout));
    }

    public boolean isDirty(GitCheckout checkout)
    {
        return withCache(c -> c.isDirty(checkout));
    }

    public Set checkoutsFor(Collection infos)
    {
        return withCache(c ->
        {
            Set result = new TreeSet<>();
            infos.forEach(pom -> c.checkoutFor(pom).ifPresent(result::add));
            return result;
        });
    }

    public Branches branches(GitCheckout checkout)
    {
        return withCache(c -> c.branches(checkout));
    }

    public Optional mostCommonBranchForGroupId(String groupId)
    {
        return withCache(c -> c.mostCommonBranchForGroupId(groupId));
    }

    public boolean isDetachedHead(GitCheckout checkout)
    {
        return withCache(c -> c.isDetachedHead(checkout));
    }

    public Set projectsWithin(GitCheckout checkout)
    {
        return withCache(c -> c.projectsWithin(checkout));
    }

    public Set nonMavenCheckouts()
    {
        return withCache(c -> c.nonMavenCheckouts());
    }

    public Set checkoutsContainingGroupId(String groupId)
    {
        return withCache(c -> c.checkoutsContainingGroupId(groupId));
    }

    public Heads remoteHeads(GitCheckout checkout)
    {
        return withCache(c -> c.remoteHeads(checkout));
    }

    final class Cache
    {

        private final Map> infoForGroupAndArtifact
                = new HashMap<>();
        private final Map> projectsByRepository
                = new HashMap<>();
        private final Map checkoutForPom = new HashMap<>();
        private final Map> branches = new HashMap<>();
        private final Map dirty = new HashMap<>();
        private final Map allBranches = new HashMap<>();
        private final Map> branchByGroupId = new HashMap<>();
        private final Map detachedHeads = new HashMap<>();
        private final Set nonMavenCheckouts = new HashSet<>();
        private final Map remoteHeads = new HashMap<>();

        public Heads remoteHeads(GitCheckout checkout)
        {
            return remoteHeads.computeIfAbsent(checkout, ck -> ck.remoteHeads());
        }

        public Set checkoutsContainingGroupId(String groupId)
        {
            Set all = new HashSet<>();
            projectsByRepository.forEach((repo, projectSet) ->
            {
                for (PomInfo project : projectSet)
                {
                    if (groupId.equals(project.coords.groupId))
                    {
                        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) || !groupId.equals(pom.coords.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, co -> co.branches());
        }

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

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

        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.pom.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();
            branches.clear();
            dirty.clear();
            allBranches.clear();
            branchByGroupId.clear();
            nonMavenCheckouts.clear();
            detachedHeads.clear();
        }

        void populate()
        {
            root.submodules().ifPresent(statii ->
            {
                statii.forEach(status ->
                {
                    status.repository().ifPresent(repo ->
                    {
                        if (!repo.hasPomInRoot())
                        {
                            nonMavenCheckouts.add(repo);
                        }
                    });
                });
            });

            root.pomFiles(true).forEach(path ->
            {
                PomInfo.from(path).ifPresent(info ->
                {
                    Map subcache
                            = infoForGroupAndArtifact.computeIfAbsent(info.coords.groupId,
                                    id -> new HashMap<>());
                    subcache.put(info.coords.artifactId, info);
                    GitCheckout.repository(info.pom).ifPresent(co ->
                    {
                        Set poms = projectsByRepository.computeIfAbsent(co, c -> new HashSet<>());
//                        System.out.println("CACHE: " + info + " is in " + co.checkoutRoot().getFileName());
                        poms.add(info);
                    });
                });
            });
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy