001package org.eclipse.aether.util.graph.transformer; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import java.util.ArrayList; 023import java.util.Arrays; 024import java.util.Collection; 025import java.util.Collections; 026import java.util.HashMap; 027import java.util.HashSet; 028import java.util.IdentityHashMap; 029import java.util.Iterator; 030import java.util.List; 031import java.util.ListIterator; 032import java.util.Map; 033import static java.util.Objects.requireNonNull; 034 035import org.eclipse.aether.RepositoryException; 036import org.eclipse.aether.artifact.Artifact; 037import org.eclipse.aether.collection.DependencyGraphTransformationContext; 038import org.eclipse.aether.collection.DependencyGraphTransformer; 039import org.eclipse.aether.graph.DefaultDependencyNode; 040import org.eclipse.aether.graph.Dependency; 041import org.eclipse.aether.graph.DependencyNode; 042import org.eclipse.aether.util.ConfigUtils; 043 044/** 045 * A dependency graph transformer that resolves version and scope conflicts among dependencies. For a given set of 046 * conflicting nodes, one node will be chosen as the winner and the other nodes are removed from the dependency graph. 047 * The exact rules by which a winning node and its effective scope are determined are controlled by user-supplied 048 * implementations of {@link VersionSelector}, {@link ScopeSelector}, {@link OptionalitySelector} and 049 * {@link ScopeDeriver}. 050 * <p> 051 * By default, this graph transformer will turn the dependency graph into a tree without duplicate artifacts. Using the 052 * configuration property {@link #CONFIG_PROP_VERBOSE}, a verbose mode can be enabled where the graph is still turned 053 * into a tree but all nodes participating in a conflict are retained. The nodes that were rejected during conflict 054 * resolution have no children and link back to the winner node via the {@link #NODE_DATA_WINNER} key in their custom 055 * data. Additionally, the keys {@link #NODE_DATA_ORIGINAL_SCOPE} and {@link #NODE_DATA_ORIGINAL_OPTIONALITY} are used 056 * to store the original scope and optionality of each node. Obviously, the resulting dependency tree is not suitable 057 * for artifact resolution unless a filter is employed to exclude the duplicate dependencies. 058 * <p> 059 * This transformer will query the keys {@link TransformationContextKeys#CONFLICT_IDS}, 060 * {@link TransformationContextKeys#SORTED_CONFLICT_IDS}, {@link TransformationContextKeys#CYCLIC_CONFLICT_IDS} for 061 * existing information about conflict ids. In absence of this information, it will automatically invoke the 062 * {@link ConflictIdSorter} to calculate it. 063 */ 064public final class ConflictResolver 065 implements DependencyGraphTransformer 066{ 067 068 /** 069 * The key in the repository session's {@link org.eclipse.aether.RepositorySystemSession#getConfigProperties() 070 * configuration properties} used to store a {@link Boolean} flag controlling the transformer's verbose mode. 071 */ 072 public static final String CONFIG_PROP_VERBOSE = "aether.conflictResolver.verbose"; 073 074 /** 075 * The key in the dependency node's {@link DependencyNode#getData() custom data} under which a reference to the 076 * {@link DependencyNode} which has won the conflict is stored. 077 */ 078 public static final String NODE_DATA_WINNER = "conflict.winner"; 079 080 /** 081 * The key in the dependency node's {@link DependencyNode#getData() custom data} under which the scope of the 082 * dependency before scope derivation and conflict resolution is stored. 083 */ 084 public static final String NODE_DATA_ORIGINAL_SCOPE = "conflict.originalScope"; 085 086 /** 087 * The key in the dependency node's {@link DependencyNode#getData() custom data} under which the optional flag of 088 * the dependency before derivation and conflict resolution is stored. 089 */ 090 public static final String NODE_DATA_ORIGINAL_OPTIONALITY = "conflict.originalOptionality"; 091 092 private final VersionSelector versionSelector; 093 094 private final ScopeSelector scopeSelector; 095 096 private final ScopeDeriver scopeDeriver; 097 098 private final OptionalitySelector optionalitySelector; 099 100 /** 101 * Creates a new conflict resolver instance with the specified hooks. 102 * 103 * @param versionSelector The version selector to use, must not be {@code null}. 104 * @param scopeSelector The scope selector to use, must not be {@code null}. 105 * @param optionalitySelector The optionality selector ot use, must not be {@code null}. 106 * @param scopeDeriver The scope deriver to use, must not be {@code null}. 107 */ 108 public ConflictResolver( VersionSelector versionSelector, ScopeSelector scopeSelector, 109 OptionalitySelector optionalitySelector, ScopeDeriver scopeDeriver ) 110 { 111 this.versionSelector = requireNonNull( versionSelector, "version selector cannot be null" ); 112 this.scopeSelector = requireNonNull( scopeSelector, "scope selector cannot be null" ); 113 this.optionalitySelector = requireNonNull( optionalitySelector, "optionality selector cannot be null" ); 114 this.scopeDeriver = requireNonNull( scopeDeriver, "scope deriver cannot be null" ); 115 } 116 117 public DependencyNode transformGraph( DependencyNode node, DependencyGraphTransformationContext context ) 118 throws RepositoryException 119 { 120 requireNonNull( node, "node cannot be null" ); 121 requireNonNull( context, "context cannot be null" ); 122 List<?> sortedConflictIds = (List<?>) context.get( TransformationContextKeys.SORTED_CONFLICT_IDS ); 123 if ( sortedConflictIds == null ) 124 { 125 ConflictIdSorter sorter = new ConflictIdSorter(); 126 sorter.transformGraph( node, context ); 127 128 sortedConflictIds = (List<?>) context.get( TransformationContextKeys.SORTED_CONFLICT_IDS ); 129 } 130 131 @SuppressWarnings( "unchecked" ) 132 Map<String, Object> stats = (Map<String, Object>) context.get( TransformationContextKeys.STATS ); 133 long time1 = System.nanoTime(); 134 135 @SuppressWarnings( "unchecked" ) 136 Collection<Collection<?>> conflictIdCycles = 137 (Collection<Collection<?>>) context.get( TransformationContextKeys.CYCLIC_CONFLICT_IDS ); 138 if ( conflictIdCycles == null ) 139 { 140 throw new RepositoryException( "conflict id cycles have not been identified" ); 141 } 142 143 Map<?, ?> conflictIds = (Map<?, ?>) context.get( TransformationContextKeys.CONFLICT_IDS ); 144 if ( conflictIds == null ) 145 { 146 throw new RepositoryException( "conflict groups have not been identified" ); 147 } 148 149 Map<Object, Collection<Object>> cyclicPredecessors = new HashMap<>(); 150 for ( Collection<?> cycle : conflictIdCycles ) 151 { 152 for ( Object conflictId : cycle ) 153 { 154 Collection<Object> predecessors = cyclicPredecessors. 155 computeIfAbsent( conflictId, k -> new HashSet<>() ); 156 predecessors.addAll( cycle ); 157 } 158 } 159 160 State state = new State( node, conflictIds, sortedConflictIds.size(), context ); 161 for ( Iterator<?> it = sortedConflictIds.iterator(); it.hasNext(); ) 162 { 163 Object conflictId = it.next(); 164 165 // reset data structures for next graph walk 166 state.prepare( conflictId, cyclicPredecessors.get( conflictId ) ); 167 168 // find nodes with the current conflict id and while walking the graph (more deeply), nuke leftover losers 169 gatherConflictItems( node, state ); 170 171 // now that we know the min depth of the parents, update depth of conflict items 172 state.finish(); 173 174 // earlier runs might have nuked all parents of the current conflict id, so it might not exist anymore 175 if ( !state.items.isEmpty() ) 176 { 177 ConflictContext ctx = state.conflictCtx; 178 state.versionSelector.selectVersion( ctx ); 179 if ( ctx.winner == null ) 180 { 181 throw new RepositoryException( "conflict resolver did not select winner among " + state.items ); 182 } 183 DependencyNode winner = ctx.winner.node; 184 185 state.scopeSelector.selectScope( ctx ); 186 if ( state.verbose ) 187 { 188 winner.setData( NODE_DATA_ORIGINAL_SCOPE, winner.getDependency().getScope() ); 189 } 190 winner.setScope( ctx.scope ); 191 192 state.optionalitySelector.selectOptionality( ctx ); 193 if ( state.verbose ) 194 { 195 winner.setData( NODE_DATA_ORIGINAL_OPTIONALITY, winner.getDependency().isOptional() ); 196 } 197 winner.setOptional( ctx.optional ); 198 199 removeLosers( state ); 200 } 201 202 // record the winner so we can detect leftover losers during future graph walks 203 state.winner(); 204 205 // in case of cycles, trigger final graph walk to ensure all leftover losers are gone 206 if ( !it.hasNext() && !conflictIdCycles.isEmpty() && state.conflictCtx.winner != null ) 207 { 208 DependencyNode winner = state.conflictCtx.winner.node; 209 state.prepare( state, null ); 210 gatherConflictItems( winner, state ); 211 } 212 } 213 214 if ( stats != null ) 215 { 216 long time2 = System.nanoTime(); 217 stats.put( "ConflictResolver.totalTime", time2 - time1 ); 218 stats.put( "ConflictResolver.conflictItemCount", state.totalConflictItems ); 219 } 220 221 return node; 222 } 223 224 private boolean gatherConflictItems( DependencyNode node, State state ) 225 throws RepositoryException 226 { 227 Object conflictId = state.conflictIds.get( node ); 228 if ( state.currentId.equals( conflictId ) ) 229 { 230 // found it, add conflict item (if not already done earlier by another path) 231 state.add( node ); 232 // we don't recurse here so we might miss losers beneath us, those will be nuked during future walks below 233 } 234 else if ( state.loser( node, conflictId ) ) 235 { 236 // found a leftover loser (likely in a cycle) of an already processed conflict id, tell caller to nuke it 237 return false; 238 } 239 else if ( state.push( node, conflictId ) ) 240 { 241 // found potential parent, no cycle and not visisted before with the same derived scope, so recurse 242 for ( Iterator<DependencyNode> it = node.getChildren().iterator(); it.hasNext(); ) 243 { 244 DependencyNode child = it.next(); 245 if ( !gatherConflictItems( child, state ) ) 246 { 247 it.remove(); 248 } 249 } 250 state.pop(); 251 } 252 return true; 253 } 254 255 private void removeLosers( State state ) 256 { 257 ConflictItem winner = state.conflictCtx.winner; 258 List<DependencyNode> previousParent = null; 259 ListIterator<DependencyNode> childIt = null; 260 boolean conflictVisualized = false; 261 for ( ConflictItem item : state.items ) 262 { 263 if ( item == winner ) 264 { 265 continue; 266 } 267 if ( item.parent != previousParent ) 268 { 269 childIt = item.parent.listIterator(); 270 previousParent = item.parent; 271 conflictVisualized = false; 272 } 273 while ( childIt.hasNext() ) 274 { 275 DependencyNode child = childIt.next(); 276 if ( child == item.node ) 277 { 278 if ( state.verbose && !conflictVisualized && item.parent != winner.parent ) 279 { 280 conflictVisualized = true; 281 DependencyNode loser = new DefaultDependencyNode( child ); 282 loser.setData( NODE_DATA_WINNER, winner.node ); 283 loser.setData( NODE_DATA_ORIGINAL_SCOPE, loser.getDependency().getScope() ); 284 loser.setData( NODE_DATA_ORIGINAL_OPTIONALITY, loser.getDependency().isOptional() ); 285 loser.setScope( item.getScopes().iterator().next() ); 286 loser.setChildren( Collections.<DependencyNode>emptyList() ); 287 childIt.set( loser ); 288 } 289 else 290 { 291 childIt.remove(); 292 } 293 break; 294 } 295 } 296 } 297 // there might still be losers beneath the winner (e.g. in case of cycles) 298 // those will be nuked during future graph walks when we include the winner in the recursion 299 } 300 301 static final class NodeInfo 302 { 303 304 /** 305 * The smallest depth at which the node was seen, used for "the" depth of its conflict items. 306 */ 307 int minDepth; 308 309 /** 310 * The set of derived scopes the node was visited with, used to check whether an already seen node needs to be 311 * revisited again in context of another scope. To conserve memory, we start with {@code String} and update to 312 * {@code Set<String>} if needed. 313 */ 314 Object derivedScopes; 315 316 /** 317 * The set of derived optionalities the node was visited with, used to check whether an already seen node needs 318 * to be revisited again in context of another optionality. To conserve memory, encoded as bit field (bit 0 -> 319 * optional=false, bit 1 -> optional=true). 320 */ 321 int derivedOptionalities; 322 323 /** 324 * The conflict items which are immediate children of the node, used to easily update those conflict items after 325 * a new parent scope/optionality was encountered. 326 */ 327 List<ConflictItem> children; 328 329 static final int CHANGE_SCOPE = 0x01; 330 331 static final int CHANGE_OPTIONAL = 0x02; 332 333 private static final int OPT_FALSE = 0x01; 334 335 private static final int OPT_TRUE = 0x02; 336 337 NodeInfo( int depth, String derivedScope, boolean optional ) 338 { 339 minDepth = depth; 340 derivedScopes = derivedScope; 341 derivedOptionalities = optional ? OPT_TRUE : OPT_FALSE; 342 } 343 344 @SuppressWarnings( "unchecked" ) 345 int update( int depth, String derivedScope, boolean optional ) 346 { 347 if ( depth < minDepth ) 348 { 349 minDepth = depth; 350 } 351 int changes; 352 if ( derivedScopes.equals( derivedScope ) ) 353 { 354 changes = 0; 355 } 356 else if ( derivedScopes instanceof Collection ) 357 { 358 changes = ( (Collection<String>) derivedScopes ).add( derivedScope ) ? CHANGE_SCOPE : 0; 359 } 360 else 361 { 362 Collection<String> scopes = new HashSet<>(); 363 scopes.add( (String) derivedScopes ); 364 scopes.add( derivedScope ); 365 derivedScopes = scopes; 366 changes = CHANGE_SCOPE; 367 } 368 int bit = optional ? OPT_TRUE : OPT_FALSE; 369 if ( ( derivedOptionalities & bit ) == 0 ) 370 { 371 derivedOptionalities |= bit; 372 changes |= CHANGE_OPTIONAL; 373 } 374 return changes; 375 } 376 377 void add( ConflictItem item ) 378 { 379 if ( children == null ) 380 { 381 children = new ArrayList<>( 1 ); 382 } 383 children.add( item ); 384 } 385 386 } 387 388 final class State 389 { 390 391 /** 392 * The conflict id currently processed. 393 */ 394 Object currentId; 395 396 /** 397 * Stats counter. 398 */ 399 int totalConflictItems; 400 401 /** 402 * Flag whether we should keep losers in the graph to enable visualization/troubleshooting of conflicts. 403 */ 404 final boolean verbose; 405 406 /** 407 * A mapping from conflict id to winner node, helps to recognize nodes that have their effective 408 * scope&optionality set or are leftovers from previous removals. 409 */ 410 final Map<Object, DependencyNode> resolvedIds; 411 412 /** 413 * The set of conflict ids which could apply to ancestors of nodes with the current conflict id, used to avoid 414 * recursion early on. This is basically a superset of the key set of resolvedIds, the additional ids account 415 * for cyclic dependencies. 416 */ 417 final Collection<Object> potentialAncestorIds; 418 419 /** 420 * The output from the conflict marker 421 */ 422 final Map<?, ?> conflictIds; 423 424 /** 425 * The conflict items we have gathered so far for the current conflict id. 426 */ 427 final List<ConflictItem> items; 428 429 /** 430 * The (conceptual) mapping from nodes to extra infos, technically keyed by the node's child list which better 431 * captures the identity of a node since we're basically concerned with effects towards children. 432 */ 433 final Map<List<DependencyNode>, NodeInfo> infos; 434 435 /** 436 * The set of nodes on the DFS stack to detect cycles, technically keyed by the node's child list to match the 437 * dirty graph structure produced by the dependency collector for cycles. 438 */ 439 final Map<List<DependencyNode>, Object> stack; 440 441 /** 442 * The stack of parent nodes. 443 */ 444 final List<DependencyNode> parentNodes; 445 446 /** 447 * The stack of derived scopes for parent nodes. 448 */ 449 final List<String> parentScopes; 450 451 /** 452 * The stack of derived optional flags for parent nodes. 453 */ 454 final List<Boolean> parentOptionals; 455 456 /** 457 * The stack of node infos for parent nodes, may contain {@code null} which is used to disable creating new 458 * conflict items when visiting their parent again (conflict items are meant to be unique by parent-node combo). 459 */ 460 final List<NodeInfo> parentInfos; 461 462 /** 463 * The conflict context passed to the version/scope/optionality selectors, updated as we move along rather than 464 * recreated to avoid tmp objects. 465 */ 466 final ConflictContext conflictCtx; 467 468 /** 469 * The scope context passed to the scope deriver, updated as we move along rather than recreated to avoid tmp 470 * objects. 471 */ 472 final ScopeContext scopeCtx; 473 474 /** 475 * The effective version selector, i.e. after initialization. 476 */ 477 final VersionSelector versionSelector; 478 479 /** 480 * The effective scope selector, i.e. after initialization. 481 */ 482 final ScopeSelector scopeSelector; 483 484 /** 485 * The effective scope deriver, i.e. after initialization. 486 */ 487 final ScopeDeriver scopeDeriver; 488 489 /** 490 * The effective optionality selector, i.e. after initialization. 491 */ 492 final OptionalitySelector optionalitySelector; 493 494 State( DependencyNode root, Map<?, ?> conflictIds, int conflictIdCount, 495 DependencyGraphTransformationContext context ) 496 throws RepositoryException 497 { 498 this.conflictIds = conflictIds; 499 verbose = ConfigUtils.getBoolean( context.getSession(), false, CONFIG_PROP_VERBOSE ); 500 potentialAncestorIds = new HashSet<>( conflictIdCount * 2 ); 501 resolvedIds = new HashMap<>( conflictIdCount * 2 ); 502 items = new ArrayList<>( 256 ); 503 infos = new IdentityHashMap<>( 64 ); 504 stack = new IdentityHashMap<>( 64 ); 505 parentNodes = new ArrayList<>( 64 ); 506 parentScopes = new ArrayList<>( 64 ); 507 parentOptionals = new ArrayList<>( 64 ); 508 parentInfos = new ArrayList<>( 64 ); 509 conflictCtx = new ConflictContext( root, conflictIds, items ); 510 scopeCtx = new ScopeContext( null, null ); 511 versionSelector = ConflictResolver.this.versionSelector.getInstance( root, context ); 512 scopeSelector = ConflictResolver.this.scopeSelector.getInstance( root, context ); 513 scopeDeriver = ConflictResolver.this.scopeDeriver.getInstance( root, context ); 514 optionalitySelector = ConflictResolver.this.optionalitySelector.getInstance( root, context ); 515 } 516 517 void prepare( Object conflictId, Collection<Object> cyclicPredecessors ) 518 { 519 currentId = conflictId; 520 conflictCtx.conflictId = conflictId; 521 conflictCtx.winner = null; 522 conflictCtx.scope = null; 523 conflictCtx.optional = null; 524 items.clear(); 525 infos.clear(); 526 if ( cyclicPredecessors != null ) 527 { 528 potentialAncestorIds.addAll( cyclicPredecessors ); 529 } 530 } 531 532 void finish() 533 { 534 List<DependencyNode> previousParent = null; 535 int previousDepth = 0; 536 totalConflictItems += items.size(); 537 for ( ListIterator<ConflictItem> iterator = items.listIterator( items.size() ); iterator.hasPrevious(); ) 538 { 539 ConflictItem item = iterator.previous(); 540 if ( item.parent == previousParent ) 541 { 542 item.depth = previousDepth; 543 } 544 else if ( item.parent != null ) 545 { 546 previousParent = item.parent; 547 NodeInfo info = infos.get( previousParent ); 548 previousDepth = info.minDepth + 1; 549 item.depth = previousDepth; 550 } 551 } 552 potentialAncestorIds.add( currentId ); 553 } 554 555 void winner() 556 { 557 resolvedIds.put( currentId, ( conflictCtx.winner != null ) ? conflictCtx.winner.node : null ); 558 } 559 560 boolean loser( DependencyNode node, Object conflictId ) 561 { 562 DependencyNode winner = resolvedIds.get( conflictId ); 563 return winner != null && winner != node; 564 } 565 566 boolean push( DependencyNode node, Object conflictId ) 567 throws RepositoryException 568 { 569 if ( conflictId == null ) 570 { 571 if ( node.getDependency() != null ) 572 { 573 if ( node.getData().get( NODE_DATA_WINNER ) != null ) 574 { 575 return false; 576 } 577 throw new RepositoryException( "missing conflict id for node " + node ); 578 } 579 } 580 else if ( !potentialAncestorIds.contains( conflictId ) ) 581 { 582 return false; 583 } 584 585 List<DependencyNode> graphNode = node.getChildren(); 586 if ( stack.put( graphNode, Boolean.TRUE ) != null ) 587 { 588 return false; 589 } 590 591 int depth = depth(); 592 String scope = deriveScope( node, conflictId ); 593 boolean optional = deriveOptional( node, conflictId ); 594 NodeInfo info = infos.get( graphNode ); 595 if ( info == null ) 596 { 597 info = new NodeInfo( depth, scope, optional ); 598 infos.put( graphNode, info ); 599 parentInfos.add( info ); 600 parentNodes.add( node ); 601 parentScopes.add( scope ); 602 parentOptionals.add( optional ); 603 } 604 else 605 { 606 int changes = info.update( depth, scope, optional ); 607 if ( changes == 0 ) 608 { 609 stack.remove( graphNode ); 610 return false; 611 } 612 parentInfos.add( null ); // disable creating new conflict items, we update the existing ones below 613 parentNodes.add( node ); 614 parentScopes.add( scope ); 615 parentOptionals.add( optional ); 616 if ( info.children != null ) 617 { 618 if ( ( changes & NodeInfo.CHANGE_SCOPE ) != 0 ) 619 { 620 ListIterator<ConflictItem> itemIterator = info.children.listIterator( info.children.size() ); 621 while ( itemIterator.hasPrevious() ) 622 { 623 ConflictItem item = itemIterator.previous(); 624 String childScope = deriveScope( item.node, null ); 625 item.addScope( childScope ); 626 } 627 } 628 if ( ( changes & NodeInfo.CHANGE_OPTIONAL ) != 0 ) 629 { 630 ListIterator<ConflictItem> itemIterator = info.children.listIterator( info.children.size() ); 631 while ( itemIterator.hasPrevious() ) 632 { 633 ConflictItem item = itemIterator.previous(); 634 boolean childOptional = deriveOptional( item.node, null ); 635 item.addOptional( childOptional ); 636 } 637 } 638 } 639 } 640 641 return true; 642 } 643 644 void pop() 645 { 646 int last = parentInfos.size() - 1; 647 parentInfos.remove( last ); 648 parentScopes.remove( last ); 649 parentOptionals.remove( last ); 650 DependencyNode node = parentNodes.remove( last ); 651 stack.remove( node.getChildren() ); 652 } 653 654 void add( DependencyNode node ) 655 throws RepositoryException 656 { 657 DependencyNode parent = parent(); 658 if ( parent == null ) 659 { 660 ConflictItem item = newConflictItem( parent, node ); 661 items.add( item ); 662 } 663 else 664 { 665 NodeInfo info = parentInfos.get( parentInfos.size() - 1 ); 666 if ( info != null ) 667 { 668 ConflictItem item = newConflictItem( parent, node ); 669 info.add( item ); 670 items.add( item ); 671 } 672 } 673 } 674 675 private ConflictItem newConflictItem( DependencyNode parent, DependencyNode node ) 676 throws RepositoryException 677 { 678 return new ConflictItem( parent, node, deriveScope( node, null ), deriveOptional( node, null ) ); 679 } 680 681 private int depth() 682 { 683 return parentNodes.size(); 684 } 685 686 private DependencyNode parent() 687 { 688 int size = parentNodes.size(); 689 return ( size <= 0 ) ? null : parentNodes.get( size - 1 ); 690 } 691 692 private String deriveScope( DependencyNode node, Object conflictId ) 693 throws RepositoryException 694 { 695 if ( ( node.getManagedBits() & DependencyNode.MANAGED_SCOPE ) != 0 696 || ( conflictId != null && resolvedIds.containsKey( conflictId ) ) ) 697 { 698 return scope( node.getDependency() ); 699 } 700 701 int depth = parentNodes.size(); 702 scopes( depth, node.getDependency() ); 703 if ( depth > 0 ) 704 { 705 scopeDeriver.deriveScope( scopeCtx ); 706 } 707 return scopeCtx.derivedScope; 708 } 709 710 private void scopes( int parent, Dependency child ) 711 { 712 scopeCtx.parentScope = ( parent > 0 ) ? parentScopes.get( parent - 1 ) : null; 713 scopeCtx.derivedScope = scope( child ); 714 scopeCtx.childScope = scope( child ); 715 } 716 717 private String scope( Dependency dependency ) 718 { 719 return ( dependency != null ) ? dependency.getScope() : null; 720 } 721 722 private boolean deriveOptional( DependencyNode node, Object conflictId ) 723 { 724 Dependency dep = node.getDependency(); 725 boolean optional = ( dep != null ) && dep.isOptional(); 726 if ( optional || ( node.getManagedBits() & DependencyNode.MANAGED_OPTIONAL ) != 0 727 || ( conflictId != null && resolvedIds.containsKey( conflictId ) ) ) 728 { 729 return optional; 730 } 731 int depth = parentNodes.size(); 732 return ( depth > 0 ) ? parentOptionals.get( depth - 1 ) : false; 733 } 734 735 } 736 737 /** 738 * A context used to hold information that is relevant for deriving the scope of a child dependency. 739 * 740 * @see ScopeDeriver 741 * @noinstantiate This class is not intended to be instantiated by clients in production code, the constructor may 742 * change without notice and only exists to enable unit testing. 743 */ 744 public static final class ScopeContext 745 { 746 747 String parentScope; 748 749 String childScope; 750 751 String derivedScope; 752 753 /** 754 * Creates a new scope context with the specified properties. 755 * 756 * @param parentScope The scope of the parent dependency, may be {@code null}. 757 * @param childScope The scope of the child dependency, may be {@code null}. 758 * @noreference This class is not intended to be instantiated by clients in production code, the constructor may 759 * change without notice and only exists to enable unit testing. 760 */ 761 public ScopeContext( String parentScope, String childScope ) 762 { 763 this.parentScope = ( parentScope != null ) ? parentScope : ""; 764 derivedScope = ( childScope != null ) ? childScope : ""; 765 this.childScope = ( childScope != null ) ? childScope : ""; 766 } 767 768 /** 769 * Gets the scope of the parent dependency. This is usually the scope that was derived by earlier invocations of 770 * the scope deriver. 771 * 772 * @return The scope of the parent dependency, never {@code null}. 773 */ 774 public String getParentScope() 775 { 776 return parentScope; 777 } 778 779 /** 780 * Gets the original scope of the child dependency. This is the scope that was declared in the artifact 781 * descriptor of the parent dependency. 782 * 783 * @return The original scope of the child dependency, never {@code null}. 784 */ 785 public String getChildScope() 786 { 787 return childScope; 788 } 789 790 /** 791 * Gets the derived scope of the child dependency. This is initially equal to {@link #getChildScope()} until the 792 * scope deriver makes changes. 793 * 794 * @return The derived scope of the child dependency, never {@code null}. 795 */ 796 public String getDerivedScope() 797 { 798 return derivedScope; 799 } 800 801 /** 802 * Sets the derived scope of the child dependency. 803 * 804 * @param derivedScope The derived scope of the dependency, may be {@code null}. 805 */ 806 public void setDerivedScope( String derivedScope ) 807 { 808 this.derivedScope = ( derivedScope != null ) ? derivedScope : ""; 809 } 810 811 } 812 813 /** 814 * A conflicting dependency. 815 * 816 * @noinstantiate This class is not intended to be instantiated by clients in production code, the constructor may 817 * change without notice and only exists to enable unit testing. 818 */ 819 public static final class ConflictItem 820 { 821 822 // nodes can share child lists, we care about the unique owner of a child node which is the child list 823 final List<DependencyNode> parent; 824 825 // only for debugging/toString() to help identify the parent node(s) 826 final Artifact artifact; 827 828 final DependencyNode node; 829 830 int depth; 831 832 // we start with String and update to Set<String> if needed 833 Object scopes; 834 835 // bit field of OPTIONAL_FALSE and OPTIONAL_TRUE 836 int optionalities; 837 838 /** 839 * Bit flag indicating whether one or more paths consider the dependency non-optional. 840 */ 841 public static final int OPTIONAL_FALSE = 0x01; 842 843 /** 844 * Bit flag indicating whether one or more paths consider the dependency optional. 845 */ 846 public static final int OPTIONAL_TRUE = 0x02; 847 848 ConflictItem( DependencyNode parent, DependencyNode node, String scope, boolean optional ) 849 { 850 if ( parent != null ) 851 { 852 this.parent = parent.getChildren(); 853 this.artifact = parent.getArtifact(); 854 } 855 else 856 { 857 this.parent = null; 858 this.artifact = null; 859 } 860 this.node = node; 861 this.scopes = scope; 862 this.optionalities = optional ? OPTIONAL_TRUE : OPTIONAL_FALSE; 863 } 864 865 /** 866 * Creates a new conflict item with the specified properties. 867 * 868 * @param parent The parent node of the conflicting dependency, may be {@code null}. 869 * @param node The conflicting dependency, must not be {@code null}. 870 * @param depth The zero-based depth of the conflicting dependency. 871 * @param optionalities The optionalities the dependency was encountered with, encoded as a bit field consisting 872 * of {@link ConflictResolver.ConflictItem#OPTIONAL_TRUE} and 873 * {@link ConflictResolver.ConflictItem#OPTIONAL_FALSE}. 874 * @param scopes The derived scopes of the conflicting dependency, must not be {@code null}. 875 * @noreference This class is not intended to be instantiated by clients in production code, the constructor may 876 * change without notice and only exists to enable unit testing. 877 */ 878 public ConflictItem( DependencyNode parent, DependencyNode node, int depth, int optionalities, 879 String... scopes ) 880 { 881 this.parent = ( parent != null ) ? parent.getChildren() : null; 882 this.artifact = ( parent != null ) ? parent.getArtifact() : null; 883 this.node = node; 884 this.depth = depth; 885 this.optionalities = optionalities; 886 this.scopes = Arrays.asList( scopes ); 887 } 888 889 /** 890 * Determines whether the specified conflict item is a sibling of this item. 891 * 892 * @param item The other conflict item, must not be {@code null}. 893 * @return {@code true} if the given item has the same parent as this item, {@code false} otherwise. 894 */ 895 public boolean isSibling( ConflictItem item ) 896 { 897 return parent == item.parent; 898 } 899 900 /** 901 * Gets the dependency node involved in the conflict. 902 * 903 * @return The involved dependency node, never {@code null}. 904 */ 905 public DependencyNode getNode() 906 { 907 return node; 908 } 909 910 /** 911 * Gets the dependency involved in the conflict, short for {@code getNode.getDependency()}. 912 * 913 * @return The involved dependency, never {@code null}. 914 */ 915 public Dependency getDependency() 916 { 917 return node.getDependency(); 918 } 919 920 /** 921 * Gets the zero-based depth at which the conflicting node occurs in the graph. As such, the depth denotes the 922 * number of parent nodes. If actually multiple paths lead to the node, the return value denotes the smallest 923 * possible depth. 924 * 925 * @return The zero-based depth of the node in the graph. 926 */ 927 public int getDepth() 928 { 929 return depth; 930 } 931 932 /** 933 * Gets the derived scopes of the dependency. In general, the same dependency node could be reached via 934 * different paths and each path might result in a different derived scope. 935 * 936 * @see ScopeDeriver 937 * @return The (read-only) set of derived scopes of the dependency, never {@code null}. 938 */ 939 @SuppressWarnings( "unchecked" ) 940 public Collection<String> getScopes() 941 { 942 if ( scopes instanceof String ) 943 { 944 return Collections.singleton( (String) scopes ); 945 } 946 return (Collection<String>) scopes; 947 } 948 949 @SuppressWarnings( "unchecked" ) 950 void addScope( String scope ) 951 { 952 if ( scopes instanceof Collection ) 953 { 954 ( (Collection<String>) scopes ).add( scope ); 955 } 956 else if ( !scopes.equals( scope ) ) 957 { 958 Collection<Object> set = new HashSet<>(); 959 set.add( scopes ); 960 set.add( scope ); 961 scopes = set; 962 } 963 } 964 965 /** 966 * Gets the derived optionalities of the dependency. In general, the same dependency node could be reached via 967 * different paths and each path might result in a different derived optionality. 968 * 969 * @return A bit field consisting of {@link ConflictResolver.ConflictItem#OPTIONAL_FALSE} and/or 970 * {@link ConflictResolver.ConflictItem#OPTIONAL_TRUE} indicating the derived optionalities the 971 * dependency was encountered with. 972 */ 973 public int getOptionalities() 974 { 975 return optionalities; 976 } 977 978 void addOptional( boolean optional ) 979 { 980 optionalities |= optional ? OPTIONAL_TRUE : OPTIONAL_FALSE; 981 } 982 983 @Override 984 public String toString() 985 { 986 return node + " @ " + depth + " < " + artifact; 987 } 988 989 } 990 991 /** 992 * A context used to hold information that is relevant for resolving version and scope conflicts. 993 * 994 * @see VersionSelector 995 * @see ScopeSelector 996 * @noinstantiate This class is not intended to be instantiated by clients in production code, the constructor may 997 * change without notice and only exists to enable unit testing. 998 */ 999 public static final class ConflictContext 1000 { 1001 1002 final DependencyNode root; 1003 1004 final Map<?, ?> conflictIds; 1005 1006 final Collection<ConflictItem> items; 1007 1008 Object conflictId; 1009 1010 ConflictItem winner; 1011 1012 String scope; 1013 1014 Boolean optional; 1015 1016 ConflictContext( DependencyNode root, Map<?, ?> conflictIds, Collection<ConflictItem> items ) 1017 { 1018 this.root = root; 1019 this.conflictIds = conflictIds; 1020 this.items = Collections.unmodifiableCollection( items ); 1021 } 1022 1023 /** 1024 * Creates a new conflict context. 1025 * 1026 * @param root The root node of the dependency graph, must not be {@code null}. 1027 * @param conflictId The conflict id for the set of conflicting dependencies in this context, must not be 1028 * {@code null}. 1029 * @param conflictIds The mapping from dependency node to conflict id, must not be {@code null}. 1030 * @param items The conflict items in this context, must not be {@code null}. 1031 * @noreference This class is not intended to be instantiated by clients in production code, the constructor may 1032 * change without notice and only exists to enable unit testing. 1033 */ 1034 public ConflictContext( DependencyNode root, Object conflictId, Map<DependencyNode, Object> conflictIds, 1035 Collection<ConflictItem> items ) 1036 { 1037 this( root, conflictIds, items ); 1038 this.conflictId = conflictId; 1039 } 1040 1041 /** 1042 * Gets the root node of the dependency graph being transformed. 1043 * 1044 * @return The root node of the dependeny graph, never {@code null}. 1045 */ 1046 public DependencyNode getRoot() 1047 { 1048 return root; 1049 } 1050 1051 /** 1052 * Determines whether the specified dependency node belongs to this conflict context. 1053 * 1054 * @param node The dependency node to check, must not be {@code null}. 1055 * @return {@code true} if the given node belongs to this conflict context, {@code false} otherwise. 1056 */ 1057 public boolean isIncluded( DependencyNode node ) 1058 { 1059 return conflictId.equals( conflictIds.get( node ) ); 1060 } 1061 1062 /** 1063 * Gets the collection of conflict items in this context. 1064 * 1065 * @return The (read-only) collection of conflict items in this context, never {@code null}. 1066 */ 1067 public Collection<ConflictItem> getItems() 1068 { 1069 return items; 1070 } 1071 1072 /** 1073 * Gets the conflict item which has been selected as the winner among the conflicting dependencies. 1074 * 1075 * @return The winning conflict item or {@code null} if not set yet. 1076 */ 1077 public ConflictItem getWinner() 1078 { 1079 return winner; 1080 } 1081 1082 /** 1083 * Sets the conflict item which has been selected as the winner among the conflicting dependencies. 1084 * 1085 * @param winner The winning conflict item, may be {@code null}. 1086 */ 1087 public void setWinner( ConflictItem winner ) 1088 { 1089 this.winner = winner; 1090 } 1091 1092 /** 1093 * Gets the effective scope of the winning dependency. 1094 * 1095 * @return The effective scope of the winning dependency or {@code null} if none. 1096 */ 1097 public String getScope() 1098 { 1099 return scope; 1100 } 1101 1102 /** 1103 * Sets the effective scope of the winning dependency. 1104 * 1105 * @param scope The effective scope, may be {@code null}. 1106 */ 1107 public void setScope( String scope ) 1108 { 1109 this.scope = scope; 1110 } 1111 1112 /** 1113 * Gets the effective optional flag of the winning dependency. 1114 * 1115 * @return The effective optional flag or {@code null} if none. 1116 */ 1117 public Boolean getOptional() 1118 { 1119 return optional; 1120 } 1121 1122 /** 1123 * Sets the effective optional flag of the winning dependency. 1124 * 1125 * @param optional The effective optional flag, may be {@code null}. 1126 */ 1127 public void setOptional( Boolean optional ) 1128 { 1129 this.optional = optional; 1130 } 1131 1132 @Override 1133 public String toString() 1134 { 1135 return winner + " @ " + scope + " < " + items; 1136 } 1137 1138 } 1139 1140 /** 1141 * An extension point of {@link ConflictResolver} that determines the winner among conflicting dependencies. The 1142 * winning node (and its children) will be retained in the dependency graph, the other nodes will get removed. The 1143 * version selector does not need to deal with potential scope conflicts, these will be addressed afterwards by the 1144 * {@link ScopeSelector}. 1145 * <p> 1146 * <strong>Note:</strong> Implementations must be stateless. 1147 */ 1148 public abstract static class VersionSelector 1149 { 1150 1151 /** 1152 * Retrieves the version selector for use during the specified graph transformation. The conflict resolver calls 1153 * this method once per 1154 * {@link ConflictResolver#transformGraph(DependencyNode, DependencyGraphTransformationContext)} invocation to 1155 * allow implementations to prepare any auxiliary data that is needed for their operation. Given that 1156 * implementations must be stateless, a new instance needs to be returned to hold such auxiliary data. The 1157 * default implementation simply returns the current instance which is appropriate for implementations which do 1158 * not require auxiliary data. 1159 * 1160 * @param root The root node of the (possibly cyclic!) graph to transform, must not be {@code null}. 1161 * @param context The graph transformation context, must not be {@code null}. 1162 * @return The scope deriver to use for the given graph transformation, never {@code null}. 1163 * @throws RepositoryException If the instance could not be retrieved. 1164 */ 1165 public VersionSelector getInstance( DependencyNode root, DependencyGraphTransformationContext context ) 1166 throws RepositoryException 1167 { 1168 return this; 1169 } 1170 1171 /** 1172 * Determines the winning node among conflicting dependencies. Implementations will usually iterate 1173 * {@link ConflictContext#getItems()}, inspect {@link ConflictItem#getNode()} and eventually call 1174 * {@link ConflictContext#setWinner(ConflictResolver.ConflictItem)} to deliver the winner. Failure to select a 1175 * winner will automatically fail the entire conflict resolution. 1176 * 1177 * @param context The conflict context, must not be {@code null}. 1178 * @throws RepositoryException If the version selection failed. 1179 */ 1180 public abstract void selectVersion( ConflictContext context ) 1181 throws RepositoryException; 1182 1183 } 1184 1185 /** 1186 * An extension point of {@link ConflictResolver} that determines the effective scope of a dependency from a 1187 * potentially conflicting set of {@link ScopeDeriver derived scopes}. The scope selector gets invoked after the 1188 * {@link VersionSelector} has picked the winning node. 1189 * <p> 1190 * <strong>Note:</strong> Implementations must be stateless. 1191 */ 1192 public abstract static class ScopeSelector 1193 { 1194 1195 /** 1196 * Retrieves the scope selector for use during the specified graph transformation. The conflict resolver calls 1197 * this method once per 1198 * {@link ConflictResolver#transformGraph(DependencyNode, DependencyGraphTransformationContext)} invocation to 1199 * allow implementations to prepare any auxiliary data that is needed for their operation. Given that 1200 * implementations must be stateless, a new instance needs to be returned to hold such auxiliary data. The 1201 * default implementation simply returns the current instance which is appropriate for implementations which do 1202 * not require auxiliary data. 1203 * 1204 * @param root The root node of the (possibly cyclic!) graph to transform, must not be {@code null}. 1205 * @param context The graph transformation context, must not be {@code null}. 1206 * @return The scope selector to use for the given graph transformation, never {@code null}. 1207 * @throws RepositoryException If the instance could not be retrieved. 1208 */ 1209 public ScopeSelector getInstance( DependencyNode root, DependencyGraphTransformationContext context ) 1210 throws RepositoryException 1211 { 1212 return this; 1213 } 1214 1215 /** 1216 * Determines the effective scope of the dependency given by {@link ConflictContext#getWinner()}. 1217 * Implementations will usually iterate {@link ConflictContext#getItems()}, inspect 1218 * {@link ConflictItem#getScopes()} and eventually call {@link ConflictContext#setScope(String)} to deliver the 1219 * effective scope. 1220 * 1221 * @param context The conflict context, must not be {@code null}. 1222 * @throws RepositoryException If the scope selection failed. 1223 */ 1224 public abstract void selectScope( ConflictContext context ) 1225 throws RepositoryException; 1226 1227 } 1228 1229 /** 1230 * An extension point of {@link ConflictResolver} that determines the scope of a dependency in relation to the scope 1231 * of its parent. 1232 * <p> 1233 * <strong>Note:</strong> Implementations must be stateless. 1234 */ 1235 public abstract static class ScopeDeriver 1236 { 1237 1238 /** 1239 * Retrieves the scope deriver for use during the specified graph transformation. The conflict resolver calls 1240 * this method once per 1241 * {@link ConflictResolver#transformGraph(DependencyNode, DependencyGraphTransformationContext)} invocation to 1242 * allow implementations to prepare any auxiliary data that is needed for their operation. Given that 1243 * implementations must be stateless, a new instance needs to be returned to hold such auxiliary data. The 1244 * default implementation simply returns the current instance which is appropriate for implementations which do 1245 * not require auxiliary data. 1246 * 1247 * @param root The root node of the (possibly cyclic!) graph to transform, must not be {@code null}. 1248 * @param context The graph transformation context, must not be {@code null}. 1249 * @return The scope deriver to use for the given graph transformation, never {@code null}. 1250 * @throws RepositoryException If the instance could not be retrieved. 1251 */ 1252 public ScopeDeriver getInstance( DependencyNode root, DependencyGraphTransformationContext context ) 1253 throws RepositoryException 1254 { 1255 return this; 1256 } 1257 1258 /** 1259 * Determines the scope of a dependency in relation to the scope of its parent. Implementors need to call 1260 * {@link ScopeContext#setDerivedScope(String)} to deliver the result of their calculation. If said method is 1261 * not invoked, the conflict resolver will assume the scope of the child dependency remains unchanged. 1262 * 1263 * @param context The scope context, must not be {@code null}. 1264 * @throws RepositoryException If the scope deriviation failed. 1265 */ 1266 public abstract void deriveScope( ScopeContext context ) 1267 throws RepositoryException; 1268 1269 } 1270 1271 /** 1272 * An extension point of {@link ConflictResolver} that determines the effective optional flag of a dependency from a 1273 * potentially conflicting set of derived optionalities. The optionality selector gets invoked after the 1274 * {@link VersionSelector} has picked the winning node. 1275 * <p> 1276 * <strong>Note:</strong> Implementations must be stateless. 1277 */ 1278 public abstract static class OptionalitySelector 1279 { 1280 1281 /** 1282 * Retrieves the optionality selector for use during the specified graph transformation. The conflict resolver 1283 * calls this method once per 1284 * {@link ConflictResolver#transformGraph(DependencyNode, DependencyGraphTransformationContext)} invocation to 1285 * allow implementations to prepare any auxiliary data that is needed for their operation. Given that 1286 * implementations must be stateless, a new instance needs to be returned to hold such auxiliary data. The 1287 * default implementation simply returns the current instance which is appropriate for implementations which do 1288 * not require auxiliary data. 1289 * 1290 * @param root The root node of the (possibly cyclic!) graph to transform, must not be {@code null}. 1291 * @param context The graph transformation context, must not be {@code null}. 1292 * @return The optionality selector to use for the given graph transformation, never {@code null}. 1293 * @throws RepositoryException If the instance could not be retrieved. 1294 */ 1295 public OptionalitySelector getInstance( DependencyNode root, DependencyGraphTransformationContext context ) 1296 throws RepositoryException 1297 { 1298 return this; 1299 } 1300 1301 /** 1302 * Determines the effective optional flag of the dependency given by {@link ConflictContext#getWinner()}. 1303 * Implementations will usually iterate {@link ConflictContext#getItems()}, inspect 1304 * {@link ConflictItem#getOptionalities()} and eventually call {@link ConflictContext#setOptional(Boolean)} to 1305 * deliver the effective optional flag. 1306 * 1307 * @param context The conflict context, must not be {@code null}. 1308 * @throws RepositoryException If the optionality selection failed. 1309 */ 1310 public abstract void selectOptionality( ConflictContext context ) 1311 throws RepositoryException; 1312 1313 } 1314 1315}