Merge "Bug 1478 - Autoboxing support"
[yangtools.git] / yang / yang-data-codec-gson / src / main / java / org / opendaylight / yangtools / yang / data / codec / gson / JSONNormalizedNodeStreamWriter.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.yangtools.yang.data.codec.gson;
9
10 import com.google.common.base.Preconditions;
11 import com.google.common.base.Strings;
12 import com.google.gson.stream.JsonWriter;
13
14 import java.io.IOException;
15 import java.io.Writer;
16 import java.net.URI;
17 import java.util.ArrayDeque;
18 import java.util.Deque;
19
20 import org.opendaylight.yangtools.concepts.Codec;
21 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
22 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
23 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
24 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
25 import org.opendaylight.yangtools.yang.data.impl.codec.SchemaTracker;
26 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
27 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
28 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
29 import org.opendaylight.yangtools.yang.model.api.Module;
30 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
31
32 /**
33  * This implementation will create JSON output as output stream.
34  *
35  * Values of leaf and leaf-list are NOT translated according to codecs.
36  *
37  * FIXME: rewrite this in terms of {@link JsonWriter}.
38  */
39 public class JSONNormalizedNodeStreamWriter implements NormalizedNodeStreamWriter {
40
41     private static enum NodeType {
42         OBJECT,
43         LIST,
44         OTHER,
45     }
46
47     private static class TypeInfo {
48         private boolean hasAtLeastOneChild = false;
49         private final NodeType type;
50         private final URI uri;
51
52         public TypeInfo(final NodeType type, final URI uri) {
53             this.type = type;
54             this.uri = uri;
55         }
56
57         public void setHasAtLeastOneChild(final boolean hasChildren) {
58             this.hasAtLeastOneChild = hasChildren;
59         }
60
61         public NodeType getType() {
62             return type;
63         }
64
65         public URI getNamespace() {
66             return uri;
67         }
68
69         public boolean hasAtLeastOneChild() {
70             return hasAtLeastOneChild;
71         }
72     }
73
74     private final Deque<TypeInfo> stack = new ArrayDeque<>();
75     private final SchemaContext schemaContext;
76     private final CodecFactory codecs;
77     private final SchemaTracker tracker;
78     private final Writer writer;
79     private final String indent;
80
81     private URI currentNamespace = null;
82     private int currentDepth = 0;
83
84     private JSONNormalizedNodeStreamWriter(final SchemaContext schemaContext,
85             final Writer writer, final int indentSize) {
86         this.schemaContext = Preconditions.checkNotNull(schemaContext);
87         this.writer = Preconditions.checkNotNull(writer);
88
89         Preconditions.checkArgument(indentSize >= 0, "Indent size must be non-negative");
90         if (indentSize != 0) {
91             indent = Strings.repeat(" ", indentSize);
92         } else {
93             indent = null;
94         }
95
96         this.codecs = CodecFactory.create(schemaContext);
97         this.tracker = SchemaTracker.create(schemaContext);
98     }
99
100     /**
101      * Create a new stream writer, which writes to the specified {@link Writer}.
102      *
103      * @param schemaContext Schema context
104      * @param writer Output writer
105      * @return A stream writer instance
106      */
107     public static NormalizedNodeStreamWriter create(final SchemaContext schemaContext, final Writer writer) {
108         return new JSONNormalizedNodeStreamWriter(schemaContext, writer, 0);
109     }
110
111     /**
112      * Create a new stream writer, which writes to the specified output stream.
113      *
114      * @param schemaContext Schema context
115      * @param writer Output writer
116      * @param indentSize indentation size
117      * @return A stream writer instance
118      */
119     public static NormalizedNodeStreamWriter create(final SchemaContext schemaContext, final Writer writer, final int indentSize) {
120         return new JSONNormalizedNodeStreamWriter(schemaContext, writer, indentSize);
121     }
122
123     @Override
124     public void leafNode(final NodeIdentifier name, final Object value) throws IOException {
125         final LeafSchemaNode schema = tracker.leafNode(name);
126         final Codec<Object, Object> codec = codecs.codecFor(schema.getType());
127
128         separateElementFromPreviousElement();
129         writeJsonIdentifier(name);
130         currentNamespace = stack.peek().getNamespace();
131         writeValue(String.valueOf(codec.serialize(value)));
132         separateNextSiblingsWithComma();
133     }
134
135     @Override
136     public void startLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
137         tracker.startLeafSet(name);
138
139         separateElementFromPreviousElement();
140         stack.push(new TypeInfo(NodeType.LIST, name.getNodeType().getNamespace()));
141         writeJsonIdentifier(name);
142         writeStartList();
143         indentRight();
144     }
145
146     @Override
147     public void leafSetEntryNode(final Object value) throws IOException {
148         final LeafListSchemaNode schema = tracker.leafSetEntryNode();
149         final Codec<Object, Object> codec = codecs.codecFor(schema.getType());
150
151         separateElementFromPreviousElement();
152         writeValue(String.valueOf(codec.serialize(value)));
153         separateNextSiblingsWithComma();
154     }
155
156     @Override
157     public void startContainerNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
158         tracker.startContainerNode(name);
159
160         separateElementFromPreviousElement();
161         stack.push(new TypeInfo(NodeType.OBJECT, name.getNodeType().getNamespace()));
162         writeJsonIdentifier(name);
163         writeStartObject();
164         indentRight();
165     }
166
167     @Override
168     public void startUnkeyedList(final NodeIdentifier name, final int childSizeHint) throws IOException {
169         tracker.startList(name);
170
171         separateElementFromPreviousElement();
172         stack.push(new TypeInfo(NodeType.LIST, name.getNodeType().getNamespace()));
173         writeJsonIdentifier(name);
174         writeStartList();
175         indentRight();
176     }
177
178     @Override
179     public void startUnkeyedListItem(final NodeIdentifier name, final int childSizeHint) throws IOException {
180         tracker.startListItem(name);
181
182         stack.push(new TypeInfo(NodeType.OBJECT, name.getNodeType().getNamespace()));
183         separateElementFromPreviousElement();
184         writeStartObject();
185         indentRight();
186     }
187
188     @Override
189     public void startMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
190         tracker.startList(name);
191
192         separateElementFromPreviousElement();
193         stack.push(new TypeInfo(NodeType.LIST, name.getNodeType().getNamespace()));
194         writeJsonIdentifier(name);
195         writeStartList();
196         indentRight();
197     }
198
199     @Override
200     public void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint)
201             throws IOException {
202         tracker.startListItem(identifier);
203
204         stack.push(new TypeInfo(NodeType.OBJECT, identifier.getNodeType().getNamespace()));
205         separateElementFromPreviousElement();
206         writeStartObject();
207         indentRight();
208     }
209
210     @Override
211     public void startOrderedMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
212         tracker.startListItem(name);
213
214         stack.push(new TypeInfo(NodeType.LIST, name.getNodeType().getNamespace()));
215         separateElementFromPreviousElement();
216         writeJsonIdentifier(name);
217         writeStartList();
218         indentRight();
219     }
220
221     @Override
222     public void startChoiceNode(final NodeIdentifier name, final int childSizeHint) throws IllegalArgumentException {
223         tracker.startChoiceNode(name);
224         handleInvisibleNode(name.getNodeType().getNamespace());
225     }
226
227     @Override
228     public void startAugmentationNode(final AugmentationIdentifier identifier) throws IllegalArgumentException {
229         tracker.startAugmentationNode(identifier);
230         handleInvisibleNode(currentNamespace);
231     }
232
233     @Override
234     public void anyxmlNode(final NodeIdentifier name, final Object value) throws IOException {
235         final AnyXmlSchemaNode schema = tracker.anyxmlNode(name);
236         // FIXME: should have a codec based on this :)
237
238         separateElementFromPreviousElement();
239         writeJsonIdentifier(name);
240         currentNamespace = stack.peek().getNamespace();
241         writeValue(value.toString());
242         separateNextSiblingsWithComma();
243     }
244
245     @Override
246     public void endNode() throws IOException {
247         tracker.endNode();
248
249         final TypeInfo t = stack.pop();
250         switch (t.getType()) {
251         case LIST:
252             indentLeft();
253             newLine();
254             writer.append(']');
255             break;
256         case OBJECT:
257             indentLeft();
258             newLine();
259             writer.append('}');
260             break;
261         default:
262             break;
263         }
264
265         currentNamespace = stack.isEmpty() ? null : stack.peek().getNamespace();
266         separateNextSiblingsWithComma();
267     }
268
269     private void separateElementFromPreviousElement() throws IOException {
270         if (!stack.isEmpty() && stack.peek().hasAtLeastOneChild()) {
271             writer.append(',');
272         }
273         newLine();
274     }
275
276     private void newLine() throws IOException {
277         if (indent != null) {
278             writer.append('\n');
279
280             for (int i = 0; i < currentDepth; i++) {
281                 writer.append(indent);
282             }
283         }
284     }
285
286     private void separateNextSiblingsWithComma() {
287         if (!stack.isEmpty()) {
288             stack.peek().setHasAtLeastOneChild(true);
289         }
290     }
291
292     /**
293      * Invisible nodes have to be also pushed to stack because of pairing of start*() and endNode() methods. Information
294      * about child existing (due to printing comma) has to be transfered to invisible node.
295      */
296     private void handleInvisibleNode(final URI uri) {
297         TypeInfo typeInfo = new TypeInfo(NodeType.OTHER, uri);
298         typeInfo.setHasAtLeastOneChild(stack.peek().hasAtLeastOneChild());
299         stack.push(typeInfo);
300     }
301
302     private void writeStartObject() throws IOException {
303         writer.append('{');
304     }
305
306     private void writeStartList() throws IOException {
307         writer.append('[');
308     }
309
310     private void writeModulName(final URI namespace) throws IOException {
311         if (this.currentNamespace == null || namespace != this.currentNamespace) {
312             Module module = schemaContext.findModuleByNamespaceAndRevision(namespace, null);
313             writer.append(module.getName());
314             writer.append(':');
315             currentNamespace = namespace;
316         }
317     }
318
319     private void writeValue(final String value) throws IOException {
320         writer.append('"');
321         writer.append(value);
322         writer.append('"');
323     }
324
325     private void writeJsonIdentifier(final NodeIdentifier name) throws IOException {
326         writer.append('"');
327         writeModulName(name.getNodeType().getNamespace());
328         writer.append(name.getNodeType().getLocalName());
329         writer.append("\":");
330     }
331
332     private void indentRight() {
333         currentDepth++;
334     }
335
336     private void indentLeft() {
337         currentDepth--;
338     }
339
340     @Override
341     public void flush() throws IOException {
342         writer.flush();
343     }
344
345     @Override
346     public void close() throws IOException {
347         writer.flush();
348         writer.close();
349     }
350
351 }