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.Collection;
023import java.util.Collections;
024import java.util.HashMap;
025import java.util.HashSet;
026import java.util.List;
027import java.util.Map;
028import java.util.Objects;
029import java.util.stream.Collectors;
030
031import org.eclipse.aether.RepositoryException;
032import org.eclipse.aether.artifact.Artifact;
033import org.eclipse.aether.collection.DependencyGraphTransformationContext;
034import org.eclipse.aether.graph.DefaultDependencyNode;
035import org.eclipse.aether.graph.Dependency;
036import org.eclipse.aether.graph.DependencyNode;
037import org.eclipse.aether.util.artifact.ArtifactIdUtils;
038
039import static java.util.Objects.requireNonNull;
040
041/**
042 * A high-performance dependency graph transformer that resolves version and scope conflicts among dependencies.
043 * This is the recommended conflict resolver implementation that provides O(N) performance characteristics,
044 * significantly improving upon the O(N²) worst-case performance of {@link ClassicConflictResolver}.
045 * <p>
046 * For a given set of conflicting nodes, one node will be chosen as the winner. How losing nodes are handled
047 * depends on the configured verbosity level: they may be removed entirely, have their children removed, or
048 * be left in place with conflict information. The exact rules by which a winning node and its effective scope
049 * are determined are controlled by user-supplied implementations of {@link ConflictResolver.VersionSelector}, {@link ConflictResolver.ScopeSelector},
050 * {@link ConflictResolver.OptionalitySelector} and {@link ConflictResolver.ScopeDeriver}.
051 * <p>
052 * <strong>Performance Characteristics:</strong>
053 * <ul>
054 * <li><strong>Time Complexity:</strong> O(N) where N is the number of dependency nodes</li>
055 * <li><strong>Memory Usage:</strong> Creates a parallel tree structure for conflict-free processing</li>
056 * <li><strong>Scalability:</strong> Excellent performance on large multi-module projects</li>
057 * </ul>
058 * <p>
059 * <strong>Algorithm Overview:</strong>
060 * <ol>
061 * <li><strong>Path Tree Construction:</strong> Builds a cycle-free parallel tree structure from the input
062 *     dependency graph, where each {@code Path} represents a unique route to a dependency node</li>
063 * <li><strong>Conflict Partitioning:</strong> Groups paths by conflict ID (based on groupId:artifactId:classifier:extension coordinates)</li>
064 * <li><strong>Topological Processing:</strong> Processes conflict groups in topologically sorted order</li>
065 * <li><strong>Winner Selection:</strong> Uses provided selectors to choose winners within each conflict group</li>
066 * <li><strong>Graph Transformation:</strong> Applies changes back to the original dependency graph</li>
067 * </ol>
068 * <p>
069 * <strong>Key Differences from {@link ClassicConflictResolver}:</strong>
070 * <ul>
071 * <li><strong>Performance:</strong> O(N) vs O(N²) time complexity</li>
072 * <li><strong>Memory Strategy:</strong> Uses parallel tree structure vs in-place graph modification</li>
073 * <li><strong>Cycle Handling:</strong> Explicitly breaks cycles during tree construction</li>
074 * <li><strong>Processing Order:</strong> Level-by-level from root vs depth-first traversal</li>
075 * </ul>
076 * <p>
077 * <strong>When to Use:</strong>
078 * <ul>
079 * <li>Default choice for all new projects and Maven 4+ installations</li>
080 * <li>Large multi-module projects with many dependencies</li>
081 * <li>Performance-critical build environments</li>
082 * <li>Any scenario where {@link ClassicConflictResolver} shows performance bottlenecks</li>
083 * </ul>
084 * <p>
085 * <strong>Implementation Note:</strong> This conflict resolver builds a cycle-free "parallel" structure based on the
086 * passed-in dependency graph, and applies operations level by level starting from the root. The parallel {@code Path}
087 * tree ensures that cycles in the original graph don't affect the conflict resolution algorithm's performance.
088 *
089 * @see ClassicConflictResolver
090 * @since 2.0.11
091 */
092public final class PathConflictResolver extends ConflictResolver {
093    private final ConflictResolver.VersionSelector versionSelector;
094    private final ConflictResolver.ScopeSelector scopeSelector;
095    private final ConflictResolver.ScopeDeriver scopeDeriver;
096    private final ConflictResolver.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 PathConflictResolver(
107            ConflictResolver.VersionSelector versionSelector,
108            ConflictResolver.ScopeSelector scopeSelector,
109            ConflictResolver.OptionalitySelector optionalitySelector,
110            ConflictResolver.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    @SuppressWarnings("unchecked")
118    @Override
119    public DependencyNode transformGraph(DependencyNode node, DependencyGraphTransformationContext context)
120            throws RepositoryException {
121        requireNonNull(node, "node cannot be null");
122        requireNonNull(context, "context cannot be null");
123        List<String> sortedConflictIds = (List<String>) context.get(TransformationContextKeys.SORTED_CONFLICT_IDS);
124        if (sortedConflictIds == null) {
125            ConflictIdSorter sorter = new ConflictIdSorter();
126            sorter.transformGraph(node, context);
127
128            sortedConflictIds = (List<String>) context.get(TransformationContextKeys.SORTED_CONFLICT_IDS);
129        }
130
131        @SuppressWarnings("unchecked")
132        Map<String, Object> stats = (Map<String, Object>) context.get(TransformationContextKeys.STATS);
133        long time1 = System.nanoTime();
134
135        @SuppressWarnings("unchecked")
136        Collection<Collection<String>> conflictIdCycles =
137                (Collection<Collection<String>>) context.get(TransformationContextKeys.CYCLIC_CONFLICT_IDS);
138        if (conflictIdCycles == null) {
139            throw new RepositoryException("conflict id cycles have not been identified");
140        }
141
142        Map<DependencyNode, String> conflictIds =
143                (Map<DependencyNode, String>) context.get(TransformationContextKeys.CONFLICT_IDS);
144        if (conflictIds == null) {
145            throw new RepositoryException("conflict groups have not been identified");
146        }
147
148        Map<String, Collection<String>> cyclicPredecessors = new HashMap<>();
149        for (Collection<String> cycle : conflictIdCycles) {
150            for (String conflictId : cycle) {
151                Collection<String> predecessors = cyclicPredecessors.computeIfAbsent(conflictId, k -> new HashSet<>());
152                predecessors.addAll(cycle);
153            }
154        }
155
156        State state = new State(
157                ConflictResolver.getVerbosity(context.getSession()),
158                versionSelector.getInstance(node, context),
159                scopeSelector.getInstance(node, context),
160                scopeDeriver.getInstance(node, context),
161                optionalitySelector.getInstance(node, context),
162                sortedConflictIds,
163                conflictIds,
164                cyclicPredecessors);
165
166        state.build(node);
167
168        // loop over topographically sorted conflictIds
169        for (String conflictId : state.sortedConflictIds) {
170            // paths in given conflict group to consider
171            List<Path> paths = state.partitions.get(conflictId);
172            if (paths.isEmpty()) {
173                // this means that whole group "fall out of scope" (are all on loser branches); skip
174                continue;
175            }
176
177            // create conflict context for given conflictId
178            ConflictContext ctx = new ConflictContext(
179                    node,
180                    state.conflictIds,
181                    paths.stream().map(ConflictItem::new).collect(Collectors.toList()),
182                    conflictId);
183
184            // select winner (is done by VersionSelector)
185            state.versionSelector.selectVersion(ctx);
186            if (ctx.winner == null) {
187                throw new RepositoryException("conflict resolver did not select winner among " + ctx.items);
188            }
189            // select scope (no side effect between this and above operations)
190            state.scopeSelector.selectScope(ctx);
191            // select optionality (no side effect between this and above operations)
192            state.optionalitySelector.selectOptionality(ctx);
193
194            // we have a winner path
195            Path winnerPath = ctx.winner.path;
196
197            // mark conflictId as resolved with winner; sanity check
198            if (state.resolvedIds.containsKey(conflictId)) {
199                throw new RepositoryException("conflict resolver already have winner for conflictId=" + conflictId
200                        + ": " + state.resolvedIds);
201            }
202            state.resolvedIds.put(conflictId, winnerPath);
203
204            // loop over considered paths and apply selection results; note: node may remove itself from iterated list
205            for (Path path : new ArrayList<>(paths)) {
206                // apply selected inherited properties scope/optional to all (winner carries version; others are losers)
207                path.scope = ctx.scope;
208                path.optional = ctx.optional;
209
210                // reset children as inheritance may be affected by this node scope/optionality change
211                path.children.forEach(c -> c.pull(0));
212                // derive with new values from this to children only; observe winner flag
213                path.derive(1, path == winnerPath);
214                // push this node full level changes to DN graph
215                path.push(0);
216            }
217        }
218
219        if (stats != null) {
220            long time2 = System.nanoTime();
221            stats.put("ConflictResolver.totalTime", time2 - time1);
222            stats.put(
223                    "ConflictResolver.conflictItemCount",
224                    state.partitions.values().stream().map(List::size).reduce(0, Integer::sum));
225        }
226
227        return node;
228    }
229
230    /**
231     * State of conflict resolution processing, to make this component (held in session) re-entrant by multiple threads.
232     */
233    private static class State {
234        /**
235         * Verbosity to be applied, see {@link ConflictResolver.Verbosity}.
236         */
237        private final ConflictResolver.Verbosity verbosity;
238
239        /**
240         * The {@link ConflictResolver.VersionSelector} to use.
241         */
242        private final ConflictResolver.VersionSelector versionSelector;
243
244        /**
245         * The {@link ConflictResolver.ScopeSelector} to use.
246         */
247        private final ConflictResolver.ScopeSelector scopeSelector;
248
249        /**
250         * The {@link ConflictResolver.ScopeDeriver} to use.
251         */
252        private final ConflictResolver.ScopeDeriver scopeDeriver;
253
254        /**
255         * The {@link ConflictResolver.OptionalitySelector} to use/
256         */
257        private final ConflictResolver.OptionalitySelector optionalitySelector;
258
259        /**
260         * Topologically sorted conflictIds from {@link ConflictIdSorter}.
261         */
262        private final List<String> sortedConflictIds;
263
264        /**
265         * The node to conflictId mapping from {@link ConflictMarker}.
266         */
267        private final Map<DependencyNode, String> conflictIds;
268
269        /**
270         * The map of conflictIds which could apply to ancestors of nodes with the key conflict id, used to avoid
271         * recursion early on. This is basically a superset of the key set of resolvedIds, the additional ids account
272         * for cyclic dependencies. From {@link ConflictIdSorter}.
273         */
274        private final Map<String, Collection<String>> cyclicPredecessors;
275
276        /**
277         * A mapping from conflictId to paths represented as {@link Path}s that exist for each conflictId. In other
278         * words all paths to each {@link DependencyNode} that are member of same conflictId group.
279         */
280        private final Map<String, List<Path>> partitions;
281
282        /**
283         * A mapping from conflictIds to winner {@link Path}, hence {@link DependencyNode}  for given conflictId.
284         */
285        private final Map<String, Path> resolvedIds;
286
287        @SuppressWarnings("checkstyle:ParameterNumber")
288        private State(
289                ConflictResolver.Verbosity verbosity,
290                ConflictResolver.VersionSelector versionSelector,
291                ConflictResolver.ScopeSelector scopeSelector,
292                ConflictResolver.ScopeDeriver scopeDeriver,
293                ConflictResolver.OptionalitySelector optionalitySelector,
294                List<String> sortedConflictIds,
295                Map<DependencyNode, String> conflictIds,
296                Map<String, Collection<String>> cyclicPredecessors) {
297            this.verbosity = verbosity;
298            this.versionSelector = versionSelector;
299            this.scopeSelector = scopeSelector;
300            this.scopeDeriver = scopeDeriver;
301            this.optionalitySelector = optionalitySelector;
302            this.sortedConflictIds = sortedConflictIds;
303            this.conflictIds = conflictIds;
304            this.cyclicPredecessors = cyclicPredecessors;
305            this.partitions = new HashMap<>();
306            this.resolvedIds = new HashMap<>();
307        }
308
309        /**
310         * Consumes the dirty graph and builds internal structures out of {@link Path} instances that is always a
311         * tree.
312         */
313        private Path build(DependencyNode node) throws RepositoryException {
314            Path root = new Path(this, node, null);
315            gatherCRNodes(root);
316            return root;
317        }
318
319        /**
320         * Recursively builds {@link Path} graph by observing each node associated {@link DependencyNode}.
321         */
322        private void gatherCRNodes(Path node) throws RepositoryException {
323            List<DependencyNode> children = node.dn.getChildren();
324            if (!children.isEmpty()) {
325                // add children; we will get back those really added (not causing cycles)
326                List<Path> added = node.addChildren(children);
327                for (Path child : added) {
328                    gatherCRNodes(child);
329                }
330            }
331        }
332    }
333
334    /**
335     * Represents a unique path within the dependency graph from the root to a specific {@link DependencyNode}.
336     * This is the core data structure that enables the O(N) performance of {@link PathConflictResolver}.
337     * <p>
338     * <strong>Key Concepts:</strong>
339     * <ul>
340     * <li><strong>Path Uniqueness:</strong> Each {@code Path} instance represents a distinct route through
341     *     the dependency graph, even if multiple paths lead to the same {@code DependencyNode}</li>
342     * <li><strong>Cycle-Free Structure:</strong> The {@code Path} tree is guaranteed to be acyclic, even
343     *     when the original dependency graph contains cycles</li>
344     * <li><strong>Parallel Structure:</strong> This creates a "clean" tree alongside the original "dirty"
345     *     graph for efficient processing</li>
346     * </ul>
347     * <p>
348     * <strong>Example:</strong> If dependency A appears in the graph via two different routes:
349     * <pre>
350     * Root → B → A (path 1)
351     * Root → C → A (path 2)
352     * </pre>
353     * Two separate {@code Path} instances will be created, both pointing to the same {@code DependencyNode} A,
354     * but representing different paths through the dependency tree.
355     * <p>
356     * <strong>Memory Optimization:</strong> While this creates additional objects, it enables the algorithm
357     * to process conflicts in O(N) time rather than O(N²), making it much more efficient for large graphs.
358     * <p>
359     * <strong>Conflict Resolution:</strong> Paths are grouped by conflict ID (based on groupId:artifactId:classifier:extension coordinates),
360     * and the conflict resolution algorithm can efficiently process each group independently.
361     */
362    private static class Path {
363        private final State state;
364        private DependencyNode dn;
365        private final String conflictId;
366        private final Path parent;
367        private final int depth;
368        private final List<Path> children;
369        private String scope;
370        private boolean optional;
371
372        private Path(State state, DependencyNode dn, Path parent) {
373            this.state = state;
374            this.dn = dn;
375            this.conflictId = state.conflictIds.get(dn);
376            this.parent = parent;
377            this.depth = parent != null ? parent.depth + 1 : 0;
378            this.children = new ArrayList<>();
379            pull(0);
380
381            this.state
382                    .partitions
383                    .computeIfAbsent(this.conflictId, k -> new ArrayList<>())
384                    .add(this);
385        }
386
387        /**
388         * Pulls (possibly updated) scope and optional values from associated {@link DependencyNode} to this instance,
389         * going down toward children recursively the required count of levels.
390         */
391        private void pull(int levels) {
392            Dependency d = dn.getDependency();
393            if (d != null) {
394                this.scope = d.getScope();
395                this.optional = d.isOptional();
396            } else {
397                this.scope = "";
398                this.optional = false;
399            }
400            int newLevels = levels - 1;
401            if (newLevels >= 0) {
402                for (Path child : this.children) {
403                    child.pull(newLevels);
404                }
405            }
406        }
407
408        /**
409         * Derives (from this to children direction) values that are "inherited" in tree: scope and optionality in the tree
410         * recursively going down required count of "levels".
411         */
412        private void derive(int levels, boolean winner) throws RepositoryException {
413            if (!winner) {
414                if (this.parent != null) {
415                    if ((dn.getManagedBits() & DependencyNode.MANAGED_SCOPE) == 0) {
416                        ScopeContext context = new ScopeContext(this.parent.scope, this.scope);
417                        state.scopeDeriver.deriveScope(context);
418                        this.scope = context.derivedScope;
419                    }
420                    if ((dn.getManagedBits() & DependencyNode.MANAGED_OPTIONAL) == 0) {
421                        if (!this.optional && this.parent.optional) {
422                            this.optional = true;
423                        }
424                    }
425                } else {
426                    this.scope = "";
427                    this.optional = false;
428                }
429            }
430            int newLevels = levels - 1;
431            if (newLevels >= 0) {
432                for (Path child : children) {
433                    child.derive(newLevels, false);
434                }
435            }
436        }
437
438        /**
439         * Pushes (applies) the scope and optional and structural changes to associated {@link DependencyNode} modifying
440         * the graph of it. Verbosity is observed, and depending on it the conflicting/loser nodes are removed, or
441         * just their children is removed (with special care for version ranges, see {@link #relatedSiblingsCount(Artifact, Path)}
442         * or by just doing nothing with them only marking losers in full verbosity mode.
443         */
444        private void push(int levels) {
445            if (this.parent != null) {
446                Path winner = this.state.resolvedIds.get(this.conflictId);
447                if (winner == null) {
448                    throw new IllegalStateException(
449                            "Winner selection did not happen for conflictId=" + this.conflictId);
450                }
451                if (!Objects.equals(winner.conflictId, this.conflictId)) {
452                    throw new IllegalStateException(
453                            "ConflictId mix-up: this=" + this.conflictId + " winner=" + winner.conflictId);
454                }
455
456                if (winner == this) {
457                    // copy onto dn; if applicable
458                    if (this.dn.getDependency() != null) {
459                        this.dn.setData(
460                                ConflictResolver.NODE_DATA_ORIGINAL_SCOPE,
461                                this.dn.getDependency().getScope());
462                        this.dn.setData(
463                                ConflictResolver.NODE_DATA_ORIGINAL_OPTIONALITY,
464                                this.dn.getDependency().getOptional());
465                        this.dn.setScope(this.scope);
466                        this.dn.setOptional(this.optional);
467                    }
468                } else {
469                    // loser; move out of scope
470                    moveOutOfScope();
471                    boolean markLoser = false;
472                    switch (state.verbosity) {
473                        case NONE:
474                            // remove this dn
475                            this.parent.children.remove(this);
476                            this.parent.dn.setChildren(new ArrayList<>(this.parent.dn.getChildren()));
477                            this.parent.dn.getChildren().remove(this.dn);
478                            this.children.clear();
479                            break;
480                        case STANDARD:
481                            // leave this dn; remove children
482                            String artifactId = ArtifactIdUtils.toId(this.dn.getArtifact());
483                            String winnerArtifactId = ArtifactIdUtils.toId(winner.dn.getArtifact());
484                            if (!Objects.equals(artifactId, winnerArtifactId)
485                                    && relatedSiblingsCount(this.dn.getArtifact(), this.parent) > 1) {
486                                this.parent.children.remove(this);
487                                this.parent.dn.setChildren(new ArrayList<>(this.parent.dn.getChildren()));
488                                this.parent.dn.getChildren().remove(this.dn);
489                                this.children.clear();
490                            } else {
491                                this.children.clear();
492                                this.dn.setChildren(Collections.emptyList());
493                                markLoser = true;
494                            }
495                            break;
496                        case FULL:
497                            // leave all in place (even cycles)
498                            markLoser = true;
499                            break;
500                        default:
501                            throw new IllegalArgumentException("Unknown " + state.verbosity);
502                    }
503                    if (markLoser) {
504                        // copy dn
505                        DependencyNode dnCopy = new DefaultDependencyNode(this.dn);
506                        dnCopy.setData(ConflictResolver.NODE_DATA_WINNER, winner.dn);
507                        dnCopy.setData(
508                                ConflictResolver.NODE_DATA_ORIGINAL_SCOPE,
509                                this.dn.getDependency().getScope());
510                        dnCopy.setData(
511                                ConflictResolver.NODE_DATA_ORIGINAL_OPTIONALITY,
512                                this.dn.getDependency().getOptional());
513                        dnCopy.setScope(this.scope);
514                        dnCopy.setOptional(this.optional);
515                        if (ConflictResolver.Verbosity.FULL != state.verbosity) {
516                            dnCopy.getChildren().clear();
517                        }
518
519                        // swap it out in DN graph
520                        this.parent
521                                .dn
522                                .getChildren()
523                                .set(this.parent.dn.getChildren().indexOf(this.dn), dnCopy);
524                        this.dn = dnCopy;
525                    }
526                }
527            }
528            if (!this.children.isEmpty()) {
529                int newLevels = levels - 1;
530                if (newLevels >= 0) {
531                    // child may remove itself from iterated list
532                    for (Path child : new ArrayList<>(children)) {
533                        child.push(newLevels);
534                    }
535                }
536            } else if (!this.dn.getChildren().isEmpty()) {
537                this.dn.setChildren(Collections.emptyList());
538            }
539        }
540
541        /**
542         * Counts "relatives" (GACE equal) artifacts under same parent; this is for cleaning up redundant nodes in
543         * case of version ranges, where same GACE is resolved into multiple GACEV as range is resolved. In {@link ConflictResolver.Verbosity#STANDARD}
544         * verbosity mode we remove "redundant" nodes (of a range) leaving only "winner equal" loser, that have same GACEV as winner.
545         */
546        private int relatedSiblingsCount(Artifact artifact, Path parent) {
547            String ga = artifact.getGroupId() + ":" + artifact.getArtifactId();
548            return Math.toIntExact(parent.children.stream()
549                    .map(n -> n.dn.getArtifact())
550                    .filter(a -> ga.equals(a.getGroupId() + ":" + a.getArtifactId()))
551                    .count());
552        }
553
554        /**
555         * Removes this and all child {@link Path} nodes from winner selection scope; essentially marks whole subtree
556         * from "this and below" as loser, to not be considered in subsequent winner selections.
557         */
558        private void moveOutOfScope() {
559            this.state.partitions.get(this.conflictId).remove(this);
560            for (Path child : this.children) {
561                child.moveOutOfScope();
562            }
563        }
564
565        /**
566         * Adds node children: this method should be "batch" used, as all (potential) children should be added at once.
567         * Method will return really added ones, as this class avoids cycles.
568         */
569        private List<Path> addChildren(List<DependencyNode> children) throws RepositoryException {
570            Collection<String> thisCyclicPredecessors =
571                    this.state.cyclicPredecessors.getOrDefault(this.conflictId, Collections.emptySet());
572
573            ArrayList<Path> added = new ArrayList<>(children.size());
574            for (DependencyNode child : children) {
575                String childConflictId = this.state.conflictIds.get(child);
576                if (!this.state.partitions.containsKey(childConflictId)
577                        || !thisCyclicPredecessors.contains(childConflictId)) {
578                    Path c = new Path(this.state, child, this);
579                    this.children.add(c);
580                    c.derive(0, false);
581                    added.add(c);
582                }
583            }
584            return added;
585        }
586
587        /**
588         * Dump for debug.
589         */
590        private void dump(String padding) {
591            System.out.println(padding + this.dn + ": " + this.scope + "/" + this.optional);
592            for (Path child : children) {
593                child.dump(padding + "  ");
594            }
595        }
596
597        /**
598         * For easier debug.
599         */
600        @Override
601        public String toString() {
602            return this.dn.toString();
603        }
604    }
605
606    /**
607     * A context used to hold information that is relevant for deriving the scope of a child dependency.
608     *
609     * @see ConflictResolver.ScopeDeriver
610     * @noinstantiate This class is not intended to be instantiated by clients in production code, the constructor may
611     *                change without notice and only exists to enable unit testing.
612     */
613    private static final class ScopeContext extends ConflictResolver.ScopeContext {
614        private final String parentScope;
615        private final String childScope;
616        private String derivedScope;
617
618        /**
619         * Creates a new scope context with the specified properties.
620         *
621         * @param parentScope The scope of the parent dependency, may be {@code null}.
622         * @param childScope The scope of the child dependency, may be {@code null}.
623         * @noreference This class is not intended to be instantiated by clients in production code, the constructor may
624         *              change without notice and only exists to enable unit testing.
625         */
626        private ScopeContext(String parentScope, String childScope) {
627            this.parentScope = (parentScope != null) ? parentScope : "";
628            this.derivedScope = (childScope != null) ? childScope : "";
629            this.childScope = (childScope != null) ? childScope : "";
630        }
631
632        /**
633         * Gets the scope of the parent dependency. This is usually the scope that was derived by earlier invocations of
634         * the scope deriver.
635         *
636         * @return The scope of the parent dependency, never {@code null}.
637         */
638        public String getParentScope() {
639            return parentScope;
640        }
641
642        /**
643         * Gets the original scope of the child dependency. This is the scope that was declared in the artifact
644         * descriptor of the parent dependency.
645         *
646         * @return The original scope of the child dependency, never {@code null}.
647         */
648        public String getChildScope() {
649            return childScope;
650        }
651
652        /**
653         * Gets the derived scope of the child dependency. This is initially equal to {@link #getChildScope()} until the
654         * scope deriver makes changes.
655         *
656         * @return The derived scope of the child dependency, never {@code null}.
657         */
658        public String getDerivedScope() {
659            return derivedScope;
660        }
661
662        /**
663         * Sets the derived scope of the child dependency.
664         *
665         * @param derivedScope The derived scope of the dependency, may be {@code null}.
666         */
667        public void setDerivedScope(String derivedScope) {
668            this.derivedScope = (derivedScope != null) ? derivedScope : "";
669        }
670    }
671
672    /**
673     * A conflicting dependency.
674     *
675     * @noinstantiate This class is not intended to be instantiated by clients in production code, the constructor may
676     *                change without notice and only exists to enable unit testing.
677     */
678    private static final class ConflictItem extends ConflictResolver.ConflictItem {
679        private final Path path;
680        private final List<DependencyNode> parent;
681        private final Artifact artifact;
682        private final DependencyNode node;
683        private final int depth;
684        private final String scope;
685        private final int optionalities;
686
687        /**
688         * Bit flag indicating whether one or more paths consider the dependency non-optional.
689         */
690        public static final int OPTIONAL_FALSE = 0x01;
691
692        /**
693         * Bit flag indicating whether one or more paths consider the dependency optional.
694         */
695        public static final int OPTIONAL_TRUE = 0x02;
696
697        private ConflictItem(Path path) {
698            this.path = path;
699            if (path.parent != null) {
700                DependencyNode parent = path.parent.dn;
701                this.parent = parent.getChildren();
702                this.artifact = parent.getArtifact();
703            } else {
704                this.parent = null;
705                this.artifact = null;
706            }
707            this.node = path.dn;
708            this.depth = path.depth;
709            this.scope = path.scope;
710            this.optionalities = path.optional ? OPTIONAL_TRUE : OPTIONAL_FALSE;
711        }
712
713        /**
714         * Determines whether the specified conflict item is a sibling of this item.
715         *
716         * @param item The other conflict item, must not be {@code null}.
717         * @return {@code true} if the given item has the same parent as this item, {@code false} otherwise.
718         */
719        @Override
720        public boolean isSibling(ConflictResolver.ConflictItem item) {
721            return parent == ((ConflictItem) item).parent;
722        }
723
724        /**
725         * Gets the dependency node involved in the conflict.
726         *
727         * @return The involved dependency node, never {@code null}.
728         */
729        @Override
730        public DependencyNode getNode() {
731            return node;
732        }
733
734        /**
735         * Gets the dependency involved in the conflict, short for {@code getNode.getDependency()}.
736         *
737         * @return The involved dependency, never {@code null}.
738         */
739        @Override
740        public Dependency getDependency() {
741            return node.getDependency();
742        }
743
744        /**
745         * Gets the zero-based depth at which the conflicting node occurs in the graph. As such, the depth denotes the
746         * number of parent nodes. If actually multiple paths lead to the node, the return value denotes the smallest
747         * possible depth.
748         *
749         * @return The zero-based depth of the node in the graph.
750         */
751        @Override
752        public int getDepth() {
753            return depth;
754        }
755
756        /**
757         * Gets the derived scopes of the dependency. In general, the same dependency node could be reached via
758         * different paths and each path might result in a different derived scope.
759         *
760         * @see ConflictResolver.ScopeDeriver
761         * @return The (read-only) set of derived scopes of the dependency, never {@code null}.
762         */
763        @Override
764        public Collection<String> getScopes() {
765            return Collections.singleton(scope);
766        }
767
768        /**
769         * Gets the derived optionalities of the dependency. In general, the same dependency node could be reached via
770         * different paths and each path might result in a different derived optionality.
771         *
772         * @return A bit field consisting of {@link PathConflictResolver.ConflictItem#OPTIONAL_FALSE} and/or
773         *         {@link PathConflictResolver.ConflictItem#OPTIONAL_TRUE} indicating the derived optionalities the
774         *         dependency was encountered with.
775         */
776        @Override
777        public int getOptionalities() {
778            return optionalities;
779        }
780
781        @Override
782        public String toString() {
783            return node + " @ " + depth + " < " + artifact;
784        }
785    }
786
787    /**
788     * A context used to hold information that is relevant for resolving version and scope conflicts.
789     *
790     * @see ConflictResolver.VersionSelector
791     * @see ConflictResolver.ScopeSelector
792     * @noinstantiate This class is not intended to be instantiated by clients in production code, the constructor may
793     *                change without notice and only exists to enable unit testing.
794     */
795    private static final class ConflictContext extends ConflictResolver.ConflictContext {
796        private final DependencyNode root;
797        private final Map<DependencyNode, String> conflictIds;
798        private final Collection<ConflictResolver.ConflictItem> items;
799        private final String conflictId;
800
801        // elected properties
802        private ConflictItem winner;
803        private String scope;
804        private Boolean optional;
805
806        private ConflictContext(
807                DependencyNode root,
808                Map<DependencyNode, String> conflictIds,
809                Collection<ConflictItem> items,
810                String conflictId) {
811            this.root = root;
812            this.conflictIds = conflictIds;
813            this.items = Collections.unmodifiableCollection(items);
814            this.conflictId = conflictId;
815        }
816
817        /**
818         * Gets the root node of the dependency graph being transformed.
819         *
820         * @return The root node of the dependency graph, never {@code null}.
821         */
822        @Override
823        public DependencyNode getRoot() {
824            return root;
825        }
826
827        /**
828         * Determines whether the specified dependency node belongs to this conflict context.
829         *
830         * @param node The dependency node to check, must not be {@code null}.
831         * @return {@code true} if the given node belongs to this conflict context, {@code false} otherwise.
832         */
833        @Override
834        public boolean isIncluded(DependencyNode node) {
835            return conflictId.equals(conflictIds.get(node));
836        }
837
838        /**
839         * Gets the collection of conflict items in this context.
840         *
841         * @return The (read-only) collection of conflict items in this context, never {@code null}.
842         */
843        @Override
844        public Collection<ConflictResolver.ConflictItem> getItems() {
845            return items;
846        }
847
848        /**
849         * Gets the conflict item which has been selected as the winner among the conflicting dependencies.
850         *
851         * @return The winning conflict item or {@code null} if not set yet.
852         */
853        @Override
854        public ConflictResolver.ConflictItem getWinner() {
855            return winner;
856        }
857
858        /**
859         * Sets the conflict item which has been selected as the winner among the conflicting dependencies.
860         *
861         * @param winner The winning conflict item, may be {@code null}.
862         */
863        @Override
864        public void setWinner(ConflictResolver.ConflictItem winner) {
865            this.winner = (ConflictItem) winner;
866        }
867
868        /**
869         * Gets the effective scope of the winning dependency.
870         *
871         * @return The effective scope of the winning dependency or {@code null} if none.
872         */
873        @Override
874        public String getScope() {
875            return scope;
876        }
877
878        /**
879         * Sets the effective scope of the winning dependency.
880         *
881         * @param scope The effective scope, may be {@code null}.
882         */
883        @Override
884        public void setScope(String scope) {
885            this.scope = scope;
886        }
887
888        /**
889         * Gets the effective optional flag of the winning dependency.
890         *
891         * @return The effective optional flag or {@code null} if none.
892         */
893        @Override
894        public Boolean getOptional() {
895            return optional;
896        }
897
898        /**
899         * Sets the effective optional flag of the winning dependency.
900         *
901         * @param optional The effective optional flag, may be {@code null}.
902         */
903        @Override
904        public void setOptional(Boolean optional) {
905            this.optional = optional;
906        }
907
908        @Override
909        public String toString() {
910            return winner + " @ " + scope + " < " + items;
911        }
912    }
913}