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:01 UTC

(maven-dependency-plugin) branch master updated (d936836b -> defaf895)

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

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


    from d936836b Simplify GH PR template boilerplate
     new 1dfc4b65 [MDEP-969] Replace maven-dependency-tree with Maven Resolver
     new a47ce2eb Filter the dependency tree by copying it
     new defaf895 Compare scopes with equals in the verbose scope selector

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 pom.xml                                            |   5 -
 src/config/checkstyle-suppressions.xml             |   2 +-
 .../tree/BuildingDependencyNodeVisitor.java        | 243 ---------------------
 .../dependency/tree/DOTDependencyNodeVisitor.java  |   3 -
 .../plugins/dependency/tree/DependencyNode.java    | 122 +++++++++++
 .../DependencyNodeVisitor.java}                    |  24 +-
 .../dependency/tree/DependencyTreeBuilder.java     | 203 +++++++++++++++++
 .../tree/DirectScopeDependencySelector.java        |  76 +++++++
 .../tree/GraphmlDependencyNodeVisitor.java         |   3 -
 .../dependency/tree/JsonDependencyNodeVisitor.java |   2 -
 .../tree/PruningDependencyNodeVisitor.java         |  54 -----
 .../tree/SerializingDependencyNodeVisitor.java     | 121 ++++++++++
 .../dependency/tree/TGFDependencyNodeVisitor.java  |   3 -
 .../maven/plugins/dependency/tree/TreeMojo.java    |  98 ++-------
 .../dependency/tree/VerboseJavaScopeSelector.java  |  53 +++++
 .../dependency/tree/DependencyNodeTest.java        | 106 +++++++++
 .../tree/PruningDependencyNodeVisitorTest.java     |  85 -------
 .../plugins/dependency/tree/TestTreeMojo.java      |   6 +-
 18 files changed, 720 insertions(+), 489 deletions(-)
 delete mode 100644 src/main/java/org/apache/maven/plugins/dependency/tree/BuildingDependencyNodeVisitor.java
 create mode 100644 src/main/java/org/apache/maven/plugins/dependency/tree/DependencyNode.java
 copy src/main/java/org/apache/maven/plugins/dependency/{utils/translators/ArtifactTranslator.java => tree/DependencyNodeVisitor.java} (62%)
 create mode 100644 src/main/java/org/apache/maven/plugins/dependency/tree/DependencyTreeBuilder.java
 create mode 100644 src/main/java/org/apache/maven/plugins/dependency/tree/DirectScopeDependencySelector.java
 delete mode 100644 src/main/java/org/apache/maven/plugins/dependency/tree/PruningDependencyNodeVisitor.java
 create mode 100644 src/main/java/org/apache/maven/plugins/dependency/tree/SerializingDependencyNodeVisitor.java
 create mode 100644 src/main/java/org/apache/maven/plugins/dependency/tree/VerboseJavaScopeSelector.java
 create mode 100644 src/test/java/org/apache/maven/plugins/dependency/tree/DependencyNodeTest.java
 delete mode 100644 src/test/java/org/apache/maven/plugins/dependency/tree/PruningDependencyNodeVisitorTest.java


(maven-dependency-plugin) 03/03: Compare scopes with equals in the verbose scope selector

Posted by sl...@apache.org.
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 defaf895363dbdcf920ffaa8dc2aeff363dc4ef6
Author: Sylwester Lachiewicz <sl...@apache.org>
AuthorDate: Thu Oct 1 22:00:17 2026 +0200

    Compare scopes with equals in the verbose scope selector
---
 .../apache/maven/plugins/dependency/tree/VerboseJavaScopeSelector.java  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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
index 1ddeb893..566ceff9 100644
--- a/src/main/java/org/apache/maven/plugins/dependency/tree/VerboseJavaScopeSelector.java
+++ b/src/main/java/org/apache/maven/plugins/dependency/tree/VerboseJavaScopeSelector.java
@@ -47,7 +47,7 @@ class VerboseJavaScopeSelector extends ScopeSelector {
                 .distinct()
                 .max(Comparator.comparingInt((String s) -> ORDERED_SCOPES.indexOf(s))
                         .reversed())
-                .filter(s -> s != context.getScope())
+                .filter(s -> !s.equals(context.getScope()))
                 .ifPresent(s -> context.getWinner().getNode().setData(REDUCED_SCOPE, s));
     }
 }


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

