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