package org.apache.maven.plugin.reactor;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Extension;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.ReportPlugin;
import org.apache.maven.project.DuplicateProjectException;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.dag.CycleDetectedException;
import org.codehaus.plexus.util.dag.DAG;
import org.codehaus.plexus.util.dag.TopologicalSorter;
/**
* Sort projects by dependencies. Just like ProjectSorter from maven-project, but this one exposes
* the DAG and the projectMap in getters.
*
*
* @author <a href="mailto:dfabulich@apache.org">Dan Fabulich</a>
* @author <a href="mailto:brett@apache.org">Brett Porter</a>
*/
public class SuperProjectSorter
{
private final DAG dag;
private final Map projectMap;
private final List sortedProjects;
private MavenProject topLevelProject;
/**
* Sort a list of projects.
* <ul>
* <li>collect all the vertices for the projects that we want to build.</li>
* <li>iterate through the deps of each project and if that dep is within
* the set of projects we want to build then add an edge, otherwise throw
* the edge away because that dependency is not within the set of projects
* we are trying to build. we assume a closed set.</li>
* <li>do a topo sort on the graph that remains.</li>
* </ul>
* @throws DuplicateProjectException if any projects are duplicated by id
*/
public SuperProjectSorter( List projects )
throws CycleDetectedException, DuplicateProjectException
{
dag = new DAG();
projectMap = new HashMap();
for (Object project2 : projects) {
MavenProject project = (MavenProject) project2;
String id = ArtifactUtils.versionlessKey(project.getGroupId(), project.getArtifactId());
if (dag.getVertex(id) != null) {
throw new DuplicateProjectException("Project '" + id + "' is duplicated in the reactor");
}
dag.addVertex(id);
projectMap.put(id, project);
}
for (Object project1 : projects) {
MavenProject project = (MavenProject) project1;
String id = ArtifactUtils.versionlessKey(project.getGroupId(), project.getArtifactId());
for (Object o1 : project.getDependencies()) {
Dependency dependency = (Dependency) o1;
String dependencyId = ArtifactUtils
.versionlessKey(dependency.getGroupId(), dependency.getArtifactId());
if (dag.getVertex(dependencyId) != null) {
project.addProjectReference((MavenProject) projectMap.get(dependencyId));
dag.addEdge(id, dependencyId);
}
}
MavenProject parent = project.getParent();
if (parent != null) {
String parentId = ArtifactUtils.versionlessKey(parent.getGroupId(), parent.getArtifactId());
if (dag.getVertex(parentId) != null) {
// Parent is added as an edge, but must not cause a cycle - so we remove any other edges it has in conflict
if (dag.hasEdge(parentId, id)) {
dag.removeEdge(parentId, id);
}
dag.addEdge(id, parentId);
}
}
List buildPlugins = project.getBuildPlugins();
if (buildPlugins != null) {
for (Object buildPlugin : buildPlugins) {
Plugin plugin = (Plugin) buildPlugin;
String pluginId = ArtifactUtils.versionlessKey(plugin.getGroupId(), plugin.getArtifactId());
if (dag.getVertex(pluginId) != null && !pluginId.equals(id)) {
addEdgeWithParentCheck(projectMap, pluginId, project, id);
}
}
}
List reportPlugins = project.getReportPlugins();
if (reportPlugins != null) {
for (Object reportPlugin : reportPlugins) {
ReportPlugin plugin = (ReportPlugin) reportPlugin;
String pluginId = ArtifactUtils.versionlessKey(plugin.getGroupId(), plugin.getArtifactId());
if (dag.getVertex(pluginId) != null && !pluginId.equals(id)) {
addEdgeWithParentCheck(projectMap, pluginId, project, id);
}
}
}
for (Object o : project.getBuildExtensions()) {
Extension extension = (Extension) o;
String extensionId = ArtifactUtils.versionlessKey(extension.getGroupId(), extension.getArtifactId());
if (dag.getVertex(extensionId) != null) {
addEdgeWithParentCheck(projectMap, extensionId, project, id);
}
}
}
List sortedProjects = new ArrayList();
for (String id : TopologicalSorter.sort(dag)) {
sortedProjects.add(projectMap.get(id));
}
this.sortedProjects = Collections.unmodifiableList( sortedProjects );
}
private void addEdgeWithParentCheck( Map projectMap, String projectRefId, MavenProject project, String id )
throws CycleDetectedException
{
MavenProject extProject = (MavenProject) projectMap.get( projectRefId );
if ( extProject == null )
{
return;
}
project.addProjectReference( extProject );
MavenProject extParent = extProject.getParent();
if ( extParent != null )
{
String parentId = ArtifactUtils.versionlessKey( extParent.getGroupId(), extParent.getArtifactId() );
// Don't add edge from parent to extension if a reverse edge already exists
if ( !dag.hasEdge( projectRefId, id ) || !parentId.equals( id ) )
{
dag.addEdge( id, projectRefId );
}
}
}
// TODO: !![jc; 28-jul-2005] check this; if we're using '-r' and there are aggregator tasks, this will result in weirdness.
public MavenProject getTopLevelProject()
{
if ( topLevelProject == null )
{
for ( Iterator i = sortedProjects.iterator(); i.hasNext() && topLevelProject == null; )
{
MavenProject project = (MavenProject) i.next();
if ( project.isExecutionRoot() )
{
topLevelProject = project;
}
}
}
return topLevelProject;
}
public List getSortedProjects()
{
return sortedProjects;
}
public boolean hasMultipleProjects()
{
return sortedProjects.size() > 1;
}
public List getDependents( String id )
{
return dag.getParentLabels( id );
}
public DAG getDAG()
{
return dag;
}
public Map getProjectMap()
{
return projectMap;
}
}