Posted by sl...@apache.org.
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<>());


(maven-dependency-plugin) 02/03: Filter the dependency tree by copying it

Posted by sl...@apache.org.
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 a47ce2eb54984f58a591e5625ff4d0568c5b3a57
Author: Sylwester Lachiewicz <sl...@apache.org>
AuthorDate: Wed Sep 30 20:40:57 2026 +0200

    Filter the dependency tree by copying it
    
    With a plugin-owned node model, one recursive copy replaces the Collecting,
    Filtering, Pruning and Building visitor chain; output is unchanged.
---
 src/config/checkstyle-suppressions.xml             |   2 +-
 .../tree/BuildingDependencyNodeVisitor.java        | 128 ---------------------
 .../tree/CollectingDependencyNodeVisitor.java      |  45 --------
 .../plugins/dependency/tree/DependencyNode.java    |  33 ++++++
 .../tree/FilteringDependencyNodeVisitor.java       |  45 --------
 .../tree/PruningDependencyNodeVisitor.java         |  51 --------
 .../maven/plugins/dependency/tree/TreeMojo.java    |  38 +-----
 .../dependency/tree/DependencyNodeTest.java        | 106 +++++++++++++++++
 .../tree/PruningDependencyNodeVisitorTest.java     |  81 -------------
 9 files changed, 144 insertions(+), 385 deletions(-)

diff --git a/src/config/checkstyle-suppressions.xml b/src/config/checkstyle-suppressions.xml
index 3fea1ee7..b87cd0c4 100644
--- a/src/config/checkstyle-suppressions.xml
+++ b/src/config/checkstyle-suppressions.xml
@@ -21,5 +21,5 @@ under the License.
   "-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN"
   "https://checkstyle.org/dtds/suppressions_1_2.dtd">
  <suppressions>
