Coverage Report - org.apache.maven.shared.dependency.tree.DependencyTreeResolutionListener
 
Classes in this File Line Coverage Branch Coverage Complexity
DependencyTreeResolutionListener
60 %
76/126
45 %
28/62
2,652
 
 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  22
     private Map<String, String> managedVersions = new HashMap<String, String>();
 77  
 
 78  
     /**
 79  
      * Map &lt; String replacementId, String premanaged scope >
 80  
      */
 81  22
     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  22
     {
 93  22
         this.logger = logger;
 94  
         
 95  22
         parentNodes = new Stack<DependencyNode>();
 96  22
         nodesByArtifact = new IdentityHashMap<Artifact, DependencyNode>();
 97  22
         rootNode = null;
 98  22
         currentNode = null;
 99  22
     }
 100  
 
 101  
     // ResolutionListener methods ---------------------------------------------
 102  
 
 103  
     /**
 104  
      * {@inheritDoc}
 105  
      */
 106  
     public void testArtifact( Artifact artifact )
 107  
     {
 108  0
         log( "testArtifact: artifact=" + artifact );
 109  0
     }
 110  
 
 111  
     /**
 112  
      * {@inheritDoc}
 113  
      */
 114  
     public void startProcessChildren( Artifact artifact )
 115  
     {
 116  42
         log( "startProcessChildren: artifact=" + artifact );
 117  
         
 118  42
         if ( !currentNode.getArtifact().equals( artifact ) )
 119  
         {
 120  0
             throw new IllegalStateException( "Artifact was expected to be " + currentNode.getArtifact() + " but was "
 121  
                             + artifact );
 122  
         }
 123  
 
 124  42
         parentNodes.push( currentNode );
 125  42
     }
 126  
 
 127  
     /**
 128  
      * {@inheritDoc}
 129  
      */
 130  
     public void endProcessChildren( Artifact artifact )
 131  
     {
 132  40
         DependencyNode node = parentNodes.pop();
 133  
 
 134  40
         log( "endProcessChildren: artifact=" + artifact );
 135  
         
 136  40
         if ( node == null )
 137  
         {
 138  0
             throw new IllegalStateException( "Parent dependency node was null" );
 139  
         }
 140  
 
 141  40
         if ( !node.getArtifact().equals( artifact ) )
 142  
         {
 143  0
             throw new IllegalStateException( "Parent dependency node artifact was expected to be " + node.getArtifact()
 144  
                             + " but was " + artifact );
 145  
         }
 146  40
     }
 147  
 
 148  
     /**
 149  
      * {@inheritDoc}
 150  
      */
 151  
     public void includeArtifact( Artifact artifact )
 152  
     {
 153  82
         log( "includeArtifact: artifact=" + artifact );
 154  
         
 155  82
         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  82
         if ( existingNode == null && isCurrentNodeIncluded() )
 162  
         {
 163  70
             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  70
             flushDependencyManagement( node );
 170  
         }
 171  82
     }
 172  
 
 173  
     /**
 174  
      * {@inheritDoc}
 175  
      */
 176  
     public void omitForNearer( Artifact omitted, Artifact kept )
 177  
     {
 178  14
         log( "omitForNearer: omitted=" + omitted + " kept=" + kept );
 179  
         
 180  14
         if ( !omitted.getDependencyConflictId().equals( kept.getDependencyConflictId() ) )
 181  
         {
 182  0
             throw new IllegalArgumentException( "Omitted artifact dependency conflict id "
 183  
                             + omitted.getDependencyConflictId() + " differs from kept artifact dependency conflict id "
 184  
                             + kept.getDependencyConflictId() );
 185  
         }
 186  
 
 187  14
         if ( isCurrentNodeIncluded() )
 188  
         {
 189  14
             DependencyNode omittedNode = getNode( omitted );
 190  
 
 191  14
             if ( omittedNode != null )
 192  
             {
 193  10
                 removeNode( omitted );
 194  
             }
 195  
             else
 196  
             {
 197  4
                 omittedNode = createNode( omitted );
 198  
 
 199  4
                 currentNode = omittedNode;
 200  
             }
 201  
 
 202  14
             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  14
             flushDependencyManagement( omittedNode );
 209  
             
 210  14
             DependencyNode keptNode = getNode( kept );
 211  
             
 212  14
             if ( keptNode == null )
 213  
             {
 214  10
                 addNode( kept );
 215  
             }
 216  
         }
 217  14
     }
 218  
 
 219  
     /**
 220  
      * {@inheritDoc}
 221  
      */
 222  
     public void updateScope( Artifact artifact, String scope )
 223  
     {
 224  0
         log( "updateScope: artifact=" + artifact + ", scope=" + scope );
 225  
         
 226  0
         DependencyNode node = getNode( artifact );
 227  
 
 228  0
         if ( node == null )
 229  
         {
 230  
             // updateScope events can be received prior to includeArtifact events
 231  0
             node = addNode( artifact );
 232  
         }
 233  
 
 234  0
         node.setOriginalScope( artifact.getScope() );
 235  0
     }
 236  
 
 237  
     /**
 238  
      * {@inheritDoc}
 239  
      */
 240  
     public void manageArtifact( Artifact artifact, Artifact replacement )
 241  
     {
 242  
         // TODO: remove when ResolutionListenerForDepMgmt merged into ResolutionListener
 243  
         
 244  0
         log( "manageArtifact: artifact=" + artifact + ", replacement=" + replacement );
 245  
         
 246  0
         if ( replacement.getVersion() != null )
 247  
         {
 248  0
             manageArtifactVersion( artifact, replacement );
 249  
         }
 250  
         
 251  0
         if ( replacement.getScope() != null )
 252  
         {
 253  0
             manageArtifactScope( artifact, replacement );
 254  
         }
 255  0
     }
 256  
 
 257  
     /**
 258  
      * {@inheritDoc}
 259  
      */
 260  
     public void omitForCycle( Artifact artifact )
 261  
     {
 262  2
         log( "omitForCycle: artifact=" + artifact );
 263  
         
 264  2
         if ( isCurrentNodeIncluded() )
 265  
         {
 266  2
             DependencyNode node = createNode( artifact );
 267  
 
 268  2
             node.omitForCycle();
 269  
         }
 270  2
     }
 271  
 
 272  
     /**
 273  
      * {@inheritDoc}
 274  
      */
 275  
     public void updateScopeCurrentPom( Artifact artifact, String scopeIgnored )
 276  
     {
 277  0
         log( "updateScopeCurrentPom: artifact=" + artifact + ", scopeIgnored=" + scopeIgnored );
 278  
         
 279  0
         DependencyNode node = getNode( artifact );
 280  
 
 281  0
         if ( node == null )
 282  
         {
 283  
             // updateScopeCurrentPom events can be received prior to includeArtifact events
 284  0
             node = addNode( artifact );
 285  
             // TODO remove the node that tried to impose its scope and add some info
 286  
         }
 287  
 
 288  0
         node.setFailedUpdateScope( scopeIgnored );
 289  0
     }
 290  
 
 291  
     /**
 292  
      * {@inheritDoc}
 293  
      */
 294  
     public void selectVersionFromRange( Artifact artifact )
 295  
     {
 296  0
         log( "selectVersionFromRange: artifact=" + artifact );
 297  
 
 298  0
         DependencyNode node = getNode( artifact );
 299  
 
 300  
         /*
 301  
          * selectVersionFromRange is called before includeArtifact
 302  
          */
 303  0
         if ( node == null && isCurrentNodeIncluded() )
 304  
         {
 305  0
             node = addNode( artifact );
 306  
         }
 307  
 
 308  0
         node.setVersionSelectedFromRange( artifact.getVersionRange() );
 309  0
         node.setAvailableVersions( artifact.getAvailableVersions() );
 310  0
     }
 311  
 
 312  
     /**
 313  
      * {@inheritDoc}
 314  
      */
 315  
     public void restrictRange( Artifact artifact, Artifact replacement, VersionRange versionRange )
 316  
     {
 317  0
         log( "restrictRange: artifact=" + artifact + ", replacement=" + replacement + ", versionRange=" + versionRange );
 318  
         
 319  
         // TODO: track range restriction in node (MNG-3093)
 320  0
     }
 321  
     
 322  
     // ResolutionListenerForDepMgmt methods -----------------------------------
 323  
     
 324  
     /**
 325  
      * {@inheritDoc}
 326  
      */
 327  
     public void manageArtifactVersion( Artifact artifact, Artifact replacement )
 328  
     {
 329  0
         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  0
         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  0
             managedVersions.put( replacement.getId(), artifact.getVersion() );
 342  
         }
 343  0
     }
 344  
 
 345  
     /**
 346  
      * {@inheritDoc}
 347  
      */
 348  
     public void manageArtifactScope( Artifact artifact, Artifact replacement )
 349  
     {
 350  0
         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  0
         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  0
             managedScopes.put( replacement.getId(), artifact.getScope() );
 363  
         }
 364  0
     }
 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  0
         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  22
         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  180
         int depth = parentNodes.size();
 400  
 
 401  180
         StringBuffer buffer = new StringBuffer();
 402  
 
 403  330
         for ( int i = 0; i < depth; i++ )
 404  
         {
 405  150
             buffer.append( "  " );
 406  
         }
 407  
 
 408  180
         buffer.append( message );
 409  
 
 410  180
         logger.debug( buffer.toString() );
 411  180
     }
 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  90
         DependencyNode node = new DependencyNode( artifact );
 423  
 
 424  90
         if ( !parentNodes.isEmpty() )
 425  
         {
 426  68
             DependencyNode parent = parentNodes.peek();
 427  
 
 428  68
             parent.addChild( node );
 429  
         }
 430  
 
 431  90
         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  84
         DependencyNode node = createNode( artifact );
 446  
 
 447  84
         DependencyNode previousNode = nodesByArtifact.put( node.getArtifact(), node );
 448  
         
 449  84
         if ( previousNode != null )
 450  
         {
 451  0
             throw new IllegalStateException( "Duplicate node registered for artifact: " + node.getArtifact() );
 452  
         }
 453  
         
 454  84
         if ( rootNode == null )
 455  
         {
 456  22
             rootNode = node;
 457  
         }
 458  
 
 459  84
         currentNode = node;
 460  
         
 461  84
         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  110
         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  10
         DependencyNode node = nodesByArtifact.remove( artifact );
 486  
 
 487  10
         if ( !artifact.equals( node.getArtifact() ) )
 488  
         {
 489  0
             throw new IllegalStateException( "Removed dependency node artifact was expected to be " + artifact
 490  
                             + " but was " + node.getArtifact() );
 491  
         }
 492  10
     }
 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  88
         for ( DependencyNode node : parentNodes )
 504  
         {
 505  94
             if ( node.getState() != DependencyNode.INCLUDED )
 506  
             {
 507  2
                 return false;
 508  
             }
 509  
         }
 510  
 
 511  86
         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  84
         Artifact artifact = node.getArtifact();
 524  84
         String premanagedVersion = managedVersions.get( artifact.getId() );
 525  84
         String premanagedScope = managedScopes.get( artifact.getId() );
 526  
         
 527  84
         if ( premanagedVersion != null || premanagedScope != null )
 528  
         {
 529  0
             if ( premanagedVersion != null )
 530  
             {
 531  0
                 node.setPremanagedVersion( premanagedVersion );
 532  
             }
 533  
             
 534  0
             if ( premanagedScope != null )
 535  
             {
 536  0
                 node.setPremanagedScope( premanagedScope );
 537  
             }
 538  
             
 539  0
             premanagedVersion = null;
 540  0
             premanagedScope = null;
 541  
         }
 542  84
     }
 543  
 }