    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 }