001: package org.apache.maven.project;
002:
003: /*
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021:
022: import org.apache.maven.artifact.ArtifactUtils;
023: import org.apache.maven.model.Dependency;
024: import org.apache.maven.model.Extension;
025: import org.apache.maven.model.Plugin;
026: import org.apache.maven.model.ReportPlugin;
027: import org.codehaus.plexus.util.dag.CycleDetectedException;
028: import org.codehaus.plexus.util.dag.DAG;
029: import org.codehaus.plexus.util.dag.TopologicalSorter;
030:
031: import java.util.ArrayList;
032: import java.util.Collections;
033: import java.util.HashMap;
034: import java.util.Iterator;
035: import java.util.List;
036: import java.util.Map;
037:
038: /**
039: * Sort projects by dependencies.
040: *
041: * @author <a href="mailto:brett@apache.org">Brett Porter</a>
042: * @version $Id: ProjectSorter.java 513038 2007-02-28 22:54:19Z jvanzyl $
043: */
044: public class ProjectSorter {
045: private final DAG dag;
046:
047: private final List sortedProjects;
048:
049: private MavenProject topLevelProject;
050:
051: /**
052: * Sort a list of projects.
053: * <ul>
054: * <li>collect all the vertices for the projects that we want to build.</li>
055: * <li>iterate through the deps of each project and if that dep is within
056: * the set of projects we want to build then add an edge, otherwise throw
057: * the edge away because that dependency is not within the set of projects
058: * we are trying to build. we assume a closed set.</li>
059: * <li>do a topo sort on the graph that remains.</li>
060: * </ul>
061: * @throws DuplicateProjectException if any projects are duplicated by id
062: */
063: // MAVENAPI FIXME: the DAG used is NOT only used to represent the dependency relation,
064: // but also for <parent>, <build><plugin>, <reports>. We need multiple DAG's
065: // since a DAG can only handle 1 type of relationship properly.
066: // Usecase: This is detected as a cycle:
067: // org.apache.maven:maven-plugin-api -(PARENT)->
068: // org.apache.maven:maven -(inherited REPORTING)->
069: // org.apache.maven.plugins:maven-checkstyle-plugin -(DEPENDENCY)->
070: // org.apache.maven:maven-plugin-api
071: // In this case, both the verify and the report goals are called
072: // in a different lifecycle. Though the compiler-plugin has a valid usecase, although
073: // that seems to work fine. We need to take versions and lifecycle into account.
074: public ProjectSorter(List projects) throws CycleDetectedException,
075: DuplicateProjectException {
076: dag = new DAG();
077:
078: Map projectMap = new HashMap();
079:
080: for (Iterator i = projects.iterator(); i.hasNext();) {
081: MavenProject project = (MavenProject) i.next();
082:
083: String id = ArtifactUtils.versionlessKey(project
084: .getGroupId(), project.getArtifactId());
085:
086: if (dag.getVertex(id) != null) {
087: throw new DuplicateProjectException("Project '" + id
088: + "' is duplicated in the reactor");
089: }
090:
091: dag.addVertex(id);
092:
093: projectMap.put(id, project);
094: }
095:
096: for (Iterator i = projects.iterator(); i.hasNext();) {
097: MavenProject project = (MavenProject) i.next();
098:
099: String id = ArtifactUtils.versionlessKey(project
100: .getGroupId(), project.getArtifactId());
101:
102: for (Iterator j = project.getDependencies().iterator(); j
103: .hasNext();) {
104: Dependency dependency = (Dependency) j.next();
105:
106: String dependencyId = ArtifactUtils.versionlessKey(
107: dependency.getGroupId(), dependency
108: .getArtifactId());
109:
110: if (dag.getVertex(dependencyId) != null) {
111: project
112: .addProjectReference((MavenProject) projectMap
113: .get(dependencyId));
114:
115: dag.addEdge(id, dependencyId);
116: }
117: }
118:
119: MavenProject parent = project.getParent();
120: if (parent != null) {
121: String parentId = ArtifactUtils.versionlessKey(parent
122: .getGroupId(), parent.getArtifactId());
123: if (dag.getVertex(parentId) != null) {
124: // Parent is added as an edge, but must not cause a cycle - so we remove any other edges it has in conflict
125: if (dag.hasEdge(parentId, id)) {
126: dag.removeEdge(parentId, id);
127: }
128: dag.addEdge(id, parentId);
129: }
130: }
131:
132: List buildPlugins = project.getBuildPlugins();
133: if (buildPlugins != null) {
134: for (Iterator j = buildPlugins.iterator(); j.hasNext();) {
135: Plugin plugin = (Plugin) j.next();
136: String pluginId = ArtifactUtils
137: .versionlessKey(plugin.getGroupId(), plugin
138: .getArtifactId());
139: if (dag.getVertex(pluginId) != null
140: && !pluginId.equals(id)) {
141: addEdgeWithParentCheck(projectMap, pluginId,
142: project, id);
143: }
144: }
145: }
146:
147: List reportPlugins = project.getReportPlugins();
148: if (reportPlugins != null) {
149: for (Iterator j = reportPlugins.iterator(); j.hasNext();) {
150: ReportPlugin plugin = (ReportPlugin) j.next();
151: String pluginId = ArtifactUtils
152: .versionlessKey(plugin.getGroupId(), plugin
153: .getArtifactId());
154: if (dag.getVertex(pluginId) != null
155: && !pluginId.equals(id)) {
156: addEdgeWithParentCheck(projectMap, pluginId,
157: project, id);
158: }
159: }
160: }
161:
162: for (Iterator j = project.getBuildExtensions().iterator(); j
163: .hasNext();) {
164: Extension extension = (Extension) j.next();
165: String extensionId = ArtifactUtils.versionlessKey(
166: extension.getGroupId(), extension
167: .getArtifactId());
168: if (dag.getVertex(extensionId) != null) {
169: addEdgeWithParentCheck(projectMap, extensionId,
170: project, id);
171: }
172: }
173: }
174:
175: List sortedProjects = new ArrayList();
176:
177: for (Iterator i = TopologicalSorter.sort(dag).iterator(); i
178: .hasNext();) {
179: String id = (String) i.next();
180:
181: sortedProjects.add(projectMap.get(id));
182: }
183:
184: this .sortedProjects = Collections
185: .unmodifiableList(sortedProjects);
186: }
187:
188: private void addEdgeWithParentCheck(Map projectMap,
189: String projectRefId, MavenProject project, String id)
190: throws CycleDetectedException {
191: MavenProject extProject = (MavenProject) projectMap
192: .get(projectRefId);
193:
194: if (extProject == null) {
195: return;
196: }
197:
198: project.addProjectReference(extProject);
199:
200: MavenProject extParent = extProject.getParent();
201: if (extParent != null) {
202: String parentId = ArtifactUtils.versionlessKey(extParent
203: .getGroupId(), extParent.getArtifactId());
204: // Don't add edge from parent to extension if a reverse edge already exists
205: if (!dag.hasEdge(projectRefId, id) || !parentId.equals(id)) {
206: dag.addEdge(id, projectRefId);
207: }
208: }
209: }
210:
211: // TODO: !![jc; 28-jul-2005] check this; if we're using '-r' and there are aggregator tasks, this will result in weirdness.
212: public MavenProject getTopLevelProject() {
213: if (topLevelProject == null) {
214: for (Iterator i = sortedProjects.iterator(); i.hasNext()
215: && topLevelProject == null;) {
216: MavenProject project = (MavenProject) i.next();
217: if (project.isExecutionRoot()) {
218: topLevelProject = project;
219: }
220: }
221: }
222:
223: return topLevelProject;
224: }
225:
226: public List getSortedProjects() {
227: return sortedProjects;
228: }
229:
230: public boolean hasMultipleProjects() {
231: return sortedProjects.size() > 1;
232: }
233:
234: public List getDependents(String id) {
235: return dag.getParentLabels(id);
236: }
237: }
|