View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.lifecycle.internal.concurrent;
20  
21  import java.util.ArrayList;
22  import java.util.Collections;
23  import java.util.HashMap;
24  import java.util.HashSet;
25  import java.util.LinkedHashMap;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.NoSuchElementException;
29  import java.util.Optional;
30  import java.util.Set;
31  import java.util.function.Function;
32  import java.util.stream.Collectors;
33  import java.util.stream.Stream;
34  
35  import org.apache.maven.plugin.MojoExecution;
36  import org.apache.maven.project.MavenProject;
37  
38  public class BuildPlan {
39  
40      private final Map<MavenProject, Map<String, BuildStep>> plan = new LinkedHashMap<>();
41      private final Map<MavenProject, List<MavenProject>> projects;
42      private final Map<String, String> aliases = new HashMap<>();
43      private volatile Set<String> duplicateIds;
44      private volatile List<BuildStep> sortedNodes;
45  
46      BuildPlan() {
47          this.projects = null;
48      }
49  
50      public BuildPlan(Map<MavenProject, List<MavenProject>> projects) {
51          this.projects = projects;
52      }
53  
54      public Map<MavenProject, List<MavenProject>> getAllProjects() {
55          return projects;
56      }
57  
58      public Map<String, String> aliases() {
59          return aliases;
60      }
61  
62      public Stream<MavenProject> projects() {
63          return plan.keySet().stream();
64      }
65  
66      public void addProject(MavenProject project, Map<String, BuildStep> steps) {
67          plan.put(project, steps);
68      }
69  
70      public void addStep(MavenProject project, String name, BuildStep step) {
71          plan.get(project).put(name, step);
72      }
73  
74      public Stream<BuildStep> allSteps() {
75          return plan.values().stream().flatMap(m -> m.values().stream());
76      }
77  
78      public Stream<BuildStep> steps(MavenProject project) {
79          return Optional.ofNullable(plan.get(project))
80                  .map(m -> m.values().stream())
81                  .orElse(Stream.empty());
82      }
83  
84      public Optional<BuildStep> step(MavenProject project, String name) {
85          return Optional.ofNullable(plan.get(project)).map(m -> m.get(name));
86      }
87  
88      public BuildStep requiredStep(MavenProject project, String name) {
89          return step(project, name).orElseThrow(() -> new NoSuchElementException("Step " + name + " not found"));
90      }
91  
92      // add a follow-up plan to this one
93      public void then(BuildPlan step) {
94          step.plan.forEach((k, v) -> plan.merge(k, v, this::merge));
95          aliases.putAll(step.aliases);
96      }
97  
98      private Map<String, BuildStep> merge(Map<String, BuildStep> org, Map<String, BuildStep> add) {
99          // all new phases should be added after the existing ones
100         List<BuildStep> lasts =
101                 org.values().stream().filter(b -> b.successors.isEmpty()).collect(Collectors.toList());
102         List<BuildStep> firsts =
103                 add.values().stream().filter(b -> b.predecessors.isEmpty()).collect(Collectors.toList());
104         firsts.stream()
105                 .filter(addNode -> !org.containsKey(addNode.name))
106                 .forEach(addNode -> lasts.forEach(orgNode -> addNode.executeAfter(orgNode)));
107         add.forEach((name, node) -> org.merge(name, node, this::merge));
108         return org;
109     }
110 
111     private BuildStep merge(BuildStep node1, BuildStep node2) {
112         node1.predecessors.addAll(node2.predecessors);
113         node1.successors.addAll(node2.successors);
114         node2.mojos.forEach((k, v) -> node1.mojos.merge(k, v, this::mergeMojos));
115         return node1;
116     }
117 
118     private Map<String, MojoExecution> mergeMojos(Map<String, MojoExecution> l1, Map<String, MojoExecution> l2) {
119         l2.forEach(l1::putIfAbsent);
120         return l1;
121     }
122 
123     // gather artifactIds which are not unique so that the respective thread names can be extended with the groupId
124     public Set<String> duplicateIds() {
125         if (duplicateIds == null) {
126             synchronized (this) {
127                 if (duplicateIds == null) {
128                     duplicateIds = projects()
129                             .map(MavenProject::getArtifactId)
130                             .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
131                             .entrySet()
132                             .stream()
133                             .filter(p -> p.getValue() > 1)
134                             .map(Map.Entry::getKey)
135                             .collect(Collectors.toSet());
136                 }
137             }
138         }
139         return duplicateIds;
140     }
141 
142     public List<BuildStep> sortedNodes() {
143         if (sortedNodes == null) {
144             synchronized (this) {
145                 if (sortedNodes == null) {
146                     List<BuildStep> sortedNodes = new ArrayList<>();
147                     Set<BuildStep> visited = new HashSet<>();
148                     // Visit each unvisited node
149                     allSteps().forEach(node -> visitNode(node, visited, sortedNodes));
150                     // Reverse the sorted nodes to get the correct order
151                     Collections.reverse(sortedNodes);
152                     this.sortedNodes = sortedNodes;
153                 }
154             }
155         }
156         return sortedNodes;
157     }
158 
159     // Helper method to visit a node
160     private static void visitNode(BuildStep node, Set<BuildStep> visited, List<BuildStep> sortedNodes) {
161         if (visited.add(node)) {
162             // For each successor of the current node, visit unvisited successors
163             node.successors.forEach(successor -> visitNode(successor, visited, sortedNodes));
164             sortedNodes.add(node);
165         }
166     }
167 }