1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 package org.eclipse.aether.util.graph.transformer; 20 21 import java.util.Arrays; 22 import java.util.Collection; 23 24 import org.eclipse.aether.ConfigurationProperties; 25 import org.eclipse.aether.RepositoryException; 26 import org.eclipse.aether.RepositorySystemSession; 27 import org.eclipse.aether.collection.DependencyGraphTransformationContext; 28 import org.eclipse.aether.collection.DependencyGraphTransformer; 29 import org.eclipse.aether.graph.Dependency; 30 import org.eclipse.aether.graph.DependencyNode; 31 import org.eclipse.aether.util.ConfigUtils; 32 33 import static java.util.Objects.requireNonNull; 34 35 /** 36 * Abstract base class for dependency graph transformers that resolve version and scope conflicts among dependencies. 37 * For a given set of conflicting nodes, one node will be chosen as the winner. How losing nodes are handled depends 38 * on the configured verbosity level: they may be removed entirely, have their children removed, or be left in place 39 * with conflict information. The exact rules by which a winning node and its effective scope are determined are 40 * controlled by user-supplied implementations of {@link VersionSelector}, {@link ScopeSelector}, 41 * {@link OptionalitySelector} and {@link ScopeDeriver}. 42 * <p> 43 * <strong>Available Implementations:</strong> 44 * <ul> 45 * <li><strong>{@link PathConflictResolver}</strong> - Recommended high-performance implementation with O(N) complexity</li> 46 * <li><strong>{@link ClassicConflictResolver}</strong> - Legacy implementation for backward compatibility (O(N²) worst-case)</li> 47 * </ul> 48 * <p> 49 * <strong>Implementation Selection Guide:</strong> 50 * <ul> 51 * <li><strong>New Projects:</strong> Use {@link PathConflictResolver} for optimal performance</li> 52 * <li><strong>Large Multi-Module Projects:</strong> Use {@link PathConflictResolver} to avoid performance bottlenecks</li> 53 * <li><strong>Maven 4+ Environments:</strong> Use {@link PathConflictResolver} for best build performance</li> 54 * <li><strong>Legacy Compatibility:</strong> Use {@link ClassicConflictResolver} only when exact Maven 3.x behavior is required</li> 55 * </ul> 56 * <p> 57 * <strong>Usage Example:</strong> 58 * <pre>{@code 59 * // Recommended: High-performance path-based resolver 60 * DependencyGraphTransformer transformer = new ChainedDependencyGraphTransformer( 61 * new PathConflictResolver( 62 * new NearestVersionSelector(), 63 * new JavaScopeSelector(), 64 * new SimpleOptionalitySelector(), 65 * new JavaScopeDeriver()), 66 * // other transformers... 67 * ); 68 * 69 * // Legacy: Classic resolver for backward compatibility 70 * DependencyGraphTransformer legacyTransformer = new ChainedDependencyGraphTransformer( 71 * new ClassicConflictResolver( 72 * new NearestVersionSelector(), 73 * new JavaScopeSelector(), 74 * new SimpleOptionalitySelector(), 75 * new JavaScopeDeriver()), 76 * // other transformers... 77 * ); 78 * }</pre> 79 * <p> 80 * <strong>Verbosity Levels and Conflict Handling:</strong> 81 * <ul> 82 * <li><strong>NONE (default):</strong> Creates a clean dependency tree without duplicate artifacts. 83 * Losing nodes are completely removed from the graph, so are cycles as well.</li> 84 * <li><strong>STANDARD:</strong> Retains losing nodes for analysis but removes their children to prevent 85 * duplicate dependencies. Special handling for version ranges: redundant nodes may still be removed 86 * if multiple versions of the same artifact exist. Losing nodes link back to the winner via 87 * {@link #NODE_DATA_WINNER} and preserve original scope/optionality information. This mode removes cycles only, 88 * while conflict nodes/duplicates are left in place. Graphs in this verbosity level cannot be resolved, 89 * their purpose is for analysis only.</li> 90 * <li><strong>FULL:</strong> Preserves the complete original graph structure including all conflicts and cycles. 91 * All nodes remain with their children, but conflict information is recorded for analysis. 92 * Graphs in this verbosity level cannot be resolved, their purpose is for analysis only.</li> 93 * </ul> 94 * The verbosity level is controlled by the {@link #CONFIG_PROP_VERBOSE} configuration property. 95 * <p> 96 * <strong>Conflict Metadata:</strong> In STANDARD and FULL modes, the keys {@link #NODE_DATA_ORIGINAL_SCOPE} 97 * and {@link #NODE_DATA_ORIGINAL_OPTIONALITY} are used to store the original scope and optionality of each node. 98 * Obviously, dependency trees with verbosity STANDARD or FULL are not suitable for artifact resolution unless 99 * a filter is employed to exclude the duplicate dependencies. 100 * <p> 101 * <strong>Conflict ID Processing Pipeline:</strong> 102 * <ol> 103 * <li><strong>{@link ConflictMarker}:</strong> Assigns conflict IDs based on GACE (groupId:artifactId:classifier:extension) 104 * coordinates, grouping artifacts that differ only in version (partitions the graph, assigning same conflict IDs 105 * to nodes belonging to same conflict group).</li> 106 * <li><strong>{@link ConflictIdSorter}:</strong> Creates topological ordering of conflict IDs and detects cycles</li> 107 * <li><strong>ConflictResolver implementation:</strong> Uses the sorted conflict IDs to resolve conflicts in dependency order</li> 108 * </ol> 109 * This transformer will query the keys {@link TransformationContextKeys#CONFLICT_IDS}, 110 * {@link TransformationContextKeys#SORTED_CONFLICT_IDS}, {@link TransformationContextKeys#CYCLIC_CONFLICT_IDS} for 111 * existing information about conflict ids. In absence of this information, it will automatically invoke the 112 * {@link ConflictIdSorter} to calculate it. 113 * 114 * @see PathConflictResolver 115 * @see ClassicConflictResolver 116 */ 117 public class ConflictResolver implements DependencyGraphTransformer { 118 119 /** 120 * The key in the repository session's {@link org.eclipse.aether.RepositorySystemSession#getConfigProperties() 121 * configuration properties} used to store a {@link Boolean} flag controlling the transformer's verbose mode. 122 * Accepted values are Boolean types, String type (where "true" would be interpreted as {@code true}) 123 * or Verbosity enum instances. 124 * 125 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 126 * @configurationType {@link java.lang.Object} 127 * @configurationDefaultValue "NONE" 128 */ 129 public static final String CONFIG_PROP_VERBOSE = ConfigurationProperties.PREFIX_AETHER + "conflictResolver.verbose"; 130 131 /** 132 * The name of the conflict resolver implementation to use: "path" (default) or "classic" (same as Maven 3). 133 * 134 * @since 2.0.11 135 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 136 * @configurationType {@link java.lang.String} 137 * @configurationDefaultValue {@link #DEFAULT_CONFLICT_RESOLVER_IMPL} 138 */ 139 public static final String CONFIG_PROP_CONFLICT_RESOLVER_IMPL = 140 ConfigurationProperties.PREFIX_AETHER + "conflictResolver.impl"; 141 142 public static final String CLASSIC_CONFLICT_RESOLVER = "classic"; 143 public static final String PATH_CONFLICT_RESOLVER = "path"; 144 145 public static final String DEFAULT_CONFLICT_RESOLVER_IMPL = PATH_CONFLICT_RESOLVER; 146 147 /** 148 * The enum representing verbosity levels of conflict resolver. 149 * 150 * @since 1.9.8 151 */ 152 public enum Verbosity { 153 /** 154 * Verbosity level to be used in all "common" resolving use cases (ie. dependencies to build class path). The 155 * {@link ConflictResolver} in this mode will trim down the graph to the barest minimum: will not leave 156 * any conflicting node in place, hence no conflicts will be present in transformed graph either. 157 */ 158 NONE, 159 160 /** 161 * Verbosity level to be used in "analyze" resolving use cases (ie. dependency convergence calculations). The 162 * {@link ConflictResolver} in this mode will remove any redundant collected nodes, in turn it will leave one 163 * with recorded conflicting information. This mode corresponds to "classic verbose" mode when 164 * {@link #CONFIG_PROP_VERBOSE} was set to {@code true}. Obviously, the resulting dependency tree is not 165 * suitable for artifact resolution unless a filter is employed to exclude the duplicate dependencies. 166 */ 167 STANDARD, 168 169 /** 170 * Verbosity level to be used in "analyze" resolving use cases (ie. dependency convergence calculations). The 171 * {@link ConflictResolver} in this mode will not remove any collected node, in turn it will record on all 172 * eliminated nodes the conflicting information. Obviously, the resulting dependency tree is not suitable 173 * for artifact resolution unless a filter is employed to exclude the duplicate dependencies. 174 */ 175 FULL 176 } 177 178 /** 179 * Helper method that uses {@link RepositorySystemSession} and {@link #CONFIG_PROP_VERBOSE} key to figure out 180 * current {@link Verbosity}: if {@link Boolean} or {@code String} found, returns {@link Verbosity#STANDARD} 181 * or {@link Verbosity#NONE}, depending on value (string is parsed with {@link Boolean#parseBoolean(String)} 182 * for {@code true} or {@code false} correspondingly. This is to retain "existing" behavior, where the config 183 * key accepted only these values. 184 * Since 1.9.8 release, this key may contain {@link Verbosity} enum instance as well, in which case that instance 185 * is returned. 186 * This method never returns {@code null}. 187 */ 188 public static Verbosity getVerbosity(RepositorySystemSession session) { 189 final Object verbosityValue = session.getConfigProperties().get(CONFIG_PROP_VERBOSE); 190 if (verbosityValue instanceof Boolean) { 191 return (Boolean) verbosityValue ? Verbosity.STANDARD : Verbosity.NONE; 192 } else if (verbosityValue instanceof String) { 193 return Boolean.parseBoolean(verbosityValue.toString()) ? Verbosity.STANDARD : Verbosity.NONE; 194 } else if (verbosityValue instanceof Verbosity) { 195 return (Verbosity) verbosityValue; 196 } else if (verbosityValue != null) { 197 throw new IllegalArgumentException("Unsupported Verbosity configuration: " + verbosityValue); 198 } 199 return Verbosity.NONE; 200 } 201 202 /** 203 * The key in the dependency node's {@link DependencyNode#getData() custom data} under which a reference to the 204 * {@link DependencyNode} which has won the conflict is stored. 205 */ 206 public static final String NODE_DATA_WINNER = "conflict.winner"; 207 208 /** 209 * The key in the dependency node's {@link DependencyNode#getData() custom data} under which the scope of the 210 * dependency before scope derivation and conflict resolution is stored. 211 */ 212 public static final String NODE_DATA_ORIGINAL_SCOPE = "conflict.originalScope"; 213 214 /** 215 * The key in the dependency node's {@link DependencyNode#getData() custom data} under which the optional flag of 216 * the dependency before derivation and conflict resolution is stored. 217 */ 218 public static final String NODE_DATA_ORIGINAL_OPTIONALITY = "conflict.originalOptionality"; 219 220 private final ConflictResolver.VersionSelector versionSelector; 221 private final ConflictResolver.ScopeSelector scopeSelector; 222 private final ConflictResolver.ScopeDeriver scopeDeriver; 223 private final ConflictResolver.OptionalitySelector optionalitySelector; 224 225 /** 226 * No arg ctor for subclasses and default cases. 227 */ 228 protected ConflictResolver() { 229 this.versionSelector = null; 230 this.scopeSelector = null; 231 this.scopeDeriver = null; 232 this.optionalitySelector = null; 233 } 234 235 /** 236 * Creates a new conflict resolver instance with the specified hooks that delegates to configured conflict resolver 237 * dynamically. 238 * 239 * @param versionSelector The version selector to use, must not be {@code null}. 240 * @param scopeSelector The scope selector to use, must not be {@code null}. 241 * @param optionalitySelector The optionality selector ot use, must not be {@code null}. 242 * @param scopeDeriver The scope deriver to use, must not be {@code null}. 243 */ 244 public ConflictResolver( 245 VersionSelector versionSelector, 246 ScopeSelector scopeSelector, 247 OptionalitySelector optionalitySelector, 248 ScopeDeriver scopeDeriver) { 249 this.versionSelector = requireNonNull(versionSelector, "version selector cannot be null"); 250 this.scopeSelector = requireNonNull(scopeSelector, "scope selector cannot be null"); 251 this.optionalitySelector = requireNonNull(optionalitySelector, "optionality selector cannot be null"); 252 this.scopeDeriver = requireNonNull(scopeDeriver, "scope deriver cannot be null"); 253 } 254 255 @Override 256 public DependencyNode transformGraph(DependencyNode node, DependencyGraphTransformationContext context) 257 throws RepositoryException { 258 String cf = ConfigUtils.getString( 259 context.getSession(), DEFAULT_CONFLICT_RESOLVER_IMPL, CONFIG_PROP_CONFLICT_RESOLVER_IMPL); 260 ConflictResolver delegate; 261 if (PATH_CONFLICT_RESOLVER.equals(cf)) { 262 delegate = new PathConflictResolver(versionSelector, scopeSelector, optionalitySelector, scopeDeriver); 263 } else if (CLASSIC_CONFLICT_RESOLVER.equals(cf)) { 264 delegate = new ClassicConflictResolver(versionSelector, scopeSelector, optionalitySelector, scopeDeriver); 265 } else { 266 throw new IllegalArgumentException("Unknown conflict resolver: " + cf + "; known are " 267 + Arrays.asList(PATH_CONFLICT_RESOLVER, CLASSIC_CONFLICT_RESOLVER)); 268 } 269 return delegate.transformGraph(node, context); 270 } 271 272 /** 273 * A context used to hold information that is relevant for deriving the scope of a child dependency. 274 * 275 * @see ScopeDeriver 276 * @noinstantiate This class is not intended to be instantiated by clients in production code, the constructor may 277 * change without notice and only exists to enable unit testing. 278 */ 279 public abstract static class ScopeContext { 280 /** 281 * Gets the scope of the parent dependency. This is usually the scope that was derived by earlier invocations of 282 * the scope deriver. 283 * 284 * @return The scope of the parent dependency, never {@code null}. 285 */ 286 public abstract String getParentScope(); 287 288 /** 289 * Gets the original scope of the child dependency. This is the scope that was declared in the artifact 290 * descriptor of the parent dependency. 291 * 292 * @return The original scope of the child dependency, never {@code null}. 293 */ 294 public abstract String getChildScope(); 295 296 /** 297 * Gets the derived scope of the child dependency. This is initially equal to {@link #getChildScope()} until the 298 * scope deriver makes changes. 299 * 300 * @return The derived scope of the child dependency, never {@code null}. 301 */ 302 public abstract String getDerivedScope(); 303 304 /** 305 * Sets the derived scope of the child dependency. 306 * 307 * @param derivedScope The derived scope of the dependency, may be {@code null}. 308 */ 309 public abstract void setDerivedScope(String derivedScope); 310 } 311 312 /** 313 * A conflicting dependency. 314 * 315 * @noinstantiate This class is not intended to be instantiated by clients in production code, the constructor may 316 * change without notice and only exists to enable unit testing. 317 */ 318 public abstract static class ConflictItem { 319 /** 320 * Determines whether the specified conflict item is a sibling of this item. 321 * 322 * @param item The other conflict item, must not be {@code null}. 323 * @return {@code true} if the given item has the same parent as this item, {@code false} otherwise. 324 */ 325 public abstract boolean isSibling(ConflictItem item); 326 327 /** 328 * Gets the dependency node involved in the conflict. 329 * 330 * @return The involved dependency node, never {@code null}. 331 */ 332 public abstract DependencyNode getNode(); 333 334 /** 335 * Gets the dependency involved in the conflict, short for {@code getNode.getDependency()}. 336 * 337 * @return The involved dependency, never {@code null}. 338 */ 339 public abstract Dependency getDependency(); 340 341 /** 342 * Gets the zero-based depth at which the conflicting node occurs in the graph. As such, the depth denotes the 343 * number of parent nodes. If actually multiple paths lead to the node, the return value denotes the smallest 344 * possible depth. 345 * 346 * @return The zero-based depth of the node in the graph. 347 */ 348 public abstract int getDepth(); 349 350 /** 351 * Gets the derived scopes of the dependency. In general, the same dependency node could be reached via 352 * different paths and each path might result in a different derived scope. 353 * 354 * @see ScopeDeriver 355 * @return The (read-only) set of derived scopes of the dependency, never {@code null}. 356 */ 357 public abstract Collection<String> getScopes(); 358 359 /** 360 * Bit flag indicating whether one or more paths consider the dependency non-optional. 361 */ 362 public static final int OPTIONAL_FALSE = 0x01; 363 364 /** 365 * Bit flag indicating whether one or more paths consider the dependency optional. 366 */ 367 public static final int OPTIONAL_TRUE = 0x02; 368 369 /** 370 * Gets the derived optionalities of the dependency. In general, the same dependency node could be reached via 371 * different paths and each path might result in a different derived optionality. 372 * 373 * @return A bit field consisting of {@link ConflictResolver.ConflictItem#OPTIONAL_FALSE} and/or 374 * {@link ConflictResolver.ConflictItem#OPTIONAL_TRUE} indicating the derived optionalities the 375 * dependency was encountered with. 376 */ 377 public abstract int getOptionalities(); 378 } 379 380 /** 381 * A context used to hold information that is relevant for resolving version and scope conflicts. 382 * 383 * @see VersionSelector 384 * @see ScopeSelector 385 * @noinstantiate This class is not intended to be instantiated by clients in production code, the constructor may 386 * change without notice and only exists to enable unit testing. 387 */ 388 public abstract static class ConflictContext { 389 /** 390 * Gets the root node of the dependency graph being transformed. 391 * 392 * @return The root node of the dependency graph, never {@code null}. 393 */ 394 public abstract DependencyNode getRoot(); 395 396 /** 397 * Determines whether the specified dependency node belongs to this conflict context. 398 * 399 * @param node The dependency node to check, must not be {@code null}. 400 * @return {@code true} if the given node belongs to this conflict context, {@code false} otherwise. 401 */ 402 public abstract boolean isIncluded(DependencyNode node); 403 404 /** 405 * Gets the collection of conflict items in this context. 406 * 407 * @return The (read-only) collection of conflict items in this context, never {@code null}. 408 */ 409 public abstract Collection<ConflictItem> getItems(); 410 411 /** 412 * Gets the conflict item which has been selected as the winner among the conflicting dependencies. 413 * 414 * @return The winning conflict item or {@code null} if not set yet. 415 */ 416 public abstract ConflictItem getWinner(); 417 418 /** 419 * Sets the conflict item which has been selected as the winner among the conflicting dependencies. 420 * 421 * @param winner The winning conflict item, may be {@code null}. 422 */ 423 public abstract void setWinner(ConflictItem winner); 424 425 /** 426 * Gets the effective scope of the winning dependency. 427 * 428 * @return The effective scope of the winning dependency or {@code null} if none. 429 */ 430 public abstract String getScope(); 431 432 /** 433 * Sets the effective scope of the winning dependency. 434 * 435 * @param scope The effective scope, may be {@code null}. 436 */ 437 public abstract void setScope(String scope); 438 439 /** 440 * Gets the effective optional flag of the winning dependency. 441 * 442 * @return The effective optional flag or {@code null} if none. 443 */ 444 public abstract Boolean getOptional(); 445 446 /** 447 * Sets the effective optional flag of the winning dependency. 448 * 449 * @param optional The effective optional flag, may be {@code null}. 450 */ 451 public abstract void setOptional(Boolean optional); 452 } 453 454 /** 455 * An extension point of {@link ConflictResolver} that determines the winner among conflicting dependencies. The 456 * winning node (and its children) will be retained in the dependency graph, the other nodes will get removed. The 457 * version selector does not need to deal with potential scope conflicts, these will be addressed afterwards by the 458 * {@link ScopeSelector}. 459 * <p> 460 * <strong>Note:</strong> Implementations must be stateless. 461 */ 462 public abstract static class VersionSelector { 463 464 /** 465 * Retrieves the version selector for use during the specified graph transformation. The conflict resolver calls 466 * this method once per 467 * {@link ConflictResolver#transformGraph(DependencyNode, DependencyGraphTransformationContext)} invocation to 468 * allow implementations to prepare any auxiliary data that is needed for their operation. Given that 469 * implementations must be stateless, a new instance needs to be returned to hold such auxiliary data. The 470 * default implementation simply returns the current instance which is appropriate for implementations which do 471 * not require auxiliary data. 472 * 473 * @param root The root node of the (possibly cyclic!) graph to transform, must not be {@code null}. 474 * @param context The graph transformation context, must not be {@code null}. 475 * @return The scope deriver to use for the given graph transformation, never {@code null}. 476 * @throws RepositoryException If the instance could not be retrieved. 477 */ 478 public VersionSelector getInstance(DependencyNode root, DependencyGraphTransformationContext context) 479 throws RepositoryException { 480 return this; 481 } 482 483 /** 484 * Determines the winning node among conflicting dependencies. Implementations will usually iterate 485 * {@link ConflictContext#getItems()}, inspect {@link ConflictItem#getNode()} and eventually call 486 * {@link ConflictContext#setWinner(ConflictResolver.ConflictItem)} to deliver the winner. Failure to select a 487 * winner will automatically fail the entire conflict resolution. 488 * 489 * @param context The conflict context, must not be {@code null}. 490 * @throws RepositoryException If the version selection failed. 491 */ 492 public abstract void selectVersion(ConflictContext context) throws RepositoryException; 493 } 494 495 /** 496 * An extension point of {@link ConflictResolver} that determines the effective scope of a dependency from a 497 * potentially conflicting set of {@link ScopeDeriver derived scopes}. The scope selector gets invoked after the 498 * {@link VersionSelector} has picked the winning node. 499 * <p> 500 * <strong>Note:</strong> Implementations must be stateless. 501 */ 502 public abstract static class ScopeSelector { 503 504 /** 505 * Retrieves the scope selector for use during the specified graph transformation. The conflict resolver calls 506 * this method once per 507 * {@link ConflictResolver#transformGraph(DependencyNode, DependencyGraphTransformationContext)} invocation to 508 * allow implementations to prepare any auxiliary data that is needed for their operation. Given that 509 * implementations must be stateless, a new instance needs to be returned to hold such auxiliary data. The 510 * default implementation simply returns the current instance which is appropriate for implementations which do 511 * not require auxiliary data. 512 * 513 * @param root The root node of the (possibly cyclic!) graph to transform, must not be {@code null}. 514 * @param context The graph transformation context, must not be {@code null}. 515 * @return The scope selector to use for the given graph transformation, never {@code null}. 516 * @throws RepositoryException If the instance could not be retrieved. 517 */ 518 public ScopeSelector getInstance(DependencyNode root, DependencyGraphTransformationContext context) 519 throws RepositoryException { 520 return this; 521 } 522 523 /** 524 * Determines the effective scope of the dependency given by {@link ConflictContext#getWinner()}. 525 * Implementations will usually iterate {@link ConflictContext#getItems()}, inspect 526 * {@link ConflictItem#getScopes()} and eventually call {@link ConflictContext#setScope(String)} to deliver the 527 * effective scope. 528 * 529 * @param context The conflict context, must not be {@code null}. 530 * @throws RepositoryException If the scope selection failed. 531 */ 532 public abstract void selectScope(ConflictContext context) throws RepositoryException; 533 } 534 535 /** 536 * An extension point of {@link ConflictResolver} that determines the scope of a dependency in relation to the scope 537 * of its parent. 538 * <p> 539 * <strong>Note:</strong> Implementations must be stateless. 540 */ 541 public abstract static class ScopeDeriver { 542 543 /** 544 * Retrieves the scope deriver for use during the specified graph transformation. The conflict resolver calls 545 * this method once per 546 * {@link ConflictResolver#transformGraph(DependencyNode, DependencyGraphTransformationContext)} invocation to 547 * allow implementations to prepare any auxiliary data that is needed for their operation. Given that 548 * implementations must be stateless, a new instance needs to be returned to hold such auxiliary data. The 549 * default implementation simply returns the current instance which is appropriate for implementations which do 550 * not require auxiliary data. 551 * 552 * @param root The root node of the (possibly cyclic!) graph to transform, must not be {@code null}. 553 * @param context The graph transformation context, must not be {@code null}. 554 * @return The scope deriver to use for the given graph transformation, never {@code null}. 555 * @throws RepositoryException If the instance could not be retrieved. 556 */ 557 public ScopeDeriver getInstance(DependencyNode root, DependencyGraphTransformationContext context) 558 throws RepositoryException { 559 return this; 560 } 561 562 /** 563 * Determines the scope of a dependency in relation to the scope of its parent. Implementors need to call 564 * {@link ScopeContext#setDerivedScope(String)} to deliver the result of their calculation. If said method is 565 * not invoked, the conflict resolver will assume the scope of the child dependency remains unchanged. 566 * 567 * @param context The scope context, must not be {@code null}. 568 * @throws RepositoryException If the scope deriviation failed. 569 */ 570 public abstract void deriveScope(ScopeContext context) throws RepositoryException; 571 } 572 573 /** 574 * An extension point of {@link ConflictResolver} that determines the effective optional flag of a dependency from a 575 * potentially conflicting set of derived optionalities. The optionality selector gets invoked after the 576 * {@link VersionSelector} has picked the winning node. 577 * <p> 578 * <strong>Note:</strong> Implementations must be stateless. 579 */ 580 public abstract static class OptionalitySelector { 581 582 /** 583 * Retrieves the optionality selector for use during the specified graph transformation. The conflict resolver 584 * calls this method once per 585 * {@link ConflictResolver#transformGraph(DependencyNode, DependencyGraphTransformationContext)} invocation to 586 * allow implementations to prepare any auxiliary data that is needed for their operation. Given that 587 * implementations must be stateless, a new instance needs to be returned to hold such auxiliary data. The 588 * default implementation simply returns the current instance which is appropriate for implementations which do 589 * not require auxiliary data. 590 * 591 * @param root The root node of the (possibly cyclic!) graph to transform, must not be {@code null}. 592 * @param context The graph transformation context, must not be {@code null}. 593 * @return The optionality selector to use for the given graph transformation, never {@code null}. 594 * @throws RepositoryException If the instance could not be retrieved. 595 */ 596 public OptionalitySelector getInstance(DependencyNode root, DependencyGraphTransformationContext context) 597 throws RepositoryException { 598 return this; 599 } 600 601 /** 602 * Determines the effective optional flag of the dependency given by {@link ConflictContext#getWinner()}. 603 * Implementations will usually iterate {@link ConflictContext#getItems()}, inspect 604 * {@link ConflictItem#getOptionalities()} and eventually call {@link ConflictContext#setOptional(Boolean)} to 605 * deliver the effective optional flag. 606 * 607 * @param context The conflict context, must not be {@code null}. 608 * @throws RepositoryException If the optionality selection failed. 609 */ 610 public abstract void selectOptionality(ConflictContext context) throws RepositoryException; 611 } 612 }