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/13 22:14:58 UTC
(maven-dependency-plugin) branch agent/mdep-962-json-verbose created (now 57ea0567)
This is an automated email from the ASF dual-hosted git repository.
slachiewicz pushed a change to branch agent/mdep-962-json-verbose
in repository https://gitbox.apache.org/repos/asf/maven-dependency-plugin.git
at 57ea0567 [MDEP-962] Report omitted reasons in verbose JSON tree output
This branch includes the following new commits:
new 57ea0567 [MDEP-962] Report omitted reasons in verbose JSON tree output
The 1 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.
(maven-dependency-plugin) 01/01: [MDEP-962] Report omitted reasons in verbose JSON tree output
Posted by sl...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
slachiewicz pushed a commit to branch agent/mdep-962-json-verbose
in repository https://gitbox.apache.org/repos/asf/maven-dependency-plugin.git
commit 57ea05673c49f3e83420eba9eb62d7d468a14e38
Author: Sylwester Lachiewicz <sl...@apache.org>
AuthorDate: Tue Oct 13 23:27:34 2026 +0200
[MDEP-962] Report omitted reasons in verbose JSON tree output
---
.../projects/tree-verbose-json/invoker.properties | 22 ++++
src/it/projects/tree-verbose-json/plain.properties | 19 ++++
src/it/projects/tree-verbose-json/pom.xml | 52 ++++++++++
.../projects/tree-verbose-json/verbose.properties | 20 ++++
src/it/projects/tree-verbose-json/verify.groovy | 53 ++++++++++
.../plugins/dependency/tree/DependencyNode.java | 92 +++++++++++++++++
.../dependency/tree/DependencyTreeBuilder.java | 40 ++++---
.../dependency/tree/JsonDependencyNodeVisitor.java | 53 +++++++++-
src/site/markdown/examples/tree-mojo.md | 1 +
.../tree/JsonDependencyNodeVisitorTest.java | 115 +++++++++++++++++++++
10 files changed, 449 insertions(+), 18 deletions(-)
diff --git a/src/it/projects/tree-verbose-json/invoker.properties b/src/it/projects/tree-verbose-json/invoker.properties
new file mode 100644
index 00000000..f7a40d29
--- /dev/null
+++ b/src/it/projects/tree-verbose-json/invoker.properties
@@ -0,0 +1,22 @@
+# 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.
+
+invoker.goals.1 = ${project.groupId}:${project.artifactId}:${project.version}:tree
+invoker.userPropertiesFile.1 = verbose.properties
+
+invoker.goals.2 = ${project.groupId}:${project.artifactId}:${project.version}:tree
+invoker.userPropertiesFile.2 = plain.properties
diff --git a/src/it/projects/tree-verbose-json/plain.properties b/src/it/projects/tree-verbose-json/plain.properties
new file mode 100644
index 00000000..4fbe19bb
--- /dev/null
+++ b/src/it/projects/tree-verbose-json/plain.properties
@@ -0,0 +1,19 @@
+# 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.
+
+outputType = json
+outputFile = target/plain.json
diff --git a/src/it/projects/tree-verbose-json/pom.xml b/src/it/projects/tree-verbose-json/pom.xml
new file mode 100644
index 00000000..7395ca17
--- /dev/null
+++ b/src/it/projects/tree-verbose-json/pom.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.apache.maven.plugins.dependency.its</groupId>
+ <artifactId>tree-verbose-json</artifactId>
+ <version>1.0-SNAPSHOT</version>
+
+ <description>
+ dependency:tree with outputType=json and verbose=true reports why a node was omitted.
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.maven.its.dependency</groupId>
+ <artifactId>scope-root</artifactId>
+ <version>1.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven.its.dependency</groupId>
+ <artifactId>scope-test-root</artifactId>
+ <version>1.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven.its.dependency</groupId>
+ <artifactId>provided-root</artifactId>
+ <version>1.0</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/src/it/projects/tree-verbose-json/verbose.properties b/src/it/projects/tree-verbose-json/verbose.properties
new file mode 100644
index 00000000..2e10bb33
--- /dev/null
+++ b/src/it/projects/tree-verbose-json/verbose.properties
@@ -0,0 +1,20 @@
+# 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.
+
+outputType = json
+verbose = true
+outputFile = target/verbose.json
diff --git a/src/it/projects/tree-verbose-json/verify.groovy b/src/it/projects/tree-verbose-json/verify.groovy
new file mode 100644
index 00000000..65b759a2
--- /dev/null
+++ b/src/it/projects/tree-verbose-json/verify.groovy
@@ -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.
+ */
+
+import groovy.json.JsonSlurper
+
+import static org.assertj.core.api.Assertions.assertThat
+
+def verbose = new JsonSlurper().parse(new File(basedir, "target/verbose.json"))
+
+// scope-root -> scope-leaf:1.0 is the node Maven keeps
+def kept = verbose.children[0].children[0]
+assertThat(kept.version).isEqualTo("1.0")
+assertThat(kept.included).isEqualTo(true)
+assertThat(kept).doesNotContainKey("omittedReason")
+
+// scope-test-root -> scope-leaf:2.0 loses to 1.0
+def omitted = verbose.children[1].children[0]
+assertThat(omitted.version).isEqualTo("2.0")
+assertThat(omitted.included).isEqualTo(false)
+assertThat(omitted.omittedReason).isEqualTo("conflict")
+assertThat(omitted.winnerVersion).isEqualTo("1.0")
+
+// provided-root -> scope-leaf:1.0 repeats the kept node
+def duplicate = verbose.children[2].children[0]
+assertThat(duplicate.version).isEqualTo("1.0")
+assertThat(duplicate.included).isEqualTo(false)
+assertThat(duplicate.omittedReason).isEqualTo("duplicate")
+assertThat(duplicate).doesNotContainKey("winnerVersion")
+
+// the direct dependencies are included
+assertThat(verbose.children*.included).containsOnly(true)
+
+// without verbose the JSON has none of the additional fields
+def plain = new File(basedir, "target/plain.json").text
+assertThat(plain).doesNotContain("included").doesNotContain("omittedReason").doesNotContain("winnerVersion")
+
+return true
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 4fbaeca6..cae0a933 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
@@ -37,6 +37,8 @@ public class DependencyNode {
private List<DependencyNode> children = Collections.emptyList();
+ private VerboseInfo verboseInfo;
+
/**
* @param parent the parent node, or <code>null</code> for the root
* @param artifact the artifact of this node
@@ -64,6 +66,22 @@ public class DependencyNode {
this.children = children;
}
+ /**
+ * @return why the node is omitted and what was managed, or <code>null</code> if the tree was not built verbose
+ * @since 3.12.0
+ */
+ public VerboseInfo getVerboseInfo() {
+ return verboseInfo;
+ }
+
+ /**
+ * @param verboseInfo see {@link #getVerboseInfo()}
+ * @since 3.12.0
+ */
+ public void setVerboseInfo(VerboseInfo verboseInfo) {
+ this.verboseInfo = verboseInfo;
+ }
+
/**
* @return the label of this node
*/
@@ -104,6 +122,7 @@ public class DependencyNode {
}
DependencyNode copy = new DependencyNode(newParent, artifact, nodeString);
+ copy.setVerboseInfo(verboseInfo);
List<DependencyNode> copiedChildren = new ArrayList<>(children.size());
for (DependencyNode child : children) {
DependencyNode copiedChild = child.filter(copy, keep, include);
@@ -119,4 +138,77 @@ public class DependencyNode {
copy.setChildren(copiedChildren);
return copy;
}
+
+ /**
+ * What the verbose tree knows about a node besides its artifact: whether it is omitted and why, and the version and
+ * scope that dependency management or scope selection replaced.
+ *
+ * @since 3.12.0
+ */
+ public static final class VerboseInfo {
+ /**
+ * The node repeats an artifact of the same version that is kept elsewhere in the tree.
+ */
+ public static final String OMITTED_FOR_DUPLICATE = "duplicate";
+
+ /**
+ * The node loses to another version of the artifact.
+ */
+ public static final String OMITTED_FOR_CONFLICT = "conflict";
+
+ private final String omittedReason;
+
+ private final String winnerVersion;
+
+ private final String premanagedVersion;
+
+ private final String premanagedScope;
+
+ private final String scopeNotUpdatedTo;
+
+ /**
+ * @param omittedReason {@link #OMITTED_FOR_DUPLICATE}, {@link #OMITTED_FOR_CONFLICT} or <code>null</code> if
+ * the node is included
+ * @param winnerVersion the version kept instead of this node, <code>null</code> if the node is included
+ * @param premanagedVersion the version before dependency management, or <code>null</code>
+ * @param premanagedScope the scope before dependency management, or <code>null</code>
+ * @param scopeNotUpdatedTo the scope that scope selection wanted but did not apply, or <code>null</code>
+ */
+ public VerboseInfo(
+ String omittedReason,
+ String winnerVersion,
+ String premanagedVersion,
+ String premanagedScope,
+ String scopeNotUpdatedTo) {
+ this.omittedReason = omittedReason;
+ this.winnerVersion = winnerVersion;
+ this.premanagedVersion = premanagedVersion;
+ this.premanagedScope = premanagedScope;
+ this.scopeNotUpdatedTo = scopeNotUpdatedTo;
+ }
+
+ public boolean isIncluded() {
+ return omittedReason == null;
+ }
+
+ public String getOmittedReason() {
+ return omittedReason;
+ }
+
+ public String getWinnerVersion() {
+ return winnerVersion;
+ }
+
+ public String getPremanagedVersion() {
+ return premanagedVersion;
+ }
+
+ public String getPremanagedScope() {
+ return premanagedScope;
+ }
+
+ public String getScopeNotUpdatedTo() {
+ return scopeNotUpdatedTo;
+ }
+ }
}
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
index 7f3ec0e4..ccd771fa 100644
--- a/src/main/java/org/apache/maven/plugins/dependency/tree/DependencyTreeBuilder.java
+++ b/src/main/java/org/apache/maven/plugins/dependency/tree/DependencyTreeBuilder.java
@@ -26,6 +26,7 @@ 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.plugins.dependency.tree.DependencyNode.VerboseInfo;
import org.apache.maven.project.DefaultDependencyResolutionRequest;
import org.apache.maven.project.DependencyResolutionException;
import org.apache.maven.project.DependencyResolutionRequest;
@@ -124,8 +125,10 @@ class DependencyTreeBuilder {
Artifact artifact,
ArtifactFilter filter,
boolean verbose) {
+ VerboseInfo info = verbose ? verboseInfo(node, artifact) : null;
DependencyNode current = new DependencyNode(
- parent, artifact, verbose ? verboseNodeString(node, artifact) : nodeString(artifact));
+ parent, artifact, verbose ? verboseNodeString(info, artifact) : nodeString(artifact));
+ current.setVerboseInfo(info);
List<DependencyNode> children = new ArrayList<>(node.getChildren().size());
for (org.eclipse.aether.graph.DependencyNode child : node.getChildren()) {
@@ -151,18 +154,31 @@ class DependencyTreeBuilder {
return artifact + (artifact.isOptional() ? " (optional)" : "");
}
- private static String verboseNodeString(org.eclipse.aether.graph.DependencyNode node, Artifact artifact) {
+ private static VerboseInfo verboseInfo(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;
+ String omittedReason = null;
if (winner != null) {
winnerVersion = winner.getArtifact().getBaseVersion();
+ omittedReason = winnerVersion.equals(artifact.getVersion())
+ ? VerboseInfo.OMITTED_FOR_DUPLICATE
+ : VerboseInfo.OMITTED_FOR_CONFLICT;
} else {
ignoredScope = (String) node.getData().get(VerboseJavaScopeSelector.REDUCED_SCOPE);
}
- boolean included = winnerVersion == null;
+ return new VerboseInfo(
+ omittedReason,
+ winnerVersion,
+ DependencyManagerUtils.getPremanagedVersion(node),
+ DependencyManagerUtils.getPremanagedScope(node),
+ ignoredScope);
+ }
+
+ private static String verboseNodeString(VerboseInfo info, Artifact artifact) {
+ boolean included = info.isIncluded();
StringBuilder buffer = new StringBuilder();
if (!included) {
@@ -171,22 +187,20 @@ class DependencyTreeBuilder {
buffer.append(artifact);
List<String> items = new ArrayList<>();
- String premanagedVersion = DependencyManagerUtils.getPremanagedVersion(node);
- if (premanagedVersion != null) {
- items.add("version managed from " + premanagedVersion);
+ if (info.getPremanagedVersion() != null) {
+ items.add("version managed from " + info.getPremanagedVersion());
}
- String premanagedScope = DependencyManagerUtils.getPremanagedScope(node);
- if (premanagedScope != null) {
- items.add("scope managed from " + premanagedScope);
+ if (info.getPremanagedScope() != null) {
+ items.add("scope managed from " + info.getPremanagedScope());
}
- if (ignoredScope != null) {
- items.add("scope not updated to " + ignoredScope);
+ if (info.getScopeNotUpdatedTo() != null) {
+ items.add("scope not updated to " + info.getScopeNotUpdatedTo());
}
if (!included) {
items.add(
- winnerVersion.equals(artifact.getVersion())
+ VerboseInfo.OMITTED_FOR_DUPLICATE.equals(info.getOmittedReason())
? "omitted for duplicate"
- : "omitted for conflict with " + winnerVersion);
+ : "omitted for conflict with " + info.getWinnerVersion());
}
if (!items.isEmpty()) {
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 630afcce..12f10a73 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
@@ -22,6 +22,9 @@ import java.io.IOException;
import java.io.UncheckedIOException;
import java.io.Writer;
import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
import java.util.Set;
import org.apache.maven.artifact.Artifact;
@@ -84,7 +87,7 @@ public class JsonDependencyNodeVisitor extends AbstractSerializingVisitor implem
return;
}
visited.add(node);
- appendNodeValues(sb, indent, node.getArtifact(), !node.getChildren().isEmpty());
+ appendNodeValues(sb, indent, node, !node.getChildren().isEmpty());
if (!node.getChildren().isEmpty()) {
writeChildren(indent, node, sb, visited);
}
@@ -123,21 +126,61 @@ public class JsonDependencyNodeVisitor extends AbstractSerializingVisitor implem
*
* @param sb the string builder to append to
* @param indent the current indent level
- * @param artifact the artifact to write
- * @param hasChildren true if the artifact has children
+ * @param node the node whose artifact, and verbose information if present, is written
+ * @param hasChildren true if the node has children
*/
- private void appendNodeValues(StringBuilder sb, int indent, Artifact artifact, boolean hasChildren) {
+ private void appendNodeValues(StringBuilder sb, int indent, DependencyNode node, boolean hasChildren) {
+ Artifact artifact = node.getArtifact();
+ Map<String, String> verboseValues = verboseValues(node.getVerboseInfo());
appendKeyValue(sb, indent, "groupId", artifact.getGroupId());
appendKeyValue(sb, indent, "artifactId", artifact.getArtifactId());
appendKeyValue(sb, indent, "version", artifact.getVersion());
appendKeyValue(sb, indent, "type", artifact.getType());
appendKeyValue(sb, indent, "scope", artifact.getScope());
appendKeyValue(sb, indent, "classifier", artifact.getClassifier());
- if (hasChildren) {
+ if (hasChildren || !verboseValues.isEmpty()) {
appendKeyValue(sb, indent, "optional", String.valueOf(artifact.isOptional()));
} else {
appendKeyWithoutComma(sb, indent, "optional", String.valueOf(artifact.isOptional()));
}
+
+ Iterator<Map.Entry<String, String>> entries = verboseValues.entrySet().iterator();
+ while (entries.hasNext()) {
+ Map.Entry<String, String> entry = entries.next();
+ sb.append(indent(indent))
+ .append("\"")
+ .append(entry.getKey())
+ .append("\": ")
+ .append(entry.getValue())
+ .append(entries.hasNext() || hasChildren ? "," : "")
+ .append(System.lineSeparator());
+ }
+ }
+
+ /**
+ * The additional JSON values of a verbose node, in output order. The values are already JSON: <code>included</code>
+ * is a boolean, the others are strings.
+ */
+ private static Map<String, String> verboseValues(DependencyNode.VerboseInfo info) {
+ Map<String, String> values = new LinkedHashMap<>();
+ if (info == null) {
+ return values;
+ }
+ values.put("included", String.valueOf(info.isIncluded()));
+ putString(values, "omittedReason", info.getOmittedReason());
+ if (DependencyNode.VerboseInfo.OMITTED_FOR_CONFLICT.equals(info.getOmittedReason())) {
+ putString(values, "winnerVersion", info.getWinnerVersion());
+ }
+ putString(values, "versionManagedFrom", info.getPremanagedVersion());
+ putString(values, "scopeManagedFrom", info.getPremanagedScope());
+ putString(values, "scopeNotUpdatedTo", info.getScopeNotUpdatedTo());
+ return values;
+ }
+
+ private static void putString(Map<String, String> values, String key, String value) {
+ if (value != null) {
+ values.put(key, "\"" + value + "\"");
+ }
}
/**
* Appends a key value pair to the string builder.
diff --git a/src/site/markdown/examples/tree-mojo.md b/src/site/markdown/examples/tree-mojo.md
index fcaa3f69..92afe5a7 100644
--- a/src/site/markdown/examples/tree-mojo.md
+++ b/src/site/markdown/examples/tree-mojo.md
@@ -64,6 +64,7 @@ mvn dependency:tree -DoutputType=<format> -DoutputFile=<filename>
- `classifier`: The classifier, if any (e.g., empty string if none).
- `optional`: Whether the dependency is optional (`true` or `false`).
- `children`: An array of dependency objects with the same structure, representing transitive dependencies.
+ - With `-Dverbose`, every node also has `included` (`false` for a node Maven leaves out of the resolved tree), and, when they apply, `omittedReason` (`duplicate` or `conflict`), `winnerVersion` (the version kept instead, for `conflict`), `versionManagedFrom`, `scopeManagedFrom` and `scopeNotUpdatedTo`. These fields are not written without `-Dverbose`.
- **Nested Dependencies**: Each dependency in the `children` array follows the same structure, allowing for recursive representation of the dependency tree.
**Example**:
diff --git a/src/test/java/org/apache/maven/plugins/dependency/tree/JsonDependencyNodeVisitorTest.java b/src/test/java/org/apache/maven/plugins/dependency/tree/JsonDependencyNodeVisitorTest.java
new file mode 100644
index 00000000..5d8d48c8
--- /dev/null
+++ b/src/test/java/org/apache/maven/plugins/dependency/tree/JsonDependencyNodeVisitorTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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.StringReader;
+import java.io.StringWriter;
+import java.util.Collections;
+
+import jakarta.json.Json;
+import jakarta.json.JsonObject;
+import jakarta.json.JsonReader;
+import jakarta.json.JsonValue;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.DefaultArtifact;
+import org.apache.maven.artifact.handler.DefaultArtifactHandler;
+import org.apache.maven.plugins.dependency.tree.DependencyNode.VerboseInfo;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class JsonDependencyNodeVisitorTest {
+
+ @Test
+ void nodeWithoutVerboseInfoHasNoVerboseFields() {
+ JsonObject json = toJson(new DependencyNode(null, artifact("a", "1.0"), "a"));
+
+ assertEquals(7, json.size());
+ assertEquals("false", json.getString("optional"));
+ assertFalse(json.containsKey("included"));
+ }
+
+ @Test
+ void includedNodeReportsIncludedAndManagement() {
+ DependencyNode node = new DependencyNode(null, artifact("a", "2.0"), "a");
+ node.setVerboseInfo(new VerboseInfo(null, null, "1.0", "test", "compile"));
+
+ JsonObject json = toJson(node);
+
+ assertEquals(JsonValue.TRUE, json.get("included"));
+ assertEquals("1.0", json.getString("versionManagedFrom"));
+ assertEquals("test", json.getString("scopeManagedFrom"));
+ assertEquals("compile", json.getString("scopeNotUpdatedTo"));
+ assertFalse(json.containsKey("omittedReason"));
+ assertFalse(json.containsKey("winnerVersion"));
+ }
+
+ @Test
+ void omittedForDuplicateHasReasonButNoWinnerVersion() {
+ DependencyNode node = new DependencyNode(null, artifact("a", "1.0"), "a");
+ node.setVerboseInfo(new VerboseInfo(VerboseInfo.OMITTED_FOR_DUPLICATE, "1.0", null, null, null));
+
+ JsonObject json = toJson(node);
+
+ assertEquals(JsonValue.FALSE, json.get("included"));
+ assertEquals("duplicate", json.getString("omittedReason"));
+ assertFalse(json.containsKey("winnerVersion"));
+ }
+
+ @Test
+ void omittedForConflictHasWinnerVersion() {
+ DependencyNode node = new DependencyNode(null, artifact("a", "1.0"), "a");
+ node.setVerboseInfo(new VerboseInfo(VerboseInfo.OMITTED_FOR_CONFLICT, "2.0", null, null, null));
+
+ JsonObject json = toJson(node);
+
+ assertEquals(JsonValue.FALSE, json.get("included"));
+ assertEquals("conflict", json.getString("omittedReason"));
+ assertEquals("2.0", json.getString("winnerVersion"));
+ }
+
+ @Test
+ void verboseFieldsOfChildrenAreNestedAndOutputStaysValidJson() {
+ DependencyNode root = new DependencyNode(null, artifact("root", "1.0"), "root");
+ DependencyNode child = new DependencyNode(root, artifact("child", "1.0"), "child");
+ child.setVerboseInfo(new VerboseInfo(VerboseInfo.OMITTED_FOR_DUPLICATE, "1.0", null, null, null));
+ root.setChildren(Collections.singletonList(child));
+
+ JsonObject json = toJson(root);
+
+ assertTrue(json.getJsonArray("children").getJsonObject(0).containsKey("omittedReason"));
+ assertFalse(json.containsKey("included"));
+ }
+
+ private static JsonObject toJson(DependencyNode node) {
+ StringWriter writer = new StringWriter();
+ node.accept(new JsonDependencyNodeVisitor(writer));
+ System.setProperty("jakarta.json.provider", "org.glassfish.json.JsonProviderImpl");
+ try (JsonReader reader = Json.createReader(new StringReader(writer.toString()))) {
+ return reader.readObject();
+ }
+ }
+
+ private static Artifact artifact(String artifactId, String version) {
+ return new DefaultArtifact(
+ "g", artifactId, version, Artifact.SCOPE_COMPILE, "jar", null, new DefaultArtifactHandler("jar"));
+ }
+}