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