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