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.ArrayList;
22 import java.util.Collection;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.Map;
28
29 import org.apache.maven.artifact.ArtifactUtils;
30 import org.apache.maven.model.Dependency;
31 import org.apache.maven.model.Extension;
32 import org.apache.maven.model.Parent;
33 import org.apache.maven.model.Plugin;
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 private MavenProject topLevelProject;
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75 public ProjectSorter(Collection<MavenProject> projects) throws CycleDetectedException, DuplicateProjectException {
76 dag = new DAG();
77
78
79 projectMap = new HashMap<>(projects.size() * 2);
80
81
82 Map<String, Map<String, Vertex>> vertexMap = new HashMap<>(projects.size() * 2);
83
84 for (MavenProject project : projects) {
85 String projectId = getId(project);
86
87 MavenProject conflictingProject = projectMap.put(projectId, project);
88
89 if (conflictingProject != null) {
90 throw new DuplicateProjectException(
91 projectId,
92 conflictingProject.getFile(),
93 project.getFile(),
94 "Project '" + projectId + "' is duplicated in the reactor");
95 }
96
97 String projectKey = ArtifactUtils.versionlessKey(project.getGroupId(), project.getArtifactId());
98
99 Map<String, Vertex> vertices = vertexMap.get(projectKey);
100 if (vertices == null) {
101 vertices = new HashMap<>(2, 1);
102 vertexMap.put(projectKey, vertices);
103 }
104 vertices.put(project.getVersion(), dag.addVertex(projectId));
105 }
106
107 for (Vertex projectVertex : dag.getVertices()) {
108 String projectId = projectVertex.getLabel();
109
110 MavenProject project = projectMap.get(projectId);
111
112 for (Dependency dependency : project.getDependencies()) {
113 addEdge(
114 projectMap,
115 vertexMap,
116 project,
117 projectVertex,
118 dependency.getGroupId(),
119 dependency.getArtifactId(),
120 dependency.getVersion(),
121 false,
122 false);
123 }
124
125 org.apache.maven.model.Model originalModel = project.getOriginalModel();
126 if (originalModel != null && originalModel.getDependencyManagement() != null) {
127 for (org.apache.maven.model.Dependency dependency :
128 originalModel.getDependencyManagement().getDependencies()) {
129 if ("import".equals(dependency.getScope()) && "pom".equals(dependency.getType())) {
130 addEdge(
131 projectMap,
132 vertexMap,
133 project,
134 projectVertex,
135 dependency.getGroupId(),
136 dependency.getArtifactId(),
137 resolveImportVersion(project, dependency.getVersion()),
138 false,
139 false);
140 }
141 }
142 }
143
144 Parent parent = project.getModel().getParent();
145
146 if (parent != null) {
147
148
149 addEdge(
150 projectMap,
151 vertexMap,
152 null,
153 projectVertex,
154 parent.getGroupId(),
155 parent.getArtifactId(),
156 parent.getVersion(),
157 true,
158 false);
159 }
160
161 List<Plugin> buildPlugins = project.getBuildPlugins();
162 if (buildPlugins != null) {
163 for (Plugin plugin : buildPlugins) {
164 addEdge(
165 projectMap,
166 vertexMap,
167 project,
168 projectVertex,
169 plugin.getGroupId(),
170 plugin.getArtifactId(),
171 plugin.getVersion(),
172 false,
173 true);
174
175 for (Dependency dependency : plugin.getDependencies()) {
176 addEdge(
177 projectMap,
178 vertexMap,
179 project,
180 projectVertex,
181 dependency.getGroupId(),
182 dependency.getArtifactId(),
183 dependency.getVersion(),
184 false,
185 true);
186 }
187 }
188 }
189
190 List<Extension> buildExtensions = project.getBuildExtensions();
191 if (buildExtensions != null) {
192 for (Extension extension : buildExtensions) {
193 addEdge(
194 projectMap,
195 vertexMap,
196 project,
197 projectVertex,
198 extension.getGroupId(),
199 extension.getArtifactId(),
200 extension.getVersion(),
201 false,
202 true);
203 }
204 }
205 }
206
207 List<MavenProject> sortedProjects = new ArrayList<>(projects.size());
208
209 List<String> sortedProjectLabels = TopologicalSorter.sort(dag);
210
211 for (String id : sortedProjectLabels) {
212 sortedProjects.add(projectMap.get(id));
213 }
214
215 this.sortedProjects = Collections.unmodifiableList(sortedProjects);
216 }
217
218 private static String resolveImportVersion(MavenProject project, String version) {
219 if ("${project.version}".equals(version) || "${pom.version}".equals(version)) {
220 return project.getVersion();
221 }
222 if (version != null && version.startsWith("${") && version.endsWith("}")) {
223 String property = project.getProperties().getProperty(version.substring(2, version.length() - 1));
224 if (property != null) {
225 return property;
226 }
227 }
228 return version;
229 }
230
231 @SuppressWarnings("checkstyle:parameternumber")
232 private void addEdge(
233 Map<String, MavenProject> projectMap,
234 Map<String, Map<String, Vertex>> vertexMap,
235 MavenProject project,
236 Vertex projectVertex,
237 String groupId,
238 String artifactId,
239 String version,
240 boolean force,
241 boolean safe)
242 throws CycleDetectedException {
243 String projectKey = ArtifactUtils.versionlessKey(groupId, artifactId);
244
245 Map<String, Vertex> vertices = vertexMap.get(projectKey);
246
247 if (vertices != null) {
248 if (isSpecificVersion(version)) {
249 Vertex vertex = vertices.get(version);
250 if (vertex != null) {
251 addEdge(projectVertex, vertex, project, projectMap, force, safe);
252 }
253 } else {
254 for (Vertex vertex : vertices.values()) {
255 addEdge(projectVertex, vertex, project, projectMap, force, safe);
256 }
257 }
258 }
259 }
260
261 private void addEdge(
262 Vertex fromVertex,
263 Vertex toVertex,
264 MavenProject fromProject,
265 Map<String, MavenProject> projectMap,
266 boolean force,
267 boolean safe)
268 throws CycleDetectedException {
269 if (fromVertex.equals(toVertex)) {
270 return;
271 }
272
273 if (fromProject != null) {
274 MavenProject toProject = projectMap.get(toVertex.getLabel());
275 fromProject.addProjectReference(toProject);
276 }
277
278 if (force && toVertex.getChildren().contains(fromVertex)) {
279 dag.removeEdge(toVertex, fromVertex);
280 }
281
282 try {
283 dag.addEdge(fromVertex, toVertex);
284 } catch (CycleDetectedException e) {
285 if (!safe) {
286 throw e;
287 }
288 }
289 }
290
291 private boolean isSpecificVersion(String version) {
292 return !(StringUtils.isEmpty(version) || version.startsWith("[") || version.startsWith("("));
293 }
294
295
296
297 public MavenProject getTopLevelProject() {
298 if (topLevelProject == null) {
299 for (Iterator<MavenProject> i = sortedProjects.iterator(); i.hasNext() && (topLevelProject == null); ) {
300 MavenProject project = i.next();
301 if (project.isExecutionRoot()) {
302 topLevelProject = project;
303 }
304 }
305 }
306
307 return topLevelProject;
308 }
309
310 public List<MavenProject> getSortedProjects() {
311 return sortedProjects;
312 }
313
314 public boolean hasMultipleProjects() {
315 return sortedProjects.size() > 1;
316 }
317
318 public List<String> getDependents(String id) {
319 return dag.getParentLabels(id);
320 }
321
322 public List<String> getDependencies(String id) {
323 return dag.getChildLabels(id);
324 }
325
326 public static String getId(MavenProject project) {
327 return ArtifactUtils.key(project.getGroupId(), project.getArtifactId(), project.getVersion());
328 }
329
330 public DAG getDAG() {
331 return dag;
332 }
333
334 public Map<String, MavenProject> getProjectMap() {
335 return projectMap;
336 }
337 }