-  <suppress checks="ParameterNumberCheck" files="DependencyUtil.java|DestFileFilter.java|UnpackUtil.java|BuildingDependencyNodeVisitor.java" />
+  <suppress checks="ParameterNumberCheck" files="DependencyUtil.java|DestFileFilter.java|UnpackUtil.java" />
 </suppressions>
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
deleted file mode 100644
index 88ac7ef5..00000000
--- a/src/main/java/org/apache/maven/plugins/dependency/tree/BuildingDependencyNodeVisitor.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * 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.Stack;
-
-/**
- * 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.
- *
- * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
- */
-public class BuildingDependencyNodeVisitor implements DependencyNodeVisitor {
-    // fields -----------------------------------------------------------------
-
-    /**
-     * The dependency node visitor to apply on the resultant dependency tree, or <code>null</code> for none.
-     */
-    private final DependencyNodeVisitor visitor;
-
-    /**
-     * The resultant tree parent nodes for the currently visited node.
-     */
-    private final Stack<DependencyNode> parentNodes;
-
-    /**
-     * The root node of the resultant tree.
-     */
-    private DependencyNode rootNode;
-
-    // constructors -----------------------------------------------------------
-
-    /**
-     * Creates a dependency node visitor that clones visited nodes into a new dependency tree.
-     */
-    public BuildingDependencyNodeVisitor() {
-        this(null);
-    }
-
-    /**
-     * Creates a dependency node visitor that clones visited nodes into a new dependency tree, and then applies the
-     * specified dependency node visitor on the resultant dependency tree.
-     *
-     * @param visitor the dependency node visitor to apply on the resultant dependency tree, or <code>null</code> for
-     *            none
-     */
-    public BuildingDependencyNodeVisitor(DependencyNodeVisitor visitor) {
-        this.visitor = visitor;
-
-        parentNodes = new Stack<>();
-    }
-
-    // DependencyNodeVisitor methods ------------------------------------------
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public boolean visit(DependencyNode node) {
-        // clone the node
-        DependencyNode newNode = new DependencyNode(
-                parentNodes.isEmpty() ? null : parentNodes.peek(), node.getArtifact(), node.toNodeString());
-        newNode.setChildren(new ArrayList<>());
-
-        if (parentNodes.empty()) {
-            rootNode = newNode;
-        } else {
-            DependencyNode parentNode = parentNodes.peek();
-            parentNode.getChildren().add(newNode);
-        }
-
-        parentNodes.push(newNode);
-
-        return true;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public boolean endVisit(DependencyNode node) {
-        parentNodes.pop();
-
-        // apply the visitor to the resultant tree on the last visit
-        if (parentNodes.empty() && visitor != null) {
-            rootNode.accept(visitor);
-        }
-
-        return true;
-    }
-
-    // public methods ---------------------------------------------------------
-
-    /**
-     * Gets the dependency node visitor that this visitor applies on the resultant dependency tree.
-     *
-     * @return the dependency node visitor, or <code>null</code> for none
-     */
-    public DependencyNodeVisitor getDependencyNodeVisitor() {
-        return visitor;
-    }
-
-    /**
-     * Gets the root node of the resultant dependency tree constructed by this visitor.
-     *
-     * @return the root node, or <code>null</code> if the source tree has not yet been visited
-     */
-    public DependencyNode getDependencyTree() {
-        return rootNode;
-    }
-}
diff --git a/src/main/java/org/apache/maven/plugins/dependency/tree/CollectingDependencyNodeVisitor.java b/src/main/java/org/apache/maven/plugins/dependency/tree/CollectingDependencyNodeVisitor.java
deleted file mode 100644
index 9d8b670d..00000000
--- a/src/main/java/org/apache/maven/plugins/dependency/tree/CollectingDependencyNodeVisitor.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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;
-
-/**
- * Collects all visited nodes.
- */
-class CollectingDependencyNodeVisitor implements DependencyNodeVisitor {
-    private final List<DependencyNode> nodes = new ArrayList<>();
-
-    @Override
-    public boolean visit(DependencyNode node) {
-        nodes.add(node);
-        return true;
-    }
-
-    @Override
-    public boolean endVisit(DependencyNode node) {
-        return true;
-    }
-
-    List<DependencyNode> getNodes() {
-        return Collections.unmodifiableList(nodes);
-    }
-}
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
index 2d90bd7e..4fbaeca6 100644
--- a/src/main/java/org/apache/maven/plugins/dependency/tree/DependencyNode.java
+++ b/src/main/java/org/apache/maven/plugins/dependency/tree/DependencyNode.java
@@ -18,8 +18,10 @@
  */
 package org.apache.maven.plugins.dependency.tree;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.function.Predicate;
 
 import org.apache.maven.artifact.Artifact;
 
@@ -86,4 +88,35 @@ public class DependencyNode {
 
         return visitor.endVisit(this);
     }
+
+    /**
+     * Copies this subtree for display. A node that <code>keep</code> rejects is left out together with its
+     * descendants; when <code>include</code> is given, a node is only kept if it or one of its descendants matches.
+     *
+     * @param newParent the parent of the copy, or <code>null</code> for the root
+     * @param keep the nodes to keep, may be <code>null</code> to keep all
+     * @param include the nodes to show together with their ancestors, may be <code>null</code> to show all
+     * @return the copy, or <code>null</code> if this node is left out
+     */
+    DependencyNode filter(DependencyNode newParent, Predicate<DependencyNode> keep, Predicate<DependencyNode> include) {
+        if (keep != null && !keep.test(this)) {
+            return null;
+        }
+
+        DependencyNode copy = new DependencyNode(newParent, artifact, nodeString);
+        List<DependencyNode> copiedChildren = new ArrayList<>(children.size());
+        for (DependencyNode child : children) {
+            DependencyNode copiedChild = child.filter(copy, keep, include);
+            if (copiedChild != null) {
+                copiedChildren.add(copiedChild);
+            }
+        }
+
+        if (include != null && copiedChildren.isEmpty() && !include.test(this)) {
+            return null;
+        }
+
+        copy.setChildren(copiedChildren);
+        return copy;
+    }
 }
