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.Collection; 023import java.util.Collections; 024import java.util.HashMap; 025import java.util.HashSet; 026import java.util.List; 027import java.util.Map; 028import java.util.Objects; 029import java.util.stream.Collectors; 030 031import org.eclipse.aether.RepositoryException; 032import org.eclipse.aether.artifact.Artifact; 033import org.eclipse.aether.collection.DependencyGraphTransformationContext; 034import org.eclipse.aether.graph.DefaultDependencyNode; 035import org.eclipse.aether.graph.Dependency; 036import org.eclipse.aether.graph.DependencyNode; 037import org.eclipse.aether.util.artifact.ArtifactIdUtils; 038 039import static java.util.Objects.requireNonNull; 040 041/** 042 * A high-performance dependency graph transformer that resolves version and scope conflicts among dependencies. 043 * This is the recommended conflict resolver implementation that provides O(N) performance characteristics, 044 * significantly improving upon the O(N²) worst-case performance of {@link ClassicConflictResolver}. 045 * <p> 046 * For a given set of conflicting nodes, one node will be chosen as the winner. How losing nodes are handled 047 * depends on the configured verbosity level: they may be removed entirely, have their children removed, or 048 * be left in place with conflict information. The exact rules by which a winning node and its effective scope 049 * are determined are controlled by user-supplied implementations of {@link ConflictResolver.VersionSelector}, {@link ConflictResolver.ScopeSelector}, 050 * {@link ConflictResolver.OptionalitySelector} and {@link ConflictResolver.ScopeDeriver}. 051 * <p> 052 * <strong>Performance Characteristics:</strong> 053 * <ul> 054 * <li><strong>Time Complexity:</strong> O(N) where N is the number of dependency nodes</li> 055 * <li><strong>Memory Usage:</strong> Creates a parallel tree structure for conflict-free processing</li> 056 * <li><strong>Scalability:</strong> Excellent performance on large multi-module projects</li> 057 * </ul> 058 * <p> 059 * <strong>Algorithm Overview:</strong> 060 * <ol> 061 * <li><strong>Path Tree Construction:</strong> Builds a cycle-free parallel tree structure from the input 062 * dependency graph, where each {@code Path} represents a unique route to a dependency node</li> 063 * <li><strong>Conflict Partitioning:</strong> Groups paths by conflict ID (based on groupId:artifactId:classifier:extension coordinates)</li> 064 * <li><strong>Topological Processing:</strong> Processes conflict groups in topologically sorted order</li> 065 * <li><strong>Winner Selection:</strong> Uses provided selectors to choose winners within each conflict group</li> 066 * <li><strong>Graph Transformation:</strong> Applies changes back to the original dependency graph</li> 067 * </ol> 068 * <p> 069 * <strong>Key Differences from {@link ClassicConflictResolver}:</strong> 070 * <ul> 071 * <li><strong>Performance:</strong> O(N) vs O(N²) time complexity</li> 072 * <li><strong>Memory Strategy:</strong> Uses parallel tree structure vs in-place graph modification</li> 073 * <li><strong>Cycle Handling:</strong> Explicitly breaks cycles during tree construction</li> 074 * <li><strong>Processing Order:</strong> Level-by-level from root vs depth-first traversal</li> 075 * </ul> 076 * <p> 077 * <strong>When to Use:</strong> 078 * <ul> 079 * <li>Default choice for all new projects and Maven 4+ installations</li> 080 * <li>Large multi-module projects with many dependencies</li> 081 * <li>Performance-critical build environments</li> 082 * <li>Any scenario where {@link ClassicConflictResolver} shows performance bottlenecks</li> 083 * </ul> 084 * <p> 085 * <strong>Implementation Note:</strong> This conflict resolver builds a cycle-free "parallel" structure based on the 086 * passed-in dependency graph, and applies operations level by level starting from the root. The parallel {@code Path} 087 * tree ensures that cycles in the original graph don't affect the conflict resolution algorithm's performance. 088 * 089 * @see ClassicConflictResolver 090 * @since 2.0.11 091 */ 092public final class PathConflictResolver extends ConflictResolver { 093 private final ConflictResolver.VersionSelector versionSelector; 094 private final ConflictResolver.ScopeSelector scopeSelector; 095 private final ConflictResolver.ScopeDeriver scopeDeriver; 096 private final ConflictResolver.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 PathConflictResolver( 107 ConflictResolver.VersionSelector versionSelector, 108 ConflictResolver.ScopeSelector scopeSelector, 109 ConflictResolver.OptionalitySelector optionalitySelector, 110 ConflictResolver.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 @SuppressWarnings("unchecked") 118 @Override 119 public DependencyNode transformGraph(DependencyNode node, DependencyGraphTransformationContext context) 120 throws RepositoryException { 121 requireNonNull(node, "node cannot be null"); 122 requireNonNull(context, "context cannot be null"); 123 List<String> sortedConflictIds = (List<String>) context.get(TransformationContextKeys.SORTED_CONFLICT_IDS); 124 if (sortedConflictIds == null) { 125 ConflictIdSorter sorter = new ConflictIdSorter(); 126 sorter.transformGraph(node, context); 127 128 sortedConflictIds = (List<String>) 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<String>> conflictIdCycles = 137 (Collection<Collection<String>>) context.get(TransformationContextKeys.CYCLIC_CONFLICT_IDS); 138 if (conflictIdCycles == null) { 139 throw new RepositoryException("conflict id cycles have not been identified"); 140 } 141 142 Map<DependencyNode, String> conflictIds = 143 (Map<DependencyNode, String>) context.get(TransformationContextKeys.CONFLICT_IDS); 144 if (conflictIds == null) { 145 throw new RepositoryException("conflict groups have not been identified"); 146 } 147 148 Map<String, Collection<String>> cyclicPredecessors = new HashMap<>(); 149 for (Collection<String> cycle : conflictIdCycles) { 150 for (String conflictId : cycle) { 151 Collection<String> predecessors = cyclicPredecessors.computeIfAbsent(conflictId, k -> new HashSet<>()); 152 predecessors.addAll(cycle); 153 } 154 } 155 156 State state = new State( 157 ConflictResolver.getVerbosity(context.getSession()), 158 versionSelector.getInstance(node, context), 159 scopeSelector.getInstance(node, context), 160 scopeDeriver.getInstance(node, context), 161 optionalitySelector.getInstance(node, context), 162 sortedConflictIds, 163 conflictIds, 164 cyclicPredecessors); 165 166 state.build(node); 167 168 // loop over topographically sorted conflictIds 169 for (String conflictId : state.sortedConflictIds) { 170 // paths in given conflict group to consider 171 List<Path> paths = state.partitions.get(conflictId); 172 if (paths.isEmpty()) { 173 // this means that whole group "fall out of scope" (are all on loser branches); skip 174 continue; 175 } 176 177 // create conflict context for given conflictId 178 ConflictContext ctx = new ConflictContext( 179 node, 180 state.conflictIds, 181 paths.stream().map(ConflictItem::new).collect(Collectors.toList()), 182 conflictId); 183 184 // select winner (is done by VersionSelector) 185 state.versionSelector.selectVersion(ctx); 186 if (ctx.winner == null) { 187 throw new RepositoryException("conflict resolver did not select winner among " + ctx.items); 188 } 189 // select scope (no side effect between this and above operations) 190 state.scopeSelector.selectScope(ctx); 191 // select optionality (no side effect between this and above operations) 192 state.optionalitySelector.selectOptionality(ctx); 193 194 // we have a winner path 195 Path winnerPath = ctx.winner.path; 196 197 // mark conflictId as resolved with winner; sanity check 198 if (state.resolvedIds.containsKey(conflictId)) { 199 throw new RepositoryException("conflict resolver already have winner for conflictId=" + conflictId 200 + ": " + state.resolvedIds); 201 } 202 state.resolvedIds.put(conflictId, winnerPath); 203 204 // loop over considered paths and apply selection results; note: node may remove itself from iterated list 205 for (Path path : new ArrayList<>(paths)) { 206 // apply selected inherited properties scope/optional to all (winner carries version; others are losers) 207 path.scope = ctx.scope; 208 path.optional = ctx.optional; 209 210 // reset children as inheritance may be affected by this node scope/optionality change 211 path.children.forEach(c -> c.pull(0)); 212 // derive with new values from this to children only; observe winner flag 213 path.derive(1, path == winnerPath); 214 // push this node full level changes to DN graph 215 path.push(0); 216 } 217 } 218 219 if (stats != null) { 220 long time2 = System.nanoTime(); 221 stats.put("ConflictResolver.totalTime", time2 - time1); 222 stats.put( 223 "ConflictResolver.conflictItemCount", 224 state.partitions.values().stream().map(List::size).reduce(0, Integer::sum)); 225 } 226 227 return node; 228 } 229 230 /** 231 * State of conflict resolution processing, to make this component (held in session) re-entrant by multiple threads. 232 */ 233 private static class State { 234 /** 235 * Verbosity to be applied, see {@link ConflictResolver.Verbosity}. 236 */ 237 private final ConflictResolver.Verbosity verbosity; 238 239 /** 240 * The {@link ConflictResolver.VersionSelector} to use. 241 */ 242 private final ConflictResolver.VersionSelector versionSelector; 243 244 /** 245 * The {@link ConflictResolver.ScopeSelector} to use. 246 */ 247 private final ConflictResolver.ScopeSelector scopeSelector; 248 249 /** 250 * The {@link ConflictResolver.ScopeDeriver} to use. 251 */ 252 private final ConflictResolver.ScopeDeriver scopeDeriver; 253 254 /** 255 * The {@link ConflictResolver.OptionalitySelector} to use/ 256 */ 257 private final ConflictResolver.OptionalitySelector optionalitySelector; 258 259 /** 260 * Topologically sorted conflictIds from {@link ConflictIdSorter}. 261 */ 262 private final List<String> sortedConflictIds; 263 264 /** 265 * The node to conflictId mapping from {@link ConflictMarker}. 266 */ 267 private final Map<DependencyNode, String> conflictIds; 268 269 /** 270 * The map of conflictIds which could apply to ancestors of nodes with the key conflict id, used to avoid 271 * recursion early on. This is basically a superset of the key set of resolvedIds, the additional ids account 272 * for cyclic dependencies. From {@link ConflictIdSorter}. 273 */ 274 private final Map<String, Collection<String>> cyclicPredecessors; 275 276 /** 277 * A mapping from conflictId to paths represented as {@link Path}s that exist for each conflictId. In other 278 * words all paths to each {@link DependencyNode} that are member of same conflictId group. 279 */ 280 private final Map<String, List<Path>> partitions; 281 282 /** 283 * A mapping from conflictIds to winner {@link Path}, hence {@link DependencyNode} for given conflictId. 284 */ 285 private final Map<String, Path> resolvedIds; 286 287 @SuppressWarnings("checkstyle:ParameterNumber") 288 private State( 289 ConflictResolver.Verbosity verbosity, 290 ConflictResolver.VersionSelector versionSelector, 291 ConflictResolver.ScopeSelector scopeSelector, 292 ConflictResolver.ScopeDeriver scopeDeriver, 293 ConflictResolver.OptionalitySelector optionalitySelector, 294 List<String> sortedConflictIds, 295 Map<DependencyNode, String> conflictIds, 296 Map<String, Collection<String>> cyclicPredecessors) { 297 this.verbosity = verbosity; 298 this.versionSelector = versionSelector; 299 this.scopeSelector = scopeSelector; 300 this.scopeDeriver = scopeDeriver; 301 this.optionalitySelector = optionalitySelector; 302 this.sortedConflictIds = sortedConflictIds; 303 this.conflictIds = conflictIds; 304 this.cyclicPredecessors = cyclicPredecessors; 305 this.partitions = new HashMap<>(); 306 this.resolvedIds = new HashMap<>(); 307 } 308 309 /** 310 * Consumes the dirty graph and builds internal structures out of {@link Path} instances that is always a 311 * tree. 312 */ 313 private Path build(DependencyNode node) throws RepositoryException { 314 Path root = new Path(this, node, null); 315 gatherCRNodes(root); 316 return root; 317 } 318 319 /** 320 * Recursively builds {@link Path} graph by observing each node associated {@link DependencyNode}. 321 */ 322 private void gatherCRNodes(Path node) throws RepositoryException { 323 List<DependencyNode> children = node.dn.getChildren(); 324 if (!children.isEmpty()) { 325 // add children; we will get back those really added (not causing cycles) 326 List<Path> added = node.addChildren(children); 327 for (Path child : added) { 328 gatherCRNodes(child); 329 } 330 } 331 } 332 } 333 334 /** 335 * Represents a unique path within the dependency graph from the root to a specific {@link DependencyNode}. 336 * This is the core data structure that enables the O(N) performance of {@link PathConflictResolver}. 337 * <p> 338 * <strong>Key Concepts:</strong> 339 * <ul> 340 * <li><strong>Path Uniqueness:</strong> Each {@code Path} instance represents a distinct route through 341 * the dependency graph, even if multiple paths lead to the same {@code DependencyNode}</li> 342 * <li><strong>Cycle-Free Structure:</strong> The {@code Path} tree is guaranteed to be acyclic, even 343 * when the original dependency graph contains cycles</li> 344 * <li><strong>Parallel Structure:</strong> This creates a "clean" tree alongside the original "dirty" 345 * graph for efficient processing</li> 346 * </ul> 347 * <p> 348 * <strong>Example:</strong> If dependency A appears in the graph via two different routes: 349 * <pre> 350 * Root → B → A (path 1) 351 * Root → C → A (path 2) 352 * </pre> 353 * Two separate {@code Path} instances will be created, both pointing to the same {@code DependencyNode} A, 354 * but representing different paths through the dependency tree. 355 * <p> 356 * <strong>Memory Optimization:</strong> While this creates additional objects, it enables the algorithm 357 * to process conflicts in O(N) time rather than O(N²), making it much more efficient for large graphs. 358 * <p> 359 * <strong>Conflict Resolution:</strong> Paths are grouped by conflict ID (based on groupId:artifactId:classifier:extension coordinates), 360 * and the conflict resolution algorithm can efficiently process each group independently. 361 */ 362 private static class Path { 363 private final State state; 364 private DependencyNode dn; 365 private final String conflictId; 366 private final Path parent; 367 private final int depth; 368 private final List<Path> children; 369 private String scope; 370 private boolean optional; 371 372 private Path(State state, DependencyNode dn, Path parent) { 373 this.state = state; 374 this.dn = dn; 375 this.conflictId = state.conflictIds.get(dn); 376 this.parent = parent; 377 this.depth = parent != null ? parent.depth + 1 : 0; 378 this.children = new ArrayList<>(); 379 pull(0); 380 381 this.state 382 .partitions 383 .computeIfAbsent(this.conflictId, k -> new ArrayList<>()) 384 .add(this); 385 } 386 387 /** 388 * Pulls (possibly updated) scope and optional values from associated {@link DependencyNode} to this instance, 389 * going down toward children recursively the required count of levels. 390 */ 391 private void pull(int levels) { 392 Dependency d = dn.getDependency(); 393 if (d != null) { 394 this.scope = d.getScope(); 395 this.optional = d.isOptional(); 396 } else { 397 this.scope = ""; 398 this.optional = false; 399 } 400 int newLevels = levels - 1; 401 if (newLevels >= 0) { 402 for (Path child : this.children) { 403 child.pull(newLevels); 404 } 405 } 406 } 407 408 /** 409 * Derives (from this to children direction) values that are "inherited" in tree: scope and optionality in the tree 410 * recursively going down required count of "levels". 411 */ 412 private void derive(int levels, boolean winner) throws RepositoryException { 413 if (!winner) { 414 if (this.parent != null) { 415 if ((dn.getManagedBits() & DependencyNode.MANAGED_SCOPE) == 0) { 416 ScopeContext context = new ScopeContext(this.parent.scope, this.scope); 417 state.scopeDeriver.deriveScope(context); 418 this.scope = context.derivedScope; 419 } 420 if ((dn.getManagedBits() & DependencyNode.MANAGED_OPTIONAL) == 0) { 421 if (!this.optional && this.parent.optional) { 422 this.optional = true; 423 } 424 } 425 } else { 426 this.scope = ""; 427 this.optional = false; 428 } 429 } 430 int newLevels = levels - 1; 431 if (newLevels >= 0) { 432 for (Path child : children) { 433 child.derive(newLevels, false); 434 } 435 } 436 } 437 438 /** 439 * Pushes (applies) the scope and optional and structural changes to associated {@link DependencyNode} modifying 440 * the graph of it. Verbosity is observed, and depending on it the conflicting/loser nodes are removed, or 441 * just their children is removed (with special care for version ranges, see {@link #relatedSiblingsCount(Artifact, Path)} 442 * or by just doing nothing with them only marking losers in full verbosity mode. 443 */ 444 private void push(int levels) { 445 if (this.parent != null) { 446 Path winner = this.state.resolvedIds.get(this.conflictId); 447 if (winner == null) { 448 throw new IllegalStateException( 449 "Winner selection did not happen for conflictId=" + this.conflictId); 450 } 451 if (!Objects.equals(winner.conflictId, this.conflictId)) { 452 throw new IllegalStateException( 453 "ConflictId mix-up: this=" + this.conflictId + " winner=" + winner.conflictId); 454 } 455 456 if (winner == this) { 457 // copy onto dn; if applicable 458 if (this.dn.getDependency() != null) { 459 this.dn.setData( 460 ConflictResolver.NODE_DATA_ORIGINAL_SCOPE, 461 this.dn.getDependency().getScope()); 462 this.dn.setData( 463 ConflictResolver.NODE_DATA_ORIGINAL_OPTIONALITY, 464 this.dn.getDependency().getOptional()); 465 this.dn.setScope(this.scope); 466 this.dn.setOptional(this.optional); 467 } 468 } else { 469 // loser; move out of scope 470 moveOutOfScope(); 471 boolean markLoser = false; 472 switch (state.verbosity) { 473 case NONE: 474 // remove this dn 475 this.parent.children.remove(this); 476 this.parent.dn.setChildren(new ArrayList<>(this.parent.dn.getChildren())); 477 this.parent.dn.getChildren().remove(this.dn); 478 this.children.clear(); 479 break; 480 case STANDARD: 481 // leave this dn; remove children 482 String artifactId = ArtifactIdUtils.toId(this.dn.getArtifact()); 483 String winnerArtifactId = ArtifactIdUtils.toId(winner.dn.getArtifact()); 484 if (!Objects.equals(artifactId, winnerArtifactId) 485 && relatedSiblingsCount(this.dn.getArtifact(), this.parent) > 1) { 486 this.parent.children.remove(this); 487 this.parent.dn.setChildren(new ArrayList<>(this.parent.dn.getChildren())); 488 this.parent.dn.getChildren().remove(this.dn); 489 this.children.clear(); 490 } else { 491 this.children.clear(); 492 this.dn.setChildren(Collections.emptyList()); 493 markLoser = true; 494 } 495 break; 496 case FULL: 497 // leave all in place (even cycles) 498 markLoser = true; 499 break; 500 default: 501 throw new IllegalArgumentException("Unknown " + state.verbosity); 502 } 503 if (markLoser) { 504 // copy dn 505 DependencyNode dnCopy = new DefaultDependencyNode(this.dn); 506 dnCopy.setData(ConflictResolver.NODE_DATA_WINNER, winner.dn); 507 dnCopy.setData( 508 ConflictResolver.NODE_DATA_ORIGINAL_SCOPE, 509 this.dn.getDependency().getScope()); 510 dnCopy.setData( 511 ConflictResolver.NODE_DATA_ORIGINAL_OPTIONALITY, 512 this.dn.getDependency().getOptional()); 513 dnCopy.setScope(this.scope); 514 dnCopy.setOptional(this.optional); 515 if (ConflictResolver.Verbosity.FULL != state.verbosity) { 516 dnCopy.getChildren().clear(); 517 } 518 519 // swap it out in DN graph 520 this.parent 521 .dn 522 .getChildren() 523 .set(this.parent.dn.getChildren().indexOf(this.dn), dnCopy); 524 this.dn = dnCopy; 525 } 526 } 527 } 528 if (!this.children.isEmpty()) { 529 int newLevels = levels - 1; 530 if (newLevels >= 0) { 531 // child may remove itself from iterated list 532 for (Path child : new ArrayList<>(children)) { 533 child.push(newLevels); 534 } 535 } 536 } else if (!this.dn.getChildren().isEmpty()) { 537 this.dn.setChildren(Collections.emptyList()); 538 } 539 } 540 541 /** 542 * Counts "relatives" (GACE equal) artifacts under same parent; this is for cleaning up redundant nodes in 543 * case of version ranges, where same GACE is resolved into multiple GACEV as range is resolved. In {@link ConflictResolver.Verbosity#STANDARD} 544 * verbosity mode we remove "redundant" nodes (of a range) leaving only "winner equal" loser, that have same GACEV as winner. 545 */ 546 private int relatedSiblingsCount(Artifact artifact, Path parent) { 547 String ga = artifact.getGroupId() + ":" + artifact.getArtifactId(); 548 return Math.toIntExact(parent.children.stream() 549 .map(n -> n.dn.getArtifact()) 550 .filter(a -> ga.equals(a.getGroupId() + ":" + a.getArtifactId())) 551 .count()); 552 } 553 554 /** 555 * Removes this and all child {@link Path} nodes from winner selection scope; essentially marks whole subtree 556 * from "this and below" as loser, to not be considered in subsequent winner selections. 557 */ 558 private void moveOutOfScope() { 559 this.state.partitions.get(this.conflictId).remove(this); 560 for (Path child : this.children) { 561 child.moveOutOfScope(); 562 } 563 } 564 565 /** 566 * Adds node children: this method should be "batch" used, as all (potential) children should be added at once. 567 * Method will return really added ones, as this class avoids cycles. 568 */ 569 private List<Path> addChildren(List<DependencyNode> children) throws RepositoryException { 570 Collection<String> thisCyclicPredecessors = 571 this.state.cyclicPredecessors.getOrDefault(this.conflictId, Collections.emptySet()); 572 573 ArrayList<Path> added = new ArrayList<>(children.size()); 574 for (DependencyNode child : children) { 575 String childConflictId = this.state.conflictIds.get(child); 576 if (!this.state.partitions.containsKey(childConflictId) 577 || !thisCyclicPredecessors.contains(childConflictId)) { 578 Path c = new Path(this.state, child, this); 579 this.children.add(c); 580 c.derive(0, false); 581 added.add(c); 582 } 583 } 584 return added; 585 } 586 587 /** 588 * Dump for debug. 589 */ 590 private void dump(String padding) { 591 System.out.println(padding + this.dn + ": " + this.scope + "/" + this.optional); 592 for (Path child : children) { 593 child.dump(padding + " "); 594 } 595 } 596 597 /** 598 * For easier debug. 599 */ 600 @Override 601 public String toString() { 602 return this.dn.toString(); 603 } 604 } 605 606 /** 607 * A context used to hold information that is relevant for deriving the scope of a child dependency. 608 * 609 * @see ConflictResolver.ScopeDeriver 610 * @noinstantiate This class is not intended to be instantiated by clients in production code, the constructor may 611 * change without notice and only exists to enable unit testing. 612 */ 613 private static final class ScopeContext extends ConflictResolver.ScopeContext { 614 private final String parentScope; 615 private final String childScope; 616 private String derivedScope; 617 618 /** 619 * Creates a new scope context with the specified properties. 620 * 621 * @param parentScope The scope of the parent dependency, may be {@code null}. 622 * @param childScope The scope of the child dependency, may be {@code null}. 623 * @noreference This class is not intended to be instantiated by clients in production code, the constructor may 624 * change without notice and only exists to enable unit testing. 625 */ 626 private ScopeContext(String parentScope, String childScope) { 627 this.parentScope = (parentScope != null) ? parentScope : ""; 628 this.derivedScope = (childScope != null) ? childScope : ""; 629 this.childScope = (childScope != null) ? childScope : ""; 630 } 631 632 /** 633 * Gets the scope of the parent dependency. This is usually the scope that was derived by earlier invocations of 634 * the scope deriver. 635 * 636 * @return The scope of the parent dependency, never {@code null}. 637 */ 638 public String getParentScope() { 639 return parentScope; 640 } 641 642 /** 643 * Gets the original scope of the child dependency. This is the scope that was declared in the artifact 644 * descriptor of the parent dependency. 645 * 646 * @return The original scope of the child dependency, never {@code null}. 647 */ 648 public String getChildScope() { 649 return childScope; 650 } 651 652 /** 653 * Gets the derived scope of the child dependency. This is initially equal to {@link #getChildScope()} until the 654 * scope deriver makes changes. 655 * 656 * @return The derived scope of the child dependency, never {@code null}. 657 */ 658 public String getDerivedScope() { 659 return derivedScope; 660 } 661 662 /** 663 * Sets the derived scope of the child dependency. 664 * 665 * @param derivedScope The derived scope of the dependency, may be {@code null}. 666 */ 667 public void setDerivedScope(String derivedScope) { 668 this.derivedScope = (derivedScope != null) ? derivedScope : ""; 669 } 670 } 671 672 /** 673 * A conflicting dependency. 674 * 675 * @noinstantiate This class is not intended to be instantiated by clients in production code, the constructor may 676 * change without notice and only exists to enable unit testing. 677 */ 678 private static final class ConflictItem extends ConflictResolver.ConflictItem { 679 private final Path path; 680 private final List<DependencyNode> parent; 681 private final Artifact artifact; 682 private final DependencyNode node; 683 private final int depth; 684 private final String scope; 685 private final int optionalities; 686 687 /** 688 * Bit flag indicating whether one or more paths consider the dependency non-optional. 689 */ 690 public static final int OPTIONAL_FALSE = 0x01; 691 692 /** 693 * Bit flag indicating whether one or more paths consider the dependency optional. 694 */ 695 public static final int OPTIONAL_TRUE = 0x02; 696 697 private ConflictItem(Path path) { 698 this.path = path; 699 if (path.parent != null) { 700 DependencyNode parent = path.parent.dn; 701 this.parent = parent.getChildren(); 702 this.artifact = parent.getArtifact(); 703 } else { 704 this.parent = null; 705 this.artifact = null; 706 } 707 this.node = path.dn; 708 this.depth = path.depth; 709 this.scope = path.scope; 710 this.optionalities = path.optional ? OPTIONAL_TRUE : OPTIONAL_FALSE; 711 } 712 713 /** 714 * Determines whether the specified conflict item is a sibling of this item. 715 * 716 * @param item The other conflict item, must not be {@code null}. 717 * @return {@code true} if the given item has the same parent as this item, {@code false} otherwise. 718 */ 719 @Override 720 public boolean isSibling(ConflictResolver.ConflictItem item) { 721 return parent == ((ConflictItem) item).parent; 722 } 723 724 /** 725 * Gets the dependency node involved in the conflict. 726 * 727 * @return The involved dependency node, never {@code null}. 728 */ 729 @Override 730 public DependencyNode getNode() { 731 return node; 732 } 733 734 /** 735 * Gets the dependency involved in the conflict, short for {@code getNode.getDependency()}. 736 * 737 * @return The involved dependency, never {@code null}. 738 */ 739 @Override 740 public Dependency getDependency() { 741 return node.getDependency(); 742 } 743 744 /** 745 * Gets the zero-based depth at which the conflicting node occurs in the graph. As such, the depth denotes the 746 * number of parent nodes. If actually multiple paths lead to the node, the return value denotes the smallest 747 * possible depth. 748 * 749 * @return The zero-based depth of the node in the graph. 750 */ 751 @Override 752 public int getDepth() { 753 return depth; 754 } 755 756 /** 757 * Gets the derived scopes of the dependency. In general, the same dependency node could be reached via 758 * different paths and each path might result in a different derived scope. 759 * 760 * @see ConflictResolver.ScopeDeriver 761 * @return The (read-only) set of derived scopes of the dependency, never {@code null}. 762 */ 763 @Override 764 public Collection<String> getScopes() { 765 return Collections.singleton(scope); 766 } 767 768 /** 769 * Gets the derived optionalities of the dependency. In general, the same dependency node could be reached via 770 * different paths and each path might result in a different derived optionality. 771 * 772 * @return A bit field consisting of {@link PathConflictResolver.ConflictItem#OPTIONAL_FALSE} and/or 773 * {@link PathConflictResolver.ConflictItem#OPTIONAL_TRUE} indicating the derived optionalities the 774 * dependency was encountered with. 775 */ 776 @Override 777 public int getOptionalities() { 778 return optionalities; 779 } 780 781 @Override 782 public String toString() { 783 return node + " @ " + depth + " < " + artifact; 784 } 785 } 786 787 /** 788 * A context used to hold information that is relevant for resolving version and scope conflicts. 789 * 790 * @see ConflictResolver.VersionSelector 791 * @see ConflictResolver.ScopeSelector 792 * @noinstantiate This class is not intended to be instantiated by clients in production code, the constructor may 793 * change without notice and only exists to enable unit testing. 794 */ 795 private static final class ConflictContext extends ConflictResolver.ConflictContext { 796 private final DependencyNode root; 797 private final Map<DependencyNode, String> conflictIds; 798 private final Collection<ConflictResolver.ConflictItem> items; 799 private final String conflictId; 800 801 // elected properties 802 private ConflictItem winner; 803 private String scope; 804 private Boolean optional; 805 806 private ConflictContext( 807 DependencyNode root, 808 Map<DependencyNode, String> conflictIds, 809 Collection<ConflictItem> items, 810 String conflictId) { 811 this.root = root; 812 this.conflictIds = conflictIds; 813 this.items = Collections.unmodifiableCollection(items); 814 this.conflictId = conflictId; 815 } 816 817 /** 818 * Gets the root node of the dependency graph being transformed. 819 * 820 * @return The root node of the dependency graph, never {@code null}. 821 */ 822 @Override 823 public DependencyNode getRoot() { 824 return root; 825 } 826 827 /** 828 * Determines whether the specified dependency node belongs to this conflict context. 829 * 830 * @param node The dependency node to check, must not be {@code null}. 831 * @return {@code true} if the given node belongs to this conflict context, {@code false} otherwise. 832 */ 833 @Override 834 public boolean isIncluded(DependencyNode node) { 835 return conflictId.equals(conflictIds.get(node)); 836 } 837 838 /** 839 * Gets the collection of conflict items in this context. 840 * 841 * @return The (read-only) collection of conflict items in this context, never {@code null}. 842 */ 843 @Override 844 public Collection<ConflictResolver.ConflictItem> getItems() { 845 return items; 846 } 847 848 /** 849 * Gets the conflict item which has been selected as the winner among the conflicting dependencies. 850 * 851 * @return The winning conflict item or {@code null} if not set yet. 852 */ 853 @Override 854 public ConflictResolver.ConflictItem getWinner() { 855 return winner; 856 } 857 858 /** 859 * Sets the conflict item which has been selected as the winner among the conflicting dependencies. 860 * 861 * @param winner The winning conflict item, may be {@code null}. 862 */ 863 @Override 864 public void setWinner(ConflictResolver.ConflictItem winner) { 865 this.winner = (ConflictItem) winner; 866 } 867 868 /** 869 * Gets the effective scope of the winning dependency. 870 * 871 * @return The effective scope of the winning dependency or {@code null} if none. 872 */ 873 @Override 874 public String getScope() { 875 return scope; 876 } 877 878 /** 879 * Sets the effective scope of the winning dependency. 880 * 881 * @param scope The effective scope, may be {@code null}. 882 */ 883 @Override 884 public void setScope(String scope) { 885 this.scope = scope; 886 } 887 888 /** 889 * Gets the effective optional flag of the winning dependency. 890 * 891 * @return The effective optional flag or {@code null} if none. 892 */ 893 @Override 894 public Boolean getOptional() { 895 return optional; 896 } 897 898 /** 899 * Sets the effective optional flag of the winning dependency. 900 * 901 * @param optional The effective optional flag, may be {@code null}. 902 */ 903 @Override 904 public void setOptional(Boolean optional) { 905 this.optional = optional; 906 } 907 908 @Override 909 public String toString() { 910 return winner + " @ " + scope + " < " + items; 911 } 912 } 913}