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 }