You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by sl...@apache.org on 2026/10/05 21:24:02 UTC

(maven-dependency-plugin) 01/03: [MDEP-969] Replace maven-dependency-tree with Maven Resolver

This is an automated email from the ASF dual-hosted git repository.

slachiewicz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-dependency-plugin.git

commit 1dfc4b659c30170c7ca36d9252ad2d5a64f4ec2d
Author: Sylwester Lachiewicz <sl...@apache.org>
AuthorDate: Wed Sep 30 19:05:05 2026 +0200

    [MDEP-969] Replace maven-dependency-tree with Maven Resolver
    
    maven-dependency-tree is obsolete. The tree goal now builds its nodes from
    ProjectDependenciesResolver and RepositorySystem; output is byte-identical.
---
 pom.xml                                            |   5 -
 .../tree/BuildingDependencyNodeVisitor.java        | 119 +-----------
 ...r.java => CollectingDependencyNodeVisitor.java} |  35 ++--
 .../dependency/tree/DOTDependencyNodeVisitor.java  |   3 -
 .../plugins/dependency/tree/DependencyNode.java    |  89 +++++++++
 .../dependency/tree/DependencyNodeVisitor.java     |  36 ++++
 .../dependency/tree/DependencyTreeBuilder.java     | 203 +++++++++++++++++++++
 .../tree/DirectScopeDependencySelector.java        |  76 ++++++++
 ...or.java => FilteringDependencyNodeVisitor.java} |  23 +--
 .../tree/GraphmlDependencyNodeVisitor.java         |   3 -
 .../dependency/tree/JsonDependencyNodeVisitor.java |   2 -
 .../tree/PruningDependencyNodeVisitor.java         |  11 +-
 .../tree/SerializingDependencyNodeVisitor.java     | 121 ++++++++++++
 .../dependency/tree/TGFDependencyNodeVisitor.java  |   3 -
 .../maven/plugins/dependency/tree/TreeMojo.java    |  86 ++++-----
 .../dependency/tree/VerboseJavaScopeSelector.java  |  53 ++++++
 .../tree/PruningDependencyNodeVisitorTest.java     |  22 +--
 .../plugins/dependency/tree/TestTreeMojo.java      |   6 +-
 18 files changed, 648 insertions(+), 248 deletions(-)

diff --git a/pom.xml b/pom.xml
index 712bbb11..6d04b829 100644
--- a/pom.xml
+++ b/pom.xml
@@ -227,11 +227,6 @@ under the License.
       <artifactId>maven-dependency-analyzer</artifactId>
       <version>1.17.2</version>
     </dependency>
-    <dependency>
-      <groupId>org.apache.maven.shared</groupId>
-      <artifactId>maven-dependency-tree</artifactId>
-      <version>3.3.0</version>
-    </dependency>
     <dependency>
       <groupId>org.apache.maven.shared</groupId>
       <artifactId>maven-common-artifact-filters</artifactId>
diff --git a/src/main/java/org/apache/maven/plugins/dependency/tree/BuildingDependencyNodeVisitor.java b/src/main/java/org/apache/maven/plugins/dependency/tree/BuildingDependencyNodeVisitor.java
index 411a1fb5..88ac7ef5 100644
--- a/src/main/java/org/apache/maven/plugins/dependency/tree/BuildingDependencyNodeVisitor.java
+++ b/src/main/java/org/apache/maven/plugins/dependency/tree/BuildingDependencyNodeVisitor.java
@@ -19,14 +19,8 @@
 package org.apache.maven.plugins.dependency.tree;
 
 import java.util.ArrayList;
-import java.util.List;
 import java.util.Stack;
 
-import org.apache.maven.artifact.Artifact;
-import org.apache.maven.model.Exclusion;
-import org.apache.maven.shared.dependency.graph.DependencyNode;
-import org.apache.maven.shared.dependency.graph.traversal.DependencyNodeVisitor;
-
 /**
  * A dependency node visitor that clones visited nodes into a new dependency tree. This can be used in conjunction with
  * a dependency node filter to construct subtrees.
@@ -81,15 +75,8 @@ public class BuildingDependencyNodeVisitor implements DependencyNodeVisitor {
     @Override
     public boolean visit(DependencyNode node) {
         // clone the node
-        WrapperNode newNode = new WrapperNode(
-                parentNodes.isEmpty() ? null : parentNodes.peek(),
-                node.getArtifact(),
-                node.getPremanagedVersion(),
-                node.getPremanagedScope(),
-                node.getVersionConstraint(),
-                node.getOptional(),
-                node.getExclusions(),
-                node.toNodeString());
+        DependencyNode newNode = new DependencyNode(
+                parentNodes.isEmpty() ? null : parentNodes.peek(), node.getArtifact(), node.toNodeString());
         newNode.setChildren(new ArrayList<>());
 
         if (parentNodes.empty()) {
@@ -138,106 +125,4 @@ public class BuildingDependencyNodeVisitor implements DependencyNodeVisitor {
     public DependencyNode getDependencyTree() {
         return rootNode;
     }
-
-    private static class WrapperNode implements DependencyNode {
-
-        private final Artifact artifact;
-
-        private final DependencyNode parent;
-
-        private final String premanagedVersion;
-
-        private final String premanagedScope;
-
-        private final String versionConstraint;
-
-        private List<DependencyNode> children;
-
-        private final Boolean optional;
-
-        private final List<Exclusion> exclusions;
-
-        private final String nodeString;
-
-        private WrapperNode(
-                DependencyNode parent,
-                Artifact artifact,
-                String premanagedVersion,
-                String premanagedScope,
-                String versionConstraint,
-                Boolean optional,
-                List<Exclusion> exclusions,
-                String nodeString) {
-            this.artifact = artifact;
-            this.parent = parent;
-            this.premanagedVersion = premanagedVersion;
-            this.premanagedScope = premanagedScope;
-            this.versionConstraint = versionConstraint;
-            this.optional = optional;
-            this.exclusions = exclusions;
-            this.nodeString = nodeString;
-        }
-
-        @Override
-        public Artifact getArtifact() {
-            return artifact;
-        }
-
-        @Override
-        public List<DependencyNode> getChildren() {
-            return children;
-        }
-
-        @Override
-        public boolean accept(DependencyNodeVisitor visitor) {
-            if (visitor.visit(this)) {
-                for (DependencyNode child : getChildren()) {
-                    if (!child.accept(visitor)) {
-                        break;
-                    }
-                }
-            }
-
-            return visitor.endVisit(this);
-        }
-
-        @Override
-        public DependencyNode getParent() {
-            return parent;
-        }
-
-        @Override
-        public String getPremanagedVersion() {
-            return premanagedVersion;
-        }
-
-        @Override
-        public String getPremanagedScope() {
-            return premanagedScope;
-        }
-
-        @Override
-        public String getVersionConstraint() {
-            return versionConstraint;
-        }
-
-        @Override
-        public String toNodeString() {
-            return nodeString;
-        }
-
-        @Override
-        public Boolean getOptional() {
-            return optional;
-        }
-
-        @Override
-        public List<Exclusion> getExclusions() {
-            return exclusions;
-        }
-
-        public void setChildren(List<DependencyNode> children) {
-            this.children = children;
-        }
-    }
 }
diff --git a/src/main/java/org/apache/maven/plugins/dependency/tree/PruningDependencyNodeVisitor.java b/src/main/java/org/apache/maven/plugins/dependency/tree/CollectingDependencyNodeVisitor.java
similarity index 50%
copy from src/main/java/org/apache/maven/plugins/dependency/tree/PruningDependencyNodeVisitor.java
copy to src/main/java/org/apache/maven/plugins/dependency/tree/CollectingDependencyNodeVisitor.java
index 380d2873..9d8b670d 100644
--- a/src/main/java/org/apache/maven/plugins/dependency/tree/PruningDependencyNodeVisitor.java
+++ b/src/main/java/org/apache/maven/plugins/dependency/tree/CollectingDependencyNodeVisitor.java
@@ -18,37 +18,28 @@
  */
 package org.apache.maven.plugins.dependency.tree;
 
