BuildPlan.java
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* http://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 org.apache.maven.lifecycle.internal.concurrent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.project.MavenProject;
public class BuildPlan {
private final Map<MavenProject, Map<String, BuildStep>> plan = new LinkedHashMap<>();
private final Map<MavenProject, List<MavenProject>> projects;
private final Map<String, String> aliases = new HashMap<>();
private volatile Set<String> duplicateIds;
private volatile List<BuildStep> sortedNodes;
BuildPlan() {
this.projects = null;
}
public BuildPlan(Map<MavenProject, List<MavenProject>> projects) {
this.projects = projects;
}
public Map<MavenProject, List<MavenProject>> getAllProjects() {
return projects;
}
public Map<String, String> aliases() {
return aliases;
}
public Stream<MavenProject> projects() {
return plan.keySet().stream();
}
public void addProject(MavenProject project, Map<String, BuildStep> steps) {
plan.put(project, steps);
}
public void addStep(MavenProject project, String name, BuildStep step) {
plan.get(project).put(name, step);
}
public Stream<BuildStep> allSteps() {
return plan.values().stream().flatMap(m -> m.values().stream());
}
public Stream<BuildStep> steps(MavenProject project) {
return Optional.ofNullable(plan.get(project))
.map(m -> m.values().stream())
.orElse(Stream.empty());
}
public Optional<BuildStep> step(MavenProject project, String name) {
return Optional.ofNullable(plan.get(project)).map(m -> m.get(name));
}
public BuildStep requiredStep(MavenProject project, String name) {
return step(project, name).orElseThrow(() -> new NoSuchElementException("Step " + name + " not found"));
}
// add a follow-up plan to this one
public void then(BuildPlan step) {
step.plan.forEach((k, v) -> plan.merge(k, v, this::merge));
aliases.putAll(step.aliases);
}
private Map<String, BuildStep> merge(Map<String, BuildStep> org, Map<String, BuildStep> add) {
// all new phases should be added after the existing ones
List<BuildStep> lasts =
org.values().stream().filter(b -> b.successors.isEmpty()).toList();
List<BuildStep> firsts =
add.values().stream().filter(b -> b.predecessors.isEmpty()).toList();
firsts.stream()
.filter(addNode -> !org.containsKey(addNode.name))
.forEach(addNode -> lasts.forEach(orgNode -> addNode.executeAfter(orgNode)));
add.forEach((name, node) -> org.merge(name, node, this::merge));
return org;
}
private BuildStep merge(BuildStep node1, BuildStep node2) {
node1.predecessors.addAll(node2.predecessors);
node1.successors.addAll(node2.successors);
node2.mojos.forEach((k, v) -> node1.mojos.merge(k, v, this::mergeMojos));
return node1;
}
private Map<String, MojoExecution> mergeMojos(Map<String, MojoExecution> l1, Map<String, MojoExecution> l2) {
l2.forEach(l1::putIfAbsent);
return l1;
}
// gather artifactIds which are not unique so that the respective thread names can be extended with the groupId
public Set<String> duplicateIds() {
if (duplicateIds == null) {
synchronized (this) {
if (duplicateIds == null) {
duplicateIds = projects()
.map(MavenProject::getArtifactId)
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.entrySet()
.stream()
.filter(p -> p.getValue() > 1)
.map(Map.Entry::getKey)
.collect(Collectors.toSet());
}
}
}
return duplicateIds;
}
public List<BuildStep> sortedNodes() {
if (sortedNodes == null) {
synchronized (this) {
if (sortedNodes == null) {
List<BuildStep> sortedNodes = new ArrayList<>();
Set<BuildStep> visited = new HashSet<>();
// Visit each unvisited node
allSteps().forEach(node -> visitNode(node, visited, sortedNodes));
// Reverse the sorted nodes to get the correct order
Collections.reverse(sortedNodes);
this.sortedNodes = sortedNodes;
}
}
}
return sortedNodes;
}
// Helper method to visit a node
private static void visitNode(BuildStep node, Set<BuildStep> visited, List<BuildStep> sortedNodes) {
if (visited.add(node)) {
// For each successor of the current node, visit unvisited successors
node.successors.forEach(successor -> visitNode(successor, visited, sortedNodes));
sortedNodes.add(node);
}
}
}