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