-import java.util.ArrayDeque;
-import java.util.Deque;
-
-import org.apache.maven.shared.dependency.graph.DependencyNode;
-import org.apache.maven.shared.dependency.graph.filter.DependencyNodeFilter;
-import org.apache.maven.shared.dependency.graph.traversal.DependencyNodeVisitor;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
 /**
- * A dependency node visitor that delegates accepted nodes and prunes rejected nodes and their descendants.
+ * Collects all visited nodes.
  */
-final class PruningDependencyNodeVisitor implements DependencyNodeVisitor {
-    private final DependencyNodeVisitor visitor;
-
-    private final DependencyNodeFilter filter;
-
-    private final Deque<Boolean> acceptedNodes = new ArrayDeque<>();
-
-    PruningDependencyNodeVisitor(DependencyNodeVisitor visitor, DependencyNodeFilter filter) {
-        this.visitor = visitor;
-        this.filter = filter;
-    }
+class CollectingDependencyNodeVisitor implements DependencyNodeVisitor {
+    private final List<DependencyNode> nodes = new ArrayList<>();
 
     @Override
     public boolean visit(DependencyNode node) {
-        boolean accepted = filter.accept(node);
-        acceptedNodes.push(accepted);
-        return accepted && visitor.visit(node);
+        nodes.add(node);
+        return true;
     }
 
     @Override
     public boolean endVisit(DependencyNode node) {
-        return !acceptedNodes.pop() || visitor.endVisit(node);
+        return true;
+    }
+
+    List<DependencyNode> getNodes() {
+        return Collections.unmodifiableList(nodes);
     }
 }
diff --git a/src/main/java/org/apache/maven/plugins/dependency/tree/DOTDependencyNodeVisitor.java b/src/main/java/org/apache/maven/plugins/dependency/tree/DOTDependencyNodeVisitor.java
index 0508a313..4fca7fa3 100644
--- a/src/main/java/org/apache/maven/plugins/dependency/tree/DOTDependencyNodeVisitor.java
+++ b/src/main/java/org/apache/maven/plugins/dependency/tree/DOTDependencyNodeVisitor.java
@@ -23,9 +23,6 @@ import java.io.UncheckedIOException;
 import java.io.Writer;
 import java.util.List;
 
-import org.apache.maven.shared.dependency.graph.DependencyNode;
-import org.apache.maven.shared.dependency.graph.traversal.DependencyNodeVisitor;
-
 /**
  * A dependency node visitor that serializes visited nodes to <a href="https://en.wikipedia.org/wiki/DOT_language">DOT
  * format</a>
diff --git a/src/main/java/org/apache/maven/plugins/dependency/tree/DependencyNode.java b/src/main/java/org/apache/maven/plugins/dependency/tree/DependencyNode.java
new file mode 100644
index 00000000..2d90bd7e
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/dependency/tree/DependencyNode.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.plugins.dependency.tree;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.maven.artifact.Artifact;
+
+/**
+ * A node of the dependency tree displayed by the {@link TreeMojo}.
+ */
+public class DependencyNode {
+    private final DependencyNode parent;
+
+    private final Artifact artifact;
+
+    private final String nodeString;
+
+    private List<DependencyNode> children = Collections.emptyList();
+
+    /**
+     * @param parent the parent node, or <code>null</code> for the root
+     * @param artifact the artifact of this node
+     * @param nodeString the label of this node
+     */
+    public DependencyNode(DependencyNode parent, Artifact artifact, String nodeString) {
+        this.parent = parent;
+        this.artifact = artifact;
+        this.nodeString = nodeString;
+    }
+
+    public Artifact getArtifact() {
+        return artifact;
+    }
+
+    public DependencyNode getParent() {
+        return parent;
+    }
+
+    public List<DependencyNode> getChildren() {
+        return children;
+    }
+
+    public void setChildren(List<DependencyNode> children) {
+        this.children = children;
+    }
+
+    /**
+     * @return the label of this node
+     */
+    public String toNodeString() {
+        return nodeString;
+    }
+
+    /**
+     * Applies the visitor to this node and, as long as the visitor agrees, to its descendants.
+     *
+     * @param visitor the visitor
+     * @return the result of {@link DependencyNodeVisitor#endVisit(DependencyNode)}
+     */
+    public boolean accept(DependencyNodeVisitor visitor) {
+        if (visitor.visit(this)) {
+            for (DependencyNode child : children) {
+                if (!child.accept(visitor)) {
+                    break;
+                }
+            }
+        }
+
+        return visitor.endVisit(this);
+    }
+}
diff --git a/src/main/java/org/apache/maven/plugins/dependency/tree/DependencyNodeVisitor.java b/src/main/java/org/apache/maven/plugins/dependency/tree/DependencyNodeVisitor.java
new file mode 100644
index 00000000..159f8f69
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/dependency/tree/DependencyNodeVisitor.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.plugins.dependency.tree;
+
+/**
+ * Visits the nodes of a dependency tree.
+ */
+public interface DependencyNodeVisitor {
+    /**
+     * @param node the node being visited
+     * @return <code>true</code> to visit the children of the node
+     */
+    boolean visit(DependencyNode node);
+
+    /**
+     * @param node the node whose visit is finished
+     * @return <code>true</code> to visit the following siblings of the node
+     */
+    boolean endVisit(DependencyNode node);
+}
diff --git a/src/main/java/org/apache/maven/plugins/dependency/tree/DependencyTreeBuilder.java b/src/main/java/org/apache/maven/plugins/dependency/tree/DependencyTreeBuilder.java
new file mode 100644
index 00000000..7f3ec0e4
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/dependency/tree/DependencyTreeBuilder.java
@@ -0,0 +1,203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.plugins.dependency.tree;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.maven.RepositoryUtils;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.project.DefaultDependencyResolutionRequest;
+import org.apache.maven.project.DependencyResolutionException;
+import org.apache.maven.project.DependencyResolutionRequest;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.project.ProjectDependenciesResolver;
+import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.ArtifactTypeRegistry;
+import org.eclipse.aether.collection.CollectRequest;
+import org.eclipse.aether.collection.DependencyCollectionException;
+import org.eclipse.aether.util.artifact.JavaScopes;
+import org.eclipse.aether.util.graph.manager.DependencyManagerUtils;
+import org.eclipse.aether.util.graph.selector.AndDependencySelector;
+import org.eclipse.aether.util.graph.selector.ExclusionDependencySelector;
+import org.eclipse.aether.util.graph.selector.OptionalDependencySelector;
+import org.eclipse.aether.util.graph.transformer.ConflictResolver;
+import org.eclipse.aether.util.graph.transformer.JavaScopeDeriver;
+import org.eclipse.aether.util.graph.transformer.NearestVersionSelector;
+import org.eclipse.aether.util.graph.transformer.SimpleOptionalitySelector;
+
+/**
+ * Builds the {@link DependencyNode} tree of a project from Maven Resolver's dependency graph.
+ * <p>
+ * The plain tree is what Maven resolves for the project, so it is consistent with the Maven version running. The
+ * verbose tree is collected again with the conflict resolver keeping the omitted nodes.
+ */
+class DependencyTreeBuilder {
+    private final RepositorySystem repositorySystem;
+
+    private final ProjectDependenciesResolver projectDependenciesResolver;
+
+    DependencyTreeBuilder(RepositorySystem repositorySystem, ProjectDependenciesResolver projectDependenciesResolver) {
+        this.repositorySystem = repositorySystem;
+        this.projectDependenciesResolver = projectDependenciesResolver;
+    }
+
+    /**
+     * Builds the tree resolved by Maven, without omitted nodes.
+     */
+    DependencyNode build(MavenProject project, RepositorySystemSession session, ArtifactFilter filter)
+            throws DependencyResolutionException {
+        DependencyResolutionRequest request = new DefaultDependencyResolutionRequest();
+        request.setMavenProject(project);
+        request.setRepositorySession(session);
+        // only collect the graph, do not download the artifacts
+        request.setResolutionFilter((node, parents) -> false);
+
+        org.eclipse.aether.graph.DependencyNode root =
+                projectDependenciesResolver.resolve(request).getDependencyGraph();
+
+        return buildNode(null, root, project.getArtifact(), filter, false);
+    }
+
+    /**
+     * Builds the tree including the nodes omitted for duplicates and conflicts, and the managed versions and scopes.
+     */
+    DependencyNode buildVerbose(MavenProject project, RepositorySystemSession repositorySession, ArtifactFilter filter)
+            throws DependencyCollectionException {
+        DefaultRepositorySystemSession session = new DefaultRepositorySystemSession(repositorySession);
+        session.setDependencyGraphTransformer(new ConflictResolver(
+                new NearestVersionSelector(),
+                new VerboseJavaScopeSelector(),
+                new SimpleOptionalitySelector(),
+                new JavaScopeDeriver()));
+        session.setDependencySelector(new AndDependencySelector(
+                new DirectScopeDependencySelector(JavaScopes.TEST),
+                new DirectScopeDependencySelector(JavaScopes.PROVIDED),
+                new OptionalDependencySelector(),
+                new ExclusionDependencySelector()));
+        session.setConfigProperty(ConflictResolver.CONFIG_PROP_VERBOSE, true);
+        session.setConfigProperty(DependencyManagerUtils.CONFIG_PROP_VERBOSE, true);
+
+        ArtifactTypeRegistry stereotypes = session.getArtifactTypeRegistry();
+        CollectRequest collectRequest = new CollectRequest();
+        collectRequest.setRootArtifact(RepositoryUtils.toArtifact(project.getArtifact()));
+        collectRequest.setRepositories(RepositoryUtils.toRepos(project.getRemoteArtifactRepositories()));
+        for (Dependency dependency : project.getDependencies()) {
+            collectRequest.addDependency(RepositoryUtils.toDependency(dependency, stereotypes));
+        }
+        if (project.getDependencyManagement() != null) {
+            for (Dependency dependency : project.getDependencyManagement().getDependencies()) {
+                collectRequest.addManagedDependency(RepositoryUtils.toDependency(dependency, stereotypes));
+            }
+        }
+
+        org.eclipse.aether.graph.DependencyNode root =
+                repositorySystem.collectDependencies(session, collectRequest).getRoot();
+
+        return buildNode(null, root, project.getArtifact(), filter, true);
+    }
+
+    private DependencyNode buildNode(
+            DependencyNode parent,
+            org.eclipse.aether.graph.DependencyNode node,
+            Artifact artifact,
+            ArtifactFilter filter,
+            boolean verbose) {
+        DependencyNode current = new DependencyNode(
+                parent, artifact, verbose ? verboseNodeString(node, artifact) : nodeString(artifact));
+
+        List<DependencyNode> children = new ArrayList<>(node.getChildren().size());
+        for (org.eclipse.aether.graph.DependencyNode child : node.getChildren()) {
+            Artifact childArtifact = toArtifact(child.getDependency());
+
+            if (filter == null || filter.include(childArtifact)) {
+                children.add(buildNode(current, child, childArtifact, filter, verbose));
+            }
+        }
+        current.setChildren(Collections.unmodifiableList(children));
+
+        return current;
+    }
+
+    private static Artifact toArtifact(org.eclipse.aether.graph.Dependency dependency) {
+        Artifact artifact = RepositoryUtils.toArtifact(dependency.getArtifact());
+        artifact.setScope(dependency.getScope());
+        artifact.setOptional(dependency.isOptional());
+        return artifact;
+    }
+
+    private static String nodeString(Artifact artifact) {
+        return artifact + (artifact.isOptional() ? " (optional)" : "");
+    }
+
+    private static String verboseNodeString(org.eclipse.aether.graph.DependencyNode node, Artifact artifact) {
+        org.eclipse.aether.graph.DependencyNode winner =
+                (org.eclipse.aether.graph.DependencyNode) node.getData().get(ConflictResolver.NODE_DATA_WINNER);
+        String winnerVersion = null;
+        String ignoredScope = null;
+        if (winner != null) {
+            winnerVersion = winner.getArtifact().getBaseVersion();
+        } else {
+            ignoredScope = (String) node.getData().get(VerboseJavaScopeSelector.REDUCED_SCOPE);
+        }
+
+        boolean included = winnerVersion == null;
+
+        StringBuilder buffer = new StringBuilder();
+        if (!included) {
+            buffer.append('(');
+        }
+        buffer.append(artifact);
+
+        List<String> items = new ArrayList<>();
+        String premanagedVersion = DependencyManagerUtils.getPremanagedVersion(node);
+        if (premanagedVersion != null) {
+            items.add("version managed from " + premanagedVersion);
+        }
+        String premanagedScope = DependencyManagerUtils.getPremanagedScope(node);
+        if (premanagedScope != null) {
+            items.add("scope managed from " + premanagedScope);
+        }
+        if (ignoredScope != null) {
+            items.add("scope not updated to " + ignoredScope);
+        }
+        if (!included) {
+            items.add(
+                    winnerVersion.equals(artifact.getVersion())
+                            ? "omitted for duplicate"
+                            : "omitted for conflict with " + winnerVersion);
+        }
+
+        if (!items.isEmpty()) {
+            buffer.append(included ? " (" : " - ")
+                    .append(String.join("; ", items))
+                    .append(included ? ")" : "");
+        }
+        if (!included) {
+            buffer.append(')');
+        }
+
+        return buffer.toString();
+    }
+}
diff --git a/src/main/java/org/apache/maven/plugins/dependency/tree/DirectScopeDependencySelector.java b/src/main/java/org/apache/maven/plugins/dependency/tree/DirectScopeDependencySelector.java
new file mode 100644
index 00000000..945d0da2
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/dependency/tree/DirectScopeDependencySelector.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.plugins.dependency.tree;
+
+import java.util.Objects;
+
+import org.eclipse.aether.collection.DependencyCollectionContext;
+import org.eclipse.aether.collection.DependencySelector;
+import org.eclipse.aether.graph.Dependency;
+
+/**
+ * Excludes dependencies of a scope, unless they are direct dependencies of the project or its dependencies' first
+ * level, as the verbose tree shows them.
+ */
+class DirectScopeDependencySelector implements DependencySelector {
+
+    private final String scope;
+
+    private final int depth;
+
+    DirectScopeDependencySelector(String scope) {
+        this(scope, 0);
+    }
+
+    private DirectScopeDependencySelector(String scope, int depth) {
+        this.scope = Objects.requireNonNull(scope, "scope is null!");
+        this.depth = depth;
+    }
+
+    @Override
+    public boolean selectDependency(Dependency dependency) {
+        return depth < 2 || !scope.equals(dependency.getScope());
+    }
+
+    @Override
+    public DependencySelector deriveChildSelector(DependencyCollectionContext context) {
+        if (depth >= 2) {
+            return this;
+        }
+
+        return new DirectScopeDependencySelector(scope, depth + 1);
+    }
+
+    @Override
+    public int hashCode() {
+        return 31 * (31 + depth) + scope.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        DirectScopeDependencySelector other = (DirectScopeDependencySelector) obj;
+        return depth == other.depth && scope.equals(other.scope);
+    }
+}
diff --git a/src/main/java/org/apache/maven/plugins/dependency/tree/PruningDependencyNodeVisitor.java b/src/main/java/org/apache/maven/plugins/dependency/tree/FilteringDependencyNodeVisitor.java
similarity index 56%
copy from src/main/java/org/apache/maven/plugins/dependency/tree/PruningDependencyNodeVisitor.java
copy to src/main/java/org/apache/maven/plugins/dependency/tree/FilteringDependencyNodeVisitor.java
index 380d2873..4761d732 100644
--- a/src/main/java/org/apache/maven/plugins/dependency/tree/PruningDependencyNodeVisitor.java
+++ b/src/main/java/org/apache/maven/plugins/dependency/tree/FilteringDependencyNodeVisitor.java
@@ -18,37 +18,28 @@
  */
 package org.apache.maven.plugins.dependency.tree;
 
-import java.util.ArrayDeque;
-import java.util.Deque;
-
-import org.apache.maven.shared.dependency.graph.DependencyNode;
-import org.apache.maven.shared.dependency.graph.filter.DependencyNodeFilter;
-import org.apache.maven.shared.dependency.graph.traversal.DependencyNodeVisitor;
+import java.util.function.Predicate;
 
 /**
- * A dependency node visitor that delegates accepted nodes and prunes rejected nodes and their descendants.
+ * Passes only the accepted nodes to the delegate, while still traversing the children of the rejected ones.
  */
-final class PruningDependencyNodeVisitor implements DependencyNodeVisitor {
+class FilteringDependencyNodeVisitor implements DependencyNodeVisitor {
     private final DependencyNodeVisitor visitor;
 
-    private final DependencyNodeFilter filter;
-
-    private final Deque<Boolean> acceptedNodes = new ArrayDeque<>();
+    private final Predicate<DependencyNode> filter;
 
-    PruningDependencyNodeVisitor(DependencyNodeVisitor visitor, DependencyNodeFilter filter) {
+    FilteringDependencyNodeVisitor(DependencyNodeVisitor visitor, Predicate<DependencyNode> filter) {
         this.visitor = visitor;
         this.filter = filter;
     }
 
     @Override
     public boolean visit(DependencyNode node) {
-        boolean accepted = filter.accept(node);
-        acceptedNodes.push(accepted);
-        return accepted && visitor.visit(node);
+        return !filter.test(node) || visitor.visit(node);
     }
 
     @Override
     public boolean endVisit(DependencyNode node) {
-        return !acceptedNodes.pop() || visitor.endVisit(node);
+        return !filter.test(node) || visitor.endVisit(node);
     }
 }
diff --git a/src/main/java/org/apache/maven/plugins/dependency/tree/GraphmlDependencyNodeVisitor.java b/src/main/java/org/apache/maven/plugins/dependency/tree/GraphmlDependencyNodeVisitor.java
index 78811c62..076bcc9e 100644
--- a/src/main/java/org/apache/maven/plugins/dependency/tree/GraphmlDependencyNodeVisitor.java
+++ b/src/main/java/org/apache/maven/plugins/dependency/tree/GraphmlDependencyNodeVisitor.java
@@ -22,9 +22,6 @@ import java.io.IOException;
 import java.io.UncheckedIOException;
 import java.io.Writer;
 
-import org.apache.maven.shared.dependency.graph.DependencyNode;
-import org.apache.maven.shared.dependency.graph.traversal.DependencyNodeVisitor;
-
 /**
  * A dependency node visitor that serializes visited nodes to a writer using the
  * <a href="https://en.wikipedia.org/wiki/GraphML">graphml format</a>.
diff --git a/src/main/java/org/apache/maven/plugins/dependency/tree/JsonDependencyNodeVisitor.java b/src/main/java/org/apache/maven/plugins/dependency/tree/JsonDependencyNodeVisitor.java
index 719d36cd..630afcce 100644
--- a/src/main/java/org/apache/maven/plugins/dependency/tree/JsonDependencyNodeVisitor.java
+++ b/src/main/java/org/apache/maven/plugins/dependency/tree/JsonDependencyNodeVisitor.java
@@ -25,8 +25,6 @@ import java.util.HashSet;
 import java.util.Set;
 
 import org.apache.maven.artifact.Artifact;
-import org.apache.maven.shared.dependency.graph.DependencyNode;
-import org.apache.maven.shared.dependency.graph.traversal.DependencyNodeVisitor;
 
 /**
  * A dependency node visitor that serializes visited nodes to a writer using the JSON format.
diff --git a/src/main/java/org/apache/maven/plugins/dependency/tree/PruningDependencyNodeVisitor.java b/src/main/java/org/apache/maven/plugins/dependency/tree/PruningDependencyNodeVisitor.java
index 380d2873..daf0afba 100644
--- a/src/main/java/org/apache/maven/plugins/dependency/tree/PruningDependencyNodeVisitor.java
+++ b/src/main/java/org/apache/maven/plugins/dependency/tree/PruningDependencyNodeVisitor.java
@@ -20,10 +20,7 @@ package org.apache.maven.plugins.dependency.tree;
 
 import java.util.ArrayDeque;
 import java.util.Deque;
-
-import org.apache.maven.shared.dependency.graph.DependencyNode;
-import org.apache.maven.shared.dependency.graph.filter.DependencyNodeFilter;
-import org.apache.maven.shared.dependency.graph.traversal.DependencyNodeVisitor;
+import java.util.function.Predicate;
 
 /**
  * A dependency node visitor that delegates accepted nodes and prunes rejected nodes and their descendants.
@@ -31,18 +28,18 @@ import org.apache.maven.shared.dependency.graph.traversal.DependencyNodeVisitor;
 final class PruningDependencyNodeVisitor implements DependencyNodeVisitor {
     private final DependencyNodeVisitor visitor;
 
-    private final DependencyNodeFilter filter;
+    private final Predicate<DependencyNode> filter;
 
     private final Deque<Boolean> acceptedNodes = new ArrayDeque<>();
 
-    PruningDependencyNodeVisitor(DependencyNodeVisitor visitor, DependencyNodeFilter filter) {
+    PruningDependencyNodeVisitor(DependencyNodeVisitor visitor, Predicate<DependencyNode> filter) {
         this.visitor = visitor;
         this.filter = filter;
     }
 
     @Override
     public boolean visit(DependencyNode node) {
-        boolean accepted = filter.accept(node);
+        boolean accepted = filter.test(node);
         acceptedNodes.push(accepted);
         return accepted && visitor.visit(node);
     }
diff --git a/src/main/java/org/apache/maven/plugins/dependency/tree/SerializingDependencyNodeVisitor.java b/src/main/java/org/apache/maven/plugins/dependency/tree/SerializingDependencyNodeVisitor.java
new file mode 100644
index 00000000..448e89f9
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/dependency/tree/SerializingDependencyNodeVisitor.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.plugins.dependency.tree;
+
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.util.List;
+
+/**
+ * Writes the dependency tree as indented text.
+ */
+class SerializingDependencyNodeVisitor implements DependencyNodeVisitor {
+
+    static class GraphTokens {
+        private final String nodeIndent;
+
+        private final String lastNodeIndent;
+
+        private final String fillIndent;
+
+        private final String lastFillIndent;
+
+        GraphTokens(String nodeIndent, String lastNodeIndent, String fillIndent, String lastFillIndent) {
+            this.nodeIndent = nodeIndent;
+            this.lastNodeIndent = lastNodeIndent;
+            this.fillIndent = fillIndent;
+            this.lastFillIndent = lastFillIndent;
+        }
+
+        String getNodeIndent(boolean last) {
+            return last ? lastNodeIndent : nodeIndent;
+        }
+
+        String getFillIndent(boolean last) {
+            return last ? lastFillIndent : fillIndent;
+        }
+    }
+
+    static final GraphTokens WHITESPACE_TOKENS = new GraphTokens("   ", "   ", "   ", "   ");
+
+    static final GraphTokens STANDARD_TOKENS = new GraphTokens("+- ", "\\- ", "|  ", "   ");
+
+    static final GraphTokens EXTENDED_TOKENS = new GraphTokens("├─ ", "└─ ", "│  ", "   ");
+
+    private final PrintWriter writer;
+
+    private final GraphTokens tokens;
+
+    private int depth;
+
+    SerializingDependencyNodeVisitor(Writer writer, GraphTokens tokens) {
+        this.writer = writer instanceof PrintWriter ? (PrintWriter) writer : new PrintWriter(writer, true);
+        this.tokens = tokens;
+    }
+
+    @Override
+    public boolean visit(DependencyNode node) {
+        indent(node);
+
+        writer.println(node.toNodeString());
+
+        depth++;
+
+        return true;
+    }
+
+    @Override
+    public boolean endVisit(DependencyNode node) {
+        depth--;
+
+        return true;
+    }
+
+    private void indent(DependencyNode node) {
+        for (int i = 1; i < depth; i++) {
+            writer.write(tokens.getFillIndent(isLast(node, i)));
+        }
+
+        if (depth > 0) {
+            writer.write(tokens.getNodeIndent(isLast(node)));
+        }
+    }
+
+    private boolean isLast(DependencyNode node) {
+        DependencyNode parent = node.getParent();
+
+        if (parent == null) {
+            return true;
+        }
+
+        List<DependencyNode> siblings = parent.getChildren();
+
+        return siblings.indexOf(node) == siblings.size() - 1;
+    }
+
+    private boolean isLast(DependencyNode node, int ancestorDepth) {
+        int distance = depth - ancestorDepth;
+
+        while (distance-- > 0) {
+            node = node.getParent();
+        }
+
+        return isLast(node);
+    }
+}
diff --git a/src/main/java/org/apache/maven/plugins/dependency/tree/TGFDependencyNodeVisitor.java b/src/main/java/org/apache/maven/plugins/dependency/tree/TGFDependencyNodeVisitor.java
index 3dc3a328..e5dbc64b 100644
--- a/src/main/java/org/apache/maven/plugins/dependency/tree/TGFDependencyNodeVisitor.java
+++ b/src/main/java/org/apache/maven/plugins/dependency/tree/TGFDependencyNodeVisitor.java
@@ -24,9 +24,6 @@ import java.io.Writer;
 import java.util.ArrayList;
 import java.util.List;
 
-import org.apache.maven.shared.dependency.graph.DependencyNode;
-import org.apache.maven.shared.dependency.graph.traversal.DependencyNodeVisitor;
-
 /**
  * A dependency node visitor that serializes visited nodes to a writer using the
  * <a href="https://en.wikipedia.org/wiki/Trivial_Graph_Format">TGF format</a>.
diff --git a/src/main/java/org/apache/maven/plugins/dependency/tree/TreeMojo.java b/src/main/java/org/apache/maven/plugins/dependency/tree/TreeMojo.java
index cbb0af33..ff213501 100644
--- a/src/main/java/org/apache/maven/plugins/dependency/tree/TreeMojo.java
+++ b/src/main/java/org/apache/maven/plugins/dependency/tree/TreeMojo.java
@@ -24,8 +24,12 @@ import java.io.File;
 import java.io.IOException;
 import java.io.StringWriter;
 import java.io.Writer;
+import java.util.Collections;
+import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
+import java.util.function.Predicate;
 
 import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
 import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
@@ -36,25 +40,15 @@ import org.apache.maven.plugin.MojoFailureException;
 import org.apache.maven.plugins.annotations.Mojo;
 import org.apache.maven.plugins.annotations.Parameter;
 import org.apache.maven.plugins.annotations.ResolutionScope;
+import org.apache.maven.plugins.dependency.tree.SerializingDependencyNodeVisitor.GraphTokens;
 import org.apache.maven.plugins.dependency.utils.DependencyUtil;
-import org.apache.maven.project.DefaultProjectBuildingRequest;
+import org.apache.maven.project.DependencyResolutionException;
 import org.apache.maven.project.MavenProject;
-import org.apache.maven.project.ProjectBuildingRequest;
+import org.apache.maven.project.ProjectDependenciesResolver;
 import org.apache.maven.shared.artifact.filter.StrictPatternExcludesArtifactFilter;
 import org.apache.maven.shared.artifact.filter.StrictPatternIncludesArtifactFilter;
-import org.apache.maven.shared.dependency.graph.DependencyCollectorBuilder;
-import org.apache.maven.shared.dependency.graph.DependencyCollectorBuilderException;
-import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
-import org.apache.maven.shared.dependency.graph.DependencyGraphBuilderException;
-import org.apache.maven.shared.dependency.graph.DependencyNode;
-import org.apache.maven.shared.dependency.graph.filter.AncestorOrSelfDependencyNodeFilter;
-import org.apache.maven.shared.dependency.graph.filter.ArtifactDependencyNodeFilter;
-import org.apache.maven.shared.dependency.graph.filter.DependencyNodeFilter;
-import org.apache.maven.shared.dependency.graph.traversal.CollectingDependencyNodeVisitor;
-import org.apache.maven.shared.dependency.graph.traversal.DependencyNodeVisitor;
-import org.apache.maven.shared.dependency.graph.traversal.FilteringDependencyNodeVisitor;
-import org.apache.maven.shared.dependency.graph.traversal.SerializingDependencyNodeVisitor;
-import org.apache.maven.shared.dependency.graph.traversal.SerializingDependencyNodeVisitor.GraphTokens;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.collection.DependencyCollectionException;
 
 /**
  * Displays the dependency tree for this project. Multiple formats are supported: text (by default), but also
@@ -77,15 +71,7 @@ public class TreeMojo extends AbstractMojo {
 
     private final MavenSession session;
 
-    /**
-     * The dependency collector builder to use.
-     */
-    private final DependencyCollectorBuilder dependencyCollectorBuilder;
-
-    /**
-     * The dependency graph builder to use.
-     */
-    private final DependencyGraphBuilder dependencyGraphBuilder;
+    private final DependencyTreeBuilder dependencyTreeBuilder;
 
     @Parameter(property = "outputEncoding", defaultValue = "${project.reporting.outputEncoding}")
     private String outputEncoding;
@@ -208,12 +194,11 @@ public class TreeMojo extends AbstractMojo {
     public TreeMojo(
             MavenProject project,
             MavenSession session,
-            DependencyCollectorBuilder dependencyCollectorBuilder,
-            DependencyGraphBuilder dependencyGraphBuilder) {
+            RepositorySystem repositorySystem,
+            ProjectDependenciesResolver projectDependenciesResolver) {
         this.project = project;
         this.session = session;
-        this.dependencyCollectorBuilder = dependencyCollectorBuilder;
-        this.dependencyGraphBuilder = dependencyGraphBuilder;
+        this.dependencyTreeBuilder = new DependencyTreeBuilder(repositorySystem, projectDependenciesResolver);
     }
 
     // Mojo methods -----------------------------------------------------------
@@ -229,27 +214,17 @@ public class TreeMojo extends AbstractMojo {
         }
 
         try {
-            String dependencyTreeString;
-
-            // TODO: note that filter does not get applied due to MSHARED-4
             ArtifactFilter artifactFilter = createResolvingArtifactFilter();
 
-            ProjectBuildingRequest buildingRequest =
-                    new DefaultProjectBuildingRequest(session.getProjectBuildingRequest());
-
-            buildingRequest.setProject(project);
-
             if (verbose) {
-                rootNode = dependencyCollectorBuilder.collectDependencyGraph(buildingRequest, artifactFilter);
-                dependencyTreeString = serializeDependencyTree(rootNode);
+                rootNode = dependencyTreeBuilder.buildVerbose(project, session.getRepositorySession(), artifactFilter);
             } else {
-                // non-verbose mode use dependency graph component, which gives consistent results with Maven version
-                // running
-                rootNode = dependencyGraphBuilder.buildDependencyGraph(buildingRequest, artifactFilter);
-
-                dependencyTreeString = serializeDependencyTree(rootNode);
+                // non-verbose mode shows what Maven resolves, consistent with the Maven version running
+                rootNode = dependencyTreeBuilder.build(project, session.getRepositorySession(), artifactFilter);
             }
 
+            String dependencyTreeString = serializeDependencyTree(rootNode);
+
             if (outputFile != null) {
                 String encoding = Objects.toString(outputEncoding, "UTF-8");
                 DependencyUtil.write(dependencyTreeString, outputFile, this.appendOutput, encoding);
@@ -258,7 +233,7 @@ public class TreeMojo extends AbstractMojo {
             } else {
                 DependencyUtil.log(dependencyTreeString, getLog());
             }
-        } catch (DependencyGraphBuilderException | DependencyCollectorBuilderException exception) {
+        } catch (DependencyResolutionException | DependencyCollectionException exception) {
             throw new MojoExecutionException("Cannot build project dependency graph", exception);
         } catch (IOException exception) {
             throw new MojoExecutionException("Cannot serialize project dependency graph", exception);
@@ -335,8 +310,8 @@ public class TreeMojo extends AbstractMojo {
         // TODO: remove the need for this when the serializer can calculate last nodes from visitor calls only
         visitor = new BuildingDependencyNodeVisitor(visitor);
 
-        DependencyNodeFilter includesFilter = createIncludesDependencyNodeFilter();
-        DependencyNodeFilter excludesFilter = createExcludesDependencyNodeFilter();
+        Predicate<DependencyNode> includesFilter = createIncludesDependencyNodeFilter();
+        Predicate<DependencyNode> excludesFilter = createExcludesDependencyNodeFilter();
 
         if (includesFilter != null) {
             CollectingDependencyNodeVisitor collectingVisitor = new CollectingDependencyNodeVisitor();
@@ -347,9 +322,14 @@ public class TreeMojo extends AbstractMojo {
             }
             theRootNode.accept(firstPassVisitor);
 
-            DependencyNodeFilter secondPassFilter =
-                    new AncestorOrSelfDependencyNodeFilter(collectingVisitor.getNodes());
-            visitor = new FilteringDependencyNodeVisitor(visitor, secondPassFilter);
+            // keep the included nodes together with their ancestors
+            Set<DependencyNode> ancestorOrSelf = Collections.newSetFromMap(new IdentityHashMap<>());
+            for (DependencyNode included : collectingVisitor.getNodes()) {
+                for (DependencyNode node = included; node != null; node = node.getParent()) {
+                    ancestorOrSelf.add(node);
+                }
+            }
+            visitor = new FilteringDependencyNodeVisitor(visitor, ancestorOrSelf::contains);
         }
 
         if (excludesFilter != null) {
@@ -408,23 +388,23 @@ public class TreeMojo extends AbstractMojo {
      *
      * @return the dependency node filter, or <code>null</code> if none required
      */
-    private DependencyNodeFilter createIncludesDependencyNodeFilter() {
+    private Predicate<DependencyNode> createIncludesDependencyNodeFilter() {
         if (includes != null && !includes.isEmpty()) {
             getLog().debug("+ Filtering dependency tree by artifact include patterns: " + includes);
 
             ArtifactFilter artifactFilter = new StrictPatternIncludesArtifactFilter(includes);
-            return new ArtifactDependencyNodeFilter(artifactFilter);
+            return node -> artifactFilter.include(node.getArtifact());
         }
 
         return null;
     }
 
-    private DependencyNodeFilter createExcludesDependencyNodeFilter() {
+    private Predicate<DependencyNode> createExcludesDependencyNodeFilter() {
         if (excludes != null && !excludes.isEmpty()) {
             getLog().debug("+ Filtering dependency tree by artifact exclude patterns: " + excludes);
 
             ArtifactFilter artifactFilter = new StrictPatternExcludesArtifactFilter(excludes);
-            return new ArtifactDependencyNodeFilter(artifactFilter);
+            return node -> artifactFilter.include(node.getArtifact());
         }
 
         return null;
diff --git a/src/main/java/org/apache/maven/plugins/dependency/tree/VerboseJavaScopeSelector.java b/src/main/java/org/apache/maven/plugins/dependency/tree/VerboseJavaScopeSelector.java
new file mode 100644
index 00000000..1ddeb893
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/dependency/tree/VerboseJavaScopeSelector.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.plugins.dependency.tree;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+
+import org.eclipse.aether.RepositoryException;
+import org.eclipse.aether.util.graph.transformer.ConflictResolver.ConflictContext;
+import org.eclipse.aether.util.graph.transformer.ConflictResolver.ScopeSelector;
+import org.eclipse.aether.util.graph.transformer.JavaScopeSelector;
+
+/**
+ * Selects the scope like Maven does and records on the winning node a wider scope that was not applied, so the verbose
+ * tree can report it.
+ */
+class VerboseJavaScopeSelector extends ScopeSelector {
+    static final String REDUCED_SCOPE = "REDUCED_SCOPE";
+
+    private static final List<String> ORDERED_SCOPES = Arrays.asList("compile", "runtime", "provided", "test");
+
+    private final ScopeSelector scopeSelector = new JavaScopeSelector();
+
+    @Override
+    public void selectScope(ConflictContext context) throws RepositoryException {
+        scopeSelector.selectScope(context);
+
+        context.getItems().stream()
+                .flatMap(i -> i.getScopes().stream())
+                .distinct()
+                .max(Comparator.comparingInt((String s) -> ORDERED_SCOPES.indexOf(s))
+                        .reversed())
+                .filter(s -> s != context.getScope())
+                .ifPresent(s -> context.getWinner().getNode().setData(REDUCED_SCOPE, s));
+    }
+}
diff --git a/src/test/java/org/apache/maven/plugins/dependency/tree/PruningDependencyNodeVisitorTest.java b/src/test/java/org/apache/maven/plugins/dependency/tree/PruningDependencyNodeVisitorTest.java
index 2231d7c5..9a1b0b27 100644
--- a/src/test/java/org/apache/maven/plugins/dependency/tree/PruningDependencyNodeVisitorTest.java
+++ b/src/test/java/org/apache/maven/plugins/dependency/tree/PruningDependencyNodeVisitorTest.java
@@ -21,13 +21,9 @@ package org.apache.maven.plugins.dependency.tree;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Predicate;
 
 import org.apache.maven.artifact.Artifact;
-import org.apache.maven.shared.dependency.graph.DependencyNode;
-import org.apache.maven.shared.dependency.graph.filter.DependencyNodeFilter;
-import org.apache.maven.shared.dependency.graph.internal.DefaultDependencyNode;
-import org.apache.maven.shared.dependency.graph.traversal.CollectingDependencyNodeVisitor;
-import org.apache.maven.shared.dependency.graph.traversal.DependencyNodeVisitor;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -36,7 +32,7 @@ import static org.mockito.Mockito.mock;
 class PruningDependencyNodeVisitorTest {
     @Test
     void evaluatesFilterOnlyWhenStartingNodeVisit() {
-        DefaultDependencyNode root = newNode(null);
+        DependencyNode root = newNode(null);
         root.setChildren(Collections.emptyList());
         AtomicInteger filterInvocations = new AtomicInteger();
         AtomicInteger endVisits = new AtomicInteger();
@@ -61,10 +57,10 @@ class PruningDependencyNodeVisitorTest {
 
     @Test
     void prunesRejectedSubtreeAndContinuesWithSiblings() {
-        DefaultDependencyNode root = newNode(null);
-        DefaultDependencyNode rejected = newNode(root);
-        DefaultDependencyNode rejectedChild = newNode(rejected);
-        DefaultDependencyNode sibling = newNode(root);
+        DependencyNode root = newNode(null);
+        DependencyNode rejected = newNode(root);
+        DependencyNode rejectedChild = newNode(rejected);
+        DependencyNode sibling = newNode(root);
 
         root.setChildren(Arrays.asList(rejected, sibling));
         rejected.setChildren(Collections.singletonList(rejectedChild));
@@ -72,14 +68,14 @@ class PruningDependencyNodeVisitorTest {
         sibling.setChildren(Collections.emptyList());
 
         CollectingDependencyNodeVisitor collectingVisitor = new CollectingDependencyNodeVisitor();
-        DependencyNodeFilter filter = node -> node != rejected;
+        Predicate<DependencyNode> filter = node -> node != rejected;
 
         root.accept(new PruningDependencyNodeVisitor(collectingVisitor, filter));
 
         assertEquals(Arrays.asList(root, sibling), collectingVisitor.getNodes());
     }
 
-    private DefaultDependencyNode newNode(DependencyNode parent) {
-        return new DefaultDependencyNode(parent, mock(Artifact.class), null, null, null);
+    private DependencyNode newNode(DependencyNode parent) {
+        return new DependencyNode(parent, mock(Artifact.class), "node");
     }
 }
diff --git a/src/test/java/org/apache/maven/plugins/dependency/tree/TestTreeMojo.java b/src/test/java/org/apache/maven/plugins/dependency/tree/TestTreeMojo.java
index 487a1e84..034ef3b9 100644
--- a/src/test/java/org/apache/maven/plugins/dependency/tree/TestTreeMojo.java
+++ b/src/test/java/org/apache/maven/plugins/dependency/tree/TestTreeMojo.java
@@ -46,8 +46,6 @@ import org.apache.maven.execution.MavenSession;
 import org.apache.maven.plugins.dependency.testUtils.DependencyArtifactStubFactory;
 import org.apache.maven.project.DefaultProjectBuildingRequest;
 import org.apache.maven.project.MavenProject;
-import org.apache.maven.shared.dependency.graph.DependencyNode;
-import org.apache.maven.shared.dependency.graph.internal.DefaultDependencyNode;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
@@ -188,8 +186,8 @@ class TestTreeMojo {
 
         Artifact artifact1 = this.stubFactory.createArtifact("testGroupId", "project1", "1.0");
         Artifact artifact2 = this.stubFactory.createArtifact("testGroupId", "project2", "1.0");
-        DefaultDependencyNode node1 = new DefaultDependencyNode(artifact1);
-        DefaultDependencyNode node2 = new DefaultDependencyNode(artifact2);
+        DependencyNode node1 = new DependencyNode(null, artifact1, artifact1.toString());
+        DependencyNode node2 = new DependencyNode(null, artifact2, artifact2.toString());
 
         node1.setChildren(new ArrayList<>());
         node2.setChildren(new ArrayList<>());