Merge "BUG-1704: rework state tracking"
[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.common.collect.ImmutableSet;
13 import com.google.gson.stream.JsonWriter;
14
15 import java.io.IOException;
16 import java.io.Writer;
17 import java.math.BigDecimal;
18 import java.math.BigInteger;
19 import java.net.URI;
20 import java.util.Collection;
21
22 import org.opendaylight.yangtools.concepts.Codec;
23 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
24 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
25 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
26 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
27 import org.opendaylight.yangtools.yang.data.impl.codec.SchemaTracker;
28 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
29 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
30 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
31 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
32 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
33 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
34
35 /**
36  * This implementation will create JSON output as output stream.
37  *
38  * Values of leaf and leaf-list are NOT translated according to codecs.
39  *
40  * FIXME: rewrite this in terms of {@link JsonWriter}.
41  */
42 public class JSONNormalizedNodeStreamWriter implements NormalizedNodeStreamWriter {
43     /**
44      * RFC6020 deviation: we are not required to emit empty containers unless they
45      * are marked as 'presence'.
46      */
47     private static final boolean DEFAULT_EMIT_EMPTY_CONTAINERS = true;
48     private static final Collection<Class<?>> NUMERIC_CLASSES =
49             ImmutableSet.<Class<?>>of(Byte.class, Short.class, Integer.class, Long.class, BigInteger.class, BigDecimal.class);
50
51     private final SchemaContext schemaContext;
52     private final SchemaTracker tracker;
53     private final CodecFactory codecs;
54     private final Writer writer;
55     private final String indent;
56     private JSONStreamWriterContext context;
57
58     private JSONNormalizedNodeStreamWriter(final SchemaContext schemaContext,
59             final Writer writer, final int indentSize) {
60         this(schemaContext, SchemaPath.ROOT, writer, null, indentSize);
61     }
62
63     private JSONNormalizedNodeStreamWriter(final SchemaContext schemaContext, final SchemaPath path,
64             final Writer writer, final URI initialNs, final int indentSize) {
65         this.schemaContext = Preconditions.checkNotNull(schemaContext);
66         this.writer = Preconditions.checkNotNull(writer);
67
68         Preconditions.checkArgument(indentSize >= 0, "Indent size must be non-negative");
69         if (indentSize != 0) {
70             indent = Strings.repeat(" ", indentSize);
71         } else {
72             indent = null;
73         }
74         this.codecs = CodecFactory.create(schemaContext);
75         this.tracker = SchemaTracker.create(schemaContext, path);
76         this.context = new JSONStreamWriterRootContext(initialNs);
77     }
78
79     /**
80      * Create a new stream writer, which writes to the specified {@link Writer}.
81      *
82      * @param schemaContext Schema context
83      * @param writer Output writer
84      * @return A stream writer instance
85      */
86     public static NormalizedNodeStreamWriter create(final SchemaContext schemaContext, final Writer writer) {
87         return new JSONNormalizedNodeStreamWriter(schemaContext, writer, 0);
88     }
89
90     /**
91      * Create a new stream writer, which writes to the specified {@link Writer}.
92      *
93      * @param schemaContext Schema context
94      * @param path Root schemapath
95      * @param writer Output writer
96      * @return A stream writer instance
97      */
98     public static NormalizedNodeStreamWriter create(final SchemaContext schemaContext, final SchemaPath path, final Writer writer) {
99         return new JSONNormalizedNodeStreamWriter(schemaContext, path, writer, null, 0);
100     }
101
102     /**
103      * Create a new stream writer, which writes to the specified {@link Writer}.
104      *
105      * @param schemaContext Schema context
106      * @param path Root schemapath
107      * @param writer Output writer
108      * @param initialNs Initial namespace
109      * @return A stream writer instance
110      */
111     public static NormalizedNodeStreamWriter create(final SchemaContext schemaContext, final SchemaPath path,
112             final URI initialNs, final Writer writer) {
113         return new JSONNormalizedNodeStreamWriter(schemaContext, path, writer, initialNs, 0);
114     }
115
116     /**
117      * Create a new stream writer, which writes to the specified output stream.
118      *
119      * @param schemaContext Schema context
120      * @param writer Output writer
121      * @param indentSize indentation size
122      * @return A stream writer instance
123      */
124     public static NormalizedNodeStreamWriter create(final SchemaContext schemaContext, final Writer writer, final int indentSize) {
125         return new JSONNormalizedNodeStreamWriter(schemaContext, writer, indentSize);
126     }
127
128     @Override
129     public void leafNode(final NodeIdentifier name, final Object value) throws IOException {
130         final LeafSchemaNode schema = tracker.leafNode(name);
131         final Codec<Object, Object> codec = codecs.codecFor(schema.getType());
132
133         context.emittingChild(schemaContext, writer, indent);
134         context.writeJsonIdentifier(schemaContext, writer, name.getNodeType());
135         writeValue(codec.serialize(value));
136     }
137
138     @Override
139     public void startLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
140         tracker.startLeafSet(name);
141         context = new JSONStreamWriterListContext(context, name);
142     }
143
144     @Override
145     public void leafSetEntryNode(final Object value) throws IOException {
146         final LeafListSchemaNode schema = tracker.leafSetEntryNode();
147         final Codec<Object, Object> codec = codecs.codecFor(schema.getType());
148
149         context.emittingChild(schemaContext, writer, indent);
150         writeValue(codec.serialize(value));
151     }
152
153     @Override
154     public void startContainerNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
155         final ContainerSchemaNode schema = tracker.startContainerNode(name);
156         context = new JSONStreamWriterNamedObjectContext(context, name, schema.isPresenceContainer() || DEFAULT_EMIT_EMPTY_CONTAINERS);
157     }
158
159     @Override
160     public void startUnkeyedList(final NodeIdentifier name, final int childSizeHint) throws IOException {
161         tracker.startList(name);
162         context = new JSONStreamWriterListContext(context, name);
163     }
164
165     @Override
166     public void startUnkeyedListItem(final NodeIdentifier name, final int childSizeHint) throws IOException {
167         tracker.startListItem(name);
168         context = new JSONStreamWriterObjectContext(context, name, DEFAULT_EMIT_EMPTY_CONTAINERS);
169     }
170
171     @Override
172     public void startMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
173         tracker.startList(name);
174         context = new JSONStreamWriterListContext(context, name);
175     }
176
177     @Override
178     public void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint)
179             throws IOException {
180         tracker.startListItem(identifier);
181         context = new JSONStreamWriterObjectContext(context, identifier, DEFAULT_EMIT_EMPTY_CONTAINERS);
182     }
183
184     @Override
185     public void startOrderedMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
186         tracker.startList(name);
187         context = new JSONStreamWriterListContext(context, name);
188     }
189
190     @Override
191     public void startChoiceNode(final NodeIdentifier name, final int childSizeHint) {
192         tracker.startChoiceNode(name);
193         context = new JSONStreamWriterInvisibleContext(context);
194     }
195
196     @Override
197     public void startAugmentationNode(final AugmentationIdentifier identifier) {
198         tracker.startAugmentationNode(identifier);
199         context = new JSONStreamWriterInvisibleContext(context);
200     }
201
202     @Override
203     public void anyxmlNode(final NodeIdentifier name, final Object value) throws IOException {
204         final AnyXmlSchemaNode schema = tracker.anyxmlNode(name);
205         // FIXME: should have a codec based on this :)
206
207         context.emittingChild(schemaContext, writer, indent);
208         context.writeJsonIdentifier(schemaContext, writer, name.getNodeType());
209         writeValue(value);
210     }
211
212     @Override
213     public void endNode() throws IOException {
214         tracker.endNode();
215         context = context.endNode(schemaContext, writer, indent);
216     }
217
218     private void writeValue(final Object value) throws IOException {
219         final String str = String.valueOf(value);
220
221         if (!NUMERIC_CLASSES.contains(value.getClass())) {
222             writer.append('"');
223             writer.append(str);
224             writer.append('"');
225         } else {
226             writer.append(str);
227         }
228     }
229
230     @Override
231     public void flush() throws IOException {
232         writer.flush();
233     }
234
235     @Override
236     public void close() throws IOException {
237         writer.flush();
238         writer.close();
239     }
240
241 }