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