1 /* 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 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 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 17 * under the License. 18 */ 19 20 package com.facebook.thrift.protocol; 21 22 import com.facebook.thrift.TException; 23 import com.facebook.thrift.meta_data.FieldMetaData; 24 import com.facebook.thrift.transport.TTransport; 25 import java.io.ByteArrayOutputStream; 26 import java.nio.charset.StandardCharsets; 27 import java.util.ArrayDeque; 28 import java.util.HashMap; 29 import java.util.Map; 30 import java.util.Stack; 31 32 abstract class AbstractTSimpleJSONProtocol extends TProtocol { 33 34 private static final byte[] COMMA = new byte[] {','}; 35 private static final byte[] COLON = new byte[] {':'}; 36 private static final byte[] LBRACE = new byte[] {'{'}; 37 private static final byte[] RBRACE = new byte[] {'}'}; 38 private static final byte[] LBRACKET = new byte[] {'['}; 39 private static final byte[] RBRACKET = new byte[] {']'}; 40 private static final byte[] QUOTE = new byte[] {'"'}; 41 private static final byte[] BOOL_TRUE = "true".getBytes(StandardCharsets.UTF_8); 42 private static final byte[] BOOL_FALSE = "false".getBytes(StandardCharsets.UTF_8); 43 private static final byte WHITE_SPACE = ' '; 44 private static final byte TAB = '\t'; 45 private static final byte NEW_LINE = '\n'; 46 private static final byte CARRIAGE_RETURN = '\r'; 47 48 private static final TStruct ANONYMOUS_STRUCT = new TStruct(); 49 private static final TField ANONYMOUS_FIELD = new TField(); 50 private static final TMessage EMPTY_MESSAGE = new TMessage(); 51 private static final TSet EMPTY_SET = new TSet(); 52 private static final TList EMPTY_LIST = new TList(); 53 private static final TMap EMPTY_MAP = new TMap(); 54 private static final String LIST = "list"; 55 private static final String SET = "set"; 56 private static final String MAP = "map"; 57 58 private static final long VERSION = 1; 59 60 protected class Context { write()61 protected void write() throws TException {} 62 63 /** Returns whether the current value is a key in a map */ isMapKey()64 protected boolean isMapKey() { 65 return false; 66 } 67 } 68 69 protected class ListContext extends Context { 70 protected boolean first_ = true; 71 write()72 protected void write() throws TException { 73 if (first_) { 74 first_ = false; 75 } else { 76 trans_.write(COMMA); 77 } 78 } 79 } 80 81 protected class StructContext extends Context { 82 protected boolean first_ = true; 83 protected boolean colon_ = true; 84 write()85 protected void write() throws TException { 86 if (first_) { 87 first_ = false; 88 colon_ = true; 89 } else { 90 trans_.write(colon_ ? COLON : COMMA); 91 colon_ = !colon_; 92 } 93 } 94 } 95 96 protected class MapContext extends StructContext { 97 protected boolean isKey = true; 98 99 @Override write()100 protected void write() throws TException { 101 super.write(); 102 isKey = !isKey; 103 } 104 isMapKey()105 protected boolean isMapKey() { 106 // we want to coerce map keys to json strings regardless 107 // of their type 108 return isKey; 109 } 110 } 111 112 protected final Context BASE_CONTEXT = new Context(); 113 114 /** Stack of nested contexts that we may be in. */ 115 protected Stack<Context> writeContextStack_ = new Stack<Context>(); 116 117 /** Current context that we are in */ 118 protected Context writeContext_ = BASE_CONTEXT; 119 120 /** Push a new write context onto the stack. */ pushWriteContext(Context c)121 protected void pushWriteContext(Context c) { 122 writeContextStack_.push(writeContext_); 123 writeContext_ = c; 124 } 125 126 /** Pop the last write context off the stack */ popWriteContext()127 protected void popWriteContext() { 128 writeContext_ = writeContextStack_.pop(); 129 } 130 131 /** Used to make sure that we are not encountering a map whose keys are containers */ assertContextIsNotMapKey(String invalidKeyType)132 protected void assertContextIsNotMapKey(String invalidKeyType) throws CollectionMapKeyException { 133 if (writeContext_.isMapKey()) { 134 throw new CollectionMapKeyException( 135 "Cannot serialize a map with keys that are of type " + invalidKeyType); 136 } 137 } 138 139 /** Constructor */ AbstractTSimpleJSONProtocol(TTransport trans)140 public AbstractTSimpleJSONProtocol(TTransport trans) { 141 super(trans); 142 } 143 writeMessageBegin(TMessage message)144 public void writeMessageBegin(TMessage message) throws TException { 145 trans_.write(LBRACKET); 146 pushWriteContext(new ListContext()); 147 writeString(message.name); 148 writeByte(message.type); 149 writeI32(message.seqid); 150 } 151 writeMessageEnd()152 public void writeMessageEnd() throws TException { 153 popWriteContext(); 154 trans_.write(RBRACKET); 155 } 156 writeStructBegin(TStruct struct)157 public void writeStructBegin(TStruct struct) throws TException { 158 writeContext_.write(); 159 trans_.write(LBRACE); 160 pushWriteContext(new StructContext()); 161 } 162 writeStructEnd()163 public void writeStructEnd() throws TException { 164 popWriteContext(); 165 trans_.write(RBRACE); 166 } 167 writeFieldBegin(TField field)168 public void writeFieldBegin(TField field) throws TException { 169 // Note that extra type information is omitted in JSON! 170 writeString(field.name); 171 } 172 writeFieldEnd()173 public void writeFieldEnd() {} 174 writeFieldStop()175 public void writeFieldStop() {} 176 writeMapBegin(TMap map)177 public void writeMapBegin(TMap map) throws TException { 178 assertContextIsNotMapKey(MAP); 179 writeContext_.write(); 180 trans_.write(LBRACE); 181 pushWriteContext(new MapContext()); 182 // No metadata! 183 } 184 writeMapEnd()185 public void writeMapEnd() throws TException { 186 popWriteContext(); 187 trans_.write(RBRACE); 188 } 189 writeListBegin(TList list)190 public void writeListBegin(TList list) throws TException { 191 assertContextIsNotMapKey(LIST); 192 writeContext_.write(); 193 trans_.write(LBRACKET); 194 pushWriteContext(new ListContext()); 195 // No metadata! 196 } 197 writeListEnd()198 public void writeListEnd() throws TException { 199 popWriteContext(); 200 trans_.write(RBRACKET); 201 } 202 writeSetBegin(TSet set)203 public void writeSetBegin(TSet set) throws TException { 204 assertContextIsNotMapKey(SET); 205 writeContext_.write(); 206 trans_.write(LBRACKET); 207 pushWriteContext(new ListContext()); 208 // No metadata! 209 } 210 writeSetEnd()211 public void writeSetEnd() throws TException { 212 popWriteContext(); 213 trans_.write(RBRACKET); 214 } 215 writeBool(boolean b)216 public void writeBool(boolean b) throws TException { 217 writeContext_.write(); 218 trans_.write(b ? BOOL_TRUE : BOOL_FALSE); 219 } 220 writeByte(byte b)221 public void writeByte(byte b) throws TException { 222 writeI32(b); 223 } 224 writeI16(short i16)225 public void writeI16(short i16) throws TException { 226 writeI32(i16); 227 } 228 writeI32(int i32)229 public void writeI32(int i32) throws TException { 230 if (writeContext_.isMapKey()) { 231 writeString(Integer.toString(i32)); 232 } else { 233 writeContext_.write(); 234 _writeStringData(Integer.toString(i32)); 235 } 236 } 237 _writeStringData(String s)238 public void _writeStringData(String s) throws TException { 239 byte[] b = s.getBytes(StandardCharsets.UTF_8); 240 trans_.write(b); 241 } 242 writeI64(long i64)243 public void writeI64(long i64) throws TException { 244 if (writeContext_.isMapKey()) { 245 writeString(Long.toString(i64)); 246 } else { 247 writeContext_.write(); 248 _writeStringData(Long.toString(i64)); 249 } 250 } 251 writeFloat(float flt)252 public void writeFloat(float flt) throws TException { 253 if (writeContext_.isMapKey()) { 254 writeString(Float.toString(flt)); 255 } else { 256 writeContext_.write(); 257 _writeStringData(Float.toString(flt)); 258 } 259 } 260 writeDouble(double dub)261 public void writeDouble(double dub) throws TException { 262 if (writeContext_.isMapKey()) { 263 writeString(Double.toString(dub)); 264 } else { 265 writeContext_.write(); 266 _writeStringData(Double.toString(dub)); 267 } 268 } 269 writeString(String str)270 public void writeString(String str) throws TException { 271 writeContext_.write(); 272 int length = str.length(); 273 StringBuffer escape = new StringBuffer(length + 16); 274 escape.append((char) QUOTE[0]); 275 for (int i = 0; i < length; ++i) { 276 char c = str.charAt(i); 277 switch (c) { 278 case '"': 279 case '\\': 280 escape.append('\\'); 281 escape.append(c); 282 break; 283 case '\b': 284 escape.append('\\'); 285 escape.append('b'); 286 break; 287 case '\f': 288 escape.append('\\'); 289 escape.append('f'); 290 break; 291 case '\n': 292 escape.append('\\'); 293 escape.append('n'); 294 break; 295 case '\r': 296 escape.append('\\'); 297 escape.append('r'); 298 break; 299 case '\t': 300 escape.append('\\'); 301 escape.append('t'); 302 break; 303 default: 304 // Control characters! According to JSON RFC u0020 (space) 305 if (c < ' ') { 306 String hex = Integer.toHexString(c); 307 escape.append('\\'); 308 escape.append('u'); 309 for (int j = 4; j > hex.length(); --j) { 310 escape.append('0'); 311 } 312 escape.append(hex); 313 } else { 314 escape.append(c); 315 } 316 break; 317 } 318 } 319 escape.append((char) QUOTE[0]); 320 _writeStringData(escape.toString()); 321 } 322 writeBinary(byte[] bin)323 public abstract void writeBinary(byte[] bin) throws TException; 324 325 /** Reading methods. */ 326 private class StructReadContext { 327 final Map<String, Integer> namesToIds; 328 final Map<Integer, TField> fieldMetadata; 329 StructReadContext(Map<String, Integer> namesToIds, Map<Integer, TField> fieldMetadata)330 StructReadContext(Map<String, Integer> namesToIds, Map<Integer, TField> fieldMetadata) { 331 this.namesToIds = namesToIds; 332 this.fieldMetadata = fieldMetadata; 333 } 334 } 335 336 private ArrayDeque<StructReadContext> readContexts = new ArrayDeque<>(); 337 338 private StructReadContext currentReadContext = null; 339 340 // Holds up to one byte from the transport 341 protected class LookaheadReader { 342 private boolean hasData_; 343 private byte[] data_ = new byte[1]; 344 345 private boolean hasNextData_ = false; 346 private byte[] nextData_ = new byte[1]; 347 348 // Return and consume the next byte to be read, either taking it from the 349 // data buffer if present or getting it from the transport otherwise. read(boolean skip)350 protected byte read(boolean skip) throws TException { 351 if (hasNextData_) { 352 byte result = data_[0]; 353 data_[0] = nextData_[0]; 354 hasNextData_ = false; 355 return result; 356 } 357 358 if (hasData_) { 359 hasData_ = false; 360 } else { 361 readDirectly(data_, skip); 362 } 363 return data_[0]; 364 } 365 read()366 protected byte read() throws TException { 367 return read(true); 368 } 369 370 // Return the next byte to be read without consuming, filling the data 371 // buffer if it has not been filled already. peek()372 private byte peek() throws TException { 373 if (!hasData_) { 374 read(); 375 } 376 hasData_ = true; 377 return data_[0]; 378 } 379 peekNext()380 private byte peekNext() throws TException { 381 if (!hasNextData_) { 382 peek(); 383 readDirectly(nextData_); 384 hasNextData_ = true; 385 } 386 return nextData_[0]; 387 } 388 readDirectly(byte[] data)389 private void readDirectly(byte[] data) { 390 readDirectly(data, true); 391 } 392 readDirectly(byte[] data, boolean skip)393 private void readDirectly(byte[] data, boolean skip) { 394 byte b; 395 do { 396 trans_.readAll(data, 0, 1); 397 b = data[0]; 398 } while (skip && (b == WHITE_SPACE || b == TAB || b == NEW_LINE || b == CARRIAGE_RETURN)); 399 } 400 } 401 402 // Stack of nested contexts that we may be in 403 private Stack<JSONBaseContext> contextStack_ = new Stack<JSONBaseContext>(); 404 405 // Current context that we are in 406 private JSONBaseContext context_ = new JSONBaseContext(); 407 408 // Base class for tracking JSON contexts that may require inserting/reading 409 // additional JSON syntax characters 410 // This base context does nothing. 411 protected class JSONBaseContext { write()412 protected void write() throws TException {} 413 read()414 protected void read() throws TException {} 415 escapeNum()416 protected boolean escapeNum() { 417 return false; 418 } 419 } 420 421 // Context for JSON lists. Will insert/read commas before each item except 422 // for the first one 423 protected class JSONListContext extends JSONBaseContext { 424 private boolean first_ = true; 425 426 @Override write()427 protected void write() throws TException { 428 if (first_) { 429 first_ = false; 430 } else { 431 trans_.write(COMMA); 432 } 433 } 434 435 @Override read()436 protected void read() throws TException { 437 if (first_) { 438 first_ = false; 439 } else { 440 readJSONSyntaxChar(COMMA); 441 } 442 } 443 } 444 445 // Push a new JSON context onto the stack. pushContext(JSONBaseContext c)446 private void pushContext(JSONBaseContext c) { 447 contextStack_.push(context_); 448 context_ = c; 449 } 450 451 // Pop the last JSON context off the stack popContext()452 private void popContext() { 453 context_ = contextStack_.pop(); 454 } 455 456 // Context for JSON records. Will insert/read colons before the value portion 457 // of each record pair, and commas before each key except the first. In 458 // addition, will indicate that numbers in the key position need to be 459 // escaped in quotes (since JSON keys must be strings). 460 protected class JSONPairContext extends JSONBaseContext { 461 private boolean first_ = true; 462 private boolean colon_ = true; 463 464 @Override write()465 protected void write() throws TException { 466 if (first_) { 467 first_ = false; 468 colon_ = true; 469 } else { 470 trans_.write(colon_ ? COLON : COMMA); 471 colon_ = !colon_; 472 } 473 } 474 475 @Override read()476 protected void read() throws TException { 477 if (first_) { 478 first_ = false; 479 } else { 480 byte[] expected = colon_ ? COLON : COMMA; 481 colon_ = !colon_; 482 readJSONSyntaxChar(expected); 483 } 484 } 485 486 @Override escapeNum()487 protected boolean escapeNum() { 488 try { 489 return reader_.peek() == QUOTE[0]; 490 } catch (Exception e) { 491 throw new RuntimeException(e); 492 } 493 } 494 } 495 496 // Reader that manages a 1-byte buffer 497 private LookaheadReader reader_ = new LookaheadReader(); 498 499 // Temporary buffer used by several methods 500 private byte[] tmpbuf_ = new byte[4]; 501 502 private static final String ESCAPE_CHARS = "\"\\/bfnrt"; 503 private static final byte[] ESCSEQ = new byte[] {'\\', 'u', '0', '0'}; 504 private static final int DEF_STRING_SIZE = 16; 505 private static final byte[] ESCAPE_CHAR_VALS = { 506 '"', '\\', '/', '\b', '\f', '\n', '\r', '\t', 507 }; 508 private static final byte[] ZERO = new byte[] {'0'}; 509 510 // Read in a JSON string, unescaping as appropriate.. readJSONString()511 private ByteArrayOutputStream readJSONString() throws TException { 512 ByteArrayOutputStream arr = new ByteArrayOutputStream(); 513 readJSONSyntaxChar(QUOTE); 514 while (true) { 515 byte ch = reader_.read(false); 516 if (ch == QUOTE[0]) { 517 break; 518 } 519 if (ch == ESCSEQ[0]) { 520 ch = reader_.read(false); 521 if (ch == ESCSEQ[1]) { 522 readJSONSyntaxChar(ZERO); 523 readJSONSyntaxChar(ZERO); 524 trans_.readAll(tmpbuf_, 0, 2); 525 ch = (byte) ((hexVal((byte) tmpbuf_[0]) << 4) + hexVal(tmpbuf_[1])); 526 } else { 527 int off = ESCAPE_CHARS.indexOf(ch); 528 if (off == -1) { 529 throw new TProtocolException(TProtocolException.INVALID_DATA, "Expected control char"); 530 } 531 ch = ESCAPE_CHAR_VALS[off]; 532 } 533 } 534 arr.write(ch); 535 } 536 return arr; 537 } 538 539 // Read a byte that must match b[0]; otherwise an exception is thrown. 540 // Marked protected to avoid synthetic accessor in JSONListContext.read 541 // and JSONPairContext.read readJSONSyntaxChar(byte[] b)542 protected void readJSONSyntaxChar(byte[] b) throws TException { 543 readJSONSyntaxString(b); 544 } 545 readJSONSyntaxString(byte[] expected)546 protected void readJSONSyntaxString(byte[] expected) throws TException { 547 int i = 0; 548 do { 549 char ch = (char) reader_.read(); 550 if (ch != expected[i]) { 551 throw new TProtocolException( 552 TProtocolException.INVALID_DATA, 553 String.format( 554 "Unexpected character '%s' at position %d (expected %s from '%s')", 555 ch, i, expected[i], new String(expected))); 556 } 557 i++; 558 } while (i < expected.length); 559 } 560 561 // Convert a byte containing a hex char ('0'-'9' or 'a'-'f') into its 562 // corresponding hex value hexVal(byte ch)563 private static final byte hexVal(byte ch) throws TException { 564 if ((ch >= '0') && (ch <= '9')) { 565 return (byte) ((char) ch - '0'); 566 } else if ((ch >= 'a') && (ch <= 'f')) { 567 return (byte) ((char) ch - 'a' + 10); 568 } else { 569 throw new TProtocolException(TProtocolException.INVALID_DATA, "Expected hex character"); 570 } 571 } 572 573 // Return true if the given byte could be a valid part of a JSON number. isJSONNumeric(byte b)574 private boolean isJSONNumeric(byte b) { 575 switch (b) { 576 case '+': 577 case '-': 578 case '.': 579 case '0': 580 case '1': 581 case '2': 582 case '3': 583 case '4': 584 case '5': 585 case '6': 586 case '7': 587 case '8': 588 case '9': 589 case 'E': 590 case 'e': 591 return true; 592 } 593 return false; 594 } 595 596 // Read in a sequence of characters that are all valid in JSON numbers. Does 597 // not do a complete regex check to validate that this is actually a number. readJSONNumericChars()598 private String readJSONNumericChars() throws TException { 599 StringBuilder strbld = new StringBuilder(); 600 while (true) { 601 byte ch = reader_.peek(); 602 if (!isJSONNumeric(ch)) { 603 break; 604 } 605 strbld.append((char) reader_.read()); 606 } 607 return strbld.toString(); 608 } 609 610 // Read in a JSON number. If the context dictates, read in enclosing quotes. readJSONInteger()611 private long readJSONInteger() throws TException { 612 context_.read(); 613 if (context_.escapeNum()) { 614 readJSONSyntaxChar(QUOTE); 615 } 616 String str = readJSONNumericChars(); 617 if (context_.escapeNum()) { 618 readJSONSyntaxChar(QUOTE); 619 } 620 try { 621 return Long.valueOf(str); 622 } catch (NumberFormatException ex) { 623 throw new TProtocolException( 624 TProtocolException.INVALID_DATA, "Bad data encounted in numeric data"); 625 } 626 } 627 628 // Read in a JSON double value. Throw if the value is not wrapped in quotes 629 // when expected or if wrapped in quotes when not expected. readJSONDouble()630 private double readJSONDouble() throws TException { 631 context_.read(); 632 if (reader_.peek() == QUOTE[0]) { 633 ByteArrayOutputStream arr = readJSONString(); 634 double dub; 635 try { 636 dub = Double.valueOf(arr.toString(StandardCharsets.UTF_8.name())); 637 } catch (Exception e) { 638 throw new TException(e); 639 } 640 if (!context_.escapeNum() && !Double.isNaN(dub) && !Double.isInfinite(dub)) { 641 // Throw exception -- we should not be in a string in this case 642 throw new TProtocolException( 643 TProtocolException.INVALID_DATA, "Numeric data unexpectedly quoted"); 644 } 645 return dub; 646 } else { 647 if (context_.escapeNum()) { 648 // This will throw - we should have had a quote if escapeNum == true 649 readJSONSyntaxChar(QUOTE); 650 } 651 try { 652 return Double.valueOf(readJSONNumericChars()); 653 } catch (NumberFormatException ex) { 654 throw new TProtocolException( 655 TProtocolException.INVALID_DATA, "Bad data encounted in numeric data"); 656 } 657 } 658 } 659 660 // Read in a JSON float value. Throw if the value is not wrapped in quotes 661 // when expected or if wrapped in quotes when not expected. readJSONFloat()662 protected float readJSONFloat() throws TException { 663 context_.read(); 664 if (reader_.peek() == QUOTE[0]) { 665 ByteArrayOutputStream arr = readJSONString(); 666 String s; 667 try { 668 s = arr.toString(StandardCharsets.UTF_8.name()); 669 } catch (Exception e) { 670 throw new TException(e); 671 } 672 float flt = Float.valueOf(s); 673 if (!context_.escapeNum() && !Float.isNaN(flt) && !Float.isInfinite(flt)) { 674 // Throw exception -- we should not be in a string in this case 675 throw new TProtocolException( 676 TProtocolException.INVALID_DATA, "Numeric data unexpectedly quoted"); 677 } 678 return flt; 679 } else { 680 if (context_.escapeNum()) { 681 // This will throw - we should have had a quote if escapeNum == true 682 readJSONSyntaxChar(QUOTE); 683 } 684 try { 685 return Float.valueOf(readJSONNumericChars()); 686 } catch (NumberFormatException ex) { 687 throw new TProtocolException( 688 TProtocolException.INVALID_DATA, "Bad data encounted in numeric data"); 689 } 690 } 691 } 692 693 // Read in a JSON string containing base-64 encoded data and decode it. readJSONBase64()694 private byte[] readJSONBase64() throws TException { 695 ByteArrayOutputStream arr = readJSONString(); 696 byte[] b = arr.toByteArray(); 697 int len = b.length; 698 int off = 0; 699 int size = 0; 700 while (len >= 4) { 701 // Decode 4 bytes at a time 702 TBase64Utils.decode(b, off, 4, b, size); // NB: decoded in place 703 off += 4; 704 len -= 4; 705 size += 3; 706 } 707 // Don't decode if we hit the end or got a single leftover byte (invalid 708 // base64 but legal for skip of regular string type) 709 if (len > 1) { 710 // Decode remainder 711 TBase64Utils.decode(b, off, len, b, size); // NB: decoded in place 712 size += len - 1; 713 } 714 // Sadly we must copy the byte[] (any way around this?) 715 byte[] result = new byte[size]; 716 System.arraycopy(b, 0, result, 0, size); 717 return result; 718 } 719 readJSONObjectStart()720 private void readJSONObjectStart() throws TException { 721 context_.read(); 722 readJSONSyntaxChar(LBRACE); 723 724 pushContext(new JSONPairContext()); 725 } 726 readJSONObjectEnd()727 private void readJSONObjectEnd() throws TException { 728 readJSONSyntaxChar(RBRACE); 729 popContext(); 730 } 731 readJSONArrayStart()732 private void readJSONArrayStart() throws TException { 733 context_.read(); 734 readJSONSyntaxChar(LBRACKET); 735 736 pushContext(new JSONListContext()); 737 } 738 readJSONArrayEnd()739 private void readJSONArrayEnd() throws TException { 740 readJSONSyntaxChar(RBRACKET); 741 popContext(); 742 } 743 744 @Override readMessageBegin()745 public TMessage readMessageBegin() throws TException { 746 readJSONArrayStart(); 747 try { 748 String name = readJSONString().toString(StandardCharsets.UTF_8.name()); 749 byte type = (byte) readJSONInteger(); 750 int seqid = (int) readJSONInteger(); 751 return new TMessage(name, type, seqid); 752 } catch (Exception e) { 753 throw new TException(); 754 } 755 } 756 757 @Override readMessageEnd()758 public void readMessageEnd() throws TException { 759 readJSONArrayEnd(); 760 } 761 762 @Override readStructBegin(Map<Integer, FieldMetaData> fieldMetadata)763 public TStruct readStructBegin(Map<Integer, FieldMetaData> fieldMetadata) throws TException { 764 if (currentReadContext != null) { 765 readContexts.push(currentReadContext); 766 } 767 768 HashMap<String, Integer> namesToIds = new HashMap<>(fieldMetadata.size()); 769 HashMap<Integer, TField> metadatas = new HashMap<>(fieldMetadata.size()); 770 for (Map.Entry<Integer, FieldMetaData> entry : fieldMetadata.entrySet()) { 771 int fieldId = entry.getKey(); 772 FieldMetaData metadata = entry.getValue(); 773 TField tField = new TField(metadata.fieldName, metadata.valueMetaData.type, (short) fieldId); 774 775 namesToIds.put(metadata.fieldName, fieldId); 776 metadatas.put(fieldId, tField); 777 } 778 currentReadContext = new StructReadContext(namesToIds, metadatas); 779 780 readJSONObjectStart(); 781 return ANONYMOUS_STRUCT; 782 } 783 784 @Override readStructBegin()785 public TStruct readStructBegin() { 786 throw new UnsupportedOperationException( 787 "You need to specify a \"field name\" -> \"field Id\" mapping to deserialize with TSimpleJSON"); 788 } 789 790 @Override readStructEnd()791 public void readStructEnd() throws TException { 792 currentReadContext = readContexts.isEmpty() ? null : readContexts.pop(); 793 readJSONObjectEnd(); 794 } 795 796 @Override readFieldBegin()797 public TField readFieldBegin() throws TException { 798 while (true) { 799 if (reader_.peek() == RBRACE[0]) { 800 return new TField("", TType.STOP, (short) 0); 801 } 802 803 context_.read(); 804 try { 805 final String fieldName = readJSONString().toString(StandardCharsets.UTF_8.name()); 806 final Integer fieldId = currentReadContext.namesToIds.get(fieldName); 807 if (fieldId == null) { 808 return new TField(fieldName, getTypeIDForPeekedByte(reader_.peekNext()), (short) 0); 809 } 810 return currentReadContext.fieldMetadata.get(fieldId); 811 } catch (Exception e) { 812 throw new TException(e); 813 } 814 } 815 } 816 817 @Override readFieldEnd()818 public void readFieldEnd() throws TException {} 819 readMapBegin()820 public TMap readMapBegin() throws TException { 821 readJSONObjectStart(); 822 return new TMap(getTypeIDForPeekedByte(reader_.peek()), TType.STOP, -1); 823 } 824 peekMap()825 public boolean peekMap() throws TException { 826 return reader_.peek() != RBRACE[0]; 827 } 828 readMapEnd()829 public void readMapEnd() throws TException { 830 readJSONObjectEnd(); 831 } 832 readListBegin()833 public TList readListBegin() throws TException { 834 readJSONArrayStart(); 835 return new TList(getTypeIDForPeekedByte(reader_.peek()), -1); 836 } 837 peekList()838 public boolean peekList() throws TException { 839 return reader_.peek() != RBRACKET[0]; 840 } 841 readListEnd()842 public void readListEnd() throws TException { 843 readJSONArrayEnd(); 844 } 845 readSetBegin()846 public TSet readSetBegin() throws TException { 847 readJSONArrayStart(); 848 return new TSet(getTypeIDForPeekedByte(reader_.peek()), -1); 849 } 850 peekSet()851 public boolean peekSet() throws TException { 852 return reader_.peek() != RBRACKET[0]; 853 } 854 readSetEnd()855 public void readSetEnd() throws TException { 856 readJSONArrayEnd(); 857 } 858 859 @Override readBool()860 public boolean readBool() throws TException { 861 context_.read(); 862 byte peek = reader_.peek(); 863 boolean hasQuote = peek == QUOTE[0]; 864 if (hasQuote) { 865 readJSONSyntaxChar(QUOTE); 866 } 867 868 boolean value; 869 peek = reader_.peek(); 870 if (peek == BOOL_TRUE[0]) { 871 readJSONSyntaxString(BOOL_TRUE); 872 value = true; 873 } else if (peek == BOOL_FALSE[0]) { 874 readJSONSyntaxString(BOOL_FALSE); 875 value = false; 876 } else { 877 throw new TException( 878 String.format("unexpected first char '%c', it doesn't match 'true' nor 'false'", peek)); 879 } 880 881 if (hasQuote) { 882 readJSONSyntaxChar(QUOTE); 883 } 884 return value; 885 } 886 887 @Override readByte()888 public byte readByte() throws TException { 889 return (byte) readJSONInteger(); 890 } 891 892 @Override readI16()893 public short readI16() throws TException { 894 return (short) readJSONInteger(); 895 } 896 897 @Override readI32()898 public int readI32() throws TException { 899 return (int) readJSONInteger(); 900 } 901 902 @Override readI64()903 public long readI64() throws TException { 904 return (long) readJSONInteger(); 905 } 906 907 @Override readDouble()908 public double readDouble() throws TException { 909 return readJSONDouble(); 910 } 911 912 @Override readFloat()913 public float readFloat() throws TException { 914 return readJSONFloat(); 915 } 916 readString()917 public String readString() throws TException { 918 context_.read(); 919 try { 920 return readJSONString().toString(StandardCharsets.UTF_8.name()); 921 } catch (Exception e) { 922 throw new TException(e); 923 } 924 } 925 readBinary()926 public abstract byte[] readBinary() throws TException; 927 928 @Override skipBinary()929 public void skipBinary() throws TException { 930 // use readString to skip bytes to prevent an error when Base64 encoding 931 readString(); 932 } 933 934 public static class CollectionMapKeyException extends TException { CollectionMapKeyException(String message)935 public CollectionMapKeyException(String message) { 936 super(message); 937 } 938 } 939 getTypeIDForPeekedByte(byte peekedByte)940 protected static final byte getTypeIDForPeekedByte(byte peekedByte) throws TException { 941 switch (peekedByte) { 942 case '}': 943 case ']': 944 return TType.STOP; 945 946 case '{': 947 return TType.STRUCT; 948 949 case '[': 950 return TType.LIST; 951 952 case 't': 953 case 'f': 954 return TType.BOOL; 955 956 case '+': 957 case '-': 958 case '.': 959 case '0': 960 case '1': 961 case '2': 962 case '3': 963 case '4': 964 case '5': 965 case '6': 966 case '7': 967 case '8': 968 case '9': 969 return TType.DOUBLE; 970 971 case '"': 972 return TType.STRING; 973 974 default: 975 throw new TProtocolException( 976 TProtocolException.NOT_IMPLEMENTED, "Unrecognized peeked byte: " + (char) peekedByte); 977 } 978 } 979 } 980