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.Optional;
29  import java.util.Set;
30  import java.util.function.Function;
31  import java.util.stream.Collectors;
32  import java.util.stream.Stream;
33  
34  import org.apache.maven.plugin.MojoExecution;
35  import org.apache.maven.project.MavenProject;
36  
37  public class BuildPlan {
38  
39      private final Map<MavenProject, Map<String, BuildStep>> plan = new LinkedHashMap<>();
40      private final Map<MavenProject, List<MavenProject>> projects;
41      private final Map<String, String> aliases = new HashMap<>();
42      private volatile Set<String> duplicateIds;
43      private volatile List<BuildStep> sortedNodes;
44  
45      BuildPlan() {
46          this.projects = null;
47      }
48  
49      public BuildPlan(Map<MavenProject, List<MavenProject>> projects) {
50          this.projects = projects;
51      }
52  
53      public Map<MavenProject, List<MavenProject>> getAllProjects() {
54          return projects;
55      }
56  
57      public Map<String, String> aliases() {
58          return aliases;
59      }
60  
61      public Stream<MavenProject> projects() {
62          return plan.keySet().stream();
63      }
64  
65      public void addProject(MavenProject project, Map<String, BuildStep> steps) {
66          plan.put(project, steps);
67      }
68  
69      public void addStep(MavenProject project, String name, BuildStep step) {
70          plan.get(project).put(name, step);
71      }
72  
73      public Stream<BuildStep> allSteps() {
74          return plan.values().stream().flatMap(m -> m.values().stream());
75      }
76  
77      public Stream<BuildStep> steps(MavenProject project) {
78          return Optional.ofNullable(plan.get(project))
79                  .map(m -> m.values().stream())
80                  .orElse(Stream.empty());
81      }
82  
83      public Optional<BuildStep> step(MavenProject project, String name) {
84          return Optional.ofNullable(plan.get(project)).map(m -> m.get(name));
85      }
86  
87      public BuildStep requiredStep(MavenProject project, String name) {
88          return step(project, name).get();
89      }
90  
91      // add a follow-up plan to this one
92      public void then(BuildPlan step) {
93          step.plan.forEach((k, v) -> plan.merge(k, v, this::merge));
94          aliases.putAll(step.aliases);
95      }
96  
97      private Map<String, BuildStep> merge(Map<String, BuildStep> org, Map<String, BuildStep> add) {
98          // all new phases should be added after the existing ones
99          List<BuildStep> lasts =
100                 org.values().stream().filter(b -> b.successors.isEmpty()).collect(Collectors.toList());
101         List<BuildStep> firsts =
102                 add.values().stream().filter(b -> b.predecessors.isEmpty()).collect(Collectors.toList());
103         firsts.stream()
104                 .filter(addNode -> !org.containsKey(addNode.name))
105                 .forEach(addNode -> lasts.forEach(orgNode -> addNode.executeAfter(orgNode)));
106         add.forEach((name, node) -> org.merge(name, node, this::merge));
107         return org;
108     }
109 
110     private BuildStep merge(BuildStep node1, BuildStep node2) {
111         node1.predecessors.addAll(node2.predecessors);
112         node1.successors.addAll(node2.successors);
113         node2.mojos.forEach((k, v) -> node1.mojos.merge(k, v, this::mergeMojos));
114         return node1;
115     }
116 
117     private Map<String, MojoExecution> mergeMojos(Map<String, MojoExecution> l1, Map<String, MojoExecution> l2) {
118         l2.forEach(l1::putIfAbsent);
119         return l1;
120     }
121 
122     // gather artifactIds which are not unique so that the respective thread names can be extended with the groupId
123     public Set<String> duplicateIds() {
124         if (duplicateIds == null) {
125             synchronized (this) {
126                 if (duplicateIds == null) {
127                     duplicateIds = projects()
128                             .map(MavenProject::getArtifactId)
129                             .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
130                             .entrySet()
131                             .stream()
132                             .filter(p -> p.getValue() > 1)
133                             .map(Map.Entry::getKey)
134                             .collect(Collectors.toSet());
135                 }
136             }
137         }
138         return duplicateIds;
139     }
140 
141     public List<BuildStep> sortedNodes() {
142         if (sortedNodes == null) {
143             synchronized (this) {
144                 if (sortedNodes == null) {
145                     List<BuildStep> sortedNodes = new ArrayList<>();
146                     Set<BuildStep> visited = new HashSet<>();
147                     // Visit each unvisited node
148                     allSteps().forEach(node -> visitNode(node, visited, sortedNodes));
149                     // Reverse the sorted nodes to get the correct order
150                     Collections.reverse(sortedNodes);
151                     this.sortedNodes = sortedNodes;
152                 }
153             }
154         }
155         return sortedNodes;
156     }
157 
158     // Helper method to visit a node
159     private static void visitNode(BuildStep node, Set<BuildStep> visited, List<BuildStep> sortedNodes) {
160         if (visited.add(node)) {
161             // For each successor of the current node, visit unvisited successors
162             node.successors.forEach(successor -> visitNode(successor, visited, sortedNodes));
163             sortedNodes.add(node);
164         }
165     }
166 }