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