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.Collection;
024import java.util.Collections;
025import java.util.HashMap;
026import java.util.Iterator;
027import java.util.List;
028import java.util.Map;
029
030import org.apache.maven.artifact.ArtifactUtils;
031import org.apache.maven.model.Dependency;
032import org.apache.maven.model.Extension;
033import org.apache.maven.model.Parent;
034import org.apache.maven.model.Plugin;
035import org.codehaus.plexus.util.StringUtils;
036import org.codehaus.plexus.util.dag.CycleDetectedException;
037import org.codehaus.plexus.util.dag.DAG;
038import org.codehaus.plexus.util.dag.TopologicalSorter;
039import org.codehaus.plexus.util.dag.Vertex;
040
041public class ProjectSorter
042{
043    private DAG dag;
044
045    private List<MavenProject> sortedProjects;
046
047    private Map<String, MavenProject> projectMap;
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( Collection<MavenProject> projects )
075        throws CycleDetectedException, DuplicateProjectException
076    {
077        dag = new DAG();
078
079        // groupId:artifactId:version -> project
080        projectMap = new HashMap<>( projects.size() * 2 );
081
082        // groupId:artifactId -> (version -> vertex)
083        Map<String, Map<String, Vertex>> vertexMap = new HashMap<>( projects.size() * 2 );
084
085        for ( MavenProject project : projects )
086        {
087            String projectId = getId( project );
088
089            MavenProject conflictingProject = projectMap.put( projectId, project );
090
091            if ( conflictingProject != null )
092            {
093                throw new DuplicateProjectException( projectId, conflictingProject.getFile(), project.getFile(),
094                                                     "Project '" + projectId + "' is duplicated in the reactor" );
095            }
096
097            String projectKey = ArtifactUtils.versionlessKey( project.getGroupId(), project.getArtifactId() );
098
099            Map<String, Vertex> vertices = vertexMap.get( projectKey );
100            if ( vertices == null )
101            {
102                vertices = new HashMap<>( 2, 1 );
103                vertexMap.put( projectKey, vertices );
104            }
105            vertices.put( project.getVersion(), dag.addVertex( projectId ) );
106        }
107
108        for ( Vertex projectVertex : dag.getVerticies() )
109        {
110            String projectId = projectVertex.getLabel();
111
112            MavenProject project = projectMap.get( projectId );
113
114            for ( Dependency dependency : project.getDependencies() )
115            {
116                addEdge( projectMap, vertexMap, project, projectVertex, dependency.getGroupId(),
117                         dependency.getArtifactId(), dependency.getVersion(), false, false );
118            }
119
120            Parent parent = project.getModel().getParent();
121
122            if ( parent != null )
123            {
124                // Parent is added as an edge, but must not cause a cycle - so we remove any other edges it has
125                // in conflict
126                addEdge( projectMap, vertexMap, null, projectVertex, parent.getGroupId(), parent.getArtifactId(),
127                         parent.getVersion(), true, false );
128            }
129
130            List<Plugin> buildPlugins = project.getBuildPlugins();
131            if ( buildPlugins != null )
132            {
133                for ( Plugin plugin : buildPlugins )
134                {
135                    addEdge( projectMap, vertexMap, project, projectVertex, plugin.getGroupId(),
136                             plugin.getArtifactId(), plugin.getVersion(), false, true );
137
138                    for ( Dependency dependency : plugin.getDependencies() )
139                    {
140                        addEdge( projectMap, vertexMap, project, projectVertex, dependency.getGroupId(),
141                                 dependency.getArtifactId(), dependency.getVersion(), false, true );
142                    }
143                }
144            }
145
146            List<Extension> buildExtensions = project.getBuildExtensions();
147            if ( buildExtensions != null )
148            {
149                for ( Extension extension : buildExtensions )
150                {
151                    addEdge( projectMap, vertexMap, project, projectVertex, extension.getGroupId(),
152                             extension.getArtifactId(), extension.getVersion(), false, true );
153                }
154            }
155        }
156
157        List<MavenProject> sortedProjects = new ArrayList<>( projects.size() );
158
159        List<String> sortedProjectLabels = TopologicalSorter.sort( dag );
160
161        for ( String id : sortedProjectLabels )
162        {
163            sortedProjects.add( projectMap.get( id ) );
164        }
165
166        this.sortedProjects = Collections.unmodifiableList( sortedProjects );
167    }
168
169    private void addEdge( Map<String, MavenProject> projectMap, Map<String, Map<String, Vertex>> vertexMap,
170                          MavenProject project, Vertex projectVertex, String groupId, String artifactId,
171                          String version, boolean force, boolean safe )
172        throws CycleDetectedException
173    {
174        String projectKey = ArtifactUtils.versionlessKey( groupId, artifactId );
175
176        Map<String, Vertex> vertices = vertexMap.get( projectKey );
177
178        if ( vertices != null )
179        {
180            if ( isSpecificVersion( version ) )
181            {
182                Vertex vertex = vertices.get( version );
183                if ( vertex != null )
184                {
185                    addEdge( projectVertex, vertex, project, projectMap, force, safe );
186                }
187            }
188            else
189            {
190                for ( Vertex vertex : vertices.values() )
191                {
192                    addEdge( projectVertex, vertex, project, projectMap, force, safe );
193                }
194            }
195        }
196    }
197
198    private void addEdge( Vertex fromVertex, Vertex toVertex, MavenProject fromProject,
199                          Map<String, MavenProject> projectMap, boolean force, boolean safe )
200        throws CycleDetectedException
201    {
202        if ( fromVertex.equals( toVertex ) )
203        {
204            return;
205        }
206
207        if ( fromProject != null )
208        {
209            MavenProject toProject = projectMap.get( toVertex.getLabel() );
210            fromProject.addProjectReference( toProject );
211        }
212
213        if ( force && toVertex.getChildren().contains( fromVertex ) )
214        {
215            dag.removeEdge( toVertex, fromVertex );
216        }
217
218        try
219        {
220            dag.addEdge( fromVertex, toVertex );
221        }
222        catch ( CycleDetectedException e )
223        {
224            if ( !safe )
225            {
226                throw e;
227            }
228        }
229    }
230
231    private boolean isSpecificVersion( String version )
232    {
233        return !( StringUtils.isEmpty( version ) || version.startsWith( "[" ) || version.startsWith( "(" ) );
234    }
235
236    // TODO: !![jc; 28-jul-2005] check this; if we're using '-r' and there are aggregator tasks, this will result in weirdness.
237    public MavenProject getTopLevelProject()
238    {
239        if ( topLevelProject == null )
240        {
241            for ( Iterator<MavenProject> i = sortedProjects.iterator(); i.hasNext() && ( topLevelProject == null ); )
242            {
243                MavenProject project = i.next();
244                if ( project.isExecutionRoot() )
245                {
246                    topLevelProject = project;
247                }
248            }
249        }
250
251        return topLevelProject;
252    }
253
254    public List<MavenProject> getSortedProjects()
255    {
256        return sortedProjects;
257    }
258
259    public boolean hasMultipleProjects()
260    {
261        return sortedProjects.size() > 1;
262    }
263
264    public List<String> getDependents( String id )
265    {
266        return dag.getParentLabels( id );
267    }
268
269    public List<String> getDependencies( String id )
270    {
271        return dag.getChildLabels( id );
272    }
273
274    public static String getId( MavenProject project )
275    {
276        return ArtifactUtils.key( project.getGroupId(), project.getArtifactId(), project.getVersion() );
277    }
278
279    public DAG getDAG()
280    {
281        return dag;
282    }
283
284    public Map<String, MavenProject> getProjectMap()
285    {
286        return projectMap;
287    }
288
289}