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