1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.project;
20
21 import java.util.Collection;
22 import java.util.Collections;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.stream.Collectors;
27
28 import org.apache.maven.api.model.Build;
29 import org.apache.maven.api.model.Dependency;
30 import org.apache.maven.api.model.Extension;
31 import org.apache.maven.api.model.Parent;
32 import org.apache.maven.api.model.Plugin;
33 import org.apache.maven.artifact.ArtifactUtils;
34 import org.codehaus.plexus.util.dag.CycleDetectedException;
35 import org.codehaus.plexus.util.dag.DAG;
36 import org.codehaus.plexus.util.dag.TopologicalSorter;
37 import org.codehaus.plexus.util.dag.Vertex;
38
39
40
41
42 public class ProjectSorter {
43 private DAG dag;
44
45 private List<MavenProject> sortedProjects;
46
47 private Map<String, MavenProject> projectMap;
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72 public ProjectSorter(Collection<MavenProject> projects) throws CycleDetectedException, DuplicateProjectException {
73 dag = new DAG();
74
75
76 projectMap = new HashMap<>(projects.size() * 2);
77
78
79 Map<String, Map<String, Vertex>> vertexMap = new HashMap<>(projects.size() * 2);
80
81 for (MavenProject project : projects) {
82 String projectId = getId(project);
83
84 MavenProject conflictingProject = projectMap.put(projectId, project);
85
86 if (conflictingProject != null) {
87 throw new DuplicateProjectException(
88 projectId,
89 conflictingProject.getFile(),
90 project.getFile(),
91 "Project '" + projectId + "' is duplicated in the reactor");
92 }
93
94 String projectKey = ArtifactUtils.versionlessKey(project.getGroupId(), project.getArtifactId());
95
96 Map<String, Vertex> vertices = vertexMap.computeIfAbsent(projectKey, k -> new HashMap<>(2, 1));
97
98 vertices.put(project.getVersion(), dag.addVertex(projectId));
99 }
100
101 for (Vertex projectVertex : dag.getVertices()) {
102 String projectId = projectVertex.getLabel();
103
104 MavenProject project = projectMap.get(projectId);
105
106 for (Dependency dependency : project.getModel().getDelegate().getDependencies()) {
107 addEdge(
108 projectMap,
109 vertexMap,
110 project,
111 projectVertex,
112 dependency.getGroupId(),
113 dependency.getArtifactId(),
114 dependency.getVersion(),
115 false,
116 false);
117 }
118
119 Parent parent = project.getModel().getDelegate().getParent();
120
121 if (parent != null) {
122
123
124 addEdge(
125 projectMap,
126 vertexMap,
127 null,
128 projectVertex,
129 parent.getGroupId(),
130 parent.getArtifactId(),
131 parent.getVersion(),
132 true,
133 false);
134 }
135
136 Build build = project.getModel().getDelegate().getBuild();
137 if (build != null) {
138 for (Plugin plugin : build.getPlugins()) {
139 addEdge(
140 projectMap,
141 vertexMap,
142 project,
143 projectVertex,
144 plugin.getGroupId(),
145 plugin.getArtifactId(),
146 plugin.getVersion(),
147 false,
148 true);
149
150 for (Dependency dependency : plugin.getDependencies()) {
151 addEdge(
152 projectMap,
153 vertexMap,
154 project,
155 projectVertex,
156 dependency.getGroupId(),
157 dependency.getArtifactId(),
158 dependency.getVersion(),
159 false,
160 true);
161 }
162 }
163
164 for (Extension extension : build.getExtensions()) {
165 addEdge(
166 projectMap,
167 vertexMap,
168 project,
169 projectVertex,
170 extension.getGroupId(),
171 extension.getArtifactId(),
172 extension.getVersion(),
173 false,
174 true);
175 }
176 }
177 }
178
179 List<String> sortedProjectLabels = TopologicalSorter.sort(dag);
180
181 this.sortedProjects = sortedProjectLabels.stream()
182 .map(id -> projectMap.get(id))
183 .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
184 }
185
186 @SuppressWarnings("checkstyle:parameternumber")
187 private void addEdge(
188 Map<String, MavenProject> projectMap,
189 Map<String, Map<String, Vertex>> vertexMap,
190 MavenProject project,
191 Vertex projectVertex,
192 String groupId,
193 String artifactId,
194 String version,
195 boolean force,
196 boolean safe)
197 throws CycleDetectedException {
198 String projectKey = ArtifactUtils.versionlessKey(groupId, artifactId);
199
200 Map<String, Vertex> vertices = vertexMap.get(projectKey);
201
202 if (vertices != null) {
203 if (isSpecificVersion(version)) {
204 Vertex vertex = vertices.get(version);
205 if (vertex != null) {
206 addEdge(projectVertex, vertex, project, projectMap, force, safe);
207 }
208 } else {
209 for (Vertex vertex : vertices.values()) {
210 addEdge(projectVertex, vertex, project, projectMap, force, safe);
211 }
212 }
213 }
214 }
215
216 private void addEdge(
217 Vertex fromVertex,
218 Vertex toVertex,
219 MavenProject fromProject,
220 Map<String, MavenProject> projectMap,
221 boolean force,
222 boolean safe)
223 throws CycleDetectedException {
224 if (fromVertex.equals(toVertex)) {
225 return;
226 }
227
228 if (fromProject != null) {
229 MavenProject toProject = projectMap.get(toVertex.getLabel());
230 fromProject.addProjectReference(toProject);
231 }
232
233 if (force && toVertex.getChildren().contains(fromVertex)) {
234 dag.removeEdge(toVertex, fromVertex);
235 }
236
237 try {
238 dag.addEdge(fromVertex, toVertex);
239 } catch (CycleDetectedException e) {
240 if (!safe) {
241 throw e;
242 }
243 }
244 }
245
246 private boolean isSpecificVersion(String version) {
247 return !((version == null || version.isEmpty()) || version.startsWith("[") || version.startsWith("("));
248 }
249
250
251
252 public MavenProject getTopLevelProject() {
253 return sortedProjects.stream()
254 .filter(MavenProject::isExecutionRoot)
255 .findFirst()
256 .orElse(null);
257 }
258
259 public List<MavenProject> getSortedProjects() {
260 return sortedProjects;
261 }
262
263 public boolean hasMultipleProjects() {
264 return sortedProjects.size() > 1;
265 }
266
267 public List<String> getDependents(String id) {
268 return dag.getParentLabels(id);
269 }
270
271 public List<String> getDependencies(String id) {
272 return dag.getChildLabels(id);
273 }
274
275 public static String getId(MavenProject project) {
276 return ArtifactUtils.key(project.getGroupId(), project.getArtifactId(), project.getVersion());
277 }
278
279 public DAG getDAG() {
280 return dag;
281 }
282
283 public Map<String, MavenProject> getProjectMap() {
284 return projectMap;
285 }
286 }