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