diff --git a/src/main/java/org/apache/maven/plugins/dependency/tree/FilteringDependencyNodeVisitor.java b/src/main/java/org/apache/maven/plugins/dependency/tree/FilteringDependencyNodeVisitor.java
deleted file mode 100644
index 4761d732..00000000
--- a/src/main/java/org/apache/maven/plugins/dependency/tree/FilteringDependencyNodeVisitor.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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.function.Predicate;
-
-/**
- * Passes only the accepted nodes to the delegate, while still traversing the children of the rejected ones.
- */
-class FilteringDependencyNodeVisitor implements DependencyNodeVisitor {
-    private final DependencyNodeVisitor visitor;
-
-    private final Predicate<DependencyNode> filter;
-
-    FilteringDependencyNodeVisitor(DependencyNodeVisitor visitor, Predicate<DependencyNode> filter) {
-        this.visitor = visitor;
-        this.filter = filter;
-    }
-
-    @Override
-    public boolean visit(DependencyNode node) {
-        return !filter.test(node) || visitor.visit(node);
-    }
-
-    @Override
-    public boolean endVisit(DependencyNode node) {
-        return !filter.test(node) || visitor.endVisit(node);
-    }
-}
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
deleted file mode 100644
index daf0afba..00000000
--- a/src/main/java/org/apache/maven/plugins/dependency/tree/PruningDependencyNodeVisitor.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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.ArrayDeque;
-import java.util.Deque;
-import java.util.function.Predicate;
-
-/**
- * A dependency node visitor that delegates accepted nodes and prunes rejected nodes and their descendants.
- */
-final class PruningDependencyNodeVisitor implements DependencyNodeVisitor {
-    private final DependencyNodeVisitor visitor;
-
-    private final Predicate<DependencyNode> filter;
-
-    private final Deque<Boolean> acceptedNodes = new ArrayDeque<>();
-
-    PruningDependencyNodeVisitor(DependencyNodeVisitor visitor, Predicate<DependencyNode> filter) {
-        this.visitor = visitor;
-        this.filter = filter;
-    }
-
-    @Override
-    public boolean visit(DependencyNode node) {
-        boolean accepted = filter.test(node);
-        acceptedNodes.push(accepted);
-        return accepted && visitor.visit(node);
-    }
-
-    @Override
-    public boolean endVisit(DependencyNode node) {
-        return !acceptedNodes.pop() || visitor.endVisit(node);
-    }
-}
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 ff213501..828454ae 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,11 +24,8 @@ 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;
@@ -305,39 +302,12 @@ public class TreeMojo extends AbstractMojo {
     private String serializeDependencyTree(DependencyNode theRootNode) {
         StringWriter writer = new StringWriter();
 
-        DependencyNodeVisitor visitor = getSerializingDependencyNodeVisitor(writer);
-
-        // TODO: remove the need for this when the serializer can calculate last nodes from visitor calls only
-        visitor = new BuildingDependencyNodeVisitor(visitor);
-
-        Predicate<DependencyNode> includesFilter = createIncludesDependencyNodeFilter();
-        Predicate<DependencyNode> excludesFilter = createExcludesDependencyNodeFilter();
-
-        if (includesFilter != null) {
-            CollectingDependencyNodeVisitor collectingVisitor = new CollectingDependencyNodeVisitor();
-            DependencyNodeVisitor firstPassVisitor =
-                    new FilteringDependencyNodeVisitor(collectingVisitor, includesFilter);
-            if (excludesFilter != null) {
-                firstPassVisitor = new PruningDependencyNodeVisitor(firstPassVisitor, excludesFilter);
-            }
-            theRootNode.accept(firstPassVisitor);
-
-            // 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) {
-            visitor = new PruningDependencyNodeVisitor(visitor, excludesFilter);
+        DependencyNode displayed =
+                theRootNode.filter(null, createExcludesDependencyNodeFilter(), createIncludesDependencyNodeFilter());
+        if (displayed != null) {
+            displayed.accept(getSerializingDependencyNodeVisitor(writer));
         }
 
-        theRootNode.accept(visitor);
-
         return writer.toString();
     }
 
diff --git a/src/test/java/org/apache/maven/plugins/dependency/tree/DependencyNodeTest.java b/src/test/java/org/apache/maven/plugins/dependency/tree/DependencyNodeTest.java
new file mode 100644
index 00000000..453771c8
--- /dev/null
+++ b/src/test/java/org/apache/maven/plugins/dependency/tree/DependencyNodeTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.Arrays;
+import java.util.List;
+
+import org.apache.maven.artifact.Artifact;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.mockito.Mockito.mock;
+
+class DependencyNodeTest {
+    //   root
+    //   +- a
+    //   |  +- a1
+    //   |  \- a2
+    //   \- b
+    //      \- b1
+    private final DependencyNode root = newNode(null, "root");
+    private final DependencyNode a = newNode(root, "a");
+    private final DependencyNode a1 = newNode(a, "a1");
+    private final DependencyNode a2 = newNode(a, "a2");
+    private final DependencyNode b = newNode(root, "b");
+    private final DependencyNode b1 = newNode(b, "b1");
+
+    DependencyNodeTest() {
+        root.setChildren(Arrays.asList(a, b));
+        a.setChildren(Arrays.asList(a1, a2));
+        b.setChildren(Arrays.asList(b1));
+    }
+
+    @Test
+    void copiesWholeTreeWithoutFilters() {
+        assertEquals(Arrays.asList("root", "a", "a1", "a2", "b", "b1"), labels(root.filter(null, null, null)));
+    }
+
+    @Test
+    void prunesRejectedSubtreeAndKeepsSiblings() {
+        assertEquals(Arrays.asList("root", "b", "b1"), labels(root.filter(null, node -> node != a, null)));
+    }
+
+    @Test
+    void includesMatchesWithTheirAncestorsOnly() {
+        DependencyNode copy = root.filter(null, null, node -> node == a2 || node == b);
+
+        assertEquals(Arrays.asList("root", "a", "a2", "b"), labels(copy));
+        DependencyNode copiedA2 = copy.getChildren().get(0).getChildren().get(0);
+        assertSame(copy.getChildren().get(0), copiedA2.getParent());
+        assertSame(copy, copiedA2.getParent().getParent());
+    }
+
+    @Test
+    void doesNotIncludeMatchesInsideAPrunedSubtree() {
+        assertEquals(
+                Arrays.asList("root", "b", "b1"),
+                labels(root.filter(null, node -> node != a, node -> node == a1 || node == b1)));
+    }
+
+    @Test
+    void leavesOutEverythingWhenNothingMatches() {
+        assertNull(root.filter(null, null, node -> false));
+        assertNull(root.filter(null, node -> node != root, null));
+    }
+
+    private static List<String> labels(DependencyNode node) {
+        List<String> labels = new ArrayList<>();
+        node.accept(new DependencyNodeVisitor() {
+            @Override
+            public boolean visit(DependencyNode visited) {
+                labels.add(visited.toNodeString());
+                return true;
+            }
+
+            @Override
+            public boolean endVisit(DependencyNode visited) {
+                return true;
+            }
+        });
+        return labels;
+    }
+
+    private static DependencyNode newNode(DependencyNode parent, String label) {
+        return new DependencyNode(parent, mock(Artifact.class), label);
+    }
+}
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
deleted file mode 100644
index 9a1b0b27..00000000
--- a/src/test/java/org/apache/maven/plugins/dependency/tree/PruningDependencyNodeVisitorTest.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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.Collections;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Predicate;
-
-import org.apache.maven.artifact.Artifact;
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.mockito.Mockito.mock;
-
-class PruningDependencyNodeVisitorTest {
-    @Test
-    void evaluatesFilterOnlyWhenStartingNodeVisit() {
-        DependencyNode root = newNode(null);
-        root.setChildren(Collections.emptyList());
-        AtomicInteger filterInvocations = new AtomicInteger();
-        AtomicInteger endVisits = new AtomicInteger();
-        DependencyNodeVisitor visitor = new DependencyNodeVisitor() {
-            @Override
-            public boolean visit(DependencyNode node) {
-                return true;
-            }
-
-            @Override
-            public boolean endVisit(DependencyNode node) {
-                endVisits.incrementAndGet();
-                return true;
-            }
-        };
-
-        root.accept(new PruningDependencyNodeVisitor(visitor, node -> filterInvocations.incrementAndGet() == 1));
-
-        assertEquals(1, filterInvocations.get());
-        assertEquals(1, endVisits.get());
-    }
-
-    @Test
-    void prunesRejectedSubtreeAndContinuesWithSiblings() {
-        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));
-        rejectedChild.setChildren(Collections.emptyList());
-        sibling.setChildren(Collections.emptyList());
-
-        CollectingDependencyNodeVisitor collectingVisitor = new CollectingDependencyNodeVisitor();
-        Predicate<DependencyNode> filter = node -> node != rejected;
-
-        root.accept(new PruningDependencyNodeVisitor(collectingVisitor, filter));
-
-        assertEquals(Arrays.asList(root, sibling), collectingVisitor.getNodes());
-    }
-
-    private DependencyNode newNode(DependencyNode parent) {
-        return new DependencyNode(parent, mock(Artifact.class), "node");
-    }
-}