| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.apache.thrift.protocol; |
| |
| import java.io.IOException; |
| import java.io.UnsupportedEncodingException; |
| import java.util.Stack; |
| |
| import org.apache.thrift.TByteArrayOutputStream; |
| import org.apache.thrift.TException; |
| import org.apache.thrift.transport.TTransport; |
| |
| /** |
| * 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. |
| */ |
| 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 TStruct ANONYMOUS_STRUCT = new TStruct(); |
| |
| 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 { |
| /** |
| * @throws TException |
| */ |
| protected void write() throws TException {} |
| |
| /** |
| * @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; |
| |
| protected void write() throws TException { |
| if (first_) { |
| first_ = false; |
| } else { |
| trans_.write(COMMA); |
| } |
| } |
| |
| 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; |
| |
| protected void write() throws TException { |
| if (first_) { |
| first_ = false; |
| colon_ = true; |
| } else { |
| trans_.write(colon_ ? COLON : COMMA); |
| colon_ = !colon_; |
| } |
| } |
| |
| protected void read() throws TException { |
| if (first_) { |
| first_ = false; |
| colon_ = true; |
| } else { |
| readJSONSyntaxChar(colon_ ? COLON : COMMA); |
| colon_ = !colon_; |
| } |
| } |
| |
| 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 of type JSONBaseContext that we may be in |
| private Stack contextStack_ = new Stack(); |
| |
| // 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_ = (JSONBaseContext)contextStack_.pop(); |
| } |
| |
| /** |
| * Constructor |
| */ |
| public TJSONProtocol(TTransport trans) { |
| super(trans); |
| } |
| |
| public void reset() { |
| contextStack_.clear(); |
| context_ = new JSONBaseContext(); |
| reader_ = new LookaheadReader(); |
| } |
| |
| // Temporary buffer used by several methods |
| private byte[] tmpbuf_ = new byte[4]; |
| |
| // Read a byte that must match b[0]; otherwise an exception 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' + 10); |
| } |
| 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 - 10) + 'a'); |
| } |
| } |
| |
| private static boolean isHighSurrogate(char c) { |
| return c >= '\uD800' && c <= '\uDBFF'; |
| } |
| |
| private static boolean isLowSurrogate(char c) { |
| return c >= '\uDC00' && c <= '\uDFFF'; |
| } |
| |
| private static byte[] toUTF8(int codepoint) { |
| final int[] FIRST_BYTE_MASK = { 0, 0xc0, 0xe0, 0xf0 }; |
| int length = 0; |
| if (codepoint <= 0x7f) length = 1; |
| else if (codepoint <= 0x7ff) length = 2; |
| else if (codepoint <= 0xffff) length = 3; |
| else if (codepoint <= 0x1fffff) length = 4; |
| else throw new RuntimeException("Code point over U+1FFFFF is not supported"); |
| |
| byte[] bytes = new byte[length]; |
| switch (length) { |
| case 4: |
| bytes[3] = (byte)((codepoint & 0x3f) | 0x80); |
| codepoint >>= 6; |
| case 3: |
| bytes[2] = (byte)((codepoint & 0x3f) | 0x80); |
| codepoint >>= 6; |
| case 2: |
| bytes[1] = (byte)((codepoint & 0x3f) | 0x80); |
| codepoint >>= 6; |
| case 1: |
| bytes[0] = (byte)(codepoint | FIRST_BYTE_MASK[length - 1]); |
| } |
| |
| return bytes; |
| } |
| |
| private static byte[] toUTF8(int high, int low) { |
| int codepoint = (1 << 16) + ((high & 0x3ff) << 10); |
| codepoint += low & 0x3ff; |
| return toUTF8(codepoint); |
| } |
| |
| // 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, int offset, int length) throws TException { |
| context_.write(); |
| trans_.write(QUOTE); |
| int len = length; |
| int off = offset; |
| 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); |
| } |
| |
| 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); |
| } |
| |
| public void writeMessageEnd() throws TException { |
| writeJSONArrayEnd(); |
| } |
| |
| public void writeStructBegin(TStruct struct) throws TException { |
| writeJSONObjectStart(); |
| } |
| |
| public void writeStructEnd() throws TException { |
| writeJSONObjectEnd(); |
| } |
| |
| public void writeFieldBegin(TField field) throws TException { |
| writeJSONInteger(field.id); |
| writeJSONObjectStart(); |
| writeJSONString(getTypeNameForTypeID(field.type)); |
| } |
| |
| public void writeFieldEnd() throws TException { |
| writeJSONObjectEnd(); |
| } |
| |
| public void writeFieldStop() {} |
| |
| public void writeMapBegin(TMap map) throws TException { |
| writeJSONArrayStart(); |
| writeJSONString(getTypeNameForTypeID(map.keyType)); |
| writeJSONString(getTypeNameForTypeID(map.valueType)); |
| writeJSONInteger(map.size); |
| writeJSONObjectStart(); |
| } |
| |
| public void writeMapEnd() throws TException { |
| writeJSONObjectEnd(); |
| writeJSONArrayEnd(); |
| } |
| |
| public void writeListBegin(TList list) throws TException { |
| writeJSONArrayStart(); |
| writeJSONString(getTypeNameForTypeID(list.elemType)); |
| writeJSONInteger(list.size); |
| } |
| |
| public void writeListEnd() throws TException { |
| writeJSONArrayEnd(); |
| } |
| |
| public void writeSetBegin(TSet set) throws TException { |
| writeJSONArrayStart(); |
| writeJSONString(getTypeNameForTypeID(set.elemType)); |
| writeJSONInteger(set.size); |
| } |
| |
| public void writeSetEnd() throws TException { |
| writeJSONArrayEnd(); |
| } |
| |
| public void writeBool(boolean b) throws TException { |
| writeJSONInteger(b ? (long)1 : (long)0); |
| } |
| |
| public void writeByte(byte b) throws TException { |
| writeJSONInteger(b); |
| } |
| |
| public void writeI16(short i16) throws TException { |
| writeJSONInteger(i16); |
| } |
| |
| public void writeI32(int i32) throws TException { |
| writeJSONInteger(i32); |
| } |
| |
| public void writeI64(long i64) throws TException { |
| writeJSONInteger(i64); |
| } |
| |
| public void writeDouble(double dub) throws TException { |
| writeJSONDouble(dub); |
| } |
| |
| 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"); |
| } |
| } |
| |
| public void writeBinary(byte[] bin) throws TException { |
| writeJSONBase64(bin, 0, bin.length); |
| } |
| |
| /** |
| * 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); |
| int highSurrogate = 0; |
| 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]) { |
| trans_.readAll(tmpbuf_, 0, 4); |
| short cu = (short)( |
| ((short)hexVal(tmpbuf_[0]) << 12) + |
| ((short)hexVal(tmpbuf_[1]) << 8) + |
| ((short)hexVal(tmpbuf_[2]) << 4) + |
| (short)hexVal(tmpbuf_[3])); |
| try { |
| if (isHighSurrogate((char)cu)) { |
| if (highSurrogate != 0) { |
| throw new TProtocolException(TProtocolException.INVALID_DATA, |
| "Expected low surrogate char"); |
| } |
| highSurrogate = cu; |
| } |
| else if (isLowSurrogate((char)cu)) { |
| if (highSurrogate == 0) { |
| throw new TProtocolException(TProtocolException.INVALID_DATA, |
| "Expected high surrogate char"); |
| } |
| |
| arr.write(toUTF8(highSurrogate, cu)); |
| highSurrogate = 0; |
| } |
| else { |
| arr.write(toUTF8(cu)); |
| } |
| continue; |
| } |
| catch (UnsupportedEncodingException ex) { |
| throw new TProtocolException(TProtocolException.NOT_IMPLEMENTED, |
| "JVM does not support UTF-8"); |
| } |
| catch (IOException ex) { |
| throw new TProtocolException(TProtocolException.INVALID_DATA, |
| "Invalid unicode sequence"); |
| } |
| } |
| 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); |
| } |
| |
| if (highSurrogate != 0) { |
| throw new TProtocolException(TProtocolException.INVALID_DATA, |
| "Expected low surrogate char"); |
| } |
| 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 { |
| StringBuffer strbuf = new StringBuffer(); |
| while (true) { |
| byte ch = reader_.peek(); |
| if (!isJSONNumeric(ch)) { |
| break; |
| } |
| strbuf.append((char)reader_.read()); |
| } |
| return strbuf.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).longValue(); |
| } 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")).doubleValue(); |
| 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()).doubleValue(); |
| } 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; |
| // Ignore padding |
| int bound = len >= 2 ? len - 2 : 0; |
| for (int i = len - 1; i >= bound && b[i] == '='; --i) { |
| --len; |
| } |
| 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(); |
| } |
| |
| public TMessage readMessageBegin() throws TException { |
| readJSONArrayStart(); |
| if (readJSONInteger() != VERSION) { |
| throw new TProtocolException(TProtocolException.BAD_VERSION, |
| "Message contained bad version."); |
| } |
| String name; |
| try { |
| name = readJSONString(false).toString("UTF-8"); |
| } catch (UnsupportedEncodingException ex) { |
| throw new TException("JVM DOES NOT SUPPORT UTF-8"); |
| } |
| byte type = (byte)readJSONInteger(); |
| int seqid = (int)readJSONInteger(); |
| return new TMessage(name, type, seqid); |
| } |
| |
| public void readMessageEnd() throws TException { |
| readJSONArrayEnd(); |
| } |
| |
| public TStruct readStructBegin() throws TException { |
| readJSONObjectStart(); |
| return ANONYMOUS_STRUCT; |
| } |
| |
| public void readStructEnd() throws TException { |
| readJSONObjectEnd(); |
| } |
| |
| public TField readFieldBegin() throws TException { |
| byte ch = reader_.peek(); |
| byte type; |
| short id = 0; |
| if (ch == RBRACE[0]) { |
| type = TType.STOP; |
| } |
| else { |
| id = (short)readJSONInteger(); |
| readJSONObjectStart(); |
| type = getTypeIDForTypeName(readJSONString(false).get()); |
| } |
| return new TField("", type, id); |
| } |
| |
| public void readFieldEnd() throws TException { |
| readJSONObjectEnd(); |
| } |
| |
| public TMap readMapBegin() throws TException { |
| readJSONArrayStart(); |
| byte keyType = getTypeIDForTypeName(readJSONString(false).get()); |
| byte valueType = getTypeIDForTypeName(readJSONString(false).get()); |
| int size = (int)readJSONInteger(); |
| readJSONObjectStart(); |
| return new TMap(keyType, valueType, size); |
| } |
| |
| public void readMapEnd() throws TException { |
| readJSONObjectEnd(); |
| readJSONArrayEnd(); |
| } |
| |
| public TList readListBegin() throws TException { |
| readJSONArrayStart(); |
| byte elemType = getTypeIDForTypeName(readJSONString(false).get()); |
| int size = (int)readJSONInteger(); |
| return new TList(elemType, size); |
| } |
| |
| public void readListEnd() throws TException { |
| readJSONArrayEnd(); |
| } |
| |
| public TSet readSetBegin() throws TException { |
| readJSONArrayStart(); |
| byte elemType = getTypeIDForTypeName(readJSONString(false).get()); |
| int size = (int)readJSONInteger(); |
| return new TSet(elemType, size); |
| } |
| |
| public void readSetEnd() throws TException { |
| readJSONArrayEnd(); |
| } |
| |
| public boolean readBool() throws TException { |
| return (readJSONInteger() == 0 ? false : true); |
| } |
| |
| public byte readByte() throws TException { |
| return (byte)readJSONInteger(); |
| } |
| |
| public short readI16() throws TException { |
| return (short)readJSONInteger(); |
| } |
| |
| public int readI32() throws TException { |
| return (int)readJSONInteger(); |
| } |
| |
| public long readI64() throws TException { |
| return readJSONInteger(); |
| } |
| |
| public double readDouble() throws TException { |
| return readJSONDouble(); |
| } |
| |
| public String readString() throws TException { |
| try { |
| return readJSONString(false).toString("UTF-8"); |
| } catch (UnsupportedEncodingException ex) { |
| throw new TException("JVM DOES NOT SUPPORT UTF-8"); |
| } |
| } |
| |
| public byte[] readBinary() throws TException { |
| return readJSONBase64(); |
| } |
| |
| } |