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
      *

Reply via email to