1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.eclipse.aether.util.graph.transformer;
20
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.IdentityHashMap;
28 import java.util.Iterator;
29 import java.util.List;
30 import java.util.ListIterator;
31 import java.util.Map;
32 import java.util.Objects;
33
34 import org.eclipse.aether.RepositoryException;
35 import org.eclipse.aether.RepositorySystemSession;
36 import org.eclipse.aether.artifact.Artifact;
37 import org.eclipse.aether.collection.DependencyGraphTransformationContext;
38 import org.eclipse.aether.collection.DependencyGraphTransformer;
39 import org.eclipse.aether.graph.DefaultDependencyNode;
40 import org.eclipse.aether.graph.Dependency;
41 import org.eclipse.aether.graph.DependencyNode;
42 import org.eclipse.aether.util.artifact.ArtifactIdUtils;
43
44 import static java.util.Objects.requireNonNull;
45
46 /**
47 * A dependency graph transformer that resolves version and scope conflicts among dependencies. For a given set of
48 * conflicting nodes, one node will be chosen as the winner and the other nodes are removed from the dependency graph.
49 * The exact rules by which a winning node and its effective scope are determined are controlled by user-supplied
50 * implementations of {@link VersionSelector}, {@link ScopeSelector}, {@link OptionalitySelector} and
51 * {@link ScopeDeriver}.
52 * <p>
53 * By default, this graph transformer will turn the dependency graph into a tree without duplicate artifacts. Using the
54 * configuration property {@link #CONFIG_PROP_VERBOSE}, a verbose mode can be enabled where the graph is still turned
55 * into a tree but all nodes participating in a conflict are retained. The nodes that were rejected during conflict
56 * resolution have no children and link back to the winner node via the {@link #NODE_DATA_WINNER} key in their custom
57 * data. Additionally, the keys {@link #NODE_DATA_ORIGINAL_SCOPE} and {@link #NODE_DATA_ORIGINAL_OPTIONALITY} are used
58 * to store the original scope and optionality of each node. Obviously, the resulting dependency tree is not suitable
59 * for artifact resolution unless a filter is employed to exclude the duplicate dependencies.
60 * <p>
61 * This transformer will query the keys {@link TransformationContextKeys#CONFLICT_IDS},
62 * {@link TransformationContextKeys#SORTED_CONFLICT_IDS}, {@link TransformationContextKeys#CYCLIC_CONFLICT_IDS} for
63 * existing information about conflict ids. In absence of this information, it will automatically invoke the
64 * {@link ConflictIdSorter} to calculate it.
65 */
66 public final class ConflictResolver implements DependencyGraphTransformer {
67
68 /**
69 * The key in the repository session's {@link org.eclipse.aether.RepositorySystemSession#getConfigProperties()
70 * configuration properties} used to store a {@link Boolean} flag controlling the transformer's verbose mode.
71 * Accepted values are {@link Boolean} type, {@link String} type (where "true" would be interpreted as {@code true}
72 * or {@link Verbosity} enum instances.
73 */
74 public static final String CONFIG_PROP_VERBOSE = "aether.conflictResolver.verbose";
75
76 /**
77 * The enum representing verbosity levels of conflict resolver.
78 *
79 * @since 1.9.8
80 */
81 public enum Verbosity {
82 /**
83 * Verbosity level to be used in all "common" resolving use cases (ie. dependencies to build class path). The
84 * {@link ConflictResolver} in this mode will trim down the graph to the barest minimum: will not leave
85 * any conflicting node in place, hence no conflicts will be present in transformed graph either.
86 */
87 NONE,
88
89 /**
90 * Verbosity level to be used in "analyze" resolving use cases (ie. dependency convergence calculations). The
91 * {@link ConflictResolver} in this mode will remove any redundant collected nodes, in turn it will leave one
92 * with recorded conflicting information. This mode corresponds to "classic verbose" mode when
93 * {@link #CONFIG_PROP_VERBOSE} was set to {@code true}. Obviously, the resulting dependency tree is not
94 * suitable for artifact resolution unless a filter is employed to exclude the duplicate dependencies.
95 */
96 STANDARD,
97
98 /**
99 * 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 }