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 extends PomInfo> 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);
});
});
});
}
}
}