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.Arrays; 022import java.util.Collection; 023 024import org.eclipse.aether.ConfigurationProperties; 025import org.eclipse.aether.RepositoryException; 026import org.eclipse.aether.RepositorySystemSession; 027import org.eclipse.aether.collection.DependencyGraphTransformationContext; 028import org.eclipse.aether.collection.DependencyGraphTransformer; 029import org.eclipse.aether.graph.Dependency; 030import org.eclipse.aether.graph.DependencyNode; 031import org.eclipse.aether.util.ConfigUtils; 032 033import static java.util.Objects.requireNonNull; 034 035/** 036 * Abstract base class for dependency graph transformers that resolve version and scope conflicts among dependencies. 037 * For a given set of conflicting nodes, one node will be chosen as the winner. How losing nodes are handled depends 038 * on the configured verbosity level: they may be removed entirely, have their children removed, or be left in place 039 * with conflict information. The exact rules by which a winning node and its effective scope are determined are 040 * controlled by user-supplied implementations of {@link VersionSelector}, {@link ScopeSelector}, 041 * {@link OptionalitySelector} and {@link ScopeDeriver}. 042 * <p> 043 * <strong>Available Implementations:</strong> 044 * <ul> 045 * <li><strong>{@link ClassicConflictResolver}</strong> - Original implementation (O(N²) worst-case)</li> 046 * <li><strong>{@link PathConflictResolver}</strong> - Not yet recommended for production; high-performance implementation with O(N) complexity</li> 047 * </ul> 048 * <p> 049 * <strong>Implementation Selection Guide:</strong> 050 * <ul> 051 * <li><strong>All projects:</strong> Use {@link ClassicConflictResolver} for optimal correctness and Maven 3.x behavior</li> 052 * <li><strong>Experimenters:</strong> Use {@link PathConflictResolver} but no guarantees it will work</li> 053 * </ul> 054 * <p> 055 * <strong>Usage Example:</strong> 056 * <pre>{@code 057 * // Classic resolver 058 * DependencyGraphTransformer legacyTransformer = new ChainedDependencyGraphTransformer( 059 * new ClassicConflictResolver( 060 * new NearestVersionSelector(), 061 * new JavaScopeSelector(), 062 * new SimpleOptionalitySelector(), 063 * new JavaScopeDeriver()), 064 * // other transformers... 065 * ); 066 * }</pre> 067 * <p> 068 * <strong>Verbosity Levels and Conflict Handling:</strong> 069 * <ul> 070 * <li><strong>NONE (default):</strong> Creates a clean dependency tree without duplicate artifacts. 071 * Losing nodes are completely removed from the graph, so are cycles as well.</li> 072 * <li><strong>STANDARD:</strong> Retains losing nodes for analysis but removes their children to prevent 073 * duplicate dependencies. Special handling for version ranges: redundant nodes may still be removed 074 * if multiple versions of the same artifact exist. Losing nodes link back to the winner via 075 * {@link #NODE_DATA_WINNER} and preserve original scope/optionality information. This mode removes cycles only, 076 * while conflict nodes/duplicates are left in place. Graphs in this verbosity level cannot be resolved, 077 * their purpose is for analysis only.</li> 078 * <li><strong>FULL:</strong> Preserves the complete original graph structure including all conflicts and cycles. 079 * All nodes remain with their children, but conflict information is recorded for analysis. 080 * Graphs in this verbosity level cannot be resolved, their purpose is for analysis only.</li> 081 * </ul> 082 * The verbosity level is controlled by the {@link #CONFIG_PROP_VERBOSE} configuration property. 083 * <p> 084 * <strong>Conflict Metadata:</strong> In STANDARD and FULL modes, the keys {@link #NODE_DATA_ORIGINAL_SCOPE} 085 * and {@link #NODE_DATA_ORIGINAL_OPTIONALITY} are used to store the original scope and optionality of each node. 086 * Obviously, dependency trees with verbosity STANDARD or FULL are not suitable for artifact resolution unless 087 * a filter is employed to exclude the duplicate dependencies. 088 * <p> 089 * <strong>Conflict ID Processing Pipeline:</strong> 090 * <ol> 091 * <li><strong>{@link ConflictMarker}:</strong> Assigns conflict IDs based on GACE (groupId:artifactId:classifier:extension) 092 * coordinates, grouping artifacts that differ only in version (partitions the graph, assigning same conflict IDs 093 * to nodes belonging to same conflict group).</li> 094 * <li><strong>{@link ConflictIdSorter}:</strong> Creates topological ordering of conflict IDs and detects cycles</li> 095 * <li><strong>ConflictResolver implementation:</strong> Uses the sorted conflict IDs to resolve conflicts in dependency order</li> 096 * </ol> 097 * This transformer will query the keys {@link TransformationContextKeys#CONFLICT_IDS}, 098 * {@link TransformationContextKeys#SORTED_CONFLICT_IDS}, {@link TransformationContextKeys#CYCLIC_CONFLICT_IDS} for 099 * existing information about conflict ids. In absence of this information, it will automatically invoke the 100 * {@link ConflictIdSorter} to calculate it. 101 * 102 * @see ClassicConflictResolver 103 * @see PathConflictResolver 104 */ 105public class ConflictResolver implements DependencyGraphTransformer { 106 107 /** 108 * The key in the repository session's {@link org.eclipse.aether.RepositorySystemSession#getConfigProperties() 109 * configuration properties} used to store a {@link Boolean} flag controlling the transformer's verbose mode. 110 * Accepted values are Boolean types, String type (where "true" would be interpreted as {@code true}) 111 * or Verbosity enum instances. 112 * 113 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 114 * @configurationType {@link java.lang.Object} 115 * @configurationDefaultValue "NONE" 116 */ 117 public static final String CONFIG_PROP_VERBOSE = ConfigurationProperties.PREFIX_AETHER + "conflictResolver.verbose"; 118 119 /** 120 * The name of the conflict resolver implementation to use: "auto" (default), "path", or "classic" (same as Maven 3). 121 * <p> 122 * When set to "auto", the resolver will currently just use "classic". The idea here, is that this value will 123 * always select the best (most robust, most performant) one, which currently is "classic". 124 * 125 * @since 2.0.11 126 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 127 * @configurationType {@link java.lang.String} 128 * @configurationDefaultValue {@link #DEFAULT_CONFLICT_RESOLVER_IMPL} 129 */ 130 public static final String CONFIG_PROP_CONFLICT_RESOLVER_IMPL = 131 ConfigurationProperties.PREFIX_AETHER + "conflictResolver.impl"; 132 133 public static final String CLASSIC_CONFLICT_RESOLVER = "classic"; 134 public static final String PATH_CONFLICT_RESOLVER = "path"; 135 public static final String AUTO_CONFLICT_RESOLVER = "auto"; 136 137 public static final String DEFAULT_CONFLICT_RESOLVER_IMPL = AUTO_CONFLICT_RESOLVER; 138 139 /** 140 * The enum representing verbosity levels of conflict resolver. 141 * 142 * @since 1.9.8 143 */ 144 public enum Verbosity { 145 /** 146 * Verbosity level to be used in all "common" resolving use cases (ie dependencies to build class path). The 147 * {@link ConflictResolver} in this mode will trim down the graph to the barest minimum: will not leave 148 * any conflicting node in place, hence no conflicts will be present in transformed graph either. 149 */ 150 NONE, 151 152 /** 153 * Verbosity level to be used in "analyze" resolving use cases (ie dependency convergence calculations). The 154 * {@link ConflictResolver} in this mode will remove any redundant collected nodes and cycles, in turn it will 155 * leave one with recorded conflicting information. This mode corresponds to "classic verbose" mode when 156 * {@link #CONFIG_PROP_VERBOSE} was set to {@code true}. Obviously, the resulting dependency tree is not 157 * suitable for artifact resolution unless a filter is employed to exclude the duplicate dependencies. 158 */ 159 STANDARD, 160 161 /** 162 * Verbosity level to be used in "analyze" resolving use cases (ie dependency convergence calculations). The 163 * {@link ConflictResolver} in this mode will not remove any collected node nor cycle, in turn it will record 164 * on all eliminated nodes the conflicting information. Obviously, the resulting dependency tree is not suitable 165 * for artifact resolution unless a filter is employed to exclude the duplicate dependencies and possible cycles. 166 * Because of left in cycles, user of this verbosity level should ensure that graph post-processing does not 167 * contain elements that would explode on them. In other words, session should be modified with proper 168 * graph transformers. 169 * 170 * @see RepositorySystemSession#getDependencyGraphTransformer() 171 */ 172 FULL 173 } 174 175 /** 176 * Helper method that uses {@link RepositorySystemSession} and {@link #CONFIG_PROP_VERBOSE} key to figure out 177 * current {@link Verbosity}: if {@link Boolean} or {@code String} found, returns {@link Verbosity#STANDARD} 178 * or {@link Verbosity#NONE}, depending on value (string is parsed with {@link Boolean#parseBoolean(String)} 179 * for {@code true} or {@code false} correspondingly. This is to retain "existing" behavior, where the config 180 * key accepted only these values. 181 * Since 1.9.8 release, this key may contain {@link Verbosity} enum instance as well, in which case that instance 182 * is returned. 183 * This method never returns {@code null}. 184 */ 185 public static Verbosity getVerbosity(RepositorySystemSession session) { 186 final Object verbosityValue = session.getConfigProperties().get(CONFIG_PROP_VERBOSE); 187 if (verbosityValue instanceof Boolean) { 188 return (Boolean) verbosityValue ? Verbosity.STANDARD : Verbosity.NONE; 189 } else if (verbosityValue instanceof String) { 190 return Boolean.parseBoolean(verbosityValue.toString()) ? Verbosity.STANDARD : Verbosity.NONE; 191 } else if (verbosityValue instanceof Verbosity) { 192 return (Verbosity) verbosityValue; 193 } else if (verbosityValue != null) { 194 throw new IllegalArgumentException("Unsupported Verbosity configuration: " + verbosityValue); 195 } 196 return Verbosity.NONE; 197 } 198 199 /** 200 * The key in the dependency node's {@link DependencyNode#getData() custom data} under which a reference to the 201 * {@link DependencyNode} which has won the conflict is stored. 202 */ 203 public static final String NODE_DATA_WINNER = "conflict.winner"; 204 205 /** 206 * The key in the dependency node's {@link DependencyNode#getData() custom data} under which the scope of the 207 * dependency before scope derivation and conflict resolution is stored. 208 */ 209 public static final String NODE_DATA_ORIGINAL_SCOPE = "conflict.originalScope"; 210 211 /** 212 * The key in the dependency node's {@link DependencyNode#getData() custom data} under which the optional flag of 213 * the dependency before derivation and conflict resolution is stored. 214 */ 215 public static final String NODE_DATA_ORIGINAL_OPTIONALITY = "conflict.originalOptionality"; 216 217 private final ConflictResolver.VersionSelector versionSelector; 218 private final ConflictResolver.ScopeSelector scopeSelector; 219 private final ConflictResolver.ScopeDeriver scopeDeriver; 220 private final ConflictResolver.OptionalitySelector optionalitySelector; 221 222 /** 223 * No arg ctor for subclasses and default cases. 224 */ 225 protected ConflictResolver() { 226 this.versionSelector = null; 227 this.scopeSelector = null; 228 this.scopeDeriver = null; 229 this.optionalitySelector = null; 230 } 231 232 /** 233 * Creates a new conflict resolver instance with the specified hooks that delegates to configured conflict resolver 234 * dynamically. 235 * 236 * @param versionSelector the version selector to use, must not be {@code null} 237 * @param scopeSelector the scope selector to use, must not be {@code null} 238 * @param optionalitySelector the optionality selector ot use, must not be {@code null} 239 * @param scopeDeriver the scope deriver to use, must not be {@code null} 240 */ 241 public ConflictResolver( 242 VersionSelector versionSelector, 243 ScopeSelector scopeSelector, 244 OptionalitySelector optionalitySelector, 245 ScopeDeriver scopeDeriver) { 246 this.versionSelector = requireNonNull(versionSelector, "version selector cannot be null"); 247 this.scopeSelector = requireNonNull(scopeSelector, "scope selector cannot be null"); 248 this.optionalitySelector = requireNonNull(optionalitySelector, "optionality selector cannot be null"); 249 this.scopeDeriver = requireNonNull(scopeDeriver, "scope deriver cannot be null"); 250 } 251 252 @Override 253 public DependencyNode transformGraph(DependencyNode node, DependencyGraphTransformationContext context) 254 throws RepositoryException { 255 String cf = ConfigUtils.getString( 256 context.getSession(), DEFAULT_CONFLICT_RESOLVER_IMPL, CONFIG_PROP_CONFLICT_RESOLVER_IMPL); 257 ConflictResolver delegate; 258 if (AUTO_CONFLICT_RESOLVER.equals(cf) || CLASSIC_CONFLICT_RESOLVER.equals(cf)) { 259 delegate = new ClassicConflictResolver(versionSelector, scopeSelector, optionalitySelector, scopeDeriver); 260 } else if (PATH_CONFLICT_RESOLVER.equals(cf)) { 261 delegate = new PathConflictResolver(versionSelector, scopeSelector, optionalitySelector, scopeDeriver); 262 } else { 263 throw new IllegalArgumentException("Unknown conflict resolver: " + cf + "; known are " 264 + Arrays.asList(AUTO_CONFLICT_RESOLVER, PATH_CONFLICT_RESOLVER, CLASSIC_CONFLICT_RESOLVER)); 265 } 266 return delegate.transformGraph(node, context); 267 } 268 269 /** 270 * A context used to hold information that is relevant for deriving the scope of a child dependency. 271 * 272 * @see ScopeDeriver 273 * @noinstantiate This class is not intended to be instantiated by clients in production code, the constructor may 274 * change without notice and only exists to enable unit testing 275 */ 276 public abstract static class ScopeContext { 277 /** 278 * Gets the scope of the parent dependency. This is usually the scope that was derived by earlier invocations of 279 * the scope deriver. 280 * 281 * @return the scope of the parent dependency, never {@code null} 282 */ 283 public abstract String getParentScope(); 284 285 /** 286 * Gets the original scope of the child dependency. This is the scope that was declared in the artifact 287 * descriptor of the parent dependency. 288 * 289 * @return the original scope of the child dependency, never {@code null} 290 */ 291 public abstract String getChildScope(); 292 293 /** 294 * Gets the derived scope of the child dependency. This is initially equal to {@link #getChildScope()} until the 295 * scope deriver makes changes. 296 * 297 * @return the derived scope of the child dependency, never {@code null} 298 */ 299 public abstract String getDerivedScope(); 300 301 /** 302 * Sets the derived scope of the child dependency. 303 * 304 * @param derivedScope the derived scope of the dependency, may be {@code null} 305 */ 306 public abstract void setDerivedScope(String derivedScope); 307 } 308 309 /** 310 * A conflicting dependency. 311 * 312 * @noinstantiate This class is not intended to be instantiated by clients in production code, the constructor may 313 * change without notice and only exists to enable unit testing 314 */ 315 public abstract static class ConflictItem { 316 /** 317 * Determines whether the specified conflict item is a sibling of this item. 318 * 319 * @param item the other conflict item, must not be {@code null} 320 * @return {@code true} if the given item has the same parent as this item, {@code false} otherwise 321 */ 322 public abstract boolean isSibling(ConflictItem item); 323 324 /** 325 * Gets the dependency node involved in the conflict. 326 * 327 * @return the involved dependency node, never {@code null} 328 */ 329 public abstract DependencyNode getNode(); 330 331 /** 332 * Gets the dependency involved in the conflict, short for {@code getNode.getDependency()}. 333 * 334 * @return the involved dependency, never {@code null} 335 */ 336 public abstract Dependency getDependency(); 337 338 /** 339 * Gets the zero-based depth at which the conflicting node occurs in the graph. As such, the depth denotes the 340 * number of parent nodes. If actually multiple paths lead to the node, the return value denotes the smallest 341 * possible depth. 342 * 343 * @return the zero-based depth of the node in the graph 344 */ 345 public abstract int getDepth(); 346 347 /** 348 * Gets the derived scopes of the dependency. In general, the same dependency node could be reached via 349 * different paths and each path might result in a different derived scope. 350 * 351 * @return the (read-only) set of derived scopes of the dependency, never {@code null} 352 * @see ScopeDeriver 353 */ 354 public abstract Collection<String> getScopes(); 355 356 /** 357 * Bit flag indicating whether one or more paths consider the dependency non-optional. 358 */ 359 public static final int OPTIONAL_FALSE = 0x01; 360 361 /** 362 * Bit flag indicating whether one or more paths consider the dependency optional. 363 */ 364 public static final int OPTIONAL_TRUE = 0x02; 365 366 /** 367 * Gets the derived optionalities of the dependency. In general, the same dependency node could be reached via 368 * different paths and each path might result in a different derived optionality. 369 * 370 * @return a bit field consisting of {@link ConflictResolver.ConflictItem#OPTIONAL_FALSE} and/or 371 * {@link ConflictResolver.ConflictItem#OPTIONAL_TRUE} indicating the derived optionalities the 372 * dependency was encountered with 373 */ 374 public abstract int getOptionalities(); 375 } 376 377 /** 378 * A context used to hold information that is relevant for resolving version and scope conflicts. 379 * 380 * @see VersionSelector 381 * @see ScopeSelector 382 * @noinstantiate This class is not intended to be instantiated by clients in production code, the constructor may 383 * change without notice and only exists to enable unit testing 384 */ 385 public abstract static class ConflictContext { 386 /** 387 * Gets the root node of the dependency graph being transformed. 388 * 389 * @return the root node of the dependency graph, never {@code null} 390 */ 391 public abstract DependencyNode getRoot(); 392 393 /** 394 * Determines whether the specified dependency node belongs to this conflict context. 395 * 396 * @param node the dependency node to check, must not be {@code null} 397 * @return {@code true} if the given node belongs to this conflict context, {@code false} otherwise 398 */ 399 public abstract boolean isIncluded(DependencyNode node); 400 401 /** 402 * Gets the collection of conflict items in this context. 403 * 404 * @return the (read-only) collection of conflict items in this context, never {@code null} 405 */ 406 public abstract Collection<ConflictItem> getItems(); 407 408 /** 409 * Gets the conflict item which has been selected as the winner among the conflicting dependencies. 410 * 411 * @return the winning conflict item or {@code null} if not set yet 412 */ 413 public abstract ConflictItem getWinner(); 414 415 /** 416 * Sets the conflict item which has been selected as the winner among the conflicting dependencies. 417 * 418 * @param winner the winning conflict item, may be {@code null} 419 */ 420 public abstract void setWinner(ConflictItem winner); 421 422 /** 423 * Gets the effective scope of the winning dependency. 424 * 425 * @return the effective scope of the winning dependency or {@code null} if none 426 */ 427 public abstract String getScope(); 428 429 /** 430 * Sets the effective scope of the winning dependency. 431 * 432 * @param scope the effective scope, may be {@code null} 433 */ 434 public abstract void setScope(String scope); 435 436 /** 437 * Gets the effective optional flag of the winning dependency. 438 * 439 * @return the effective optional flag or {@code null} if none 440 */ 441 public abstract Boolean getOptional(); 442 443 /** 444 * Sets the effective optional flag of the winning dependency. 445 * 446 * @param optional the effective optional flag, may be {@code null} 447 */ 448 public abstract void setOptional(Boolean optional); 449 } 450 451 /** 452 * An extension point of {@link ConflictResolver} that determines the winner among conflicting dependencies. The 453 * winning node (and its children) will be retained in the dependency graph, the other nodes will get removed. The 454 * version selector does not need to deal with potential scope conflicts, these will be addressed afterwards by the 455 * {@link ScopeSelector}. 456 * <p> 457 * <strong>Note:</strong> Implementations must be stateless. 458 */ 459 public abstract static class VersionSelector { 460 461 /** 462 * Retrieves the version selector for use during the specified graph transformation. The conflict resolver calls 463 * this method once per 464 * {@link ConflictResolver#transformGraph(DependencyNode, DependencyGraphTransformationContext)} invocation to 465 * allow implementations to prepare any auxiliary data that is needed for their operation. Given that 466 * implementations must be stateless, a new instance needs to be returned to hold such auxiliary data. The 467 * default implementation simply returns the current instance which is appropriate for implementations which do 468 * not require auxiliary data. 469 * 470 * @param root the root node of the (possibly cyclic!) graph to transform, must not be {@code null} 471 * @param context the graph transformation context, must not be {@code null} 472 * @return the scope deriver to use for the given graph transformation, never {@code null} 473 * @throws RepositoryException if the instance could not be retrieved 474 */ 475 public VersionSelector getInstance(DependencyNode root, DependencyGraphTransformationContext context) 476 throws RepositoryException { 477 return this; 478 } 479 480 /** 481 * Determines the winning node among conflicting dependencies. Implementations will usually iterate 482 * {@link ConflictContext#getItems()}, inspect {@link ConflictItem#getNode()} and eventually call 483 * {@link ConflictContext#setWinner(ConflictResolver.ConflictItem)} to deliver the winner. Failure to select a 484 * winner will automatically fail the entire conflict resolution. 485 * 486 * @param context the conflict context, must not be {@code null} 487 * @throws RepositoryException if the version selection failed 488 */ 489 public abstract void selectVersion(ConflictContext context) throws RepositoryException; 490 } 491 492 /** 493 * An extension point of {@link ConflictResolver} that determines the effective scope of a dependency from a 494 * potentially conflicting set of {@link ScopeDeriver derived scopes}. The scope selector gets invoked after the 495 * {@link VersionSelector} has picked the winning node. 496 * <p> 497 * <strong>Note:</strong> Implementations must be stateless. 498 */ 499 public abstract static class ScopeSelector { 500 501 /** 502 * Retrieves the scope selector for use during the specified graph transformation. The conflict resolver calls 503 * this method once per 504 * {@link ConflictResolver#transformGraph(DependencyNode, DependencyGraphTransformationContext)} invocation to 505 * allow implementations to prepare any auxiliary data that is needed for their operation. Given that 506 * implementations must be stateless, a new instance needs to be returned to hold such auxiliary data. The 507 * default implementation simply returns the current instance which is appropriate for implementations which do 508 * not require auxiliary data. 509 * 510 * @param root the root node of the (possibly cyclic!) graph to transform, must not be {@code null} 511 * @param context the graph transformation context, must not be {@code null} 512 * @return the scope selector to use for the given graph transformation, never {@code null} 513 * @throws RepositoryException if the instance could not be retrieved 514 */ 515 public ScopeSelector getInstance(DependencyNode root, DependencyGraphTransformationContext context) 516 throws RepositoryException { 517 return this; 518 } 519 520 /** 521 * Determines the effective scope of the dependency given by {@link ConflictContext#getWinner()}. 522 * Implementations will usually iterate {@link ConflictContext#getItems()}, inspect 523 * {@link ConflictItem#getScopes()} and eventually call {@link ConflictContext#setScope(String)} to deliver the 524 * effective scope. 525 * 526 * @param context the conflict context, must not be {@code null} 527 * @throws RepositoryException if the scope selection failed 528 */ 529 public abstract void selectScope(ConflictContext context) throws RepositoryException; 530 } 531 532 /** 533 * An extension point of {@link ConflictResolver} that determines the scope of a dependency in relation to the scope 534 * of its parent. 535 * <p> 536 * <strong>Note:</strong> Implementations must be stateless. 537 */ 538 public abstract static class ScopeDeriver { 539 540 /** 541 * Retrieves the scope deriver for use during the specified graph transformation. The conflict resolver calls 542 * this method once per 543 * {@link ConflictResolver#transformGraph(DependencyNode, DependencyGraphTransformationContext)} invocation to 544 * allow implementations to prepare any auxiliary data that is needed for their operation. Given that 545 * implementations must be stateless, a new instance needs to be returned to hold such auxiliary data. The 546 * default implementation simply returns the current instance which is appropriate for implementations which do 547 * not require auxiliary data. 548 * 549 * @param root the root node of the (possibly cyclic!) graph to transform, must not be {@code null} 550 * @param context the graph transformation context, must not be {@code null} 551 * @return the scope deriver to use for the given graph transformation, never {@code null} 552 * @throws RepositoryException if the instance could not be retrieved 553 */ 554 public ScopeDeriver getInstance(DependencyNode root, DependencyGraphTransformationContext context) 555 throws RepositoryException { 556 return this; 557 } 558 559 /** 560 * Determines the scope of a dependency in relation to the scope of its parent. Implementors need to call 561 * {@link ScopeContext#setDerivedScope(String)} to deliver the result of their calculation. If said method is 562 * not invoked, the conflict resolver will assume the scope of the child dependency remains unchanged. 563 * 564 * @param context the scope context, must not be {@code null} 565 * @throws RepositoryException if the scope deriviation failed 566 */ 567 public abstract void deriveScope(ScopeContext context) throws RepositoryException; 568 } 569 570 /** 571 * An extension point of {@link ConflictResolver} that determines the effective optional flag of a dependency from a 572 * potentially conflicting set of derived optionalities. The optionality selector gets invoked after the 573 * {@link VersionSelector} has picked the winning node. 574 * <p> 575 * <strong>Note:</strong> Implementations must be stateless. 576 */ 577 public abstract static class OptionalitySelector { 578 579 /** 580 * Retrieves the optionality selector for use during the specified graph transformation. The conflict resolver 581 * calls this method once per 582 * {@link ConflictResolver#transformGraph(DependencyNode, DependencyGraphTransformationContext)} invocation to 583 * allow implementations to prepare any auxiliary data that is needed for their operation. Given that 584 * implementations must be stateless, a new instance needs to be returned to hold such auxiliary data. The 585 * default implementation simply returns the current instance which is appropriate for implementations which do 586 * not require auxiliary data. 587 * 588 * @param root the root node of the (possibly cyclic!) graph to transform, must not be {@code null} 589 * @param context the graph transformation context, must not be {@code null} 590 * @return the optionality selector to use for the given graph transformation, never {@code null} 591 * @throws RepositoryException if the instance could not be retrieved 592 */ 593 public OptionalitySelector getInstance(DependencyNode root, DependencyGraphTransformationContext context) 594 throws RepositoryException { 595 return this; 596 } 597 598 /** 599 * Determines the effective optional flag of the dependency given by {@link ConflictContext#getWinner()}. 600 * Implementations will usually iterate {@link ConflictContext#getItems()}, inspect 601 * {@link ConflictItem#getOptionalities()} and eventually call {@link ConflictContext#setOptional(Boolean)} to 602 * deliver the effective optional flag. 603 * 604 * @param context the conflict context, must not be {@code null} 605 * @throws RepositoryException if the optionality selection failed 606 */ 607 public abstract void selectOptionality(ConflictContext context) throws RepositoryException; 608 } 609}