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.Writer;
22  import java.util.HashSet;
23  import java.util.Set;
24  
25  import org.apache.maven.artifact.Artifact;
26  import org.apache.maven.shared.dependency.graph.DependencyNode;
27  import org.apache.maven.shared.dependency.graph.traversal.DependencyNodeVisitor;
28  
29  /**
30   * A dependency node visitor that serializes visited nodes to a writer using the JSON format.
31   */
32  public class JsonDependencyNodeVisitor extends AbstractSerializingVisitor implements DependencyNodeVisitor {
33  
34      private String indentChar = " ";
35  
36      /**
37       * Creates a new instance of {@link JsonDependencyNodeVisitor}. The writer will be used to write the output.
38       *
39       * @param writer  the writer to write to
40       */
41      public JsonDependencyNodeVisitor(Writer writer) {
42          super(writer);
43      }
44  
45      @Override
46      public boolean visit(DependencyNode node) {
47          if (node.getParent() == null || node.getParent() == node) {
48              writeRootNode(node);
49          }
50          return true;
51      }
52  
53      /**
54       * Writes the node to the writer. This method is recursive and will write all children nodes.
55       *
56       * @param node  the node to write
57       */
58      private void writeRootNode(DependencyNode node) {
59          Set<DependencyNode> visited = new HashSet<DependencyNode>();
60          int indent = 2;
61          StringBuilder sb = new StringBuilder();
62          sb.append("{").append("\n");
63          writeNode(indent, node, sb, visited);
64          sb.append("}").append("\n");
65          writer.write(sb.toString());
66      }
67      /**
68       * Appends the node and its children to the string builder.
69       *
70       * @param indent  the current indent level
71       * @param node  the node to write
72       * @param sb  the string builder to append to
73       */
74      private void writeNode(int indent, DependencyNode node, StringBuilder sb, Set<DependencyNode> visited) {
75          if (visited.contains(node)) {
76              // Circular dependency detected
77              // Should an exception be thrown?
78              return;
79          }
80          visited.add(node);
81          appendNodeValues(sb, indent, node.getArtifact(), !node.getChildren().isEmpty());
82          if (!node.getChildren().isEmpty()) {
83              writeChildren(indent, node, sb, visited);
84          }
85      }
86      /**
87       * Writes the children of the node to the string builder. And each children of each node will be written recursively.
88       *
89       * @param indent  the current indent level
90       * @param node  the node to write
91       * @param sb  the string builder to append to
92       */
93      private void writeChildren(int indent, DependencyNode node, StringBuilder sb, Set<DependencyNode> visited) {
94          sb.append(indent(indent)).append("\"children\": [").append("\n");
95          indent += 2;
96          for (int i = 0; i < node.getChildren().size(); i++) {
97              DependencyNode child = node.getChildren().get(i);
98              sb.append(indent(indent));
99              sb.append("{").append("\n");
100             writeNode(indent + 2, child, sb, visited);
101             sb.append(indent(indent)).append("}");
102             // we skip the comma for the last child
103             if (i != node.getChildren().size() - 1) {
104                 sb.append(",");
105             }
106             sb.append("\n");
107         }
108         sb.append(indent(indent)).append("]").append("\n");
109     }
110 
111     @Override
112     public boolean endVisit(DependencyNode node) {
113         return true;
114     }
115     /**
116      * Appends the artifact values to the string builder.
117      *
118      * @param sb  the string builder to append to
119      * @param indent  the current indent level
120      * @param artifact  the artifact to write
121      * @param hasChildren  true if the artifact has children
122      */
123     private void appendNodeValues(StringBuilder sb, int indent, Artifact artifact, boolean hasChildren) {
124         appendKeyValue(sb, indent, "groupId", artifact.getGroupId());
125         appendKeyValue(sb, indent, "artifactId", artifact.getArtifactId());
126         appendKeyValue(sb, indent, "version", artifact.getVersion());
127         appendKeyValue(sb, indent, "type", artifact.getType());
128         appendKeyValue(sb, indent, "scope", artifact.getScope());
129         appendKeyValue(sb, indent, "classifier", artifact.getClassifier());
130         if (hasChildren) {
131             appendKeyValue(sb, indent, "optional", String.valueOf(artifact.isOptional()));
132         } else {
133             appendKeyWithoutComma(sb, indent, "optional", String.valueOf(artifact.isOptional()));
134         }
135     }
136     /**
137      * Appends a key value pair to the string builder.
138      *
139      * @param sb  the string builder to append to
140      * @param indent  the current indent level
141      * @param key  the key used as json key
142      * @param value  the value used as json value
143      */
144     private void appendKeyValue(StringBuilder sb, int indent, String key, String value) {
145         if (value == null) {
146             value = "";
147         }
148 
149         sb.append(indent(indent))
150                 .append("\"")
151                 .append(key)
152                 .append("\"")
153                 .append(":")
154                 .append(indentChar)
155                 .append("\"")
156                 .append(value)
157                 .append("\"")
158                 .append(",")
159                 .append("\n");
160     }
161     /**
162      * 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.
163      *
164      * @param sb  the string builder to append to
165      * @param indent  the current indent level
166      * @param key  the key used as json key
167      * @param value  the value used as json value
168      */
169     private void appendKeyWithoutComma(StringBuilder sb, int indent, String key, String value) {
170         if (value == null) {
171             value = "";
172         }
173 
174         sb.append(indent(indent))
175                 .append("\"")
176                 .append(key)
177                 .append("\"")
178                 .append(":")
179                 .append(indentChar)
180                 .append("\"")
181                 .append(value)
182                 .append("\"")
183                 .append("\n");
184     }
185 
186     /**
187      * Returns a string of {@link #indentChar} for the indent level.
188      *
189      * @param indent  the number of indent levels
190      * @return  the string of indent characters
191      */
192     private String indent(int indent) {
193         if (indent < 1) {
194             return "";
195         }
196 
197         StringBuilder sb = new StringBuilder();
198         for (int i = 0; i < indent; i++) {
199             sb.append(indentChar);
200         }
201 
202         return sb.toString();
203     }
204 }