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