View Javadoc

1   package org.apache.maven.project;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Map;
28  
29  import org.apache.maven.artifact.ArtifactUtils;
30  import org.apache.maven.model.Dependency;
31  import org.apache.maven.model.Extension;
32  import org.apache.maven.model.Parent;
33  import org.apache.maven.model.Plugin;
34  import org.codehaus.plexus.util.StringUtils;
35  import org.codehaus.plexus.util.dag.CycleDetectedException;
36  import org.codehaus.plexus.util.dag.DAG;
37  import org.codehaus.plexus.util.dag.TopologicalSorter;
38  import org.codehaus.plexus.util.dag.Vertex;
39  
40  public class ProjectSorter
41  {
42      private DAG dag;
43  
44      private List<MavenProject> sortedProjects;
45  
46      private Map<String, MavenProject> projectMap;
47  
48      private MavenProject topLevelProject;
49  
50      /**
51       * Sort a list of projects.
52       * <ul>
53       * <li>collect all the vertices for the projects that we want to build.</li>
54       * <li>iterate through the deps of each project and if that dep is within
55       * the set of projects we want to build then add an edge, otherwise throw
56       * the edge away because that dependency is not within the set of projects
57       * we are trying to build. we assume a closed set.</li>
58       * <li>do a topo sort on the graph that remains.</li>
59       * </ul>
60       * @throws DuplicateProjectException if any projects are duplicated by id
61       */
62      // MAVENAPI FIXME: the DAG used is NOT only used to represent the dependency relation,
63      // but also for <parent>, <build><plugin>, <reports>. We need multiple DAG's
64      // since a DAG can only handle 1 type of relationship properly.
65      // Usecase:  This is detected as a cycle:
66      // org.apache.maven:maven-plugin-api                -(PARENT)->
67      // org.apache.maven:maven                           -(inherited REPORTING)->
68      // org.apache.maven.plugins:maven-checkstyle-plugin -(DEPENDENCY)->
69      // org.apache.maven:maven-plugin-api
70      // In this case, both the verify and the report goals are called
71      // in a different lifecycle. Though the compiler-plugin has a valid usecase, although
72      // that seems to work fine. We need to take versions and lifecycle into account.
73      public ProjectSorter( List<MavenProject> projects )
74          throws CycleDetectedException, DuplicateProjectException
75      {
76          dag = new DAG();
77  
78          // groupId:artifactId:version -> project
79          projectMap = new HashMap<String, MavenProject>( projects.size() * 2 );
80  
81          // groupId:artifactId -> (version -> vertex)
82          Map<String, Map<String, Vertex>> vertexMap = new HashMap<String, Map<String, Vertex>>( projects.size() * 2 );
83  
84          for ( MavenProject project : projects )
85          {
86              String projectId = getId( project );
87  
88              MavenProject conflictingProject = projectMap.put( projectId, project );
89  
90              if ( conflictingProject != null )
91              {
92                  throw new DuplicateProjectException( projectId, conflictingProject.getFile(), project.getFile(),
93                                                       "Project '" + projectId + "' is duplicated in the reactor" );
94              }
95  
96              String projectKey = ArtifactUtils.versionlessKey( project.getGroupId(), project.getArtifactId() );
97  
98              Map<String, Vertex> vertices = vertexMap.get( projectKey );
99              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 }