X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=yang%2Fyang-data-codec-gson%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fyangtools%2Fyang%2Fdata%2Fcodec%2Fgson%2FJSONNormalizedNodeStreamWriter.java;h=59ef1f1e501ffc53011230f5f2f8ba61271f530c;hb=cb247b77dfab26658e434a593ca282fcd723022f;hp=c1a4c31d43f1c1b21146fd1dd87f84ff2ae556d9;hpb=300aed51915840a41a1483980ff7d045bde905cb;p=yangtools.git diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONNormalizedNodeStreamWriter.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONNormalizedNodeStreamWriter.java index c1a4c31d43..59ef1f1e50 100644 --- a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONNormalizedNodeStreamWriter.java +++ b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONNormalizedNodeStreamWriter.java @@ -7,15 +7,13 @@ */ package org.opendaylight.yangtools.yang.data.codec.gson; +import com.google.common.base.CharMatcher; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.gson.stream.JsonWriter; import java.io.IOException; import java.io.Writer; import java.net.URI; -import java.util.ArrayDeque; -import java.util.Deque; -import org.opendaylight.yangtools.concepts.Codec; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; @@ -24,8 +22,8 @@ import org.opendaylight.yangtools.yang.data.impl.codec.SchemaTracker; import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode; import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode; import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; -import org.opendaylight.yangtools.yang.model.api.Module; import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.SchemaNode; import org.opendaylight.yangtools.yang.model.api.SchemaPath; /** @@ -36,53 +34,25 @@ import org.opendaylight.yangtools.yang.model.api.SchemaPath; * FIXME: rewrite this in terms of {@link JsonWriter}. */ public class JSONNormalizedNodeStreamWriter implements NormalizedNodeStreamWriter { + /** + * RFC6020 deviation: we are not required to emit empty containers unless they + * are marked as 'presence'. + */ + private static final boolean DEFAULT_EMIT_EMPTY_CONTAINERS = true; - private static enum NodeType { - OBJECT, - LIST, - OTHER, - } - - private static class TypeInfo { - private boolean hasAtLeastOneChild = false; - private final NodeType type; - private final URI uri; - - public TypeInfo(final NodeType type, final URI uri) { - this.type = type; - this.uri = uri; - } - - public void setHasAtLeastOneChild(final boolean hasChildren) { - this.hasAtLeastOneChild = hasChildren; - } - - public NodeType getType() { - return type; - } - - public URI getNamespace() { - return uri; - } - - public boolean hasAtLeastOneChild() { - return hasAtLeastOneChild; - } - } + /** + * Matcher used to check if a string needs to be escaped. + */ + private static final CharMatcher JSON_ILLEGAL_STRING_CHARACTERS = CharMatcher.anyOf("\\\"\n\r"); - private final Deque stack = new ArrayDeque<>(); - private final SchemaContext schemaContext; - private final CodecFactory codecs; private final SchemaTracker tracker; + private final JSONCodecFactory codecs; private final Writer writer; private final String indent; + private JSONStreamWriterContext context; - private URI currentNamespace = null; - private int currentDepth = 0; - - private JSONNormalizedNodeStreamWriter(final SchemaContext schemaContext, - final Writer writer, final int indentSize) { - this.schemaContext = Preconditions.checkNotNull(schemaContext); + private JSONNormalizedNodeStreamWriter(final JSONCodecFactory codecFactory, final SchemaPath path, + final Writer writer, final URI initialNs, final int indentSize) { this.writer = Preconditions.checkNotNull(writer); Preconditions.checkArgument(indentSize >= 0, "Indent size must be non-negative"); @@ -91,47 +61,46 @@ public class JSONNormalizedNodeStreamWriter implements NormalizedNodeStreamWrite } else { indent = null; } - - this.codecs = CodecFactory.create(schemaContext); - this.tracker = SchemaTracker.create(schemaContext); + this.codecs = Preconditions.checkNotNull(codecFactory); + this.tracker = SchemaTracker.create(codecFactory.getSchemaContext(), path); + this.context = new JSONStreamWriterRootContext(initialNs); } - private JSONNormalizedNodeStreamWriter(final SchemaContext schemaContext, final SchemaPath path, - final Writer writer, final int indentSize) { - this.schemaContext = Preconditions.checkNotNull(schemaContext); - this.writer = Preconditions.checkNotNull(writer); - - Preconditions.checkArgument(indentSize >= 0, "Indent size must be non-negative"); - if (indentSize != 0) { - indent = Strings.repeat(" ", indentSize); - } else { - indent = null; - } - - this.codecs = CodecFactory.create(schemaContext); - this.tracker = SchemaTracker.create(schemaContext,path); + /** + * Create a new stream writer, which writes to the specified {@link Writer}. + * + * @param schemaContext Schema context + * @param writer Output writer + * @return A stream writer instance + */ + public static NormalizedNodeStreamWriter create(final SchemaContext schemaContext, final Writer writer) { + return new JSONNormalizedNodeStreamWriter(JSONCodecFactory.create(schemaContext), SchemaPath.ROOT, writer, null, 0); } /** * Create a new stream writer, which writes to the specified {@link Writer}. * * @param schemaContext Schema context + * @param path Root schemapath * @param writer Output writer * @return A stream writer instance */ - public static NormalizedNodeStreamWriter create(final SchemaContext schemaContext, final Writer writer) { - return new JSONNormalizedNodeStreamWriter(schemaContext, writer, 0); + public static NormalizedNodeStreamWriter create(final SchemaContext schemaContext, final SchemaPath path, final Writer writer) { + return new JSONNormalizedNodeStreamWriter(JSONCodecFactory.create(schemaContext), path, writer, null, 0); } /** * Create a new stream writer, which writes to the specified {@link Writer}. * * @param schemaContext Schema context + * @param path Root schemapath * @param writer Output writer + * @param initialNs Initial namespace * @return A stream writer instance */ - public static NormalizedNodeStreamWriter create(final SchemaContext schemaContext, SchemaPath path,final Writer writer) { - return new JSONNormalizedNodeStreamWriter(schemaContext, path, writer, 0); + public static NormalizedNodeStreamWriter create(final SchemaContext schemaContext, final SchemaPath path, + final URI initialNs, final Writer writer) { + return new JSONNormalizedNodeStreamWriter(JSONCodecFactory.create(schemaContext), path, writer, initialNs, 0); } /** @@ -143,227 +112,148 @@ public class JSONNormalizedNodeStreamWriter implements NormalizedNodeStreamWrite * @return A stream writer instance */ public static NormalizedNodeStreamWriter create(final SchemaContext schemaContext, final Writer writer, final int indentSize) { - return new JSONNormalizedNodeStreamWriter(schemaContext, writer, indentSize); + return new JSONNormalizedNodeStreamWriter(JSONCodecFactory.create(schemaContext), SchemaPath.ROOT, writer, null, indentSize); + } + + /** + * Create a new stream writer, which writes to the specified output stream. The codec factory + * can be reused between multiple writers. + * + * @param codecFactory JSON codec factory + * @param writer Output writer + * @param indentSize indentation size + * @return A stream writer instance + */ + public static NormalizedNodeStreamWriter create(final JSONCodecFactory codecFactory, final Writer writer, final int indentSize) { + return new JSONNormalizedNodeStreamWriter(codecFactory, SchemaPath.ROOT, writer, null, indentSize); } @Override public void leafNode(final NodeIdentifier name, final Object value) throws IOException { final LeafSchemaNode schema = tracker.leafNode(name); - final Codec codec = codecs.codecFor(schema.getType()); + final JSONCodec codec = codecs.codecFor(schema.getType()); - separateElementFromPreviousElement(); - writeJsonIdentifier(name); - currentNamespace = stack.peek().getNamespace(); - writeValue(String.valueOf(codec.serialize(value))); - separateNextSiblingsWithComma(); + context.emittingChild(codecs.getSchemaContext(), writer, indent); + context.writeChildJsonIdentifier(codecs.getSchemaContext(), writer, name.getNodeType()); + writeValue(codec.serialize(value), codec.needQuotes()); } @Override public void startLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException { tracker.startLeafSet(name); - - separateElementFromPreviousElement(); - stack.push(new TypeInfo(NodeType.LIST, name.getNodeType().getNamespace())); - writeJsonIdentifier(name); - writeStartList(); - indentRight(); + context = new JSONStreamWriterListContext(context, name); } @Override public void leafSetEntryNode(final Object value) throws IOException { final LeafListSchemaNode schema = tracker.leafSetEntryNode(); - final Codec codec = codecs.codecFor(schema.getType()); + final JSONCodec codec = codecs.codecFor(schema.getType()); - separateElementFromPreviousElement(); - writeValue(String.valueOf(codec.serialize(value))); - separateNextSiblingsWithComma(); + context.emittingChild(codecs.getSchemaContext(), writer, indent); + writeValue(codec.serialize(value), codec.needQuotes()); } + /* + * Warning suppressed due to static final constant which triggers a warning + * for the call to schema.isPresenceContainer(). + */ + @SuppressWarnings("unused") @Override public void startContainerNode(final NodeIdentifier name, final int childSizeHint) throws IOException { - tracker.startContainerNode(name); + final SchemaNode schema = tracker.startContainerNode(name); - separateElementFromPreviousElement(); - stack.push(new TypeInfo(NodeType.OBJECT, name.getNodeType().getNamespace())); - writeJsonIdentifier(name); - writeStartObject(); - indentRight(); + // FIXME this code ignores presence for containers + // but datastore does as well and it needs be fixed first (2399) + context = new JSONStreamWriterNamedObjectContext(context, name, DEFAULT_EMIT_EMPTY_CONTAINERS); } @Override public void startUnkeyedList(final NodeIdentifier name, final int childSizeHint) throws IOException { tracker.startList(name); - - separateElementFromPreviousElement(); - stack.push(new TypeInfo(NodeType.LIST, name.getNodeType().getNamespace())); - writeJsonIdentifier(name); - writeStartList(); - indentRight(); + context = new JSONStreamWriterListContext(context, name); } @Override public void startUnkeyedListItem(final NodeIdentifier name, final int childSizeHint) throws IOException { tracker.startListItem(name); - - stack.push(new TypeInfo(NodeType.OBJECT, name.getNodeType().getNamespace())); - separateElementFromPreviousElement(); - writeStartObject(); - indentRight(); + context = new JSONStreamWriterObjectContext(context, name, DEFAULT_EMIT_EMPTY_CONTAINERS); } @Override public void startMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException { tracker.startList(name); - - separateElementFromPreviousElement(); - stack.push(new TypeInfo(NodeType.LIST, name.getNodeType().getNamespace())); - writeJsonIdentifier(name); - writeStartList(); - indentRight(); + context = new JSONStreamWriterListContext(context, name); } @Override public void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint) throws IOException { tracker.startListItem(identifier); - separateElementFromPreviousElement(); - stack.push(new TypeInfo(NodeType.OBJECT, identifier.getNodeType().getNamespace())); - - - writeStartObject(); - indentRight(); + context = new JSONStreamWriterObjectContext(context, identifier, DEFAULT_EMIT_EMPTY_CONTAINERS); } @Override public void startOrderedMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException { - tracker.startListItem(name); - - stack.push(new TypeInfo(NodeType.LIST, name.getNodeType().getNamespace())); - separateElementFromPreviousElement(); - writeJsonIdentifier(name); - writeStartList(); - indentRight(); + tracker.startList(name); + context = new JSONStreamWriterListContext(context, name); } @Override - public void startChoiceNode(final NodeIdentifier name, final int childSizeHint) throws IllegalArgumentException { + public void startChoiceNode(final NodeIdentifier name, final int childSizeHint) { tracker.startChoiceNode(name); - handleInvisibleNode(name.getNodeType().getNamespace()); + context = new JSONStreamWriterInvisibleContext(context); } @Override - public void startAugmentationNode(final AugmentationIdentifier identifier) throws IllegalArgumentException { + public void startAugmentationNode(final AugmentationIdentifier identifier) { tracker.startAugmentationNode(identifier); - handleInvisibleNode(currentNamespace); + context = new JSONStreamWriterInvisibleContext(context); } @Override public void anyxmlNode(final NodeIdentifier name, final Object value) throws IOException { + @SuppressWarnings("unused") final AnyXmlSchemaNode schema = tracker.anyxmlNode(name); // FIXME: should have a codec based on this :) - separateElementFromPreviousElement(); - writeJsonIdentifier(name); - currentNamespace = stack.peek().getNamespace(); - writeValue(value.toString()); - separateNextSiblingsWithComma(); + context.emittingChild(codecs.getSchemaContext(), writer, indent); + context.writeChildJsonIdentifier(codecs.getSchemaContext(), writer, name.getNodeType()); + writeValue(String.valueOf(value), true); } @Override public void endNode() throws IOException { tracker.endNode(); - - final TypeInfo t = stack.pop(); - switch (t.getType()) { - case LIST: - indentLeft(); - newLine(); - writer.append(']'); - break; - case OBJECT: - indentLeft(); - newLine(); - writer.append('}'); - break; - default: - break; - } - - currentNamespace = stack.isEmpty() ? null : stack.peek().getNamespace(); - separateNextSiblingsWithComma(); - } - - private void separateElementFromPreviousElement() throws IOException { - if (!stack.isEmpty() && stack.peek().hasAtLeastOneChild()) { - writer.append(','); - } - newLine(); - } - - private void newLine() throws IOException { - if (indent != null) { - writer.append('\n'); - - for (int i = 0; i < currentDepth; i++) { - writer.append(indent); + context = context.endNode(codecs.getSchemaContext(), writer, indent); + } + + private void writeValue(final String str, final boolean needQuotes) throws IOException { + if (needQuotes) { + writer.append('"'); + + final int needEscape = JSON_ILLEGAL_STRING_CHARACTERS.countIn(str); + if (needEscape != 0) { + final char[] escaped = new char[str.length() + needEscape]; + int offset = 0; + + for (int i = 0; i < str.length(); i++) { + final char c = str.charAt(i); + if (JSON_ILLEGAL_STRING_CHARACTERS.matches(c)) { + escaped[offset++] = '\\'; + } + escaped[offset++] = c; + } + writer.write(escaped); + } else { + writer.append(str); } - } - } - - private void separateNextSiblingsWithComma() { - if (!stack.isEmpty()) { - stack.peek().setHasAtLeastOneChild(true); - } - } - /** - * Invisible nodes have to be also pushed to stack because of pairing of start*() and endNode() methods. Information - * about child existing (due to printing comma) has to be transfered to invisible node. - */ - private void handleInvisibleNode(final URI uri) { - TypeInfo typeInfo = new TypeInfo(NodeType.OTHER, uri); - typeInfo.setHasAtLeastOneChild(stack.peek().hasAtLeastOneChild()); - stack.push(typeInfo); - } - - private void writeStartObject() throws IOException { - writer.append('{'); - } - - private void writeStartList() throws IOException { - writer.append('['); - } - - private void writeModulName(final URI namespace) throws IOException { - if (this.currentNamespace == null || namespace != this.currentNamespace) { - Module module = schemaContext.findModuleByNamespaceAndRevision(namespace, null); - writer.append(module.getName()); - writer.append(':'); - currentNamespace = namespace; + writer.append('"'); + } else { + writer.append(str); } } - private void writeValue(final String value) throws IOException { - writer.append('"'); - writer.append(value); - writer.append('"'); - } - - private void writeJsonIdentifier(final NodeIdentifier name) throws IOException { - writer.append('"'); - writeModulName(name.getNodeType().getNamespace()); - writer.append(name.getNodeType().getLocalName()); - writer.append("\":"); - } - - private void indentRight() { - currentDepth++; - } - - private void indentLeft() { - currentDepth--; - } - @Override public void flush() throws IOException { writer.flush();