This is an automated email from the ASF dual-hosted git repository.
elharo pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-dependency-plugin.git
The following commit(s) were added to refs/heads/master by this push:
new 09775b3c [MDEP-799] tree: add optional output type json (#391)
09775b3c is described below
commit 09775b3c0effa7d102b4e71ce8c3688caa6dee71
Author: Elias Lundell <[email protected]>
AuthorDate: Thu May 23 13:59:40 2024 +0200
[MDEP-799] tree: add optional output type json (#391)
* [MDEP-799] tree: add optional output type json
---------
Co-authored-by: Martin Wittlinger <[email protected]>
---
pom.xml | 12 ++
.../dependency/tree/JsonDependencyNodeVisitor.java | 204 +++++++++++++++++++++
.../maven/plugins/dependency/tree/TreeMojo.java | 2 +
.../plugins/dependency/tree/TestTreeMojo.java | 79 ++++++++
4 files changed, 297 insertions(+)
diff --git a/pom.xml b/pom.xml
index b28e8a3d..44ff40a4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -343,6 +343,18 @@ under the License.
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>javax.json</groupId>
+ <artifactId>javax.json-api</artifactId>
+ <version>1.1.4</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.glassfish</groupId>
+ <artifactId>javax.json</artifactId>
+ <version>1.1.4</version>
+ <scope>test</scope>
+ </dependency>
<dependency>
<!-- support for JUnit 4 -->
<groupId>org.junit.vintage</groupId>
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
new file mode 100644
index 00000000..52fd180b
--- /dev/null
+++
b/src/main/java/org/apache/maven/plugins/dependency/tree/JsonDependencyNodeVisitor.java
@@ -0,0 +1,204 @@
+/*
+ * 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.Writer;
+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.
+ */
+public class JsonDependencyNodeVisitor extends AbstractSerializingVisitor
implements DependencyNodeVisitor {
+
+ private String indentChar = " ";
+
+ /**
+ * Creates a new instance of {@link JsonDependencyNodeVisitor}. The writer
will be used to write the output.
+ *
+ * @param writer the writer to write to
+ */
+ public JsonDependencyNodeVisitor(Writer writer) {
+ super(writer);
+ }
+
+ @Override
+ public boolean visit(DependencyNode node) {
+ if (node.getParent() == null || node.getParent() == node) {
+ writeRootNode(node);
+ }
+ return true;
+ }
+
+ /**
+ * Writes the node to the writer. This method is recursive and will write
all children nodes.
+ *
+ * @param node the node to write
+ */
+ private void writeRootNode(DependencyNode node) {
+ Set<DependencyNode> visited = new HashSet<DependencyNode>();
+ int indent = 2;
+ StringBuilder sb = new StringBuilder();
+ sb.append("{").append("\n");
+ writeNode(indent, node, sb, visited);
+ sb.append("}").append("\n");
+ writer.write(sb.toString());
+ }
+ /**
+ * Appends the node and its children to the string builder.
+ *
+ * @param indent the current indent level
+ * @param node the node to write
+ * @param sb the string builder to append to
+ */
+ private void writeNode(int indent, DependencyNode node, StringBuilder sb,
Set<DependencyNode> visited) {
+ if (visited.contains(node)) {
+ // Circular dependency detected
+ // Should an exception be thrown?
+ return;
+ }
+ visited.add(node);
+ appendNodeValues(sb, indent, node.getArtifact(),
!node.getChildren().isEmpty());
+ if (!node.getChildren().isEmpty()) {
+ writeChildren(indent, node, sb, visited);
+ }
+ }
+ /**
+ * Writes the children of the node to the string builder. And each
children of each node will be written recursively.
+ *
+ * @param indent the current indent level
+ * @param node the node to write
+ * @param sb the string builder to append to
+ */
+ private void writeChildren(int indent, DependencyNode node, StringBuilder
sb, Set<DependencyNode> visited) {
+ sb.append(indent(indent)).append("\"children\": [").append("\n");
+ indent += 2;
+ for (int i = 0; i < node.getChildren().size(); i++) {
+ DependencyNode child = node.getChildren().get(i);
+ sb.append(indent(indent));
+ sb.append("{").append("\n");
+ writeNode(indent + 2, child, sb, visited);
+ sb.append(indent(indent)).append("}");
+ // we skip the comma for the last child
+ if (i != node.getChildren().size() - 1) {
+ sb.append(",");
+ }
+ sb.append("\n");
+ }
+ sb.append(indent(indent)).append("]").append("\n");
+ }
+
+ @Override
+ public boolean endVisit(DependencyNode node) {
+ return true;
+ }
+ /**
+ * Appends the artifact values to the string builder.
+ *
+ * @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
+ */
+ private void appendNodeValues(StringBuilder sb, int indent, Artifact
artifact, boolean hasChildren) {
+ 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) {
+ appendKeyValue(sb, indent, "optional",
String.valueOf(artifact.isOptional()));
+ } else {
+ appendKeyWithoutComma(sb, indent, "optional",
String.valueOf(artifact.isOptional()));
+ }
+ }
+ /**
+ * Appends a key value pair to the string builder.
+ *
+ * @param sb the string builder to append to
+ * @param indent the current indent level
+ * @param key the key used as json key
+ * @param value the value used as json value
+ */
+ private void appendKeyValue(StringBuilder sb, int indent, String key,
String value) {
+ if (value == null) {
+ value = "";
+ }
+
+ sb.append(indent(indent))
+ .append("\"")
+ .append(key)
+ .append("\"")
+ .append(":")
+ .append(indentChar)
+ .append("\"")
+ .append(value)
+ .append("\"")
+ .append(",")
+ .append("\n");
+ }
+ /**
+ * Appends a key value pair to the string builder without a comma at the
end. This is used for the last children of a node.
+ *
+ * @param sb the string builder to append to
+ * @param indent the current indent level
+ * @param key the key used as json key
+ * @param value the value used as json value
+ */
+ private void appendKeyWithoutComma(StringBuilder sb, int indent, String
key, String value) {
+ if (value == null) {
+ value = "";
+ }
+
+ sb.append(indent(indent))
+ .append("\"")
+ .append(key)
+ .append("\"")
+ .append(":")
+ .append(indentChar)
+ .append("\"")
+ .append(value)
+ .append("\"")
+ .append("\n");
+ }
+
+ /**
+ * Returns a string of {@link #indentChar} for the indent level.
+ *
+ * @param indent the number of indent levels
+ * @return the string of indent characters
+ */
+ private String indent(int indent) {
+ if (indent < 1) {
+ return "";
+ }
+
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < indent; i++) {
+ sb.append(indentChar);
+ }
+
+ return sb.toString();
+ }
+}
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 0b587a0d..3ee40cf5 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
@@ -377,6 +377,8 @@ public class TreeMojo extends AbstractMojo {
return new TGFDependencyNodeVisitor(writer);
} else if ("dot".equals(outputType)) {
return new DOTDependencyNodeVisitor(writer);
+ } else if ("json".equals(outputType)) {
+ return new JsonDependencyNodeVisitor(writer);
} else {
return new SerializingDependencyNodeVisitor(writer,
toGraphTokens(tokens));
}
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 79e73482..dc805152 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
@@ -18,9 +18,19 @@
*/
package org.apache.maven.plugins.dependency.tree;
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonObject;
+import javax.json.JsonReader;
+
import java.io.BufferedReader;
import java.io.File;
+import java.io.FileOutputStream;
import java.io.FileReader;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.StringReader;
+import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -32,6 +42,7 @@ import org.apache.maven.plugin.testing.stubs.MavenProjectStub;
import org.apache.maven.plugins.dependency.AbstractDependencyMojoTestCase;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.dependency.graph.DependencyNode;
+import org.apache.maven.shared.dependency.graph.internal.DefaultDependencyNode;
/**
* Tests <code>TreeMojo</code>.
@@ -137,6 +148,74 @@ public class TestTreeMojo extends
AbstractDependencyMojoTestCase {
assertTrue(findString(contents,
"testGroupId:release:jar:1.0:compile"));
}
+ /**
+ * Test the JSON format serialization on DependencyNodes with circular
dependence
+ */
+ public void testTreeJsonCircularDependency() throws IOException {
+ String outputFileName = testDir.getAbsolutePath() + "tree1.json";
+ File outputFile = new File(outputFileName);
+ Files.createDirectories(outputFile.getParentFile().toPath());
+ outputFile.createNewFile();
+
+ 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);
+
+ node1.setChildren(new ArrayList<DependencyNode>());
+ node2.setChildren(new ArrayList<DependencyNode>());
+
+ node1.getChildren().add(node2);
+ node2.getChildren().add(node1);
+
+ JsonDependencyNodeVisitor jsonDependencyNodeVisitor =
+ new JsonDependencyNodeVisitor(new OutputStreamWriter(new
FileOutputStream(outputFile)));
+
+ jsonDependencyNodeVisitor.visit(node1);
+ }
+
+ /*
+ * Test parsing of Json output and verify all key-value pairs
+ */
+ public void testTreeJsonParsing() throws Exception {
+ List<String> contents = runTreeMojo("tree2.json", "json");
+
+ try (JsonReader reader = Json.createReader(new
StringReader(String.join("\n", contents)))) {
+ JsonObject root = reader.readObject();
+
+ assertEquals(root.getString("groupId"), "testGroupId");
+ assertEquals(root.getString("artifactId"), "project");
+ assertEquals(root.getString("version"), "1.0");
+ assertEquals(root.getString("type"), "jar");
+ assertEquals(root.getString("scope"), "compile");
+ assertEquals(root.getString("classifier"), "");
+ assertEquals(root.getString("optional"), "false");
+
+ JsonArray children = root.getJsonArray("children");
+ assertEquals(children.size(), 2);
+
+ JsonObject child0 = children.getJsonObject(0);
+
+ assertEquals(child0.getString("groupId"), "testGroupId");
+ assertEquals(child0.getString("artifactId"), "release");
+ assertEquals(child0.getString("version"), "1.0");
+ assertEquals(child0.getString("type"), "jar");
+ assertEquals(child0.getString("scope"), "compile");
+ assertEquals(child0.getString("classifier"), "");
+ assertEquals(child0.getString("optional"), "false");
+
+ JsonObject child1 = children.getJsonObject(1);
+
+ assertEquals(child1.getString("groupId"), "testGroupId");
+ assertEquals(child1.getString("artifactId"), "snapshot");
+ assertEquals(child1.getString("version"), "2.0-SNAPSHOT");
+ assertEquals(child1.getString("type"), "jar");
+ assertEquals(child1.getString("scope"), "compile");
+ assertEquals(child1.getString("classifier"), "");
+ assertEquals(child1.getString("optional"), "false");
+ }
+ }
+
/**
* Help finding content in the given list of string
*