2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.yangtools.yang.data.codec.gson;
10 import com.google.common.base.Preconditions;
11 import com.google.common.base.Strings;
12 import com.google.gson.stream.JsonWriter;
14 import java.io.IOException;
15 import java.io.Writer;
17 import java.util.ArrayDeque;
18 import java.util.Deque;
20 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
21 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
22 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
23 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
24 import org.opendaylight.yangtools.yang.model.api.Module;
25 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
28 * This implementation will create JSON output as output stream.
30 * Values of leaf and leaf-list are NOT translated according to codecs.
32 * FIXME: rewrite this in terms of {@link JsonWriter}.
34 public class JSONNormalizedNodeStreamWriter implements NormalizedNodeStreamWriter {
36 private static enum NodeType {
42 private static class TypeInfo {
43 private boolean hasAtLeastOneChild = false;
44 private final NodeType type;
45 private final URI uri;
47 public TypeInfo(final NodeType type, final URI uri) {
52 public void setHasAtLeastOneChild(final boolean hasChildren) {
53 this.hasAtLeastOneChild = hasChildren;
56 public NodeType getType() {
60 public URI getNamespace() {
64 public boolean hasAtLeastOneChild() {
65 return hasAtLeastOneChild;
69 private final Deque<TypeInfo> stack = new ArrayDeque<>();
70 private final SchemaContext schemaContext;
71 private final Writer writer;
72 private final String indent;
74 private URI currentNamespace = null;
75 private int currentDepth = 0;
77 private JSONNormalizedNodeStreamWriter(final SchemaContext schemaContext,
78 final Writer writer, final int indentSize) {
79 this.schemaContext = Preconditions.checkNotNull(schemaContext);
80 this.writer = Preconditions.checkNotNull(writer);
82 Preconditions.checkArgument(indentSize >= 0, "Indent size must be non-negative");
84 if (indentSize != 0) {
85 indent = Strings.repeat(" ", indentSize);
92 * Create a new stream writer, which writes to the specified {@link Writer}.
94 * @param schemaContext Schema context
95 * @param writer Output writer
96 * @return A stream writer instance
98 public static NormalizedNodeStreamWriter create(final SchemaContext schemaContext, final Writer writer) {
99 return new JSONNormalizedNodeStreamWriter(schemaContext, writer, 0);
103 * Create a new stream writer, which writes to the specified output stream.
105 * @param schemaContext Schema context
106 * @param writer Output writer
107 * @param indentSize indentation size
108 * @return A stream writer instance
110 public static NormalizedNodeStreamWriter create(final SchemaContext schemaContext, final Writer writer, final int indentSize) {
111 return new JSONNormalizedNodeStreamWriter(schemaContext, writer, indentSize);
115 public void leafNode(final NodeIdentifier name, final Object value) throws IOException {
116 separateElementFromPreviousElement();
117 writeJsonIdentifier(name);
118 currentNamespace = stack.peek().getNamespace();
119 writeValue(value.toString());
120 separateNextSiblingsWithComma();
124 public void startLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
125 separateElementFromPreviousElement();
126 stack.push(new TypeInfo(NodeType.LIST, name.getNodeType().getNamespace()));
127 writeJsonIdentifier(name);
133 public void leafSetEntryNode(final Object value) throws IOException {
134 separateElementFromPreviousElement();
135 writeValue(value.toString());
136 separateNextSiblingsWithComma();
140 public void startContainerNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
141 separateElementFromPreviousElement();
142 stack.push(new TypeInfo(NodeType.OBJECT, name.getNodeType().getNamespace()));
143 writeJsonIdentifier(name);
149 public void startUnkeyedList(final NodeIdentifier name, final int childSizeHint) throws IOException {
150 separateElementFromPreviousElement();
151 stack.push(new TypeInfo(NodeType.LIST, name.getNodeType().getNamespace()));
152 writeJsonIdentifier(name);
158 public void startUnkeyedListItem(final NodeIdentifier name, final int childSizeHint) throws IOException {
159 stack.push(new TypeInfo(NodeType.OBJECT, name.getNodeType().getNamespace()));
160 separateElementFromPreviousElement();
166 public void startMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
167 separateElementFromPreviousElement();
168 stack.push(new TypeInfo(NodeType.LIST, name.getNodeType().getNamespace()));
169 writeJsonIdentifier(name);
175 public void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint)
177 stack.push(new TypeInfo(NodeType.OBJECT, identifier.getNodeType().getNamespace()));
178 separateElementFromPreviousElement();
184 public void startOrderedMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
185 stack.push(new TypeInfo(NodeType.LIST, name.getNodeType().getNamespace()));
186 separateElementFromPreviousElement();
187 writeJsonIdentifier(name);
193 public void startChoiceNode(final NodeIdentifier name, final int childSizeHint) throws IllegalArgumentException {
194 handleInvisibleNode(name.getNodeType().getNamespace());
198 public void startAugmentationNode(final AugmentationIdentifier identifier) throws IllegalArgumentException {
199 handleInvisibleNode(currentNamespace);
203 public void anyxmlNode(final NodeIdentifier name, final Object value) throws IOException {
204 separateElementFromPreviousElement();
205 writeJsonIdentifier(name);
206 currentNamespace = stack.peek().getNamespace();
207 writeValue(value.toString());
208 separateNextSiblingsWithComma();
212 public void endNode() throws IOException {
213 switch (stack.peek().getType()) {
228 currentNamespace = stack.isEmpty() ? null : stack.peek().getNamespace();
229 separateNextSiblingsWithComma();
232 private void separateElementFromPreviousElement() throws IOException {
233 if (!stack.isEmpty() && stack.peek().hasAtLeastOneChild()) {
239 private void newLine() throws IOException {
240 if (indent != null) {
243 for (int i = 0; i < currentDepth; i++) {
244 writer.append(indent);
249 private void separateNextSiblingsWithComma() {
250 if (!stack.isEmpty()) {
251 stack.peek().setHasAtLeastOneChild(true);
256 * Invisible nodes have to be also pushed to stack because of pairing of start*() and endNode() methods. Information
257 * about child existing (due to printing comma) has to be transfered to invisible node.
259 private void handleInvisibleNode(final URI uri) {
260 TypeInfo typeInfo = new TypeInfo(NodeType.OTHER, uri);
261 typeInfo.setHasAtLeastOneChild(stack.peek().hasAtLeastOneChild());
262 stack.push(typeInfo);
265 private void writeStartObject() throws IOException {
269 private void writeStartList() throws IOException {
273 private void writeModulName(final URI namespace) throws IOException {
274 if (this.currentNamespace == null || namespace != this.currentNamespace) {
275 Module module = schemaContext.findModuleByNamespaceAndRevision(namespace, null);
276 writer.append(module.getName());
278 currentNamespace = namespace;
282 private void writeValue(final String value) throws IOException {
284 writer.append(value);
288 private void writeJsonIdentifier(final NodeIdentifier name) throws IOException {
290 writeModulName(name.getNodeType().getNamespace());
291 writer.append(name.getNodeType().getLocalName());
292 writer.append("\":");
295 private void indentRight() {
299 private void indentLeft() {
304 public void flush() throws IOException {
309 public void close() throws IOException {