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