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