2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
20 package org
.apache
.thrift
.protocol
;
22 import java
.io
.IOException
;
23 import java
.io
.UnsupportedEncodingException
;
24 import java
.util
.Stack
;
26 import org
.apache
.thrift
.TByteArrayOutputStream
;
27 import org
.apache
.thrift
.TException
;
28 import org
.apache
.thrift
.transport
.TTransport
;
31 * JSON protocol implementation for thrift.
32 * This is a full-featured protocol supporting write and read.
33 * Please see the C++ class header for a detailed description of the
34 * protocol's wire format.
36 public class TJSONProtocol
extends TProtocol
{
39 * Factory for JSON protocol objects
41 public static class Factory
implements TProtocolFactory
{
43 public TProtocol
getProtocol(TTransport trans
) {
44 return new TJSONProtocol(trans
);
49 private static final byte[] COMMA
= new byte[] { ',' };
50 private static final byte[] COLON
= new byte[] { ':' };
51 private static final byte[] LBRACE
= new byte[] { '{' };
52 private static final byte[] RBRACE
= new byte[] { '}' };
53 private static final byte[] LBRACKET
= new byte[] { '[' };
54 private static final byte[] RBRACKET
= new byte[] { ']' };
55 private static final byte[] QUOTE
= new byte[] { '"' };
56 private static final byte[] BACKSLASH
= new byte[] { '\\' };
57 private static final byte[] ZERO
= new byte[] { '0' };
59 private static final byte[] ESCSEQ
= new byte[] { '\\', 'u', '0', '0' };
61 private static final long VERSION
= 1;
63 private static final byte[] JSON_CHAR_TABLE
= {
64 /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */
65 0, 0, 0, 0, 0, 0, 0, 0, 'b', 't', 'n', 0, 'f', 'r', 0, 0, // 0
66 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
67 1, 1, '"', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2
70 private static final String ESCAPE_CHARS
= "\"\\/bfnrt";
72 private static final byte[] ESCAPE_CHAR_VALS
= {
73 '"', '\\', '/', '\b', '\f', '\n', '\r', '\t',
76 private static final int DEF_STRING_SIZE
= 16;
78 private static final byte[] NAME_BOOL
= new byte[] { 't', 'f' };
79 private static final byte[] NAME_BYTE
= new byte[] { 'i', '8' };
80 private static final byte[] NAME_I16
= new byte[] { 'i', '1', '6' };
81 private static final byte[] NAME_I32
= new byte[] { 'i', '3', '2' };
82 private static final byte[] NAME_I64
= new byte[] { 'i', '6', '4' };
83 private static final byte[] NAME_DOUBLE
= new byte[] { 'd', 'b', 'l' };
84 private static final byte[] NAME_STRUCT
= new byte[] { 'r', 'e', 'c' };
85 private static final byte[] NAME_STRING
= new byte[] { 's', 't', 'r' };
86 private static final byte[] NAME_MAP
= new byte[] { 'm', 'a', 'p' };
87 private static final byte[] NAME_LIST
= new byte[] { 'l', 's', 't' };
88 private static final byte[] NAME_SET
= new byte[] { 's', 'e', 't' };
90 private static final TStruct ANONYMOUS_STRUCT
= new TStruct();
92 private static final byte[] getTypeNameForTypeID(byte typeID
)
118 throw new TProtocolException(TProtocolException
.NOT_IMPLEMENTED
,
119 "Unrecognized type");
123 private static final byte getTypeIDForTypeName(byte[] name
)
125 byte result
= TType
.STOP
;
126 if (name
.length
> 1) {
129 result
= TType
.DOUBLE
;
154 result
= TType
.STRUCT
;
157 if (name
[1] == 't') {
158 result
= TType
.STRING
;
160 else if (name
[1] == 'e') {
169 if (result
== TType
.STOP
) {
170 throw new TProtocolException(TProtocolException
.NOT_IMPLEMENTED
,
171 "Unrecognized type");
176 // Base class for tracking JSON contexts that may require inserting/reading
177 // additional JSON syntax characters
178 // This base context does nothing.
179 protected class JSONBaseContext
{
183 protected void write() throws TException
{}
188 protected void read() throws TException
{}
190 protected boolean escapeNum() {
195 // Context for JSON lists. Will insert/read commas before each item except
197 protected class JSONListContext
extends JSONBaseContext
{
198 private boolean first_
= true;
200 protected void write() throws TException
{
208 protected void read() throws TException
{
212 readJSONSyntaxChar(COMMA
);
217 // Context for JSON records. Will insert/read colons before the value portion
218 // of each record pair, and commas before each key except the first. In
219 // addition, will indicate that numbers in the key position need to be
220 // escaped in quotes (since JSON keys must be strings).
221 protected class JSONPairContext
extends JSONBaseContext
{
222 private boolean first_
= true;
223 private boolean colon_
= true;
225 protected void write() throws TException
{
230 trans_
.write(colon_ ? COLON
: COMMA
);
235 protected void read() throws TException
{
240 readJSONSyntaxChar(colon_ ? COLON
: COMMA
);
245 protected boolean escapeNum() {
250 // Holds up to one byte from the transport
251 protected class LookaheadReader
{
253 private boolean hasData_
;
254 private byte[] data_
= new byte[1];
256 // Return and consume the next byte to be read, either taking it from the
257 // data buffer if present or getting it from the transport otherwise.
258 protected byte read() throws TException
{
263 trans_
.readAll(data_
, 0, 1);
268 // Return the next byte to be read without consuming, filling the data
269 // buffer if it has not been filled already.
270 protected byte peek() throws TException
{
272 trans_
.readAll(data_
, 0, 1);
279 // Stack of nested contexts of type JSONBaseContext that we may be in
280 private Stack contextStack_
= new Stack();
282 // Current context that we are in
283 private JSONBaseContext context_
= new JSONBaseContext();
285 // Reader that manages a 1-byte buffer
286 private LookaheadReader reader_
= new LookaheadReader();
288 // Push a new JSON context onto the stack.
289 private void pushContext(JSONBaseContext c
) {
290 contextStack_
.push(context_
);
294 // Pop the last JSON context off the stack
295 private void popContext() {
296 context_
= (JSONBaseContext
)contextStack_
.pop();
302 public TJSONProtocol(TTransport trans
) {
306 public void reset() {
307 contextStack_
.clear();
308 context_
= new JSONBaseContext();
309 reader_
= new LookaheadReader();
312 // Temporary buffer used by several methods
313 private byte[] tmpbuf_
= new byte[4];
315 // Read a byte that must match b[0]; otherwise an exception is thrown.
316 // Marked protected to avoid synthetic accessor in JSONListContext.read
317 // and JSONPairContext.read
318 protected void readJSONSyntaxChar(byte[] b
) throws TException
{
319 byte ch
= reader_
.read();
321 throw new TProtocolException(TProtocolException
.INVALID_DATA
,
322 "Unexpected character:" + (char)ch
);
326 // Convert a byte containing a hex char ('0'-'9' or 'a'-'f') into its
327 // corresponding hex value
328 private static final byte hexVal(byte ch
) throws TException
{
329 if ((ch
>= '0') && (ch
<= '9')) {
330 return (byte)((char)ch
- '0');
332 else if ((ch
>= 'a') && (ch
<= 'f')) {
333 return (byte)((char)ch
- 'a' + 10);
336 throw new TProtocolException(TProtocolException
.INVALID_DATA
,
337 "Expected hex character");
341 // Convert a byte containing a hex value to its corresponding hex character
342 private static final byte hexChar(byte val
) {
345 return (byte)((char)val
+ '0');
348 return (byte)((char)(val
- 10) + 'a');
352 private static boolean isHighSurrogate(char c
) {
353 return c
>= '\uD800' && c
<= '\uDBFF';
356 private static boolean isLowSurrogate(char c
) {
357 return c
>= '\uDC00' && c
<= '\uDFFF';
360 private static byte[] toUTF8(int codepoint
) {
361 final int[] FIRST_BYTE_MASK
= { 0, 0xc0, 0xe0, 0xf0 };
363 if (codepoint
<= 0x7f) length
= 1;
364 else if (codepoint
<= 0x7ff) length
= 2;
365 else if (codepoint
<= 0xffff) length
= 3;
366 else if (codepoint
<= 0x1fffff) length
= 4;
367 else throw new RuntimeException("Code point over U+1FFFFF is not supported");
369 byte[] bytes
= new byte[length
];
372 bytes
[3] = (byte)((codepoint
& 0x3f) | 0x80);
375 bytes
[2] = (byte)((codepoint
& 0x3f) | 0x80);
378 bytes
[1] = (byte)((codepoint
& 0x3f) | 0x80);
381 bytes
[0] = (byte)(codepoint
| FIRST_BYTE_MASK
[length
- 1]);
387 private static byte[] toUTF8(int high
, int low
) {
388 int codepoint
= (1 << 16) + ((high
& 0x3ff) << 10);
389 codepoint
+= low
& 0x3ff;
390 return toUTF8(codepoint
);
393 // Write the bytes in array buf as a JSON characters, escaping as needed
394 private void writeJSONString(byte[] b
) throws TException
{
398 for (int i
= 0; i
< len
; i
++) {
399 if ((b
[i
] & 0x00FF) >= 0x30) {
400 if (b
[i
] == BACKSLASH
[0]) {
401 trans_
.write(BACKSLASH
);
402 trans_
.write(BACKSLASH
);
405 trans_
.write(b
, i
, 1);
409 tmpbuf_
[0] = JSON_CHAR_TABLE
[b
[i
]];
410 if (tmpbuf_
[0] == 1) {
411 trans_
.write(b
, i
, 1);
413 else if (tmpbuf_
[0] > 1) {
414 trans_
.write(BACKSLASH
);
415 trans_
.write(tmpbuf_
, 0, 1);
418 trans_
.write(ESCSEQ
);
419 tmpbuf_
[0] = hexChar((byte)(b
[i
] >> 4));
420 tmpbuf_
[1] = hexChar(b
[i
]);
421 trans_
.write(tmpbuf_
, 0, 2);
428 // Write out number as a JSON value. If the context dictates so, it will be
429 // wrapped in quotes to output as a JSON string.
430 private void writeJSONInteger(long num
) throws TException
{
432 String str
= Long
.toString(num
);
433 boolean escapeNum
= context_
.escapeNum();
438 byte[] buf
= str
.getBytes("UTF-8");
440 } catch (UnsupportedEncodingException uex
) {
441 throw new TException("JVM DOES NOT SUPPORT UTF-8");
448 // Write out a double as a JSON value. If it is NaN or infinity or if the
449 // context dictates escaping, write out as JSON string.
450 private void writeJSONDouble(double num
) throws TException
{
452 String str
= Double
.toString(num
);
453 boolean special
= false;
454 switch (str
.charAt(0)) {
456 case 'I': // Infinity
460 if (str
.charAt(1) == 'I') { // -Infinity
466 boolean escapeNum
= special
|| context_
.escapeNum();
471 byte[] b
= str
.getBytes("UTF-8");
472 trans_
.write(b
, 0, b
.length
);
473 } catch (UnsupportedEncodingException uex
) {
474 throw new TException("JVM DOES NOT SUPPORT UTF-8");
481 // Write out contents of byte array b as a JSON string with base-64 encoded
483 private void writeJSONBase64(byte[] b
, int offset
, int length
) throws TException
{
489 // Encode 3 bytes at a time
490 TBase64Utils
.encode(b
, off
, 3, tmpbuf_
, 0);
491 trans_
.write(tmpbuf_
, 0, 4);
497 TBase64Utils
.encode(b
, off
, len
, tmpbuf_
, 0);
498 trans_
.write(tmpbuf_
, 0, len
+ 1);
503 private void writeJSONObjectStart() throws TException
{
505 trans_
.write(LBRACE
);
506 pushContext(new JSONPairContext());
509 private void writeJSONObjectEnd() throws TException
{
511 trans_
.write(RBRACE
);
514 private void writeJSONArrayStart() throws TException
{
516 trans_
.write(LBRACKET
);
517 pushContext(new JSONListContext());
520 private void writeJSONArrayEnd() throws TException
{
522 trans_
.write(RBRACKET
);
525 public void writeMessageBegin(TMessage message
) throws TException
{
526 writeJSONArrayStart();
527 writeJSONInteger(VERSION
);
529 byte[] b
= message
.name
.getBytes("UTF-8");
531 } catch (UnsupportedEncodingException uex
) {
532 throw new TException("JVM DOES NOT SUPPORT UTF-8");
534 writeJSONInteger(message
.type
);
535 writeJSONInteger(message
.seqid
);
538 public void writeMessageEnd() throws TException
{
542 public void writeStructBegin(TStruct struct
) throws TException
{
543 writeJSONObjectStart();
546 public void writeStructEnd() throws TException
{
547 writeJSONObjectEnd();
550 public void writeFieldBegin(TField field
) throws TException
{
551 writeJSONInteger(field
.id
);
552 writeJSONObjectStart();
553 writeJSONString(getTypeNameForTypeID(field
.type
));
556 public void writeFieldEnd() throws TException
{
557 writeJSONObjectEnd();
560 public void writeFieldStop() {}
562 public void writeMapBegin(TMap map
) throws TException
{
563 writeJSONArrayStart();
564 writeJSONString(getTypeNameForTypeID(map
.keyType
));
565 writeJSONString(getTypeNameForTypeID(map
.valueType
));
566 writeJSONInteger(map
.size
);
567 writeJSONObjectStart();
570 public void writeMapEnd() throws TException
{
571 writeJSONObjectEnd();
575 public void writeListBegin(TList list
) throws TException
{
576 writeJSONArrayStart();
577 writeJSONString(getTypeNameForTypeID(list
.elemType
));
578 writeJSONInteger(list
.size
);
581 public void writeListEnd() throws TException
{
585 public void writeSetBegin(TSet set
) throws TException
{
586 writeJSONArrayStart();
587 writeJSONString(getTypeNameForTypeID(set
.elemType
));
588 writeJSONInteger(set
.size
);
591 public void writeSetEnd() throws TException
{
595 public void writeBool(boolean b
) throws TException
{
596 writeJSONInteger(b ?
(long)1 : (long)0);
599 public void writeByte(byte b
) throws TException
{
603 public void writeI16(short i16
) throws TException
{
604 writeJSONInteger(i16
);
607 public void writeI32(int i32
) throws TException
{
608 writeJSONInteger(i32
);
611 public void writeI64(long i64
) throws TException
{
612 writeJSONInteger(i64
);
615 public void writeDouble(double dub
) throws TException
{
616 writeJSONDouble(dub
);
619 public void writeString(String str
) throws TException
{
621 byte[] b
= str
.getBytes("UTF-8");
623 } catch (UnsupportedEncodingException uex
) {
624 throw new TException("JVM DOES NOT SUPPORT UTF-8");
628 public void writeBinary(byte[] bin
) throws TException
{
629 writeJSONBase64(bin
, 0, bin
.length
);
636 // Read in a JSON string, unescaping as appropriate.. Skip reading from the
637 // context if skipContext is true.
638 private TByteArrayOutputStream
readJSONString(boolean skipContext
)
640 TByteArrayOutputStream arr
= new TByteArrayOutputStream(DEF_STRING_SIZE
);
641 int highSurrogate
= 0;
645 readJSONSyntaxChar(QUOTE
);
647 byte ch
= reader_
.read();
648 if (ch
== QUOTE
[0]) {
651 if (ch
== ESCSEQ
[0]) {
653 if (ch
== ESCSEQ
[1]) {
654 trans_
.readAll(tmpbuf_
, 0, 4);
656 ((short)hexVal(tmpbuf_
[0]) << 12) +
657 ((short)hexVal(tmpbuf_
[1]) << 8) +
658 ((short)hexVal(tmpbuf_
[2]) << 4) +
659 (short)hexVal(tmpbuf_
[3]));
661 if (isHighSurrogate((char)cu
)) {
662 if (highSurrogate
!= 0) {
663 throw new TProtocolException(TProtocolException
.INVALID_DATA
,
664 "Expected low surrogate char");
668 else if (isLowSurrogate((char)cu
)) {
669 if (highSurrogate
== 0) {
670 throw new TProtocolException(TProtocolException
.INVALID_DATA
,
671 "Expected high surrogate char");
674 arr
.write(toUTF8(highSurrogate
, cu
));
678 arr
.write(toUTF8(cu
));
682 catch (UnsupportedEncodingException ex
) {
683 throw new TProtocolException(TProtocolException
.NOT_IMPLEMENTED
,
684 "JVM does not support UTF-8");
686 catch (IOException ex
) {
687 throw new TProtocolException(TProtocolException
.INVALID_DATA
,
688 "Invalid unicode sequence");
692 int off
= ESCAPE_CHARS
.indexOf(ch
);
694 throw new TProtocolException(TProtocolException
.INVALID_DATA
,
695 "Expected control char");
697 ch
= ESCAPE_CHAR_VALS
[off
];
703 if (highSurrogate
!= 0) {
704 throw new TProtocolException(TProtocolException
.INVALID_DATA
,
705 "Expected low surrogate char");
710 // Return true if the given byte could be a valid part of a JSON number.
711 private boolean isJSONNumeric(byte b
) {
733 // Read in a sequence of characters that are all valid in JSON numbers. Does
734 // not do a complete regex check to validate that this is actually a number.
735 private String
readJSONNumericChars() throws TException
{
736 StringBuffer strbuf
= new StringBuffer();
738 byte ch
= reader_
.peek();
739 if (!isJSONNumeric(ch
)) {
742 strbuf
.append((char)reader_
.read());
744 return strbuf
.toString();
747 // Read in a JSON number. If the context dictates, read in enclosing quotes.
748 private long readJSONInteger() throws TException
{
750 if (context_
.escapeNum()) {
751 readJSONSyntaxChar(QUOTE
);
753 String str
= readJSONNumericChars();
754 if (context_
.escapeNum()) {
755 readJSONSyntaxChar(QUOTE
);
758 return Long
.valueOf(str
).longValue();
759 } catch (NumberFormatException ex
) {
760 throw new TProtocolException(TProtocolException
.INVALID_DATA
,
761 "Bad data encounted in numeric data");
765 // Read in a JSON double value. Throw if the value is not wrapped in quotes
766 // when expected or if wrapped in quotes when not expected.
767 private double readJSONDouble() throws TException
{
769 if (reader_
.peek() == QUOTE
[0]) {
770 TByteArrayOutputStream arr
= readJSONString(true);
772 double dub
= Double
.valueOf(arr
.toString("UTF-8")).doubleValue();
773 if (!context_
.escapeNum() && !Double
.isNaN(dub
) &&
774 !Double
.isInfinite(dub
)) {
775 // Throw exception -- we should not be in a string in this case
776 throw new TProtocolException(TProtocolException
.INVALID_DATA
,
777 "Numeric data unexpectedly quoted");
780 } catch (UnsupportedEncodingException ex
) {
781 throw new TException("JVM DOES NOT SUPPORT UTF-8");
785 if (context_
.escapeNum()) {
786 // This will throw - we should have had a quote if escapeNum == true
787 readJSONSyntaxChar(QUOTE
);
790 return Double
.valueOf(readJSONNumericChars()).doubleValue();
791 } catch (NumberFormatException ex
) {
792 throw new TProtocolException(TProtocolException
.INVALID_DATA
,
793 "Bad data encounted in numeric data");
798 // Read in a JSON string containing base-64 encoded data and decode it.
799 private byte[] readJSONBase64() throws TException
{
800 TByteArrayOutputStream arr
= readJSONString(false);
801 byte[] b
= arr
.get();
806 int bound
= len
>= 2 ? len
- 2 : 0;
807 for (int i
= len
- 1; i
>= bound
&& b
[i
] == '='; --i
) {
811 // Decode 4 bytes at a time
812 TBase64Utils
.decode(b
, off
, 4, b
, size
); // NB: decoded in place
817 // Don't decode if we hit the end or got a single leftover byte (invalid
818 // base64 but legal for skip of regular string type)
821 TBase64Utils
.decode(b
, off
, len
, b
, size
); // NB: decoded in place
824 // Sadly we must copy the byte[] (any way around this?)
825 byte[] result
= new byte[size
];
826 System
.arraycopy(b
, 0, result
, 0, size
);
830 private void readJSONObjectStart() throws TException
{
832 readJSONSyntaxChar(LBRACE
);
833 pushContext(new JSONPairContext());
836 private void readJSONObjectEnd() throws TException
{
837 readJSONSyntaxChar(RBRACE
);
841 private void readJSONArrayStart() throws TException
{
843 readJSONSyntaxChar(LBRACKET
);
844 pushContext(new JSONListContext());
847 private void readJSONArrayEnd() throws TException
{
848 readJSONSyntaxChar(RBRACKET
);
852 public TMessage
readMessageBegin() throws TException
{
853 readJSONArrayStart();
854 if (readJSONInteger() != VERSION
) {
855 throw new TProtocolException(TProtocolException
.BAD_VERSION
,
856 "Message contained bad version.");
860 name
= readJSONString(false).toString("UTF-8");
861 } catch (UnsupportedEncodingException ex
) {
862 throw new TException("JVM DOES NOT SUPPORT UTF-8");
864 byte type
= (byte)readJSONInteger();
865 int seqid
= (int)readJSONInteger();
866 return new TMessage(name
, type
, seqid
);
869 public void readMessageEnd() throws TException
{
873 public TStruct
readStructBegin() throws TException
{
874 readJSONObjectStart();
875 return ANONYMOUS_STRUCT
;
878 public void readStructEnd() throws TException
{
882 public TField
readFieldBegin() throws TException
{
883 byte ch
= reader_
.peek();
886 if (ch
== RBRACE
[0]) {
890 id
= (short)readJSONInteger();
891 readJSONObjectStart();
892 type
= getTypeIDForTypeName(readJSONString(false).get());
894 return new TField("", type
, id
);
897 public void readFieldEnd() throws TException
{
901 public TMap
readMapBegin() throws TException
{
902 readJSONArrayStart();
903 byte keyType
= getTypeIDForTypeName(readJSONString(false).get());
904 byte valueType
= getTypeIDForTypeName(readJSONString(false).get());
905 int size
= (int)readJSONInteger();
906 readJSONObjectStart();
907 return new TMap(keyType
, valueType
, size
);
910 public void readMapEnd() throws TException
{
915 public TList
readListBegin() throws TException
{
916 readJSONArrayStart();
917 byte elemType
= getTypeIDForTypeName(readJSONString(false).get());
918 int size
= (int)readJSONInteger();
919 return new TList(elemType
, size
);
922 public void readListEnd() throws TException
{
926 public TSet
readSetBegin() throws TException
{
927 readJSONArrayStart();
928 byte elemType
= getTypeIDForTypeName(readJSONString(false).get());
929 int size
= (int)readJSONInteger();
930 return new TSet(elemType
, size
);
933 public void readSetEnd() throws TException
{
937 public boolean readBool() throws TException
{
938 return (readJSONInteger() == 0 ?
false : true);
941 public byte readByte() throws TException
{
942 return (byte)readJSONInteger();
945 public short readI16() throws TException
{
946 return (short)readJSONInteger();
949 public int readI32() throws TException
{
950 return (int)readJSONInteger();
953 public long readI64() throws TException
{
954 return readJSONInteger();
957 public double readDouble() throws TException
{
958 return readJSONDouble();
961 public String
readString() throws TException
{
963 return readJSONString(false).toString("UTF-8");
964 } catch (UnsupportedEncodingException ex
) {
965 throw new TException("JVM DOES NOT SUPPORT UTF-8");
969 public byte[] readBinary() throws TException
{
970 return readJSONBase64();