001package 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
022import java.util.ArrayList;
023import java.util.Collections;
024import java.util.HashMap;
025import java.util.Iterator;
026import java.util.List;
027import java.util.Map;
028
029import org.apache.maven.artifact.ArtifactUtils;
030import org.apache.maven.model.Dependency;
031import org.apache.maven.model.Extension;
032import org.apache.maven.model.Parent;
033import org.apache.maven.model.Plugin;
034import org.codehaus.plexus.util.StringUtils;
035import org.codehaus.plexus.util.dag.CycleDetectedException;
036import org.codehaus.plexus.util.dag.DAG;
037import org.codehaus.plexus.util.dag.TopologicalSorter;
038import org.codehaus.plexus.util.dag.Vertex;
039
040public class ProjectSorter
041{
042    private DAG dag;
043
044    private List<MavenProject> sortedProjects;
045
046    private Map<String, MavenProject> projectMap;
047
048    private MavenProject topLevelProject;
049
050    /**
051     * Sort a list of projects.
052     * <ul>
053     * <li>collect all the vertices for the projects that we want to build.</li>
054     * <li>iterate through the deps of each project and if that dep is within
055     * the set of projects we want to build then add an edge, otherwise throw
056     * the edge away because that dependency is not within the set of projects
057     * we are trying to build. we assume a closed set.</li>
058     * <li>do a topo sort on the graph that remains.</li>
059     * </ul>
060     * @throws DuplicateProjectException if any projects are duplicated by id
061     */
062    // MAVENAPI FIXME: the DAG used is NOT only used to represent the dependency relation,
063    // but also for <parent>, <build><plugin>, <reports>. We need multiple DAG's
064    // since a DAG can only handle 1 type of relationship properly.
065    // Usecase:  This is detected as a cycle:
066    // org.apache.maven:maven-plugin-api                -(PARENT)->
067    // org.apache.maven:maven                           -(inherited REPORTING)->
068    // org.apache.maven.plugins:maven-checkstyle-plugin -(DEPENDENCY)->
069    // org.apache.maven:maven-plugin-api
070    // In this case, both the verify and the report goals are called
071    // in a different lifecycle. Though the compiler-plugin has a valid usecase, although
072    // that seems to work fine. We need to take versions and lifecycle into account.
073    public ProjectSorter( List<MavenProject> projects )
074        throws CycleDetectedException, DuplicateProjectException
075    {
076        dag = new DAG();
077
078        // groupId:artifactId:version -> project
079        projectMap = new HashMap<String, MavenProject>( projects.size() * 2 );
080
081        // groupId:artifactId -> (version -> vertex)
082        Map<String, Map<String, Vertex>> vertexMap = new HashMap<String, Map<String, Vertex>>( projects.size() * 2 );
083
084        for ( MavenProject project : projects )
085        {
086            String projectId = getId( project );
087
088            MavenProject conflictingProject = projectMap.put( projectId, project );
089
090            if ( conflictingProject != null )
091            {
092                throw new DuplicateProjectException( projectId, conflictingProject.getFile(), project.getFile(),
093                                                     "Project '" + projectId + "' is duplicated in the reactor" );
094            }
095
096            String projectKey = ArtifactUtils.versionlessKey( project.getGroupId(), project.getArtifactId() );
097
098            Map<String, Vertex> vertices = vertexMap.get( projectKey );
099            if ( vertices == null )
100            {
101                vertices = new HashMap<String, Vertex>( 2, 1 );
102                vertexMap.put( projectKey, vertices );
103            }
104            vertices.put( project.getVersion(), dag.addVertex( projectId ) );
105        }
106
107        for ( Vertex projectVertex : (List<Vertex>) dag.getVerticies() )
108        {
109            String projectId = projectVertex.getLabel();
110
111            MavenProject project = projectMap.get( projectId );
112
113            for ( Dependency dependency : project.getDependencies() )
114            {
115                addEdge( projectMap, vertexMap, project, projectVertex, dependency.getGroupId(),
116                         dependency.getArtifactId(), dependency.getVersion(), false, false );
117            }
118
119            Parent parent = project.getModel().getParent();
120
121            if ( parent != null )
122            {
123                // Parent is added as an edge, but must not cause a cycle - so we remove any other edges it has
124                // in conflict
125                addEdge( projectMap, vertexMap, null, projectVertex, parent.getGroupId(), parent.getArtifactId(),
126                         parent.getVersion(), true, false );
127            }
128
129            List<Plugin> buildPlugins = project.getBuildPlugins();
130            if ( buildPlugins != null )
131            {
132                for ( Plugin plugin : buildPlugins )
133                {
134                    addEdge( projectMap, vertexMap, project, projectVertex, plugin.getGroupId(),
135                             plugin.getArtifactId(), plugin.getVersion(), false, true );
136
137                    for ( Dependency dependency : plugin.getDependencies() )
138                    {
139                        addEdge( projectMap, vertexMap, project, projectVertex, dependency.getGroupId(),
140                                 dependency.getArtifactId(), dependency.getVersion(), false, true );
141                    }
142                }
143            }
144
145            List<Extension> buildExtensions = project.getBuildExtensions();
146            if ( buildExtensions != null )
147            {
148                for ( Extension extension : buildExtensions )
149                {
150                    addEdge( projectMap, vertexMap, project, projectVertex, extension.getGroupId(),
151                             extension.getArtifactId(), extension.getVersion(), false, true );
152                }
153            }
154        }
155
156        List<MavenProject> sortedProjects = new ArrayList<MavenProject>( projects.size() );
157
158        List<String> sortedProjectLabels = TopologicalSorter.sort( dag );
159
160        for ( String id : sortedProjectLabels )
161        {
162            sortedProjects.add( projectMap.get( id ) );
163        }
164
165        this.sortedProjects = Collections.unmodifiableList( sortedProjects );
166    }
167
168    private void addEdge( Map<String, MavenProject> projectMap, Map<String, Map<String, Vertex>> vertexMap,
169                          MavenProject project, Vertex projectVertex, String groupId, String artifactId,
170                          String version, boolean force, boolean safe )
171        throws CycleDetectedException
172    {
173        String projectKey = ArtifactUtils.versionlessKey( groupId, artifactId );
174
175        Map<String, Vertex> vertices = vertexMap.get( projectKey );
176
177        if ( vertices != null )
178        {
179            if ( isSpecificVersion( version ) )
180            {
181                Vertex vertex = vertices.get( version );
182                if ( vertex != null )
183                {
184                    addEdge( projectVertex, vertex, project, projectMap, force, safe );
185                }
186            }
187            else
188            {
189                for ( Vertex vertex : vertices.values() )
190                {
191                    addEdge( projectVertex, vertex, project, projectMap, force, safe );
192                }
193            }
194        }
195    }
196
197    private void addEdge( Vertex fromVertex, Vertex toVertex, MavenProject fromProject,
198                          Map<String, MavenProject> projectMap, boolean force, boolean safe )
199        throws CycleDetectedException
200    {
201        if ( fromVertex.equals( toVertex ) )
202        {
203            return;
204        }
205
206        if ( fromProject != null )
207        {
208            MavenProject toProject = projectMap.get( toVertex.getLabel() );
209            fromProject.addProjectReference( toProject );
210        }
211
212        if ( force && toVertex.getChildren().contains( fromVertex ) )
213        {
214            dag.removeEdge( toVertex, fromVertex );
215        }
216
217        try
218        {
219            dag.addEdge( fromVertex, toVertex );
220        }
221        catch ( CycleDetectedException e )
222        {
223            if ( !safe )
224            {
225                throw e;
226            }
227        }
228    }
229
230    private boolean isSpecificVersion( String version )
231    {
232        return !( StringUtils.isEmpty( version ) || version.startsWith( "[" ) || version.startsWith( "(" ) );
233    }
234
235    // TODO: !![jc; 28-jul-2005] check this; if we're using '-r' and there are aggregator tasks, this will result in weirdness.
236    public MavenProject getTopLevelProject()
237    {
238        if ( topLevelProject == null )
239        {
240            for ( Iterator<MavenProject> i = sortedProjects.iterator(); i.hasNext() && ( topLevelProject == null ); )
241            {
242                MavenProject project = i.next();
243                if ( project.isExecutionRoot() )
244                {
245                    topLevelProject = project;
246                }
247            }
248        }
249
250        return topLevelProject;
251    }
252
253    public List<MavenProject> getSortedProjects()
254    {
255        return sortedProjects;
256    }
257
258    public boolean hasMultipleProjects()
259    {
260        return sortedProjects.size() > 1;
261    }
262
263    public List<String> getDependents( String id )
264    {
265        return dag.getParentLabels( id );
266    }
267
268    public List<String> getDependencies( String id )
269    {
270        return dag.getChildLabels( id );
271    }
272
273    public static String getId( MavenProject project )
274    {
275        return ArtifactUtils.key( project.getGroupId(), project.getArtifactId(), project.getVersion() );
276    }
277
278    public DAG getDAG()
279    {
280        return dag;
281    }
282
283    public Map<String, MavenProject> getProjectMap()
284    {
285        return projectMap;
286    }
287
288}