View Javadoc

1   package org.apache.maven.plugin.dependency;
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.io.File;
23  import java.io.IOException;
24  import java.io.StringWriter;
25  import java.util.ArrayList;
26  import java.util.Arrays;
27  import java.util.Iterator;
28  import java.util.List;
29  
30  import org.apache.maven.artifact.factory.ArtifactFactory;
31  import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
32  import org.apache.maven.artifact.repository.ArtifactRepository;
33  import org.apache.maven.artifact.resolver.ArtifactCollector;
34  import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
35  import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
36  import org.apache.maven.artifact.versioning.ArtifactVersion;
37  import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
38  import org.apache.maven.artifact.versioning.Restriction;
39  import org.apache.maven.artifact.versioning.VersionRange;
40  import org.apache.maven.execution.RuntimeInformation;
41  import org.apache.maven.plugin.AbstractMojo;
42  import org.apache.maven.plugin.MojoExecutionException;
43  import org.apache.maven.plugin.MojoFailureException;
44  import org.apache.maven.plugin.dependency.utils.DependencyUtil;
45  import org.apache.maven.project.MavenProject;
46  import org.apache.maven.shared.artifact.filter.StrictPatternExcludesArtifactFilter;
47  import org.apache.maven.shared.artifact.filter.StrictPatternIncludesArtifactFilter;
48  import org.apache.maven.shared.dependency.tree.DependencyNode;
49  import org.apache.maven.shared.dependency.tree.DependencyTreeBuilder;
50  import org.apache.maven.shared.dependency.tree.DependencyTreeBuilderException;
51  import org.apache.maven.shared.dependency.tree.filter.AncestorOrSelfDependencyNodeFilter;
52  import org.apache.maven.shared.dependency.tree.filter.AndDependencyNodeFilter;
53  import org.apache.maven.shared.dependency.tree.filter.ArtifactDependencyNodeFilter;
54  import org.apache.maven.shared.dependency.tree.filter.DependencyNodeFilter;
55  import org.apache.maven.shared.dependency.tree.filter.StateDependencyNodeFilter;
56  import org.apache.maven.shared.dependency.tree.traversal.BuildingDependencyNodeVisitor;
57  import org.apache.maven.shared.dependency.tree.traversal.CollectingDependencyNodeVisitor;
58  import org.apache.maven.shared.dependency.tree.traversal.DependencyNodeVisitor;
59  import org.apache.maven.shared.dependency.tree.traversal.FilteringDependencyNodeVisitor;
60  import org.apache.maven.shared.dependency.tree.traversal.SerializingDependencyNodeVisitor;
61  import org.apache.maven.shared.dependency.tree.traversal.SerializingDependencyNodeVisitor.TreeTokens;
62  
63  /**
64   * Displays the dependency tree for this project.
65   * 
66   * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
67   * @version $Id: TreeMojo.java 728546 2008-12-21 22:56:51Z bentmann $
68   * @since 2.0-alpha-5
69   * @goal tree
70   * @requiresDependencyResolution test
71   */
72  public class TreeMojo extends AbstractMojo
73  {
74      // fields -----------------------------------------------------------------
75  
76      /**
77       * The Maven project.
78       * 
79       * @parameter expression="${project}"
80       * @required
81       * @readonly
82       */
83      private MavenProject project;
84  
85      /**
86       * The artifact repository to use.
87       * 
88       * @parameter expression="${localRepository}"
89       * @required
90       * @readonly
91       */
92      private ArtifactRepository localRepository;
93  
94      /**
95       * The artifact factory to use.
96       * 
97       * @component
98       * @required
99       * @readonly
100      */
101     private ArtifactFactory artifactFactory;
102 
103     /**
104      * The artifact metadata source to use.
105      * 
106      * @component
107      * @required
108      * @readonly
109      */
110     private ArtifactMetadataSource artifactMetadataSource;
111 
112     /**
113      * The artifact collector to use.
114      * 
115      * @component
116      * @required
117      * @readonly
118      */
119     private ArtifactCollector artifactCollector;
120 
121     /**
122      * The dependency tree builder to use.
123      * 
124      * @component
125      * @required
126      * @readonly
127      */
128     private DependencyTreeBuilder dependencyTreeBuilder;
129 
130     /**
131      * If specified, this parameter will cause the dependency tree to be written to the path specified, instead of
132      * writing to the console.
133      * @deprecated use outputFile instead.
134      * @parameter expression="${output}"
135      */
136     private File output;
137 
138     /**
139      * If specified, this parameter will cause the dependency tree to be written to the path specified, instead of
140      * writing to the console.
141      * @parameter expression="${outputFile}"
142      * @since 2.0-alpha-5
143      */
144     private File outputFile;
145     
146     /**
147      * The scope to filter by when resolving the dependency tree, or <code>null</code> to include dependencies from
148      * all scopes. Note that this feature does not currently work due to MNG-3236.
149      * 
150      * @since 2.0-alpha-5
151      * @see <a href="http://jira.codehaus.org/browse/MNG-3236">MNG-3236</a>
152      * 
153      * @parameter expression="${scope}"
154      */
155     private String scope;
156 
157     /**
158      * Whether to include omitted nodes in the serialized dependency tree.
159      * 
160      * @since 2.0-alpha-6
161      * 
162      * @parameter expression="${verbose}" default-value="false"
163      */
164     private boolean verbose;
165 
166     /**
167      * The token set name to use when outputting the dependency tree. Possible values are <code>whitespace</code>,
168      * <code>standard</code> or <code>extended</code>, which use whitespace, standard or extended ASCII sets
169      * respectively.
170      * 
171      * @since 2.0-alpha-6
172      * 
173      * @parameter expression="${tokens}" default-value="standard"
174      */
175     private String tokens;
176 
177     /**
178      * A comma-separated list of artifacts to filter the serialized dependency tree by, or <code>null</code> not to
179      * filter the dependency tree. The artifact syntax is defined by <code>StrictPatternIncludesArtifactFilter</code>.
180      * 
181      * @see StrictPatternIncludesArtifactFilter
182      * @since 2.0-alpha-6
183      * 
184      * @parameter expression="${includes}"
185      */
186     private String includes;
187 
188     /**
189      * A comma-separated list of artifacts to filter from the serialized dependency tree, or <code>null</code> not to
190      * filter any artifacts from the dependency tree. The artifact syntax is defined by
191      * <code>StrictPatternExcludesArtifactFilter</code>.
192      * 
193      * @see StrictPatternExcludesArtifactFilter
194      * @since 2.0-alpha-6
195      * 
196      * @parameter expression="${excludes}"
197      */
198     private String excludes;
199 
200     /**
201      * Runtime Information used to check the Maven version
202      * @since 2.0
203      * @component role="org.apache.maven.execution.RuntimeInformation"
204      */
205     private RuntimeInformation rti;
206     
207     /**
208      * The computed dependency tree root node of the Maven project.
209      */
210     private DependencyNode rootNode;
211 
212     // Mojo methods -----------------------------------------------------------
213 
214     /*
215      * @see org.apache.maven.plugin.Mojo#execute()
216      */
217     public void execute() throws MojoExecutionException, MojoFailureException
218     {
219         
220         ArtifactVersion detectedMavenVersion = rti.getApplicationVersion();
221         VersionRange vr;
222         try
223         {
224             vr = VersionRange.createFromVersionSpec( "[2.0.8,)" );
225             if ( !containsVersion( vr, detectedMavenVersion ) )
226             {
227                 getLog().warn(
228                                "The tree mojo requires at least Maven 2.0.8 to function properly. You may get eroneous results on earlier versions" );
229             }
230         }
231         catch ( InvalidVersionSpecificationException e )
232         {
233             throw new MojoExecutionException(e.getLocalizedMessage());
234         }
235 
236         
237         if (output != null)
238         {
239             getLog().warn( "The parameter output is deprecated. Use outputFile instead." );
240             this.outputFile = output;
241         }
242         
243         ArtifactFilter artifactFilter = createResolvingArtifactFilter();
244 
245         try
246         {
247             // TODO: note that filter does not get applied due to MNG-3236
248 
249             rootNode =
250                 dependencyTreeBuilder.buildDependencyTree( project, localRepository, artifactFactory,
251                                                            artifactMetadataSource, artifactFilter, artifactCollector );
252 
253             String dependencyTreeString = serialiseDependencyTree( rootNode );
254 
255             if ( outputFile != null )
256             {
257                 DependencyUtil.write( dependencyTreeString, outputFile, getLog() );
258 
259                 getLog().info( "Wrote dependency tree to: " + outputFile );
260             }
261             else
262             {
263                 DependencyUtil.log( dependencyTreeString, getLog() );
264             }
265         }
266         catch ( DependencyTreeBuilderException exception )
267         {
268             throw new MojoExecutionException( "Cannot build project dependency tree", exception );
269         }
270         catch ( IOException exception )
271         {
272             throw new MojoExecutionException( "Cannot serialise project dependency tree", exception );
273         }
274     }
275 
276     // public methods ---------------------------------------------------------
277 
278     /**
279      * Gets the Maven project used by this mojo.
280      * 
281      * @return the Maven project
282      */
283     public MavenProject getProject()
284     {
285         return project;
286     }
287 
288     /**
289      * Gets the computed dependency tree root node for the Maven project.
290      * 
291      * @return the dependency tree root node
292      */
293     public DependencyNode getDependencyTree()
294     {
295         return rootNode;
296     }
297 
298     // private methods --------------------------------------------------------
299 
300     /**
301      * Gets the artifact filter to use when resolving the dependency tree.
302      * 
303      * @return the artifact filter
304      */
305     private ArtifactFilter createResolvingArtifactFilter()
306     {
307         ArtifactFilter filter;
308 
309         // filter scope
310         if ( scope != null )
311         {
312             getLog().debug( "+ Resolving dependency tree for scope '" + scope + "'" );
313 
314             filter = new ScopeArtifactFilter( scope );
315         }
316         else
317         {
318             filter = null;
319         }
320 
321         return filter;
322     }
323 
324     /**
325      * Serialises the specified dependency tree to a string.
326      * 
327      * @param rootNode
328      *            the dependency tree root node to serialise
329      * @return the serialised dependency tree
330      */
331     private String serialiseDependencyTree( DependencyNode rootNode )
332     {
333         StringWriter writer = new StringWriter();
334         TreeTokens treeTokens = toTreeTokens( tokens );
335 
336         DependencyNodeVisitor visitor = new SerializingDependencyNodeVisitor( writer, treeTokens );
337 
338         // TODO: remove the need for this when the serializer can calculate last nodes from visitor calls only
339         visitor = new BuildingDependencyNodeVisitor( visitor );
340 
341         DependencyNodeFilter filter = createDependencyNodeFilter();
342 
343         if ( filter != null )
344         {
345             CollectingDependencyNodeVisitor collectingVisitor = new CollectingDependencyNodeVisitor();
346             DependencyNodeVisitor firstPassVisitor = new FilteringDependencyNodeVisitor( collectingVisitor, filter );
347             rootNode.accept( firstPassVisitor );
348 
349             DependencyNodeFilter secondPassFilter = new AncestorOrSelfDependencyNodeFilter( collectingVisitor.getNodes() );
350             visitor = new FilteringDependencyNodeVisitor( visitor, secondPassFilter );
351         }
352 
353         rootNode.accept( visitor );
354 
355         return writer.toString();
356     }
357 
358     /**
359      * Gets the tree tokens instance for the specified name.
360      * 
361      * @param tokens
362      *            the tree tokens name
363      * @return the <code>TreeTokens</code> instance
364      */
365     private TreeTokens toTreeTokens( String tokens )
366     {
367         TreeTokens treeTokens;
368 
369         if ( "whitespace".equals( tokens ) )
370         {
371             getLog().debug( "+ Using whitespace tree tokens" );
372 
373             treeTokens = SerializingDependencyNodeVisitor.WHITESPACE_TOKENS;
374         }
375         else if ( "extended".equals( tokens ) )
376         {
377             getLog().debug( "+ Using extended tree tokens" );
378 
379             treeTokens = SerializingDependencyNodeVisitor.EXTENDED_TOKENS;
380         }
381         else
382         {
383             treeTokens = SerializingDependencyNodeVisitor.STANDARD_TOKENS;
384         }
385 
386         return treeTokens;
387     }
388 
389     /**
390      * Gets the dependency node filter to use when serializing the dependency tree.
391      * 
392      * @return the dependency node filter, or <code>null</code> if none required
393      */
394     private DependencyNodeFilter createDependencyNodeFilter()
395     {
396         List filters = new ArrayList();
397 
398         // filter node states
399         if ( !verbose )
400         {
401             getLog().debug( "+ Filtering omitted nodes from dependency tree" );
402 
403             filters.add( StateDependencyNodeFilter.INCLUDED );
404         }
405 
406         // filter includes
407         if ( includes != null )
408         {
409             List patterns = Arrays.asList( includes.split( "," ) );
410 
411             getLog().debug( "+ Filtering dependency tree by artifact include patterns: " + patterns );
412 
413             ArtifactFilter artifactFilter = new StrictPatternIncludesArtifactFilter( patterns );
414             filters.add( new ArtifactDependencyNodeFilter( artifactFilter ) );
415         }
416 
417         // filter excludes
418         if ( excludes != null )
419         {
420             List patterns = Arrays.asList( excludes.split( "," ) );
421 
422             getLog().debug( "+ Filtering dependency tree by artifact exclude patterns: " + patterns );
423 
424             ArtifactFilter artifactFilter = new StrictPatternExcludesArtifactFilter( patterns );
425             filters.add( new ArtifactDependencyNodeFilter( artifactFilter ) );
426         }
427 
428         return filters.isEmpty() ? null : new AndDependencyNodeFilter( filters );
429     }
430 
431     //following is required because the version handling in maven code 
432     //doesn't work properly. I ripped it out of the enforcer rules.
433     
434 
435 
436     /**
437      * Copied from Artifact.VersionRange. This is tweaked to handle singular ranges properly. Currently the default
438      * containsVersion method assumes a singular version means allow everything. This method assumes that "2.0.4" ==
439      * "[2.0.4,)"
440      * 
441      * @param allowedRange range of allowed versions.
442      * @param theVersion the version to be checked.
443      * @return true if the version is contained by the range.
444      */
445     public static boolean containsVersion( VersionRange allowedRange, ArtifactVersion theVersion )
446     {
447         boolean matched = false;
448         ArtifactVersion recommendedVersion = allowedRange.getRecommendedVersion();
449         if ( recommendedVersion == null )
450         {
451 
452             for ( Iterator i = allowedRange.getRestrictions().iterator(); i.hasNext() && !matched; )
453             {
454                 Restriction restriction = (Restriction) i.next();
455                 if ( restriction.containsVersion( theVersion ) )
456                 {
457                     matched = true;
458                 }
459             }
460         }
461         else
462         {
463             // only singular versions ever have a recommendedVersion
464             int compareTo = recommendedVersion.compareTo( theVersion );
465             matched = ( compareTo <= 0 );
466         }
467         return matched;
468     }
469 
470 }