View Javadoc

1   package org.apache.maven.shared.dependency.tree;
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.Collection;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.IdentityHashMap;
26  import java.util.Map;
27  import java.util.Stack;
28  
29  import org.apache.maven.artifact.Artifact;
30  import org.apache.maven.artifact.resolver.ResolutionListener;
31  import org.apache.maven.artifact.resolver.ResolutionListenerForDepMgmt;
32  import org.apache.maven.artifact.versioning.VersionRange;
33  import org.apache.maven.shared.dependency.tree.traversal.CollectingDependencyNodeVisitor;
34  import org.codehaus.plexus.logging.Logger;
35  
36  /**
37   * An artifact resolution listener that constructs a dependency tree.
38   * 
39   * @author Edwin Punzalan
40   * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
41   * @version $Id: DependencyTreeResolutionListener.java 1171276 2011-09-15 21:09:18Z hboutemy $
42   */
43  public class DependencyTreeResolutionListener
44      implements ResolutionListener, ResolutionListenerForDepMgmt
45  {
46      // fields -----------------------------------------------------------------
47      
48      /**
49       * The log to write debug messages to.
50       */
51      private final Logger logger;
52  
53      /**
54       * The parent dependency nodes of the current dependency node.
55       */
56      private final Stack<DependencyNode> parentNodes;
57  
58      /**
59       * A map of dependency nodes by their attached artifact.
60       */
61      private final Map<Artifact, DependencyNode> nodesByArtifact;
62  
63      /**
64       * The root dependency node of the computed dependency tree.
65       */
66      private DependencyNode rootNode;
67  
68      /**
69       * The dependency node currently being processed by this listener.
70       */
71      private DependencyNode currentNode;
72      
73      /**
74       * Map &lt; String replacementId, String premanaged version >
75       */
76      private Map<String, String> managedVersions = new HashMap<String, String>();
77  
78      /**
79       * Map &lt; String replacementId, String premanaged scope >
80       */
81      private Map<String, String> managedScopes = new HashMap<String, String>();
82  
83      // constructors -----------------------------------------------------------
84  
85      /**
86       * Creates a new dependency tree resolution listener that writes to the specified log.
87       * 
88       * @param logger
89       *            the log to write debug messages to
90       */
91      public DependencyTreeResolutionListener( Logger logger )
92      {
93          this.logger = logger;
94          
95          parentNodes = new Stack<DependencyNode>();
96          nodesByArtifact = new IdentityHashMap<Artifact, DependencyNode>();
97          rootNode = null;
98          currentNode = null;
99      }
100 
101     // ResolutionListener methods ---------------------------------------------
102 
103     /**
104      * {@inheritDoc}
105      */
106     public void testArtifact( Artifact artifact )
107     {
108         log( "testArtifact: artifact=" + artifact );
109     }
110 
111     /**
112      * {@inheritDoc}
113      */
114     public void startProcessChildren( Artifact artifact )
115     {
116         log( "startProcessChildren: artifact=" + artifact );
117         
118         if ( !currentNode.getArtifact().equals( artifact ) )
119         {
120             throw new IllegalStateException( "Artifact was expected to be " + currentNode.getArtifact() + " but was "
121                             + artifact );
122         }
123 
124         parentNodes.push( currentNode );
125     }
126 
127     /**
128      * {@inheritDoc}
129      */
130     public void endProcessChildren( Artifact artifact )
131     {
132         DependencyNode node = parentNodes.pop();
133 
134         log( "endProcessChildren: artifact=" + artifact );
135         
136         if ( node == null )
137         {
138             throw new IllegalStateException( "Parent dependency node was null" );
139         }
140 
141         if ( !node.getArtifact().equals( artifact ) )
142         {
143             throw new IllegalStateException( "Parent dependency node artifact was expected to be " + node.getArtifact()
144                             + " but was " + artifact );
145         }
146     }
147 
148     /**
149      * {@inheritDoc}
150      */
151     public void includeArtifact( Artifact artifact )
152     {
153         log( "includeArtifact: artifact=" + artifact );
154         
155         DependencyNode existingNode = getNode( artifact );
156 
157         /*
158          * Ignore duplicate includeArtifact calls since omitForNearer can be called prior to includeArtifact on the same
159          * artifact, and we don't wish to include it twice.
160          */
161         if ( existingNode == null && isCurrentNodeIncluded() )
162         {
163             DependencyNode node = addNode( artifact );
164 
165             /*
166              * Add the dependency management information cached in any prior manageArtifact calls, since includeArtifact
167              * is always called after manageArtifact.
168              */
169             flushDependencyManagement( node );
170         }
171     }
172 
173     /**
174      * {@inheritDoc}
175      */
176     public void omitForNearer( Artifact omitted, Artifact kept )
177     {
178         log( "omitForNearer: omitted=" + omitted + " kept=" + kept );
179         
180         if ( !omitted.getDependencyConflictId().equals( kept.getDependencyConflictId() ) )
181         {
182             throw new IllegalArgumentException( "Omitted artifact dependency conflict id "
183                             + omitted.getDependencyConflictId() + " differs from kept artifact dependency conflict id "
184                             + kept.getDependencyConflictId() );
185         }
186 
187         if ( isCurrentNodeIncluded() )
188         {
189             DependencyNode omittedNode = getNode( omitted );
190 
191             if ( omittedNode != null )
192             {
193                 removeNode( omitted );
194             }
195             else
196             {
197                 omittedNode = createNode( omitted );
198 
199                 currentNode = omittedNode;
200             }
201 
202             omittedNode.omitForConflict( kept );
203             
204             /*
205              * Add the dependency management information cached in any prior manageArtifact calls, since omitForNearer
206              * is always called after manageArtifact.
207              */
208             flushDependencyManagement( omittedNode );
209             
210             DependencyNode keptNode = getNode( kept );
211             
212             if ( keptNode == null )
213             {
214                 addNode( kept );
215             }
216         }
217     }
218 
219     /**
220      * {@inheritDoc}
221      */
222     public void updateScope( Artifact artifact, String scope )
223     {
224         log( "updateScope: artifact=" + artifact + ", scope=" + scope );
225         
226         DependencyNode node = getNode( artifact );
227 
228         if ( node == null )
229         {
230             // updateScope events can be received prior to includeArtifact events
231             node = addNode( artifact );
232         }
233 
234         node.setOriginalScope( artifact.getScope() );
235     }
236 
237     /**
238      * {@inheritDoc}
239      */
240     public void manageArtifact( Artifact artifact, Artifact replacement )
241     {
242         // TODO: remove when ResolutionListenerForDepMgmt merged into ResolutionListener
243         
244         log( "manageArtifact: artifact=" + artifact + ", replacement=" + replacement );
245         
246         if ( replacement.getVersion() != null )
247         {
248             manageArtifactVersion( artifact, replacement );
249         }
250         
251         if ( replacement.getScope() != null )
252         {
253             manageArtifactScope( artifact, replacement );
254         }
255     }
256 
257     /**
258      * {@inheritDoc}
259      */
260     public void omitForCycle( Artifact artifact )
261     {
262         log( "omitForCycle: artifact=" + artifact );
263         
264         if ( isCurrentNodeIncluded() )
265         {
266             DependencyNode node = createNode( artifact );
267 
268             node.omitForCycle();
269         }
270     }
271 
272     /**
273      * {@inheritDoc}
274      */
275     public void updateScopeCurrentPom( Artifact artifact, String scopeIgnored )
276     {
277         log( "updateScopeCurrentPom: artifact=" + artifact + ", scopeIgnored=" + scopeIgnored );
278         
279         DependencyNode node = getNode( artifact );
280 
281         if ( node == null )
282         {
283             // updateScopeCurrentPom events can be received prior to includeArtifact events
284             node = addNode( artifact );
285             // TODO remove the node that tried to impose its scope and add some info
286         }
287 
288         node.setFailedUpdateScope( scopeIgnored );
289     }
290 
291     /**
292      * {@inheritDoc}
293      */
294     public void selectVersionFromRange( Artifact artifact )
295     {
296         log( "selectVersionFromRange: artifact=" + artifact );
297 
298         DependencyNode node = getNode( artifact );
299 
300         /*
301          * selectVersionFromRange is called before includeArtifact
302          */
303         if ( node == null && isCurrentNodeIncluded() )
304         {
305             node = addNode( artifact );
306         }
307 
308         node.setVersionSelectedFromRange( artifact.getVersionRange() );
309         node.setAvailableVersions( artifact.getAvailableVersions() );
310     }
311 
312     /**
313      * {@inheritDoc}
314      */
315     public void restrictRange( Artifact artifact, Artifact replacement, VersionRange versionRange )
316     {
317         log( "restrictRange: artifact=" + artifact + ", replacement=" + replacement + ", versionRange=" + versionRange );
318         
319         // TODO: track range restriction in node (MNG-3093)
320     }
321     
322     // ResolutionListenerForDepMgmt methods -----------------------------------
323     
324     /**
325      * {@inheritDoc}
326      */
327     public void manageArtifactVersion( Artifact artifact, Artifact replacement )
328     {
329         log( "manageArtifactVersion: artifact=" + artifact + ", replacement=" + replacement );
330         
331         /*
332          * DefaultArtifactCollector calls manageArtifact twice: first with the change; then subsequently with no change.
333          * We ignore the second call when the versions are equal.
334          */
335         if ( isCurrentNodeIncluded() && !replacement.getVersion().equals( artifact.getVersion() ) )
336         {
337             /*
338              * Cache management information and apply in includeArtifact, since DefaultArtifactCollector mutates the
339              * artifact and then calls includeArtifact after manageArtifact.
340              */
341             managedVersions.put( replacement.getId(), artifact.getVersion() );
342         }
343     }
344 
345     /**
346      * {@inheritDoc}
347      */
348     public void manageArtifactScope( Artifact artifact, Artifact replacement )
349     {
350         log( "manageArtifactScope: artifact=" + artifact + ", replacement=" + replacement );
351         
352         /*
353          * DefaultArtifactCollector calls manageArtifact twice: first with the change; then subsequently with no change.
354          * We ignore the second call when the scopes are equal.
355          */
356         if ( isCurrentNodeIncluded() && !replacement.getScope().equals( artifact.getScope() ) )
357         {
358             /*
359              * Cache management information and apply in includeArtifact, since DefaultArtifactCollector mutates the
360              * artifact and then calls includeArtifact after manageArtifact.
361              */
362             managedScopes.put( replacement.getId(), artifact.getScope() );
363         }
364     }
365     
366     // public methods ---------------------------------------------------------
367 
368     /**
369      * Gets a list of all dependency nodes in the computed dependency tree.
370      * 
371      * @return a list of dependency nodes
372      * @deprecated As of 1.1, use a {@link CollectingDependencyNodeVisitor} on the root dependency node
373      */
374     public Collection<DependencyNode> getNodes()
375     {
376         return Collections.unmodifiableCollection( nodesByArtifact.values() );
377     }
378 
379     /**
380      * Gets the root dependency node of the computed dependency tree.
381      * 
382      * @return the root node
383      */
384     public DependencyNode getRootNode()
385     {
386         return rootNode;
387     }
388 
389     // private methods --------------------------------------------------------
390     
391     /**
392      * Writes the specified message to the log at debug level with indentation for the current node's depth.
393      * 
394      * @param message
395      *            the message to write to the log
396      */
397     private void log( String message )
398     {
399         int depth = parentNodes.size();
400 
401         StringBuffer buffer = new StringBuffer();
402 
403         for ( int i = 0; i < depth; i++ )
404         {
405             buffer.append( "  " );
406         }
407 
408         buffer.append( message );
409 
410         logger.debug( buffer.toString() );
411     }
412 
413     /**
414      * Creates a new dependency node for the specified artifact and appends it to the current parent dependency node.
415      * 
416      * @param artifact
417      *            the attached artifact for the new dependency node
418      * @return the new dependency node
419      */
420     private DependencyNode createNode( Artifact artifact )
421     {
422         DependencyNode node = new DependencyNode( artifact );
423 
424         if ( !parentNodes.isEmpty() )
425         {
426             DependencyNode parent = parentNodes.peek();
427 
428             parent.addChild( node );
429         }
430 
431         return node;
432     }
433     
434     /**
435      * Creates a new dependency node for the specified artifact, appends it to the current parent dependency node and
436      * puts it into the dependency node cache.
437      * 
438      * @param artifact
439      *            the attached artifact for the new dependency node
440      * @return the new dependency node
441      */
442     // package protected for unit test
443     DependencyNode addNode( Artifact artifact )
444     {
445         DependencyNode node = createNode( artifact );
446 
447         DependencyNode previousNode = nodesByArtifact.put( node.getArtifact(), node );
448         
449         if ( previousNode != null )
450         {
451             throw new IllegalStateException( "Duplicate node registered for artifact: " + node.getArtifact() );
452         }
453         
454         if ( rootNode == null )
455         {
456             rootNode = node;
457         }
458 
459         currentNode = node;
460         
461         return node;
462     }
463 
464     /**
465      * Gets the dependency node for the specified artifact from the dependency node cache.
466      * 
467      * @param artifact
468      *            the artifact to find the dependency node for
469      * @return the dependency node, or <code>null</code> if the specified artifact has no corresponding dependency
470      *         node
471      */
472     private DependencyNode getNode( Artifact artifact )
473     {
474         return nodesByArtifact.get( artifact );
475     }
476 
477     /**
478      * Removes the dependency node for the specified artifact from the dependency node cache.
479      * 
480      * @param artifact
481      *            the artifact to remove the dependency node for
482      */
483     private void removeNode( Artifact artifact )
484     {
485         DependencyNode node = nodesByArtifact.remove( artifact );
486 
487         if ( !artifact.equals( node.getArtifact() ) )
488         {
489             throw new IllegalStateException( "Removed dependency node artifact was expected to be " + artifact
490                             + " but was " + node.getArtifact() );
491         }
492     }
493 
494     /**
495      * Gets whether the all the ancestors of the dependency node currently being processed by this listener have an
496      * included state.
497      * 
498      * @return <code>true</code> if all the ancestors of the current dependency node have a state of
499      *         <code>INCLUDED</code>
500      */
501     private boolean isCurrentNodeIncluded()
502     {
503         for ( DependencyNode node : parentNodes )
504         {
505             if ( node.getState() != DependencyNode.INCLUDED )
506             {
507                 return false;
508             }
509         }
510 
511         return true;
512     }
513 
514     /**
515      * Updates the specified node with any dependency management information cached in prior <code>manageArtifact</code>
516      * calls.
517      * 
518      * @param node
519      *            the node to update
520      */
521     private void flushDependencyManagement( DependencyNode node )
522     {
523         Artifact artifact = node.getArtifact();
524         String premanagedVersion = managedVersions.get( artifact.getId() );
525         String premanagedScope = managedScopes.get( artifact.getId() );
526         
527         if ( premanagedVersion != null || premanagedScope != null )
528         {
529             if ( premanagedVersion != null )
530             {
531                 node.setPremanagedVersion( premanagedVersion );
532             }
533             
534             if ( premanagedScope != null )
535             {
536                 node.setPremanagedScope( premanagedScope );
537             }
538             
539             premanagedVersion = null;
540             premanagedScope = null;
541         }
542     }
543 }