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