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
20package org.apache.thrift.protocol;
21
22import haxe.io.Bytes;
23import haxe.io.BytesInput;
24import haxe.io.BytesOutput;
25import haxe.io.BytesBuffer;
26import haxe.io.Encoding;
27import haxe.ds.GenericStack;
28import haxe.crypto.Base64;
29import haxe.Int64;
30
31import uuid.Uuid;
32
33import org.apache.thrift.TException;
34import org.apache.thrift.protocol.TMessage;
35import org.apache.thrift.protocol.TField;
36import org.apache.thrift.protocol.TMap;
37import org.apache.thrift.protocol.TSet;
38import org.apache.thrift.protocol.TList;
39import org.apache.thrift.transport.TTransport;
40import org.apache.thrift.helper.UuidHelper;
41
42
43
44/* JSON protocol implementation for thrift.
45*  This is a full-featured protocol supporting Write and Read.
46*
47*  Please see the C++ class header for a detailed description of the wire format.
48*
49*  Adapted from the Java version.
50*/
51class TJSONProtocol extends TProtocolImplBase implements TProtocol {
52
53    // Stack of nested contexts that we may be in
54    private var contextStack : GenericStack<JSONBaseContext> = new GenericStack<JSONBaseContext>();
55
56    // Current context that we are in
57    private var context : JSONBaseContext;
58
59    // Reader that manages a 1-byte buffer
60    private var reader : LookaheadReader;
61
62    // TJSONProtocol Constructor
63    public function new( transport : TTransport)
64    {
65		super(transport);
66        this.context = new JSONBaseContext(this);
67        this.reader = new LookaheadReader(this);
68    }
69
70    public function writeMessageBegin(message:TMessage) : Void {
71        WriteJSONArrayStart();
72        WriteJSONInteger( JSONConstants.VERSION);
73        WriteJSONString( BytesFromString(message.name));
74        WriteJSONInteger( message.type);
75        WriteJSONInteger( message.seqid);
76    }
77
78    public function writeMessageEnd() : Void {
79        WriteJSONArrayEnd();
80    }
81
82    public function writeStructBegin(struct:TStruct) : Void {
83        WriteJSONObjectStart();
84    }
85
86    public function writeStructEnd() : Void {
87        WriteJSONObjectEnd();
88    }
89
90    public function writeFieldBegin(field:TField) : Void {
91        WriteJSONInteger( field.id );
92        WriteJSONObjectStart();
93        WriteJSONString( BytesFromString( JSONConstants.GetTypeNameForTypeID( field.type)));
94    }
95
96    public function writeFieldEnd() : Void {
97        WriteJSONObjectEnd();
98    }
99
100    public function writeFieldStop() : Void { }
101
102    public function writeMapBegin(map:TMap) : Void {
103        WriteJSONArrayStart();
104        WriteJSONString( BytesFromString( JSONConstants.GetTypeNameForTypeID( map.keyType)));
105        WriteJSONString( BytesFromString( JSONConstants.GetTypeNameForTypeID( map.valueType)));
106        WriteJSONInteger( map.size);
107        WriteJSONObjectStart();
108    }
109
110    public function writeMapEnd() : Void {
111        WriteJSONObjectEnd();
112        WriteJSONArrayEnd();
113    }
114
115    public function writeListBegin(list:TList) : Void {
116        WriteJSONArrayStart();
117        WriteJSONString( BytesFromString( JSONConstants.GetTypeNameForTypeID( list.elemType )));
118        WriteJSONInteger( list.size);
119    }
120
121    public function writeListEnd() : Void {
122        WriteJSONArrayEnd();
123    }
124
125    public function writeSetBegin(set:TSet) : Void {
126        WriteJSONArrayStart();
127        WriteJSONString( BytesFromString( JSONConstants.GetTypeNameForTypeID( set.elemType)));
128        WriteJSONInteger( set.size);
129    }
130
131    public function writeSetEnd() : Void {
132        WriteJSONArrayEnd();
133    }
134
135    public function writeBool(b : Bool) : Void {
136        if( b)
137            WriteJSONInteger( 1);
138        else
139            WriteJSONInteger( 0);
140    }
141
142    public function writeByte(b : Int) : Void {
143        WriteJSONInteger( b);
144    }
145
146    public function writeI16(i16 : Int) : Void {
147        WriteJSONInteger( i16);
148    }
149
150    public function writeI32(i32 : Int) : Void {
151        WriteJSONInteger( i32);
152    }
153
154    public function writeI64(i64 : haxe.Int64) : Void {
155        WriteJSONInt64( i64);
156    }
157
158    public function writeDouble(dub:Float) : Void {
159        WriteJSONDouble(dub);
160    }
161
162    public function writeString(str : String) : Void {
163        WriteJSONString( BytesFromString(str));
164    }
165
166    public function writeBinary(bin:Bytes) : Void {
167        WriteJSONBase64(bin);
168    }
169
170    public function writeUuid(uuid : String) : Void {
171		writeString( UuidHelper.CanonicalUuid(uuid));
172    }
173
174    public function readMessageBegin():TMessage {
175        var message : TMessage = new TMessage();
176        ReadJSONArrayStart();
177        if (ReadJSONInteger() != JSONConstants.VERSION)
178        {
179            throw new TProtocolException(TProtocolException.BAD_VERSION,
180                                         "Message contained bad version.");
181        }
182
183        message.name = ReadJSONString(false);
184        message.type = ReadJSONInteger();
185        message.seqid = ReadJSONInteger();
186        return message;
187    }
188
189    public function readMessageEnd() : Void {
190        ReadJSONArrayEnd();
191    }
192
193    public function readStructBegin():TStruct {
194        ReadJSONObjectStart();
195        return new TStruct();
196    }
197
198    public function readStructEnd() : Void {
199        ReadJSONObjectEnd();
200    }
201
202    public function readFieldBegin() : TField {
203        var field : TField = new TField();
204        var ch = reader.Peek();
205        if (StringFromBytes(ch) == JSONConstants.RBRACE)
206        {
207            field.type = TType.STOP;
208        }
209        else
210        {
211            field.id = ReadJSONInteger();
212            ReadJSONObjectStart();
213            field.type = JSONConstants.GetTypeIDForTypeName( ReadJSONString(false));
214        }
215        return field;
216    }
217
218    public function readFieldEnd() : Void {
219        ReadJSONObjectEnd();
220    }
221
222    public function readMapBegin() : TMap {
223        ReadJSONArrayStart();
224        var KeyType = JSONConstants.GetTypeIDForTypeName( ReadJSONString(false));
225        var ValueType = JSONConstants.GetTypeIDForTypeName( ReadJSONString(false));
226        var Count : Int = ReadJSONInteger();
227        ReadJSONObjectStart();
228
229        var map = new TMap( KeyType, ValueType, Count);
230		CheckReadBytesAvailableMap(map);
231		return map;
232    }
233
234    public function readMapEnd() : Void {
235        ReadJSONObjectEnd();
236        ReadJSONArrayEnd();
237    }
238
239    public function readListBegin():TList {
240        ReadJSONArrayStart();
241        var ElementType = JSONConstants.GetTypeIDForTypeName( ReadJSONString(false));
242        var Count : Int = ReadJSONInteger();
243
244        var list = new TList( ElementType, Count);
245		CheckReadBytesAvailableList(list);
246        return list;
247    }
248
249    public function readListEnd() : Void {
250        ReadJSONArrayEnd();
251    }
252
253    public function readSetBegin() : TSet {
254        ReadJSONArrayStart();
255        var ElementType = JSONConstants.GetTypeIDForTypeName( ReadJSONString(false));
256        var Count : Int = ReadJSONInteger();
257
258        var set = new TSet( ElementType, Count);
259		CheckReadBytesAvailableSet(set);
260        return set;
261    }
262
263    public function readSetEnd() : Void {
264        ReadJSONArrayEnd();
265    }
266
267    public function readBool() : Bool {
268        return (ReadJSONInteger() != 0);
269    }
270
271    public function readByte() : Int {
272        return ReadJSONInteger();
273    }
274
275    public function readI16() : Int {
276        return ReadJSONInteger();
277    }
278
279    public function readI32() : Int {
280        return ReadJSONInteger();
281    }
282
283    public function readI64() : haxe.Int64 {
284        return ReadJSONInt64();
285    }
286
287    public function readDouble():Float {
288        return ReadJSONDouble();
289    }
290
291    public function readString() : String {
292        return ReadJSONString(false);
293    }
294
295    public function readBinary() : Bytes {
296        return ReadJSONBase64();
297    }
298
299    public function readUuid() : String {
300        return UuidHelper.CanonicalUuid( readString());
301    }
302
303    // Push a new JSON context onto the stack.
304    private function  PushContext(c : JSONBaseContext) : Void {
305        contextStack.add(context);
306        context = c;
307    }
308
309    // Pop the last JSON context off the stack
310    private function  PopContext() : Void {
311        context = contextStack.pop();
312    }
313
314
315    // Write the bytes in array buf as a JSON characters, escaping as needed
316    private function WriteJSONString( b : Bytes) : Void {
317        context.Write();
318
319        var tmp = BytesFromString( JSONConstants.QUOTE);
320        Transport.write( tmp, 0, tmp.length);
321
322        for (i in 0 ... b.length) {
323            var value = b.get(i);
324
325            if ((value & 0x00FF) >= 0x30)
326            {
327                if (String.fromCharCode(value) == JSONConstants.BACKSLASH.charAt(0))
328                {
329                    tmp = BytesFromString( JSONConstants.BACKSLASH + JSONConstants.BACKSLASH);
330                    Transport.write( tmp, 0, tmp.length);
331                }
332                else
333                {
334                    Transport.write( b, i, 1);
335                }
336            }
337            else
338            {
339                var num = JSONConstants.JSON_CHAR_TABLE[value];
340                if (num == 1)
341                {
342                    Transport.write( b, i, 1);
343                }
344                else if (num > 1)
345                {
346                    var buf = new BytesBuffer();
347                    buf.addString( JSONConstants.BACKSLASH);
348                    buf.addByte( num);
349                    tmp = buf.getBytes();
350                    Transport.write( tmp, 0, tmp.length);
351                }
352                else
353                {
354                    var buf = new BytesBuffer();
355                    buf.addString( JSONConstants.ESCSEQ);
356                    buf.addString( HexChar( (value & 0xFF000000) >> 12));
357                    buf.addString( HexChar( (value & 0x00FF0000) >> 8));
358                    buf.addString( HexChar( (value & 0x0000FF00) >> 4));
359                    buf.addString( HexChar( value & 0x000000FF));
360                    tmp = buf.getBytes();
361                    Transport.write( tmp, 0, tmp.length);
362                }
363            }
364        }
365
366        tmp = BytesFromString( JSONConstants.QUOTE);
367        Transport.write( tmp, 0, tmp.length);
368    }
369
370    // Write out number as a JSON value. If the context dictates so,
371    // it will be wrapped in quotes to output as a JSON string.
372    private function WriteJSONInteger( num : Int) : Void {
373        context.Write();
374
375        var str : String = "";
376        var escapeNum : Bool = context.EscapeNumbers();
377
378        if (escapeNum) {
379            str += JSONConstants.QUOTE;
380        }
381
382        str += Std.string(num);
383
384        if (escapeNum) {
385            str += JSONConstants.QUOTE;
386        }
387
388        var tmp = BytesFromString( str);
389        Transport.write( tmp, 0, tmp.length);
390    }
391
392    // Write out number as a JSON value. If the context dictates so,
393    // it will be wrapped in quotes to output as a JSON string.
394    private function WriteJSONInt64( num : Int64) : Void {
395        context.Write();
396
397        var str : String = "";
398        var escapeNum : Bool = context.EscapeNumbers();
399
400        if (escapeNum) {
401            str += JSONConstants.QUOTE;
402        }
403
404        str += Std.string(num);
405
406        if (escapeNum) {
407            str += JSONConstants.QUOTE;
408        }
409
410        var tmp = BytesFromString( str);
411        Transport.write( tmp, 0, tmp.length);
412    }
413
414    // Write out a double as a JSON value. If it is NaN or infinity or if the
415    // context dictates escaping, Write out as JSON string.
416    private function WriteJSONDouble(num : Float) : Void {
417        context.Write();
418
419
420        var special : Bool = false;
421        var rendered : String = "";
422        if( Math.isNaN(num)) {
423            special = true;
424            rendered = JSONConstants.FLOAT_IS_NAN;
425        } else if (! Math.isFinite(num)) {
426            special = true;
427            if( num > 0) {
428                rendered = JSONConstants.FLOAT_IS_POS_INF;
429            } else {
430                rendered = JSONConstants.FLOAT_IS_NEG_INF;
431            }
432        } else {
433            rendered = Std.string(num);  // plain and simple float number
434        }
435
436        // compose output
437        var escapeNum : Bool = special || context.EscapeNumbers();
438        var str : String = "";
439        if (escapeNum) {
440            str += JSONConstants.QUOTE;
441        }
442        str += rendered;
443        if (escapeNum) {
444            str += JSONConstants.QUOTE;
445        }
446
447        var tmp = BytesFromString( str);
448        Transport.write( tmp, 0, tmp.length);
449    }
450
451    // Write out contents of byte array b as a JSON string with base-64 encoded data
452    private function WriteJSONBase64( b : Bytes) : Void {
453        context.Write();
454
455        var buf = new BytesBuffer();
456        buf.addString( JSONConstants.QUOTE);
457        buf.addString( Base64.encode(b));
458        buf.addString( JSONConstants.QUOTE);
459
460        var tmp = buf.getBytes();
461        Transport.write( tmp, 0, tmp.length);
462    }
463
464    private function WriteJSONObjectStart() : Void {
465        context.Write();
466        var tmp = BytesFromString( JSONConstants.LBRACE);
467        Transport.write( tmp, 0, tmp.length);
468        PushContext( new JSONPairContext(this));
469    }
470
471    private function WriteJSONObjectEnd() : Void {
472        PopContext();
473        var tmp = BytesFromString( JSONConstants.RBRACE);
474        Transport.write( tmp, 0, tmp.length);
475    }
476
477    private function WriteJSONArrayStart() : Void {
478        context.Write();
479        var tmp = BytesFromString( JSONConstants.LBRACKET);
480        Transport.write( tmp, 0, tmp.length);
481        PushContext( new JSONListContext(this));
482    }
483
484    private function WriteJSONArrayEnd() : Void {
485        PopContext();
486        var tmp = BytesFromString( JSONConstants.RBRACKET);
487        Transport.write( tmp, 0, tmp.length);
488    }
489
490
491    /**
492     * Reading methods.
493     */
494
495    // Read a byte that must match char, otherwise an exception is thrown.
496    public function ReadJSONSyntaxChar( char : String) : Void {
497        var b = BytesFromString( char);
498
499        var ch = reader.Read();
500        if (ch.get(0) != b.get(0))
501        {
502            throw new TProtocolException(TProtocolException.INVALID_DATA,
503                                         'Unexpected character: $ch');
504        }
505    }
506
507    // Read in a JSON string, unescaping as appropriate.
508    // Skip Reading from the context if skipContext is true.
509    private function ReadJSONString(skipContext : Bool) : String
510    {
511        if (!skipContext)
512        {
513            context.Read();
514        }
515
516        var buffer : BytesBuffer = new BytesBuffer();
517
518        ReadJSONSyntaxChar( JSONConstants.QUOTE);
519        while (true)
520        {
521            var ch = reader.Read();
522
523            // end of string?
524            if (StringFromBytes(ch) == JSONConstants.QUOTE)
525            {
526                break;
527            }
528
529            // escaped?
530            if (StringFromBytes(ch) != JSONConstants.ESCSEQ.charAt(0))
531            {
532                buffer.addByte( ch.get(0));
533                continue;
534            }
535
536            // distinguish between \uXXXX (hex unicode) and \X (control chars)
537            ch = reader.Read();
538            if (StringFromBytes(ch) != JSONConstants.ESCSEQ.charAt(1))
539            {
540                var value = JSONConstants.ESCAPE_CHARS_TO_VALUES[ch.get(0)];
541                if( value == null)
542                {
543                    throw new TProtocolException( TProtocolException.INVALID_DATA, "Expected control char");
544                }
545                buffer.addByte( value);
546                continue;
547            }
548
549
550            // it's \uXXXX
551            var hexbuf = new BytesBuffer();
552            var hexlen = Transport.readAll( hexbuf, 0, 4);
553            if( hexlen != 4)
554            {
555                throw new TProtocolException( TProtocolException.INVALID_DATA, "Not enough data for \\uNNNN sequence");
556            }
557
558            var hexdigits = hexbuf.getBytes();
559            var charcode = 0;
560            charcode = (charcode << 4) + HexVal( String.fromCharCode(hexdigits.get(0)));
561            charcode = (charcode << 4) + HexVal( String.fromCharCode(hexdigits.get(1)));
562            charcode = (charcode << 4) + HexVal( String.fromCharCode(hexdigits.get(2)));
563            charcode = (charcode << 4) + HexVal( String.fromCharCode(hexdigits.get(3)));
564            buffer.addString( String.fromCharCode(charcode));
565        }
566
567        return StringFromBytes( buffer.getBytes());
568    }
569
570    // Return true if the given byte could be a valid part of a JSON number.
571    private function IsJSONNumeric(b : Int) : Bool {
572        switch (b)
573        {
574            case "+".code:  return true;
575            case "-".code:  return true;
576            case ".".code:  return true;
577            case "0".code:  return true;
578            case "1".code:  return true;
579            case "2".code:  return true;
580            case "3".code:  return true;
581            case "4".code:  return true;
582            case "5".code:  return true;
583            case "6".code:  return true;
584            case "7".code:  return true;
585            case "8".code:  return true;
586            case "9".code:  return true;
587            case "E".code:  return true;
588            case "e".code:  return true;
589        }
590        return false;
591    }
592
593    // Read in a sequence of characters that are all valid in JSON numbers. Does
594    // not do a complete regex check to validate that this is actually a number.
595    private function ReadJSONNumericChars() : String
596    {
597        var buffer : BytesBuffer = new BytesBuffer();
598        while (true)
599        {
600            var ch = reader.Peek();
601            if( ! IsJSONNumeric( ch.get(0)))
602            {
603                break;
604            }
605            buffer.addByte( reader.Read().get(0));
606        }
607        return StringFromBytes( buffer.getBytes());
608    }
609
610    // Read in a JSON number. If the context dictates, Read in enclosing quotes.
611    private function ReadJSONInteger() : Int {
612        context.Read();
613
614        if (context.EscapeNumbers()) {
615            ReadJSONSyntaxChar( JSONConstants.QUOTE);
616        }
617
618        var str : String = ReadJSONNumericChars();
619
620        if (context.EscapeNumbers()) {
621            ReadJSONSyntaxChar( JSONConstants.QUOTE);
622        }
623
624        var value = Std.parseInt(str);
625        if( value == null) {
626            throw new TProtocolException(TProtocolException.INVALID_DATA, 'Bad numeric data: $str');
627        }
628
629        return value;
630    }
631
632    // Read in a JSON number. If the context dictates, Read in enclosing quotes.
633    private function ReadJSONInt64() : haxe.Int64 {
634        context.Read();
635
636        if (context.EscapeNumbers()) {
637            ReadJSONSyntaxChar( JSONConstants.QUOTE);
638        }
639
640        var str : String = ReadJSONNumericChars();
641        if( str.length == 0) {
642            throw new TProtocolException(TProtocolException.INVALID_DATA, 'Bad numeric data: $str');
643        }
644
645        if (context.EscapeNumbers()) {
646            ReadJSONSyntaxChar( JSONConstants.QUOTE);
647        }
648
649        // process sign
650        var bMinus = false;
651        var startAt = 0;
652        if( (str.charAt(0) == "+") || (str.charAt(0) == "-")) {
653            bMinus = (str.charAt(0) == "-");
654            startAt++;
655        }
656
657        // process digits
658        var value : Int64 = Int64.make(0,0);
659        var bGotDigits = false;
660        for( i in startAt ... str.length) {
661            var ch = str.charAt(i);
662            var digit = JSONConstants.DECIMAL_DIGITS[ch];
663            if( digit == null) {
664                throw new TProtocolException(TProtocolException.INVALID_DATA, 'Bad numeric data: $str');
665            }
666            bGotDigits = true;
667
668            // these are decimal digits
669            value = Int64.mul( value, Int64.make(0,10));
670            value = Int64.add( value, Int64.make(0,digit));
671        }
672
673        // process pending minus sign, if applicable
674        // this should also handle the edge case MIN_INT64 correctly
675        if( bMinus && (Int64.compare(value,Int64.make(0,0)) > 0)) {
676            value = Int64.neg( value);
677            bMinus = false;
678        }
679
680        if( ! bGotDigits) {
681            throw new TProtocolException(TProtocolException.INVALID_DATA, 'Bad numeric data: $str');
682        }
683
684        return value;
685    }
686
687    // Read in a JSON double value. Throw if the value is not wrapped in quotes
688    // when expected or if wrapped in quotes when not expected.
689    private function ReadJSONDouble() : Float {
690        context.Read();
691
692        var str : String = "";
693        if (StringFromBytes(reader.Peek()) == JSONConstants.QUOTE) {
694            str = ReadJSONString(true);
695
696            // special cases
697            if( str == JSONConstants.FLOAT_IS_NAN) {
698                return Math.NaN;
699            }
700            if( str == JSONConstants.FLOAT_IS_POS_INF) {
701                return Math.POSITIVE_INFINITY;
702            }
703            if( str == JSONConstants.FLOAT_IS_NEG_INF) {
704                return Math.NEGATIVE_INFINITY;
705            }
706
707            if( ! context.EscapeNumbers())    {
708                // throw - we should not be in a string in this case
709                throw new TProtocolException(TProtocolException.INVALID_DATA, "Numeric data unexpectedly quoted");
710            }
711        }
712        else
713        {
714            if( context.EscapeNumbers())    {
715                // This will throw - we should have had a quote if EscapeNumbers() == true
716                ReadJSONSyntaxChar( JSONConstants.QUOTE);
717            }
718
719            str = ReadJSONNumericChars();
720        }
721
722        // parse and check - we should have at least one valid digit
723        var dub = Std.parseFloat( str);
724        if( (str.length == 0) || Math.isNaN(dub)) {
725            throw new TProtocolException(TProtocolException.INVALID_DATA, 'Bad numeric data: $str');
726        }
727
728        return dub;
729    }
730
731    // Read in a JSON string containing base-64 encoded data and decode it.
732    private function ReadJSONBase64() : Bytes
733    {
734        var str = ReadJSONString(false);
735        return Base64.decode( str);
736    }
737
738    private function ReadJSONObjectStart() : Void {
739        context.Read();
740        ReadJSONSyntaxChar( JSONConstants.LBRACE);
741        PushContext(new JSONPairContext(this));
742    }
743
744    private function ReadJSONObjectEnd() : Void {
745        ReadJSONSyntaxChar( JSONConstants.RBRACE);
746        PopContext();
747    }
748
749    private function ReadJSONArrayStart() : Void {
750        context.Read();
751        ReadJSONSyntaxChar( JSONConstants.LBRACKET);
752        PushContext(new JSONListContext(this));
753    }
754
755    private function ReadJSONArrayEnd() : Void {
756        ReadJSONSyntaxChar( JSONConstants.RBRACKET);
757        PopContext();
758    }
759
760
761    public static function BytesFromString( str : String) : Bytes {
762        var buf = new BytesBuffer();
763        buf.addString( str, Encoding.UTF8);
764        return buf.getBytes();
765    }
766
767    public static function StringFromBytes( buf : Bytes) : String {
768        var inp = new BytesInput( buf);
769        if( buf.length == 0)
770            return "";  // readString() would return null in that case, which is wrong
771        return inp.readString( buf.length, Encoding.UTF8);
772    }
773
774    // Convert a byte containing a hex char ('0'-'9' or 'a'-'f') into its corresponding hex value
775    private static function HexVal(char : String) : Int {
776        var value = JSONConstants.HEX_DIGITS[char];
777        if( value == null) {
778            throw new TProtocolException(TProtocolException.INVALID_DATA, 'Expected hex character: $char');
779        }
780        return value;
781    }
782
783    // Convert a byte containing a hex nibble to its corresponding hex character
784    private static function HexChar(nibble : Int) : String
785    {
786        return "0123456789abcdef".charAt(nibble & 0x0F);
787    }
788
789
790	// Return the minimum number of bytes a type will consume on the wire
791	public override function GetMinSerializedSize(type : TType) : Int
792	{
793		switch (type)
794		{
795			case TType.STOP: return 0;
796			case TType.VOID_: return 0;
797			case TType.BOOL: return 1;  // written as int
798			case TType.BYTE: return 1;
799			case TType.DOUBLE: return 1;
800			case TType.I16: return 1;
801			case TType.I32: return 1;
802			case TType.I64: return 1;
803			case TType.STRING: return 2;  // empty string
804			case TType.STRUCT: return 2;  // empty struct
805			case TType.MAP: return 2;  // empty map
806			case TType.SET: return 2;  // empty set
807			case TType.LIST: return 2;  // empty list
808			case TType.UUID: return 36;  // "E236974D-F0B0-4E05-8F29-0B455D41B1A1"
809			default: throw new TProtocolException(TProtocolException.NOT_IMPLEMENTED, "unrecognized type code");
810		}
811	}
812
813}
814
815
816@:allow(TJSONProtocol)
817class JSONConstants {
818    public static var COMMA = ",";
819    public static var COLON = ":";
820    public static var LBRACE = "{";
821    public static var RBRACE = "}";
822    public static var LBRACKET = "[";
823    public static var RBRACKET = "]";
824    public static var QUOTE = "\"";
825    public static var BACKSLASH = "\\";
826
827    public static var ESCSEQ = "\\u";
828
829    public static var FLOAT_IS_NAN = "NaN";
830    public static var FLOAT_IS_POS_INF = "Infinity";
831    public static var FLOAT_IS_NEG_INF = "-Infinity";
832
833    public static var VERSION = 1;
834    public static var JSON_CHAR_TABLE = [
835        0,  0,  0,  0,  0,  0,  0,  0,
836        "b".code, "t".code, "n".code,  0, "f".code, "r".code,  0,  0,
837        0,  0,  0,  0,  0,  0,  0,  0,
838        0,  0,  0,  0,  0,  0,  0,  0,
839        1,  1, "\"".code,  1,  1,  1,  1,  1,
840        1,  1,  1,  1,  1,  1,  1,  1,
841    ];
842
843    public static var ESCAPE_CHARS     = ['"','\\','/','b','f','n','r','t'];
844    public static var ESCAPE_CHARS_TO_VALUES = [
845        "\"".code => 0x22,
846        "\\".code => 0x5C,
847        "/".code  => 0x2F,
848        "b".code  => 0x08,
849        "f".code  => 0x0C,
850        "n".code  => 0x0A,
851        "r".code  => 0x0D,
852        "t".code  => 0x09
853    ];
854
855    public static var DECIMAL_DIGITS = [
856        "0" => 0,
857        "1" => 1,
858        "2" => 2,
859        "3" => 3,
860        "4" => 4,
861        "5" => 5,
862        "6" => 6,
863        "7" => 7,
864        "8" => 8,
865        "9" => 9
866    ];
867
868    public static var HEX_DIGITS = [
869        "0" => 0,
870        "1" => 1,
871        "2" => 2,
872        "3" => 3,
873        "4" => 4,
874        "5" => 5,
875        "6" => 6,
876        "7" => 7,
877        "8" => 8,
878        "9" => 9,
879        "A" => 10,
880        "a" => 10,
881        "B" => 11,
882        "b" => 11,
883        "C" => 12,
884        "c" => 12,
885        "D" => 13,
886        "d" => 13,
887        "E" => 14,
888        "e" => 14,
889        "F" => 15,
890        "f" => 15
891    ];
892
893
894    public static var DEF_STRING_SIZE = 16;
895
896    public static var NAME_BOOL   = 'tf';
897    public static var NAME_BYTE   = 'i8';
898    public static var NAME_I16    = 'i16';
899    public static var NAME_I32    = 'i32';
900    public static var NAME_I64    = 'i64';
901    public static var NAME_DOUBLE = 'dbl';
902    public static var NAME_STRUCT = 'rec';
903    public static var NAME_STRING = 'str';
904    public static var NAME_MAP    = 'map';
905    public static var NAME_LIST   = 'lst';
906    public static var NAME_SET    = 'set';
907	public static var NAME_UUID   = 'uid';
908
909    public static function GetTypeNameForTypeID(typeID : Int) : String {
910        switch (typeID)
911        {
912            case TType.BOOL:     return NAME_BOOL;
913            case TType.BYTE:     return NAME_BYTE;
914            case TType.I16:         return NAME_I16;
915            case TType.I32:         return NAME_I32;
916            case TType.I64:         return NAME_I64;
917            case TType.DOUBLE:     return NAME_DOUBLE;
918            case TType.STRING:     return NAME_STRING;
919            case TType.STRUCT:     return NAME_STRUCT;
920            case TType.MAP:         return NAME_MAP;
921            case TType.SET:         return NAME_SET;
922            case TType.LIST:     return NAME_LIST;
923            case TType.UUID:     return NAME_UUID;
924        }
925        throw new TProtocolException(TProtocolException.NOT_IMPLEMENTED, "Unrecognized type");
926    }
927
928    private static var NAMES_TO_TYPES = [
929        NAME_BOOL   => TType.BOOL,
930        NAME_BYTE   => TType.BYTE,
931        NAME_I16    => TType.I16,
932        NAME_I32    => TType.I32,
933        NAME_I64    => TType.I64,
934        NAME_DOUBLE => TType.DOUBLE,
935        NAME_STRING => TType.STRING,
936        NAME_STRUCT => TType.STRUCT,
937        NAME_MAP    => TType.MAP,
938        NAME_SET    => TType.SET,
939        NAME_LIST   => TType.LIST,
940		NAME_UUID   => TType.UUID
941    ];
942
943    public static function GetTypeIDForTypeName(name : String) : Int
944    {
945        var type = NAMES_TO_TYPES[name];
946        if( null != type) {
947            return type;
948        }
949        throw new TProtocolException(TProtocolException.NOT_IMPLEMENTED, "Unrecognized type");
950    }
951
952}
953
954
955// Base class for tracking JSON contexts that may require inserting/Reading
956// additional JSON syntax characters. This base context does nothing.
957@:allow(TJSONProtocol)
958class JSONBaseContext
959{
960    private var proto : TJSONProtocol;
961
962    public function new(proto : TJSONProtocol )
963    {
964        this.proto = proto;
965    }
966
967    public function Write() : Void { }
968    public function Read() : Void { }
969
970    public function EscapeNumbers() : Bool {
971        return false;
972    }
973}
974
975
976// Context for JSON lists.
977// Will insert/Read commas before each item except for the first one
978@:allow(TJSONProtocol)
979class JSONListContext extends JSONBaseContext
980{
981    public function new( proto : TJSONProtocol) {
982        super(proto);
983    }
984
985    private var first : Bool = true;
986
987    public override function Write() : Void {
988        if (first)
989        {
990            first = false;
991        }
992        else
993        {
994            var buf = new BytesBuffer();
995            buf.addString( JSONConstants.COMMA);
996            var tmp = buf.getBytes();
997            proto.Transport.write( tmp, 0, tmp.length);
998        }
999    }
1000
1001    public override function Read() : Void {
1002        if (first)
1003        {
1004            first = false;
1005        }
1006        else
1007        {
1008            proto.ReadJSONSyntaxChar( JSONConstants.COMMA);
1009        }
1010    }
1011}
1012
1013
1014// Context for JSON records.
1015// Will insert/Read colons before the value portion of each record
1016// pair, and commas before each key except the first. In addition,
1017// will indicate that numbers in the key position need to be escaped
1018// in quotes (since JSON keys must be strings).
1019@:allow(TJSONProtocol)
1020class JSONPairContext extends JSONBaseContext
1021{
1022    public function new( proto : TJSONProtocol ) {
1023        super( proto);
1024    }
1025
1026    private var first : Bool = true;
1027    private var colon : Bool  = true;
1028
1029    public override function Write() : Void {
1030        if (first)
1031        {
1032            first = false;
1033            colon = true;
1034        }
1035        else
1036        {
1037            var buf = new BytesBuffer();
1038            buf.addString( colon ? JSONConstants.COLON : JSONConstants.COMMA);
1039            var tmp = buf.getBytes();
1040            proto.Transport.write( tmp, 0, tmp.length);
1041            colon = !colon;
1042        }
1043    }
1044
1045    public override function Read() : Void {
1046        if (first)
1047        {
1048            first = false;
1049            colon = true;
1050        }
1051        else
1052        {
1053            proto.ReadJSONSyntaxChar( colon ? JSONConstants.COLON : JSONConstants.COMMA);
1054            colon = !colon;
1055        }
1056    }
1057
1058    public override function EscapeNumbers() : Bool
1059    {
1060        return colon;
1061    }
1062}
1063
1064// Holds up to one byte from the transport
1065@:allow(TJSONProtocol)
1066class LookaheadReader {
1067
1068    private var proto : TJSONProtocol;
1069    private var data : Bytes;
1070
1071    public function new( proto : TJSONProtocol ) {
1072        this.proto = proto;
1073        data = null;
1074    }
1075
1076
1077    // Return and consume the next byte to be Read, either taking it from the
1078    // data buffer if present or getting it from the transport otherwise.
1079    public function Read() : Bytes {
1080        var retval = Peek();
1081        data = null;
1082        return retval;
1083    }
1084
1085    // Return the next byte to be Read without consuming, filling the data
1086    // buffer if it has not been filled alReady.
1087    public function Peek() : Bytes {
1088        if (data == null) {
1089            var buf = new BytesBuffer();
1090            proto.Transport.readAll(buf, 0, 1);
1091            data = buf.getBytes();
1092        }
1093        return data;
1094    }
1095}
1096
1097