Author: sisbell Date: Wed Sep 3 16:36:03 2008 New Revision: 691831 URL: http://svn.apache.org/viewvc?rev=691831&view=rev Log: Moved Project sorter out of maven-project. Only one class from a different package used it. Now its an inner class of ReactorManager.
Added: maven/components/branches/sisbell-maven-2.1-profile/maven-core/src/test/java/org/apache/maven/execution/ProjectSorterTest.java Removed: maven/components/branches/sisbell-maven-2.1-profile/maven-project/src/main/aspect/org/apache/maven/project/aspect/ProjectCollisionReporterAspect.aj maven/components/branches/sisbell-maven-2.1-profile/maven-project/src/main/java/org/apache/maven/project/ProjectSorter.java maven/components/branches/sisbell-maven-2.1-profile/maven-project/src/test/java/org/apache/maven/project/ProjectSorterTest.java Modified: maven/components/branches/sisbell-maven-2.1-profile/maven-core/src/main/java/org/apache/maven/ProjectCycleException.java maven/components/branches/sisbell-maven-2.1-profile/maven-core/src/main/java/org/apache/maven/execution/ReactorManager.java Modified: maven/components/branches/sisbell-maven-2.1-profile/maven-core/src/main/java/org/apache/maven/ProjectCycleException.java URL: http://svn.apache.org/viewvc/maven/components/branches/sisbell-maven-2.1-profile/maven-core/src/main/java/org/apache/maven/ProjectCycleException.java?rev=691831&r1=691830&r2=691831&view=diff ============================================================================== --- maven/components/branches/sisbell-maven-2.1-profile/maven-core/src/main/java/org/apache/maven/ProjectCycleException.java (original) +++ maven/components/branches/sisbell-maven-2.1-profile/maven-core/src/main/java/org/apache/maven/ProjectCycleException.java Wed Sep 3 16:36:03 2008 @@ -1,7 +1,6 @@ package org.apache.maven; import org.apache.maven.execution.ReactorManager; -import org.apache.maven.project.ProjectSorter; import org.codehaus.plexus.util.dag.CycleDetectedException; import java.util.List; @@ -9,7 +8,7 @@ /** * Exception which occurs when creating a new [EMAIL PROTECTED] ReactorManager} instance, * due to failure to sort the current projects. The embedded [EMAIL PROTECTED] CycleDetectedException} - * is thrown by the [EMAIL PROTECTED] ProjectSorter}, and context of this wrapped exception + * is thrown by the ProjectSorter and context of this wrapped exception * includes the list of projects that contain the cycle, along with a friendly * rendering of the cycle message indicating that it comes from the current projects list. * Modified: maven/components/branches/sisbell-maven-2.1-profile/maven-core/src/main/java/org/apache/maven/execution/ReactorManager.java URL: http://svn.apache.org/viewvc/maven/components/branches/sisbell-maven-2.1-profile/maven-core/src/main/java/org/apache/maven/execution/ReactorManager.java?rev=691831&r1=691830&r2=691831&view=diff ============================================================================== --- maven/components/branches/sisbell-maven-2.1-profile/maven-core/src/main/java/org/apache/maven/execution/ReactorManager.java (original) +++ maven/components/branches/sisbell-maven-2.1-profile/maven-core/src/main/java/org/apache/maven/execution/ReactorManager.java Wed Sep 3 16:36:03 2008 @@ -24,14 +24,15 @@ import org.apache.maven.plugin.descriptor.PluginDescriptor; import org.apache.maven.project.DuplicateProjectException; import org.apache.maven.project.MavenProject; -import org.apache.maven.project.ProjectSorter; +import org.apache.maven.model.Dependency; +import org.apache.maven.model.Plugin; +import org.apache.maven.model.ReportPlugin; +import org.apache.maven.model.Extension; import org.codehaus.plexus.util.dag.CycleDetectedException; +import org.codehaus.plexus.util.dag.DAG; +import org.codehaus.plexus.util.dag.TopologicalSorter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; +import java.util.*; public class ReactorManager { @@ -188,4 +189,210 @@ { return buildFailuresByProject.size() + buildSuccessesByProject.size() > 1; } + + protected static class ProjectSorter + { + private final DAG dag; + + 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 + */ + // MAVENAPI FIXME: the DAG used is NOT only used to represent the dependency relation, + // but also for <parent>, <build><plugin>, <reports>. We need multiple DAG's + // since a DAG can only handle 1 type of relationship properly. + // Usecase: This is detected as a cycle: + // org.apache.maven:maven-plugin-api -(PARENT)-> + // org.apache.maven:maven -(inherited REPORTING)-> + // org.apache.maven.plugins:maven-checkstyle-plugin -(DEPENDENCY)-> + // org.apache.maven:maven-plugin-api + // In this case, both the verify and the report goals are called + // in a different lifecycle. Though the compiler-plugin has a valid usecase, although + // that seems to work fine. We need to take versions and lifecycle into account. + public ProjectSorter( List projects ) + throws CycleDetectedException, DuplicateProjectException + { + dag = new DAG(); + + Map projectMap = new HashMap(); + + for ( Iterator i = projects.iterator(); i.hasNext(); ) + { + MavenProject project = (MavenProject) i.next(); + + String id = ArtifactUtils.versionlessKey( project.getGroupId(), project.getArtifactId() ); + + if ( dag.getVertex( id ) != null ) + { + MavenProject conflictingProject = (MavenProject) projectMap.get( id ); + + throw new DuplicateProjectException( id, + conflictingProject.getFile(), + project.getFile(), + "Project '" + + id + + "' is duplicated in the reactor" ); + } + + dag.addVertex( id ); + + projectMap.put( id, project ); + } + + for ( Iterator i = projects.iterator(); i.hasNext(); ) + { + MavenProject project = (MavenProject) i.next(); + + String id = ArtifactUtils.versionlessKey( project.getGroupId(), project.getArtifactId() ); + + for ( Iterator j = project.getDependencies().iterator(); j.hasNext(); ) + { + Dependency dependency = (Dependency) j.next(); + + 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 ( Iterator j = buildPlugins.iterator(); j.hasNext(); ) + { + Plugin plugin = (Plugin) j.next(); + 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 ( Iterator j = reportPlugins.iterator(); j.hasNext(); ) + { + ReportPlugin plugin = (ReportPlugin) j.next(); + String pluginId = ArtifactUtils.versionlessKey( plugin.getGroupId(), plugin.getArtifactId() ); + if ( ( dag.getVertex( pluginId ) != null ) && !pluginId.equals( id ) ) + { + addEdgeWithParentCheck( projectMap, pluginId, project, id ); + } + } + } + + for ( Iterator j = project.getBuildExtensions().iterator(); j.hasNext(); ) + { + Extension extension = (Extension) j.next(); + String extensionId = ArtifactUtils.versionlessKey( extension.getGroupId(), extension.getArtifactId() ); + if ( dag.getVertex( extensionId ) != null ) + { + addEdgeWithParentCheck( projectMap, extensionId, project, id ); + } + } + } + + List sortedProjects = new ArrayList(); + + for ( Iterator i = TopologicalSorter.sort( dag ).iterator(); i.hasNext(); ) + { + String id = (String) i.next(); + + 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; + } + + private List getDependents( String id ) + { + return dag.getParentLabels( id ); + } + } } Added: maven/components/branches/sisbell-maven-2.1-profile/maven-core/src/test/java/org/apache/maven/execution/ProjectSorterTest.java URL: http://svn.apache.org/viewvc/maven/components/branches/sisbell-maven-2.1-profile/maven-core/src/test/java/org/apache/maven/execution/ProjectSorterTest.java?rev=691831&view=auto ============================================================================== --- maven/components/branches/sisbell-maven-2.1-profile/maven-core/src/test/java/org/apache/maven/execution/ProjectSorterTest.java (added) +++ maven/components/branches/sisbell-maven-2.1-profile/maven-core/src/test/java/org/apache/maven/execution/ProjectSorterTest.java Wed Sep 3 16:36:03 2008 @@ -0,0 +1,161 @@ +package org.apache.maven.execution; + +/* + * 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 junit.framework.TestCase; +import org.apache.maven.model.Build; +import org.apache.maven.model.Dependency; +import org.apache.maven.model.Extension; +import org.apache.maven.model.Model; +import org.apache.maven.project.DuplicateProjectException; +import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.util.dag.CycleDetectedException; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Test sorting projects by dependencies. + * + * @author <a href="mailto:[EMAIL PROTECTED]">Brett Porter</a> + * @version $Id: ProjectSorterTest.java 513038 2007-02-28 22:54:19Z jvanzyl $ + */ +public class ProjectSorterTest + extends TestCase +{ + + public void testShouldNotFailWhenProjectReferencesNonExistentProject() + throws CycleDetectedException, DuplicateProjectException + { + MavenProject project = createProject( "group", "artifact", "1.0" ); + Model model = project.getModel(); + + Build build = model.getBuild(); + + if ( build == null ) + { + build = new Build(); + model.setBuild( build ); + } + + Extension extension = new Extension(); + + extension.setArtifactId( "other-artifact" ); + extension.setGroupId( "other.group" ); + extension.setVersion( "1.0" ); + + build.addExtension( extension ); + + new ReactorManager.ProjectSorter( Collections.singletonList( project ) ); + } + + public void testMatchingArtifactIdsDifferentGroupIds() + throws CycleDetectedException, DuplicateProjectException + { + List projects = new ArrayList(); + MavenProject project1 = createProject( "groupId1", "artifactId", "1.0" ); + projects.add( project1 ); + MavenProject project2 = createProject( "groupId2", "artifactId", "1.0" ); + projects.add( project2 ); + project1.getDependencies().add( createDependency( project2 ) ); + + projects = new ReactorManager.ProjectSorter( projects ).getSortedProjects(); + + assertEquals( project2, projects.get( 0 ) ); + assertEquals( project1, projects.get( 1 ) ); + } + + public void testMatchingGroupIdsDifferentArtifactIds() + throws CycleDetectedException, DuplicateProjectException + { + List projects = new ArrayList(); + MavenProject project1 = createProject( "groupId", "artifactId1", "1.0" ); + projects.add( project1 ); + MavenProject project2 = createProject( "groupId", "artifactId2", "1.0" ); + projects.add( project2 ); + project1.getDependencies().add( createDependency( project2 ) ); + + projects = new ReactorManager.ProjectSorter( projects ).getSortedProjects(); + + assertEquals( project2, projects.get( 0 ) ); + assertEquals( project1, projects.get( 1 ) ); + } + + public void testMatchingIdsAndVersions() + throws CycleDetectedException + { + List projects = new ArrayList(); + MavenProject project1 = createProject( "groupId", "artifactId", "1.0" ); + projects.add( project1 ); + MavenProject project2 = createProject( "groupId", "artifactId", "1.0" ); + projects.add( project2 ); + + try + { + projects = new ReactorManager.ProjectSorter( projects ).getSortedProjects(); + fail( "Duplicate projects should fail" ); + } + catch ( DuplicateProjectException e ) + { + // expected + assertTrue( true ); + } + } + + public void testMatchingIdsAndDifferentVersions() + throws CycleDetectedException + { + List projects = new ArrayList(); + MavenProject project1 = createProject( "groupId", "artifactId", "1.0" ); + projects.add( project1 ); + MavenProject project2 = createProject( "groupId", "artifactId", "2.0" ); + projects.add( project2 ); + + try + { + projects = new ReactorManager.ProjectSorter( projects ).getSortedProjects(); + fail( "Duplicate projects should fail" ); + } + catch ( DuplicateProjectException e ) + { + // expected + assertTrue( true ); + } + } + + private Dependency createDependency( MavenProject project ) + { + Dependency depdendency = new Dependency(); + depdendency.setArtifactId( project.getArtifactId() ); + depdendency.setGroupId( project.getGroupId() ); + depdendency.setVersion( project.getVersion() ); + return depdendency; + } + + private static MavenProject createProject( String groupId, String artifactId, String version ) + { + Model model = new Model(); + model.setGroupId( groupId ); + model.setArtifactId( artifactId ); + model.setVersion( version ); + return new MavenProject( model ); + } +}