summaryrefslogtreecommitdiffstats
path: root/vespajlib/src/main/java/com/yahoo/slime/JsonFormat.java
diff options
context:
space:
mode:
Diffstat (limited to 'vespajlib/src/main/java/com/yahoo/slime/JsonFormat.java')
-rw-r--r--vespajlib/src/main/java/com/yahoo/slime/JsonFormat.java220
1 files changed, 220 insertions, 0 deletions
diff --git a/vespajlib/src/main/java/com/yahoo/slime/JsonFormat.java b/vespajlib/src/main/java/com/yahoo/slime/JsonFormat.java
new file mode 100644
index 00000000000..28879311372
--- /dev/null
+++ b/vespajlib/src/main/java/com/yahoo/slime/JsonFormat.java
@@ -0,0 +1,220 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.slime;
+
+import com.yahoo.io.AbstractByteWriter;
+import com.yahoo.io.ByteWriter;
+import com.yahoo.text.AbstractUtf8Array;
+import com.yahoo.text.Utf8;
+import com.yahoo.text.Utf8String;
+import com.yahoo.text.DoubleFormatter;
+
+import java.io.*;
+
+/**
+ * Encodes json from a slime object.
+ *
+ * @author lulf
+ */
+public final class JsonFormat implements SlimeFormat
+{
+ private final static byte [] HEX = Utf8.toBytes("0123456789ABCDEF");
+ private final boolean compact;
+ public JsonFormat(boolean compact) {
+ this.compact = compact;
+ }
+
+ @Override
+ public void encode(OutputStream os, Slime slime) throws IOException {
+ new Encoder(slime.get(), os, compact).encode();
+ }
+
+ public void encode(OutputStream os, Inspector value) throws IOException {
+ new Encoder(value, os, compact).encode();
+ }
+
+ public void encode(AbstractByteWriter os, Slime slime) throws IOException {
+ new Encoder(slime.get(), os, compact).encode();
+ }
+
+ public void encode(AbstractByteWriter os, Inspector value) throws IOException {
+ new Encoder(value, os, compact).encode();
+ }
+
+ @Override
+ public void decode(InputStream is, Slime slime) throws IOException {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ public static final class Encoder implements ArrayTraverser, ObjectTraverser
+ {
+ private final Inspector top;
+ private final AbstractByteWriter out;
+ private boolean head = true;
+ private boolean compact;
+ private int level = 0;
+ final static AbstractUtf8Array NULL=new Utf8String("null");
+ final static AbstractUtf8Array FALSE=new Utf8String("false");
+ final static AbstractUtf8Array TRUE=new Utf8String("true");
+
+ public Encoder(Inspector value, OutputStream out, boolean compact) {
+ this.top = value;
+ this.out = new ByteWriter(out);
+ this.compact = compact;
+ }
+
+ public Encoder(Inspector value, AbstractByteWriter out, boolean compact) {
+ this.top = value;
+ this.out = out;
+ this.compact = compact;
+ }
+
+ public void encode() throws IOException {
+ encodeValue(top);
+ if (!compact) {
+ out.append((byte) '\n');
+ }
+ out.flush();
+ }
+
+ private void encodeNIX() throws IOException {
+ out.write(NULL);
+ }
+
+ private void encodeBOOL(boolean value) throws IOException {
+ out.write(value ? TRUE : FALSE);
+ }
+
+ private void encodeLONG(long value) throws IOException {
+ out.write(value);
+ }
+
+ private void encodeDOUBLE(double value) throws IOException {
+ if (Double.isNaN(value) || Double.isInfinite(value)) {
+ out.write(NULL);
+ } else {
+ out.write(DoubleFormatter.stringValue(value));
+ }
+ }
+
+ private void encodeSTRING(byte[] value) throws IOException {
+
+ byte [] data = new byte[value.length * 6 + 2];
+ int len = 2;
+ int p = 0;
+ data[p++] = '"';
+ for (int pos = 0; pos < value.length; pos++) {
+ byte c = value[pos];
+ switch (c) {
+ case '"': data[p++] = '\\'; data[p++] = '"'; len += 2; break;
+ case '\\': data[p++] = '\\'; data[p++] = '\\'; len += 2; break;
+ case '\b': data[p++] = '\\'; data[p++] = 'b'; len += 2; break;
+ case '\f': data[p++] = '\\'; data[p++] = 'f'; len += 2; break;
+ case '\n': data[p++] = '\\'; data[p++] = 'n'; len += 2; break;
+ case '\r': data[p++] = '\\'; data[p++] = 'r'; len += 2; break;
+ case '\t': data[p++] = '\\'; data[p++] = 't'; len += 2; break;
+ default:
+ if (c > 0x1f || c < 0) {
+ data[p++] = c;
+ len++;
+ } else { // requires escaping according to RFC 4627
+ data[p++] = '\\'; data[p++] = 'u'; data[p++] = '0'; data[p++] = '0';
+ data[p++] = HEX[(c >> 4) & 0xf]; data[p++] = HEX[c & 0xf];
+ len += 6;
+ }
+ }
+ }
+ data[p] = '"';
+ out.append(data, 0, len);
+ }
+
+ private void encodeDATA(byte[] value) throws IOException {
+ int len = value.length * 2 + 4;
+ byte [] data = new byte[len];
+ int p = 0;
+
+ data[p++] = '"'; data[p++] = '0'; data[p++] = 'x';
+ for (int pos = 0; pos < value.length; pos++) {
+ data[p++] = HEX[(value[pos] >> 4) & 0xf]; data[p++] = HEX[value[pos] & 0xf];
+ }
+ data[p] = '"';
+ out.append(data, 0, len);
+ }
+
+ private void encodeARRAY(Inspector inspector) throws IOException {
+ openScope((byte)'[');
+ ArrayTraverser at = this;
+ inspector.traverse(at);
+ closeScope((byte)']');
+ }
+
+ private void encodeOBJECT(Inspector inspector) throws IOException {
+ openScope((byte)'{');
+ ObjectTraverser ot = this;
+ inspector.traverse(ot);
+ closeScope((byte) '}');
+ }
+
+ private void openScope(byte opener) throws IOException {
+ out.append(opener);
+ level++;
+ head = true;
+ }
+
+ private void closeScope(byte closer) throws IOException {
+ level--;
+ separate(false);
+ out.append(closer);
+ }
+
+ private void encodeValue(Inspector inspector) throws IOException {
+ switch(inspector.type()) {
+ case NIX: encodeNIX(); return;
+ case BOOL: encodeBOOL(inspector.asBool()); return;
+ case LONG: encodeLONG(inspector.asLong()); return;
+ case DOUBLE: encodeDOUBLE(inspector.asDouble()); return;
+ case STRING: encodeSTRING(inspector.asUtf8()); return;
+ case DATA: encodeDATA(inspector.asData()); return;
+ case ARRAY: encodeARRAY(inspector); return;
+ case OBJECT: encodeOBJECT(inspector); return;
+ }
+ assert false : "Should not be reached";
+ }
+
+ private void separate(boolean useComma) throws IOException {
+ if (!head && useComma) {
+ out.append((byte)',');
+ } else {
+ head = false;
+ }
+ if (!compact) {
+ out.append((byte)'\n');
+ for (int lvl = 0; lvl < level; lvl++) { out.append((byte)' '); }
+ }
+ }
+
+ public void entry(int idx, Inspector inspector) {
+ try {
+ separate(true);
+ encodeValue(inspector);
+ } catch (Exception e) {
+ // FIXME: Should we fix ArrayTraverser/ObjectTraverser API or do something more fancy here?
+ e.printStackTrace();
+ }
+ }
+
+ public void field(String name, Inspector inspector) {
+ try {
+ separate(true);
+ encodeSTRING(Utf8Codec.encode(name));
+ out.append((byte)':');
+ if (!compact)
+ out.append((byte)' ');
+ encodeValue(inspector);
+ } catch (Exception e) {
+ // FIXME: Should we fix ArrayTraverser/ObjectTraverser API or do something more fancy here?
+ e.printStackTrace();
+ }
+ }
+ }
+}
+