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.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;
33 * This implementation will create JSON output as output stream.
35 * Values of leaf and leaf-list are NOT translated according to codecs.
37 * FIXME: rewrite this in terms of {@link JsonWriter}.
39 public class JSONNormalizedNodeStreamWriter implements NormalizedNodeStreamWriter {
41 private static enum NodeType {
47 private static class TypeInfo {
48 private boolean hasAtLeastOneChild = false;
49 private final NodeType type;
50 private final URI uri;
52 public TypeInfo(final NodeType type, final URI uri) {
57 public void setHasAtLeastOneChild(final boolean hasChildren) {
58 this.hasAtLeastOneChild = hasChildren;
61 public NodeType getType() {
65 public URI getNamespace() {
69 public boolean hasAtLeastOneChild() {
70 return hasAtLeastOneChild;
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;
81 private URI currentNamespace = null;
82 private int currentDepth = 0;
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);
89 Preconditions.checkArgument(indentSize >= 0, "Indent size must be non-negative");
90 if (indentSize != 0) {
91 indent = Strings.repeat(" ", indentSize);
96 this.codecs = CodecFactory.create(schemaContext);
97 this.tracker = SchemaTracker.create(schemaContext);
101 * Create a new stream writer, which writes to the specified {@link Writer}.
103 * @param schemaContext Schema context
104 * @param writer Output writer
105 * @return A stream writer instance
107 public static NormalizedNodeStreamWriter create(final SchemaContext schemaContext, final Writer writer) {
108 return new JSONNormalizedNodeStreamWriter(schemaContext, writer, 0);
112 * Create a new stream writer, which writes to the specified output stream.
114 * @param schemaContext Schema context
115 * @param writer Output writer
116 * @param indentSize indentation size
117 * @return A stream writer instance
119 public static NormalizedNodeStreamWriter create(final SchemaContext schemaContext, final Writer writer, final int indentSize) {
120 return new JSONNormalizedNodeStreamWriter(schemaContext, writer, indentSize);
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());
128 separateElementFromPreviousElement();
129 writeJsonIdentifier(name);
130 currentNamespace = stack.peek().getNamespace();
131 writeValue(String.valueOf(codec.serialize(value)));
132 separateNextSiblingsWithComma();
136 public void startLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException {
137 tracker.startLeafSet(name);
139 separateElementFromPreviousElement();
140 stack.push(new TypeInfo(NodeType.LIST, name.getNodeType().getNamespace()));
141 writeJsonIdentifier(name);
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());
151 separateElementFromPreviousElement();
152 writeValue(String.valueOf(codec.serialize(value)));
153 separateNextSiblingsWithComma();
157 public void startContainerNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
158 tracker.startContainerNode(name);
160 separateElementFromPreviousElement();
161 stack.push(new TypeInfo(NodeType.OBJECT, name.getNodeType().getNamespace()));
162 writeJsonIdentifier(name);
168 public void startUnkeyedList(final NodeIdentifier name, final int childSizeHint) throws IOException {
169 tracker.startList(name);
171 separateElementFromPreviousElement();
172 stack.push(new TypeInfo(NodeType.LIST, name.getNodeType().getNamespace()));
173 writeJsonIdentifier(name);
179 public void startUnkeyedListItem(final NodeIdentifier name, final int childSizeHint) throws IOException {
180 tracker.startListItem(name);
182 stack.push(new TypeInfo(NodeType.OBJECT, name.getNodeType().getNamespace()));
183 separateElementFromPreviousElement();
189 public void startMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
190 tracker.startList(name);
192 separateElementFromPreviousElement();
193 stack.push(new TypeInfo(NodeType.LIST, name.getNodeType().getNamespace()));
194 writeJsonIdentifier(name);
200 public void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint)
202 tracker.startListItem(identifier);
204 stack.push(new TypeInfo(NodeType.OBJECT, identifier.getNodeType().getNamespace()));
205 separateElementFromPreviousElement();
211 public void startOrderedMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException {
212 tracker.startListItem(name);
214 stack.push(new TypeInfo(NodeType.LIST, name.getNodeType().getNamespace()));
215 separateElementFromPreviousElement();
216 writeJsonIdentifier(name);
222 public void startChoiceNode(final NodeIdentifier name, final int childSizeHint) throws IllegalArgumentException {
223 tracker.startChoiceNode(name);
224 handleInvisibleNode(name.getNodeType().getNamespace());
228 public void startAugmentationNode(final AugmentationIdentifier identifier) throws IllegalArgumentException {
229 tracker.startAugmentationNode(identifier);
230 handleInvisibleNode(currentNamespace);
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 :)
238 separateElementFromPreviousElement();
239 writeJsonIdentifier(name);
240 currentNamespace = stack.peek().getNamespace();
241 writeValue(value.toString());
242 separateNextSiblingsWithComma();
246 public void endNode() throws IOException {
249 final TypeInfo t = stack.pop();
250 switch (t.getType()) {
265 currentNamespace = stack.isEmpty() ? null : stack.peek().getNamespace();
266 separateNextSiblingsWithComma();
269 private void separateElementFromPreviousElement() throws IOException {
270 if (!stack.isEmpty() && stack.peek().hasAtLeastOneChild()) {
276 private void newLine() throws IOException {
277 if (indent != null) {
280 for (int i = 0; i < currentDepth; i++) {
281 writer.append(indent);
286 private void separateNextSiblingsWithComma() {
287 if (!stack.isEmpty()) {
288 stack.peek().setHasAtLeastOneChild(true);
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.
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);
302 private void writeStartObject() throws IOException {
306 private void writeStartList() throws IOException {
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());
315 currentNamespace = namespace;
319 private void writeValue(final String value) throws IOException {
321 writer.append(value);
325 private void writeJsonIdentifier(final NodeIdentifier name) throws IOException {
327 writeModulName(name.getNodeType().getNamespace());
328 writer.append(name.getNodeType().getLocalName());
329 writer.append("\":");
332 private void indentRight() {
336 private void indentLeft() {
341 public void flush() throws IOException {
346 public void close() throws IOException {