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