View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugins.dependency.tree;
20  
21  import java.io.IOException;
22  import java.io.UncheckedIOException;
23  import java.io.Writer;
24  import java.util.HashSet;
25  import java.util.Set;
26  
27  import org.apache.maven.artifact.Artifact;
28  import org.apache.maven.shared.dependency.graph.DependencyNode;
29  import org.apache.maven.shared.dependency.graph.traversal.DependencyNodeVisitor;
30  
31  /**
32   * A dependency node visitor that serializes visited nodes to a writer using the JSON format.
33   */
34  public class JsonDependencyNodeVisitor extends AbstractSerializingVisitor implements DependencyNodeVisitor {
35  
36      private String indentChar = " ";
37  
38      /**
39       * Creates a new instance of {@link JsonDependencyNodeVisitor}. The writer will be used to write the output.
40       *
41       * @param writer  the writer to write to
42       */
43      public JsonDependencyNodeVisitor(Writer writer) {
44          super(writer);
45      }
46  
47      @Override
48      public boolean visit(DependencyNode node) {
49          try {
50              if (node.getParent() == null || node.getParent() == node) {
51                  writeRootNode(node);
52              }
53              return true;
54          } catch (IOException e) {
55              throw new UncheckedIOException("Failed to write JSON format output", e);
56          }
57      }
58  
59      /**
60       * Writes the node to the writer. This method is recursive and will write all children nodes.
61       *
62       * @param node  the node to write
63       * @throws IOException if an I/O error occurs while writing
64       */
65      private void writeRootNode(DependencyNode node) throws IOException {
66          Set<DependencyNode> visited = new HashSet<>();
67          int indent = 2;
68          StringBuilder sb = new StringBuilder();
69          sb.append("{").append(System.lineSeparator());
70          writeNode(indent, node, sb, visited);
71          sb.append("}").append(System.lineSeparator());
72          writer.write(sb.toString());
73          writer.flush();
74      }
75      /**
76       * Appends the node and its children to the string builder.
77       *
78       * @param indent  the current indent level
79       * @param node  the node to write
80       * @param sb  the string builder to append to
81       */
82      private void writeNode(int indent, DependencyNode node, StringBuilder sb, Set<DependencyNode> visited) {
83          if (visited.contains(node)) {
84              // Circular dependency detected
85              // Should an exception be thrown?
86              return;
87          }
88          visited.add(node);
89          appendNodeValues(sb, indent, node.getArtifact(), !node.getChildren().isEmpty());
90          if (!node.getChildren().isEmpty()) {
91              writeChildren(indent, node, sb, visited);
92          }
93      }
94      /**
95       * Writes the children of the node to the string builder. Each child of each node will be written recursively.
96       *
97       * @param indent  the current indent level
98       * @param node  the node to write
99       * @param sb  the string builder to append to
100      */
101     private void writeChildren(int indent, DependencyNode node, StringBuilder sb, Set<DependencyNode> visited) {
102         sb.append(indent(indent)).append("\"children\": [").append(System.lineSeparator());
103         indent += 2;
104         for (int i = 0; i < node.getChildren().size(); i++) {
105             DependencyNode child = node.getChildren().get(i);
106             sb.append(indent(indent));
107             sb.append("{").append(System.lineSeparator());
108             writeNode(indent + 2, child, sb, visited);
109             sb.append(indent(indent)).append("}");
110             // we skip the comma for the last child
111             if (i != node.getChildren().size() - 1) {
112                 sb.append(",");
113             }
114             sb.append(System.lineSeparator());
115         }
116         sb.append(indent(indent)).append("]").append(System.lineSeparator());
117     }
118 
119     @Override
120     public boolean endVisit(DependencyNode node) {
121         return true;
122     }
123     /**
124      * Appends the artifact values to the string builder.
125      *
126      * @param sb  the string builder to append to
127      * @param indent  the current indent level
128      * @param artifact  the artifact to write
129      * @param hasChildren  true if the artifact has children
130      */
131     private void appendNodeValues(StringBuilder sb, int indent, Artifact artifact, boolean hasChildren) {
132         appendKeyValue(sb, indent, "groupId", artifact.getGroupId());
133         appendKeyValue(sb, indent, "artifactId", artifact.getArtifactId());
134         appendKeyValue(sb, indent, "version", artifact.getVersion());
135         appendKeyValue(sb, indent, "type", artifact.getType());
136         appendKeyValue(sb, indent, "scope", artifact.getScope());
137         appendKeyValue(sb, indent, "classifier", artifact.getClassifier());
138         if (hasChildren) {
139             appendKeyValue(sb, indent, "optional", String.valueOf(artifact.isOptional()));
140         } else {
141             appendKeyWithoutComma(sb, indent, "optional", String.valueOf(artifact.isOptional()));
142         }
143     }
144     /**
145      * Appends a key value pair to the string builder.
146      *
147      * @param sb  the string builder to append to
148      * @param indent  the current indent level
149      * @param key  the key used as json key
150      * @param value  the value used as json value
151      */
152     private void appendKeyValue(StringBuilder sb, int indent, String key, String value) {
153         if (value == null) {
154             value = "";
155         }
156 
157         sb.append(indent(indent))
158                 .append("\"")
159                 .append(key)
160                 .append("\"")
161                 .append(":")
162                 .append(indentChar)
163                 .append("\"")
164                 .append(value)
165                 .append("\"")
166                 .append(",")
167                 .append(System.lineSeparator());
168     }
169     /**
170      * 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.
171      *
172      * @param sb  the string builder to append to
173      * @param indent  the current indent level
174      * @param key  the key used as json key
175      * @param value  the value used as json value
176      */
177     private void appendKeyWithoutComma(StringBuilder sb, int indent, String key, String value) {
178         if (value == null) {
179             value = "";
180         }
181 
182         sb.append(indent(indent))
183                 .append("\"")
184                 .append(key)
185                 .append("\"")
186                 .append(":")
187                 .append(indentChar)
188                 .append("\"")
189                 .append(value)
190                 .append("\"")
191                 .append(System.lineSeparator());
192     }
193 
194     /**
195      * Returns a string of {@link #indentChar} for the indent level.
196      *
197      * @param indent  the number of indent levels
198      * @return  the string of indent characters
199      */
200     private String indent(int indent) {
201         if (indent < 1) {
202             return "";
203         }
204 
205         StringBuilder sb = new StringBuilder();
206         for (int i = 0; i < indent; i++) {
207             sb.append(indentChar);
208         }
209 
210         return sb.toString();
211     }
212 }