| // Copyright (c) 2006- Facebook |
| // Distributed under the Thrift Software License |
| // |
| // See accompanying file LICENSE or visit the Thrift site at: |
| // http://developers.facebook.com/thrift/ |
| |
| package com.facebook.thrift.protocol; |
| |
| import com.facebook.thrift.TException; |
| import com.facebook.thrift.TByteArrayOutputStream; |
| import com.facebook.thrift.transport.TTransport; |
| import java.io.UnsupportedEncodingException; |
| import java.util.Stack; |
| |
| /** |
| * JSON protocol implementation for thrift. |
| * |
| * This is a full-featured protocol supporting write and read. |
| * |
| * Please see the C++ class header for a detailed description of the |
| * protocol's wire format. |
| * |
| * @author Chad Walters <chad@powerset.com> |
| */ |
| public class TJSONProtocol extends TProtocol { |
| |
| /** |
| * Factory for JSON protocol objects |
| */ |
| public static class Factory implements TProtocolFactory { |
| |
| public TProtocol getProtocol(TTransport trans) { |
| return new TJSONProtocol(trans); |
| } |
| |
| } |
| |
| private static final byte[] COMMA = new byte[] {','}; |
| private static final byte[] COLON = new byte[] {':'}; |
| private static final byte[] LBRACE = new byte[] {'{'}; |
| private static final byte[] RBRACE = new byte[] {'}'}; |
| private static final byte[] LBRACKET = new byte[] {'['}; |
| private static final byte[] RBRACKET = new byte[] {']'}; |
| private static final byte[] QUOTE = new byte[] {'"'}; |
| private static final byte[] BACKSLASH = new byte[] {'\\'}; |
| private static final byte[] ZERO = new byte[] {'0'}; |
| |
| private static final byte[] ESCSEQ = new byte[] {'\\','u','0','0'}; |
| |
| private static final long VERSION = 1; |
| |
| private static final byte[] JSON_CHAR_TABLE = { |
| /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ |
| 0, 0, 0, 0, 0, 0, 0, 0,'b','t','n', 0,'f','r', 0, 0, // 0 |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1 |
| 1, 1,'"', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2 |
| }; |
| |
| private static final String ESCAPE_CHARS = "\"\\bfnrt"; |
| |
| private static final byte[] ESCAPE_CHAR_VALS = { |
| '"', '\\', '\b', '\f', '\n', '\r', '\t', |
| }; |
| |
| private static final int DEF_STRING_SIZE = 16; |
| |
| private static final byte[] NAME_BOOL = new byte[] {'t', 'f'}; |
| private static final byte[] NAME_BYTE = new byte[] {'i','8'}; |
| private static final byte[] NAME_I16 = new byte[] {'i','1','6'}; |
| private static final byte[] NAME_I32 = new byte[] {'i','3','2'}; |
| private static final byte[] NAME_I64 = new byte[] {'i','6','4'}; |
| private static final byte[] NAME_DOUBLE = new byte[] {'d','b','l'}; |
| private static final byte[] NAME_STRUCT = new byte[] {'r','e','c'}; |
| private static final byte[] NAME_STRING = new byte[] {'s','t','r'}; |
| private static final byte[] NAME_MAP = new byte[] {'m','a','p'}; |
| private static final byte[] NAME_LIST = new byte[] {'l','s','t'}; |
| private static final byte[] NAME_SET = new byte[] {'s','e','t'}; |
| |
| private static final byte[] getTypeNameForTypeID(byte typeID) |
| throws TException { |
| switch (typeID) { |
| case TType.BOOL: |
| return NAME_BOOL; |
| case TType.BYTE: |
| return NAME_BYTE; |
| case TType.I16: |
| return NAME_I16; |
| case TType.I32: |
| return NAME_I32; |
| case TType.I64: |
| return NAME_I64; |
| case TType.DOUBLE: |
| return NAME_DOUBLE; |
| case TType.STRING: |
| return NAME_STRING; |
| case TType.STRUCT: |
| return NAME_STRUCT; |
| case TType.MAP: |
| return NAME_MAP; |
| case TType.SET: |
| return NAME_SET; |
| case TType.LIST: |
| return NAME_LIST; |
| default: |
| throw new TProtocolException(TProtocolException.NOT_IMPLEMENTED, |
| "Unrecognized type"); |
| } |
| } |
| |
| private static final byte getTypeIDForTypeName(byte[] name) |
| throws TException { |
| byte result = TType.STOP; |
| if (name.length > 1) { |
| switch (name[0]) { |
| case 'd': |
| result = TType.DOUBLE; |
| break; |
| case 'i': |
| switch (name[1]) { |
| case '8': |
| result = TType.BYTE; |
| break; |
| case '1': |
| result = TType.I16; |
| break; |
| case '3': |
| result = TType.I32; |
| break; |
| case '6': |
| result = TType.I64; |
| break; |
| } |
| break; |
| case 'l': |
| result = TType.LIST; |
| break; |
| case 'm': |
| result = TType.MAP; |
| break; |
| case 'r': |
| result = TType.STRUCT; |
| break; |
| case 's': |
| if (name[1] == 't') { |
| result = TType.STRING; |
| } |
| else if (name[1] == 'e') { |
| result = TType.SET; |
| } |
| break; |
| case 't': |
| result = TType.BOOL; |
| break; |
| } |
| } |
| if (result == TType.STOP) { |
| throw new TProtocolException(TProtocolException.NOT_IMPLEMENTED, |
| "Unrecognized type"); |
| } |
| return result; |
| } |
| |
| // Base class for tracking JSON contexts that may require inserting/reading |
| // additional JSON syntax characters |
| // This base context does nothing. |
| protected class JSONBaseContext { |
| protected void write() throws TException {} |
| |
| protected void read() throws TException {} |
| |
| protected boolean escapeNum() { return false; } |
| } |
| |
| // Context for JSON lists. Will insert/read commas before each item except |
| // for the first one |
| protected class JSONListContext extends JSONBaseContext { |
| private boolean first_ = true; |
| |
| @Override |
| protected void write() throws TException { |
| if (first_) { |
| first_ = false; |
| } else { |
| trans_.write(COMMA); |
| } |
| } |
| |
| @Override |
| protected void read() throws TException { |
| if (first_) { |
| first_ = false; |
| } else { |
| readJSONSyntaxChar(COMMA); |
| } |
| } |
| } |
| |
| // Context for JSON records. Will insert/read colons before the value portion |
| // of each record pair, and commas before each key except the first. In |
| // addition, will indicate that numbers in the key position need to be |
| // escaped in quotes (since JSON keys must be strings). |
| protected class JSONPairContext extends JSONBaseContext { |
| private boolean first_ = true; |
| private boolean colon_ = true; |
| |
| @Override |
| protected void write() throws TException { |
| if (first_) { |
| first_ = false; |
| colon_ = true; |
| } else { |
| trans_.write(colon_ ? COLON : COMMA); |
| colon_ = !colon_; |
| } |
| } |
| |
| @Override |
| protected void read() throws TException { |
| if (first_) { |
| first_ = false; |
| colon_ = true; |
| } else { |
| readJSONSyntaxChar(colon_ ? COLON : COMMA); |
| colon_ = !colon_; |
| } |
| } |
| |
| @Override |
| protected boolean escapeNum() { |
| return colon_; |
| } |
| } |
| |
| // Holds up to one byte from the transport |
| protected class LookaheadReader { |
| |
| private boolean hasData_; |
| private byte[] data_ = new byte[1]; |
| |
| // Return and consume the next byte to be read, either taking it from the |
| // data buffer if present or getting it from the transport otherwise. |
| protected byte read() throws TException { |
| if (hasData_) { |
| hasData_ = false; |
| } |
| else { |
| trans_.readAll(data_, 0, 1); |
| } |
| return data_[0]; |
| } |
| |
| // Return the next byte to be read without consuming, filling the data |
| // buffer if it has not been filled already. |
| protected byte peek() throws TException { |
| if (!hasData_) { |
| trans_.readAll(data_, 0, 1); |
| } |
| hasData_ = true; |
| return data_[0]; |
| } |
| } |
| |
| // Stack of nested contexts that we may be in |
| private Stack<JSONBaseContext> contextStack_ = new Stack<JSONBaseContext>(); |
| |
| // Current context that we are in |
| private JSONBaseContext context_ = new JSONBaseContext(); |
| |
| // Reader that manages a 1-byte buffer |
| private LookaheadReader reader_ = new LookaheadReader(); |
| |
| // Push a new JSON context onto the stack. |
| private void pushContext(JSONBaseContext c) { |
| contextStack_.push(context_); |
| context_ = c; |
| } |
| |
| // Pop the last JSON context off the stack |
| private void popContext() { |
| context_ = contextStack_.pop(); |
| } |
| |
| /** |
| * Constructor |
| */ |
| public TJSONProtocol(TTransport trans) { |
| super(trans); |
| } |
| |
| // Temporary buffer used by several methods |
| private byte[] tmpbuf_ = new byte[4]; |
| |
| // Read a byte that must match b[0]; otherwise an excpetion is thrown. |
| // Marked protected to avoid synthetic accessor in JSONListContext.read |
| // and JSONPairContext.read |
| protected void readJSONSyntaxChar(byte[] b) throws TException { |
| byte ch = reader_.read(); |
| if (ch != b[0]) { |
| throw new TProtocolException(TProtocolException.INVALID_DATA, |
| "Unexpected character:" + (char)ch); |
| } |
| } |
| |
| // Convert a byte containing a hex char ('0'-'9' or 'a'-'f') into its |
| // corresponding hex value |
| private static final byte hexVal(byte ch) throws TException { |
| if ((ch >= '0') && (ch <= '9')) { |
| return (byte)((char)ch - '0'); |
| } |
| else if ((ch >= 'a') && (ch <= 'f')) { |
| return (byte)((char)ch - 'a'); |
| } |
| else { |
| throw new TProtocolException(TProtocolException.INVALID_DATA, |
| "Expected hex character"); |
| } |
| } |
| |
| // Convert a byte containing a hex value to its corresponding hex character |
| private static final byte hexChar(byte val) { |
| val &= 0x0F; |
| if (val < 10) { |
| return (byte)((char)val + '0'); |
| } |
| else { |
| return (byte)((char)val + 'a'); |
| } |
| } |
| |
| // Write the bytes in array buf as a JSON characters, escaping as needed |
| private void writeJSONString(byte[] b) throws TException { |
| context_.write(); |
| trans_.write(QUOTE); |
| int len = b.length; |
| for (int i = 0; i < len; i++) { |
| if ((b[i] & 0x00FF) >= 0x30) { |
| if (b[i] == BACKSLASH[0]) { |
| trans_.write(BACKSLASH); |
| trans_.write(BACKSLASH); |
| } |
| else { |
| trans_.write(b, i, 1); |
| } |
| } |
| else { |
| tmpbuf_[0] = JSON_CHAR_TABLE[b[i]]; |
| if (tmpbuf_[0] == 1) { |
| trans_.write(b, i, 1); |
| } |
| else if (tmpbuf_[0] > 1) { |
| trans_.write(BACKSLASH); |
| trans_.write(tmpbuf_, 0, 1); |
| } |
| else { |
| trans_.write(ESCSEQ); |
| tmpbuf_[0] = hexChar((byte)(b[i] >> 4)); |
| tmpbuf_[1] = hexChar(b[i]); |
| trans_.write(tmpbuf_, 0, 2); |
| } |
| } |
| } |
| trans_.write(QUOTE); |
| } |
| |
| // Write out number as a JSON value. If the context dictates so, it will be |
| // wrapped in quotes to output as a JSON string. |
| private void writeJSONInteger(long num) throws TException { |
| context_.write(); |
| String str = Long.toString(num); |
| boolean escapeNum = context_.escapeNum(); |
| if (escapeNum) { |
| trans_.write(QUOTE); |
| } |
| try { |
| byte[] buf = str.getBytes("UTF-8"); |
| trans_.write(buf); |
| } catch (UnsupportedEncodingException uex) { |
| throw new TException("JVM DOES NOT SUPPORT UTF-8"); |
| } |
| if (escapeNum) { |
| trans_.write(QUOTE); |
| } |
| } |
| |
| // Write out a double as a JSON value. If it is NaN or infinity or if the |
| // context dictates escaping, write out as JSON string. |
| private void writeJSONDouble(double num) throws TException { |
| context_.write(); |
| String str = Double.toString(num); |
| boolean special = false; |
| switch (str.charAt(0)) { |
| case 'N': // NaN |
| case 'I': // Infinity |
| special = true; |
| break; |
| case '-': |
| if (str.charAt(1) == 'I') { // -Infinity |
| special = true; |
| } |
| break; |
| } |
| |
| boolean escapeNum = special || context_.escapeNum(); |
| if (escapeNum) { |
| trans_.write(QUOTE); |
| } |
| try { |
| byte[] b = str.getBytes("UTF-8"); |
| trans_.write(b, 0, b.length); |
| } catch (UnsupportedEncodingException uex) { |
| throw new TException("JVM DOES NOT SUPPORT UTF-8"); |
| } |
| if (escapeNum) { |
| trans_.write(QUOTE); |
| } |
| } |
| |
| // Write out contents of byte array b as a JSON string with base-64 encoded |
| // data |
| private void writeJSONBase64(byte[] b) throws TException { |
| context_.write(); |
| trans_.write(QUOTE); |
| int len = b.length; |
| int off = 0; |
| while (len >= 3) { |
| // Encode 3 bytes at a time |
| TBase64Utils.encode(b, off, 3, tmpbuf_, 0); |
| trans_.write(tmpbuf_, 0, 4); |
| off += 3; |
| len -= 3; |
| } |
| if (len > 0) { |
| // Encode remainder |
| TBase64Utils.encode(b, off, len, tmpbuf_, 0); |
| trans_.write(tmpbuf_, 0, len + 1); |
| } |
| trans_.write(QUOTE); |
| } |
| |
| private void writeJSONObjectStart() throws TException { |
| context_.write(); |
| trans_.write(LBRACE); |
| pushContext(new JSONPairContext()); |
| } |
| |
| private void writeJSONObjectEnd() throws TException { |
| popContext(); |
| trans_.write(RBRACE); |
| } |
| |
| private void writeJSONArrayStart() throws TException { |
| context_.write(); |
| trans_.write(LBRACKET); |
| pushContext(new JSONListContext()); |
| } |
| |
| private void writeJSONArrayEnd() throws TException { |
| popContext(); |
| trans_.write(RBRACKET); |
| } |
| |
| @Override |
| public void writeMessageBegin(TMessage message) throws TException { |
| writeJSONArrayStart(); |
| writeJSONInteger(VERSION); |
| try { |
| byte[] b = message.name.getBytes("UTF-8"); |
| writeJSONString(b); |
| } catch (UnsupportedEncodingException uex) { |
| throw new TException("JVM DOES NOT SUPPORT UTF-8"); |
| } |
| writeJSONInteger(message.type); |
| writeJSONInteger(message.seqid); |
| } |
| |
| @Override |
| public void writeMessageEnd() throws TException { |
| writeJSONArrayEnd(); |
| } |
| |
| @Override |
| public void writeStructBegin(TStruct struct) throws TException { |
| writeJSONObjectStart(); |
| } |
| |
| @Override |
| public void writeStructEnd() throws TException { |
| writeJSONObjectEnd(); |
| } |
| |
| @Override |
| public void writeFieldBegin(TField field) throws TException { |
| writeJSONInteger(field.id); |
| writeJSONObjectStart(); |
| writeJSONString(getTypeNameForTypeID(field.type)); |
| } |
| |
| @Override |
| public void writeFieldEnd() throws TException { |
| writeJSONObjectEnd(); |
| } |
| |
| @Override |
| public void writeFieldStop() {} |
| |
| @Override |
| public void writeMapBegin(TMap map) throws TException { |
| writeJSONArrayStart(); |
| writeJSONString(getTypeNameForTypeID(map.keyType)); |
| writeJSONString(getTypeNameForTypeID(map.valueType)); |
| writeJSONInteger(map.size); |
| writeJSONObjectStart(); |
| } |
| |
| @Override |
| public void writeMapEnd() throws TException { |
| writeJSONObjectEnd(); |
| writeJSONArrayEnd(); |
| } |
| |
| @Override |
| public void writeListBegin(TList list) throws TException { |
| writeJSONArrayStart(); |
| writeJSONString(getTypeNameForTypeID(list.elemType)); |
| writeJSONInteger(list.size); |
| } |
| |
| @Override |
| public void writeListEnd() throws TException { |
| writeJSONArrayEnd(); |
| } |
| |
| @Override |
| public void writeSetBegin(TSet set) throws TException { |
| writeJSONArrayStart(); |
| writeJSONString(getTypeNameForTypeID(set.elemType)); |
| writeJSONInteger(set.size); |
| } |
| |
| @Override |
| public void writeSetEnd() throws TException { |
| writeJSONArrayEnd(); |
| } |
| |
| @Override |
| public void writeBool(boolean b) throws TException { |
| writeJSONInteger(b ? (long)1 : (long)0); |
| } |
| |
| @Override |
| public void writeByte(byte b) throws TException { |
| writeJSONInteger((long)b); |
| } |
| |
| @Override |
| public void writeI16(short i16) throws TException { |
| writeJSONInteger((long)i16); |
| } |
| |
| @Override |
| public void writeI32(int i32) throws TException { |
| writeJSONInteger((long)i32); |
| } |
| |
| @Override |
| public void writeI64(long i64) throws TException { |
| writeJSONInteger(i64); |
| } |
| |
| @Override |
| public void writeDouble(double dub) throws TException { |
| writeJSONDouble(dub); |
| } |
| |
| @Override |
| public void writeString(String str) throws TException { |
| try { |
| byte[] b = str.getBytes("UTF-8"); |
| writeJSONString(b); |
| } catch (UnsupportedEncodingException uex) { |
| throw new TException("JVM DOES NOT SUPPORT UTF-8"); |
| } |
| } |
| |
| @Override |
| public void writeBinary(byte[] bin) throws TException { |
| writeJSONBase64(bin); |
| } |
| |
| /** |
| * Reading methods. |
| */ |
| |
| // Read in a JSON string, unescaping as appropriate.. Skip reading from the |
| // context if skipContext is true. |
| private TByteArrayOutputStream readJSONString(boolean skipContext) |
| throws TException { |
| TByteArrayOutputStream arr = new TByteArrayOutputStream(DEF_STRING_SIZE); |
| if (!skipContext) { |
| context_.read(); |
| } |
| readJSONSyntaxChar(QUOTE); |
| while (true) { |
| byte ch = reader_.read(); |
| if (ch == QUOTE[0]) { |
| break; |
| } |
| if (ch == ESCSEQ[0]) { |
| ch = reader_.read(); |
| if (ch == ESCSEQ[1]) { |
| readJSONSyntaxChar(ZERO); |
| readJSONSyntaxChar(ZERO); |
| trans_.readAll(tmpbuf_, 0, 2); |
| ch = (byte)((hexVal((byte)tmpbuf_[0]) << 4) + hexVal(tmpbuf_[1])); |
| } |
| else { |
| int off = ESCAPE_CHARS.indexOf(ch); |
| if (off == -1) { |
| throw new TProtocolException(TProtocolException.INVALID_DATA, |
| "Expected control char"); |
| } |
| ch = ESCAPE_CHAR_VALS[off]; |
| } |
| } |
| arr.write(ch); |
| } |
| return arr; |
| } |
| |
| // Return true if the given byte could be a valid part of a JSON number. |
| private boolean isJSONNumeric(byte b) { |
| switch (b) { |
| case '+': |
| case '-': |
| case '.': |
| case '0': |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| case '8': |
| case '9': |
| case 'E': |
| case 'e': |
| return true; |
| } |
| return false; |
| } |
| |
| // Read in a sequence of characters that are all valid in JSON numbers. Does |
| // not do a complete regex check to validate that this is actually a number. |
| private String readJSONNumericChars() throws TException { |
| StringBuilder strbld = new StringBuilder(); |
| while (true) { |
| byte ch = reader_.peek(); |
| if (!isJSONNumeric(ch)) { |
| break; |
| } |
| strbld.append((char)reader_.read()); |
| } |
| return strbld.toString(); |
| } |
| |
| // Read in a JSON number. If the context dictates, read in enclosing quotes. |
| private long readJSONInteger() throws TException { |
| context_.read(); |
| if (context_.escapeNum()) { |
| readJSONSyntaxChar(QUOTE); |
| } |
| String str = readJSONNumericChars(); |
| if (context_.escapeNum()) { |
| readJSONSyntaxChar(QUOTE); |
| } |
| try { |
| return Long.valueOf(str); |
| } |
| catch (NumberFormatException ex) { |
| throw new TProtocolException(TProtocolException.INVALID_DATA, |
| "Bad data encounted in numeric data"); |
| } |
| } |
| |
| // Read in a JSON double value. Throw if the value is not wrapped in quotes |
| // when expected or if wrapped in quotes when not expected. |
| private double readJSONDouble() throws TException { |
| context_.read(); |
| if (reader_.peek() == QUOTE[0]) { |
| TByteArrayOutputStream arr = readJSONString(true); |
| try { |
| double dub = Double.valueOf(arr.toString("UTF-8")); |
| if (!context_.escapeNum() && !Double.isNaN(dub) && |
| !Double.isInfinite(dub)) { |
| // Throw exception -- we should not be in a string in this case |
| throw new TProtocolException(TProtocolException.INVALID_DATA, |
| "Numeric data unexpectedly quoted"); |
| } |
| return dub; |
| } |
| catch (UnsupportedEncodingException ex) { |
| throw new TException("JVM DOES NOT SUPPORT UTF-8"); |
| } |
| } |
| else { |
| if (context_.escapeNum()) { |
| // This will throw - we should have had a quote if escapeNum == true |
| readJSONSyntaxChar(QUOTE); |
| } |
| try { |
| return Double.valueOf(readJSONNumericChars()); |
| } |
| catch (NumberFormatException ex) { |
| throw new TProtocolException(TProtocolException.INVALID_DATA, |
| "Bad data encounted in numeric data"); |
| } |
| } |
| } |
| |
| // Read in a JSON string containing base-64 encoded data and decode it. |
| private byte[] readJSONBase64() throws TException { |
| TByteArrayOutputStream arr = readJSONString(false); |
| byte[] b = arr.get(); |
| int len = arr.len(); |
| int off = 0; |
| int size = 0; |
| while (len >= 4) { |
| // Decode 4 bytes at a time |
| TBase64Utils.decode(b, off, 4, b, size); // NB: decoded in place |
| off += 4; |
| len -= 4; |
| size += 3; |
| } |
| // Don't decode if we hit the end or got a single leftover byte (invalid |
| // base64 but legal for skip of regular string type) |
| if (len > 1) { |
| // Decode remainder |
| TBase64Utils.decode(b, off, len, b, size); // NB: decoded in place |
| size += len - 1; |
| } |
| // Sadly we must copy the byte[] (any way around this?) |
| byte [] result = new byte[size]; |
| System.arraycopy(b, 0, result, 0, size); |
| return result; |
| } |
| |
| private void readJSONObjectStart() throws TException { |
| context_.read(); |
| readJSONSyntaxChar(LBRACE); |
| pushContext(new JSONPairContext()); |
| } |
| |
| private void readJSONObjectEnd() throws TException { |
| readJSONSyntaxChar(RBRACE); |
| popContext(); |
| } |
| |
| private void readJSONArrayStart() throws TException { |
| context_.read(); |
| readJSONSyntaxChar(LBRACKET); |
| pushContext(new JSONListContext()); |
| } |
| |
| private void readJSONArrayEnd() throws TException { |
| readJSONSyntaxChar(RBRACKET); |
| popContext(); |
| } |
| |
| @Override |
| public TMessage readMessageBegin() throws TException { |
| TMessage message = new TMessage(); |
| readJSONArrayStart(); |
| if (readJSONInteger() != VERSION) { |
| throw new TProtocolException(TProtocolException.BAD_VERSION, |
| "Message contained bad version."); |
| } |
| try { |
| message.name = readJSONString(false).toString("UTF-8"); |
| } |
| catch (UnsupportedEncodingException ex) { |
| throw new TException("JVM DOES NOT SUPPORT UTF-8"); |
| } |
| message.type = (byte) readJSONInteger(); |
| message.seqid = (int) readJSONInteger(); |
| return message; |
| } |
| |
| @Override |
| public void readMessageEnd() throws TException { |
| readJSONArrayEnd(); |
| } |
| |
| @Override |
| public TStruct readStructBegin() throws TException { |
| readJSONObjectStart(); |
| return new TStruct(); |
| } |
| |
| @Override |
| public void readStructEnd() throws TException { |
| readJSONObjectEnd(); |
| } |
| |
| @Override |
| public TField readFieldBegin() throws TException { |
| TField field = new TField(); |
| byte ch = reader_.peek(); |
| if (ch == RBRACE[0]) { |
| field.type = TType.STOP; |
| } |
| else { |
| field.id = (short) readJSONInteger(); |
| readJSONObjectStart(); |
| field.type = getTypeIDForTypeName(readJSONString(false).get()); |
| } |
| return field; |
| } |
| |
| @Override |
| public void readFieldEnd() throws TException { |
| readJSONObjectEnd(); |
| } |
| |
| @Override |
| public TMap readMapBegin() throws TException { |
| TMap map = new TMap(); |
| readJSONArrayStart(); |
| map.keyType = getTypeIDForTypeName(readJSONString(false).get()); |
| map.valueType = getTypeIDForTypeName(readJSONString(false).get()); |
| map.size = (int)readJSONInteger(); |
| readJSONObjectStart(); |
| return map; |
| } |
| |
| @Override |
| public void readMapEnd() throws TException { |
| readJSONObjectEnd(); |
| readJSONArrayEnd(); |
| } |
| |
| @Override |
| public TList readListBegin() throws TException { |
| TList list = new TList(); |
| readJSONArrayStart(); |
| list.elemType = getTypeIDForTypeName(readJSONString(false).get()); |
| list.size = (int)readJSONInteger(); |
| return list; |
| } |
| |
| @Override |
| public void readListEnd() throws TException { |
| readJSONArrayEnd(); |
| } |
| |
| @Override |
| public TSet readSetBegin() throws TException { |
| TSet set = new TSet(); |
| readJSONArrayStart(); |
| set.elemType = getTypeIDForTypeName(readJSONString(false).get()); |
| set.size = (int)readJSONInteger(); |
| return set; |
| } |
| |
| @Override |
| public void readSetEnd() throws TException { |
| readJSONArrayEnd(); |
| } |
| |
| @Override |
| public boolean readBool() throws TException { |
| return (readJSONInteger() == 0 ? false : true); |
| } |
| |
| @Override |
| public byte readByte() throws TException { |
| return (byte) readJSONInteger(); |
| } |
| |
| @Override |
| public short readI16() throws TException { |
| return (short) readJSONInteger(); |
| } |
| |
| @Override |
| public int readI32() throws TException { |
| return (int) readJSONInteger(); |
| } |
| |
| @Override |
| public long readI64() throws TException { |
| return (long) readJSONInteger(); |
| } |
| |
| @Override |
| public double readDouble() throws TException { |
| return readJSONDouble(); |
| } |
| |
| @Override |
| public String readString() throws TException { |
| try { |
| return readJSONString(false).toString("UTF-8"); |
| } |
| catch (UnsupportedEncodingException ex) { |
| throw new TException("JVM DOES NOT SUPPORT UTF-8"); |
| } |
| } |
| |
| @Override |
| public byte[] readBinary() throws TException { |
| return readJSONBase64(); |
| } |
| |
| } |