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