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