json 1.4.6 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of json might be problematic. Click here for more details.
- data/CHANGES +6 -0
- data/COPYING-json-jruby +57 -0
- data/README-json-jruby.markdown +33 -0
- data/Rakefile +224 -119
- data/VERSION +1 -1
- data/benchmarks/generator2_benchmark.rb +1 -1
- data/benchmarks/generator_benchmark.rb +1 -1
- data/ext/json/ext/generator/generator.c +20 -20
- data/ext/json/ext/generator/generator.h +7 -7
- data/ext/json/ext/parser/extconf.rb +1 -0
- data/ext/json/ext/parser/parser.c +122 -88
- data/ext/json/ext/parser/parser.h +7 -0
- data/ext/json/ext/parser/parser.rl +54 -20
- data/java/lib/bytelist-1.0.6.jar +0 -0
- data/java/lib/jcodings.jar +0 -0
- data/java/src/json/ext/ByteListTranscoder.java +167 -0
- data/java/src/json/ext/Generator.java +441 -0
- data/java/src/json/ext/GeneratorMethods.java +231 -0
- data/java/src/json/ext/GeneratorService.java +42 -0
- data/java/src/json/ext/GeneratorState.java +473 -0
- data/java/src/json/ext/OptionsReader.java +119 -0
- data/java/src/json/ext/Parser.java +2295 -0
- data/java/src/json/ext/Parser.rl +825 -0
- data/java/src/json/ext/ParserService.java +34 -0
- data/java/src/json/ext/RuntimeInfo.java +119 -0
- data/java/src/json/ext/StringDecoder.java +166 -0
- data/java/src/json/ext/StringEncoder.java +106 -0
- data/java/src/json/ext/Utils.java +89 -0
- data/json-java.gemspec +20 -0
- data/lib/json/add/core.rb +1 -2
- data/lib/json/add/rails.rb +4 -54
- data/lib/json/common.rb +36 -8
- data/lib/json/editor.rb +1 -3
- data/lib/json/ext.rb +2 -2
- data/lib/json/pure.rb +2 -64
- data/lib/json/pure/generator.rb +10 -8
- data/lib/json/pure/parser.rb +23 -12
- data/lib/json/version.rb +1 -1
- data/tests/setup_variant.rb +11 -0
- data/tests/test_json.rb +1 -5
- data/tests/test_json_addition.rb +14 -9
- data/tests/test_json_encoding.rb +9 -12
- data/tests/test_json_fixtures.rb +9 -8
- data/tests/test_json_generate.rb +3 -5
- data/tests/test_json_string_matching.rb +40 -0
- data/tests/test_json_unicode.rb +1 -5
- metadata +51 -13
- data/tests/test_json_rails.rb +0 -144
@@ -13,6 +13,11 @@
|
|
13
13
|
#else
|
14
14
|
#define FORCE_UTF8(obj)
|
15
15
|
#endif
|
16
|
+
#ifdef HAVE_RUBY_ST_H
|
17
|
+
#include "ruby/st.h"
|
18
|
+
#else
|
19
|
+
#include "st.h"
|
20
|
+
#endif
|
16
21
|
|
17
22
|
#define option_given_p(opts, key) RTEST(rb_funcall(opts, i_key_p, 1, key))
|
18
23
|
|
@@ -41,6 +46,8 @@ typedef struct JSON_ParserStruct {
|
|
41
46
|
int symbolize_names;
|
42
47
|
VALUE object_class;
|
43
48
|
VALUE array_class;
|
49
|
+
int create_additions;
|
50
|
+
VALUE match_string;
|
44
51
|
} JSON_Parser;
|
45
52
|
|
46
53
|
#define GET_PARSER \
|
@@ -77,7 +77,7 @@ static VALUE CNaN, CInfinity, CMinusInfinity;
|
|
77
77
|
|
78
78
|
static ID i_json_creatable_p, i_json_create, i_create_id, i_create_additions,
|
79
79
|
i_chr, i_max_nesting, i_allow_nan, i_symbolize_names, i_object_class,
|
80
|
-
i_array_class, i_key_p, i_deep_const_get;
|
80
|
+
i_array_class, i_key_p, i_deep_const_get, i_match, i_match_string;
|
81
81
|
|
82
82
|
%%{
|
83
83
|
machine JSON_common;
|
@@ -159,11 +159,11 @@ static char *JSON_parse_object(JSON_Parser *json, char *p, char *pe, VALUE *resu
|
|
159
159
|
%% write exec;
|
160
160
|
|
161
161
|
if (cs >= JSON_object_first_final) {
|
162
|
-
if (
|
162
|
+
if (json->create_additions) {
|
163
163
|
VALUE klassname = rb_hash_aref(*result, json->create_id);
|
164
164
|
if (!NIL_P(klassname)) {
|
165
165
|
VALUE klass = rb_funcall(mJSON, i_deep_const_get, 1, klassname);
|
166
|
-
if RTEST(rb_funcall(klass, i_json_creatable_p, 0)) {
|
166
|
+
if (RTEST(rb_funcall(klass, i_json_creatable_p, 0))) {
|
167
167
|
*result = rb_funcall(klass, i_json_create, 1, *result);
|
168
168
|
}
|
169
169
|
}
|
@@ -457,28 +457,52 @@ static VALUE json_string_unescape(VALUE result, char *string, char *stringEnd)
|
|
457
457
|
action parse_string {
|
458
458
|
*result = json_string_unescape(*result, json->memo + 1, p);
|
459
459
|
if (NIL_P(*result)) {
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
460
|
+
fhold;
|
461
|
+
fbreak;
|
462
|
+
} else {
|
463
|
+
FORCE_UTF8(*result);
|
464
|
+
fexec p + 1;
|
465
|
+
}
|
466
|
+
}
|
467
467
|
|
468
468
|
action exit { fhold; fbreak; }
|
469
469
|
|
470
470
|
main := '"' ((^(["\\] | 0..0x1f) | '\\'["\\/bfnrt] | '\\u'[0-9a-fA-F]{4} | '\\'^(["\\/bfnrtu]|0..0x1f))* %parse_string) '"' @exit;
|
471
471
|
}%%
|
472
472
|
|
473
|
+
static int
|
474
|
+
match_i(VALUE regexp, VALUE klass, VALUE memo)
|
475
|
+
{
|
476
|
+
if (regexp == Qundef) return ST_STOP;
|
477
|
+
if (RTEST(rb_funcall(klass, i_json_creatable_p, 0)) &&
|
478
|
+
RTEST(rb_funcall(regexp, i_match, 1, rb_ary_entry(memo, 0)))) {
|
479
|
+
rb_ary_push(memo, klass);
|
480
|
+
return ST_STOP;
|
481
|
+
}
|
482
|
+
return ST_CONTINUE;
|
483
|
+
}
|
484
|
+
|
473
485
|
static char *JSON_parse_string(JSON_Parser *json, char *p, char *pe, VALUE *result)
|
474
486
|
{
|
475
487
|
int cs = EVIL;
|
488
|
+
VALUE match_string;
|
476
489
|
|
477
490
|
*result = rb_str_buf_new(0);
|
478
491
|
%% write init;
|
479
492
|
json->memo = p;
|
480
493
|
%% write exec;
|
481
494
|
|
495
|
+
if (json->create_additions && RTEST(match_string = json->match_string)) {
|
496
|
+
VALUE klass;
|
497
|
+
VALUE memo = rb_ary_new2(2);
|
498
|
+
rb_ary_push(memo, *result);
|
499
|
+
rb_hash_foreach(match_string, match_i, memo);
|
500
|
+
klass = rb_ary_entry(memo, 1);
|
501
|
+
if (RTEST(klass)) {
|
502
|
+
*result = rb_funcall(klass, i_json_create, 1, *result);
|
503
|
+
}
|
504
|
+
}
|
505
|
+
|
482
506
|
if (json->symbolize_names && json->parsing_name) {
|
483
507
|
*result = rb_str_intern(*result);
|
484
508
|
}
|
@@ -632,26 +656,25 @@ static VALUE cParser_initialize(int argc, VALUE *argv, VALUE self)
|
|
632
656
|
}
|
633
657
|
tmp = ID2SYM(i_allow_nan);
|
634
658
|
if (option_given_p(opts, tmp)) {
|
635
|
-
|
636
|
-
json->allow_nan = RTEST(allow_nan) ? 1 : 0;
|
659
|
+
json->allow_nan = RTEST(rb_hash_aref(opts, tmp)) ? 1 : 0;
|
637
660
|
} else {
|
638
661
|
json->allow_nan = 0;
|
639
662
|
}
|
640
663
|
tmp = ID2SYM(i_symbolize_names);
|
641
664
|
if (option_given_p(opts, tmp)) {
|
642
|
-
|
643
|
-
json->symbolize_names = RTEST(symbolize_names) ? 1 : 0;
|
665
|
+
json->symbolize_names = RTEST(rb_hash_aref(opts, tmp)) ? 1 : 0;
|
644
666
|
} else {
|
645
667
|
json->symbolize_names = 0;
|
646
668
|
}
|
647
669
|
tmp = ID2SYM(i_create_additions);
|
648
670
|
if (option_given_p(opts, tmp)) {
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
671
|
+
json->create_additions = RTEST(rb_hash_aref(opts, tmp));
|
672
|
+
} else {
|
673
|
+
json->create_additions = 1;
|
674
|
+
}
|
675
|
+
tmp = ID2SYM(i_create_id);
|
676
|
+
if (option_given_p(opts, tmp)) {
|
677
|
+
json->create_id = rb_hash_aref(opts, tmp);
|
655
678
|
} else {
|
656
679
|
json->create_id = rb_funcall(mJSON, i_create_id, 0);
|
657
680
|
}
|
@@ -667,10 +690,18 @@ static VALUE cParser_initialize(int argc, VALUE *argv, VALUE self)
|
|
667
690
|
} else {
|
668
691
|
json->array_class = Qnil;
|
669
692
|
}
|
693
|
+
tmp = ID2SYM(i_match_string);
|
694
|
+
if (option_given_p(opts, tmp)) {
|
695
|
+
VALUE match_string = rb_hash_aref(opts, tmp);
|
696
|
+
json->match_string = RTEST(match_string) ? match_string : Qnil;
|
697
|
+
} else {
|
698
|
+
json->match_string = Qnil;
|
699
|
+
}
|
670
700
|
}
|
671
701
|
} else {
|
672
702
|
json->max_nesting = 19;
|
673
703
|
json->allow_nan = 0;
|
704
|
+
json->create_additions = 1;
|
674
705
|
json->create_id = rb_funcall(mJSON, i_create_id, 0);
|
675
706
|
json->object_class = Qnil;
|
676
707
|
json->array_class = Qnil;
|
@@ -721,6 +752,7 @@ static void JSON_mark(JSON_Parser *json)
|
|
721
752
|
rb_gc_mark_maybe(json->create_id);
|
722
753
|
rb_gc_mark_maybe(json->object_class);
|
723
754
|
rb_gc_mark_maybe(json->array_class);
|
755
|
+
rb_gc_mark_maybe(json->match_string);
|
724
756
|
}
|
725
757
|
|
726
758
|
static void JSON_free(JSON_Parser *json)
|
@@ -773,6 +805,8 @@ void Init_parser()
|
|
773
805
|
i_symbolize_names = rb_intern("symbolize_names");
|
774
806
|
i_object_class = rb_intern("object_class");
|
775
807
|
i_array_class = rb_intern("array_class");
|
808
|
+
i_match = rb_intern("match");
|
809
|
+
i_match_string = rb_intern("match_string");
|
776
810
|
i_key_p = rb_intern("key?");
|
777
811
|
i_deep_const_get = rb_intern("deep_const_get");
|
778
812
|
#ifdef HAVE_RUBY_ENCODING_H
|
Binary file
|
Binary file
|
@@ -0,0 +1,167 @@
|
|
1
|
+
/*
|
2
|
+
* This code is copyrighted work by Daniel Luz <dev at mernen dot com>.
|
3
|
+
*
|
4
|
+
* Distributed under the Ruby and GPLv2 licenses; see COPYING and GPL files
|
5
|
+
* for details.
|
6
|
+
*/
|
7
|
+
package json.ext;
|
8
|
+
|
9
|
+
import org.jruby.exceptions.RaiseException;
|
10
|
+
import org.jruby.runtime.ThreadContext;
|
11
|
+
import org.jruby.util.ByteList;
|
12
|
+
|
13
|
+
/**
|
14
|
+
* A class specialized in transcoding a certain String format into another,
|
15
|
+
* using UTF-8 ByteLists as both input and output.
|
16
|
+
*/
|
17
|
+
abstract class ByteListTranscoder {
|
18
|
+
protected final ThreadContext context;
|
19
|
+
|
20
|
+
protected ByteList src;
|
21
|
+
protected int srcEnd;
|
22
|
+
/** Position where the last read character started */
|
23
|
+
protected int charStart;
|
24
|
+
/** Position of the next character to read */
|
25
|
+
protected int pos;
|
26
|
+
|
27
|
+
private ByteList out;
|
28
|
+
/**
|
29
|
+
* When a character that can be copied straight into the output is found,
|
30
|
+
* its index is stored on this variable, and copying is delayed until
|
31
|
+
* the sequence of characters that can be copied ends.
|
32
|
+
*
|
33
|
+
* <p>The variable stores -1 when not in a plain sequence.
|
34
|
+
*/
|
35
|
+
private int quoteStart = -1;
|
36
|
+
|
37
|
+
protected ByteListTranscoder(ThreadContext context) {
|
38
|
+
this.context = context;
|
39
|
+
}
|
40
|
+
|
41
|
+
protected void init(ByteList src, ByteList out) {
|
42
|
+
this.init(src, 0, src.length(), out);
|
43
|
+
}
|
44
|
+
|
45
|
+
protected void init(ByteList src, int start, int end, ByteList out) {
|
46
|
+
this.src = src;
|
47
|
+
this.pos = start;
|
48
|
+
this.charStart = start;
|
49
|
+
this.srcEnd = end;
|
50
|
+
this.out = out;
|
51
|
+
}
|
52
|
+
|
53
|
+
/**
|
54
|
+
* Returns whether there are any characters left to be read.
|
55
|
+
*/
|
56
|
+
protected boolean hasNext() {
|
57
|
+
return pos < srcEnd;
|
58
|
+
}
|
59
|
+
|
60
|
+
/**
|
61
|
+
* Returns the next character in the buffer.
|
62
|
+
*/
|
63
|
+
private char next() {
|
64
|
+
return src.charAt(pos++);
|
65
|
+
}
|
66
|
+
|
67
|
+
/**
|
68
|
+
* Reads an UTF-8 character from the input and returns its code point,
|
69
|
+
* while advancing the input position.
|
70
|
+
*
|
71
|
+
* <p>Raises an {@link #invalidUtf8()} exception if an invalid byte
|
72
|
+
* is found.
|
73
|
+
*/
|
74
|
+
protected int readUtf8Char() {
|
75
|
+
charStart = pos;
|
76
|
+
char head = next();
|
77
|
+
if (head <= 0x7f) { // 0b0xxxxxxx (ASCII)
|
78
|
+
return head;
|
79
|
+
}
|
80
|
+
if (head <= 0xbf) { // 0b10xxxxxx
|
81
|
+
throw invalidUtf8(); // tail byte with no head
|
82
|
+
}
|
83
|
+
if (head <= 0xdf) { // 0b110xxxxx
|
84
|
+
ensureMin(1);
|
85
|
+
int cp = ((head & 0x1f) << 6)
|
86
|
+
| nextPart();
|
87
|
+
if (cp < 0x0080) throw invalidUtf8();
|
88
|
+
return cp;
|
89
|
+
}
|
90
|
+
if (head <= 0xef) { // 0b1110xxxx
|
91
|
+
ensureMin(2);
|
92
|
+
int cp = ((head & 0x0f) << 12)
|
93
|
+
| (nextPart() << 6)
|
94
|
+
| nextPart();
|
95
|
+
if (cp < 0x0800) throw invalidUtf8();
|
96
|
+
return cp;
|
97
|
+
}
|
98
|
+
if (head <= 0xf7) { // 0b11110xxx
|
99
|
+
ensureMin(3);
|
100
|
+
int cp = ((head & 0x07) << 18)
|
101
|
+
| (nextPart() << 12)
|
102
|
+
| (nextPart() << 6)
|
103
|
+
| nextPart();
|
104
|
+
if (!Character.isValidCodePoint(cp)) throw invalidUtf8();
|
105
|
+
return cp;
|
106
|
+
}
|
107
|
+
// 0b11111xxx?
|
108
|
+
throw invalidUtf8();
|
109
|
+
}
|
110
|
+
|
111
|
+
/**
|
112
|
+
* Throws a GeneratorError if the input list doesn't have at least this
|
113
|
+
* many bytes left.
|
114
|
+
*/
|
115
|
+
protected void ensureMin(int n) {
|
116
|
+
if (pos + n > srcEnd) throw incompleteUtf8();
|
117
|
+
}
|
118
|
+
|
119
|
+
/**
|
120
|
+
* Reads the next byte of a multi-byte UTF-8 character and returns its
|
121
|
+
* contents (lower 6 bits).
|
122
|
+
*
|
123
|
+
* <p>Throws a GeneratorError if the byte is not a valid tail.
|
124
|
+
*/
|
125
|
+
private int nextPart() {
|
126
|
+
char c = next();
|
127
|
+
// tail bytes must be 0b10xxxxxx
|
128
|
+
if ((c & 0xc0) != 0x80) throw invalidUtf8();
|
129
|
+
return c & 0x3f;
|
130
|
+
}
|
131
|
+
|
132
|
+
|
133
|
+
protected void quoteStart() {
|
134
|
+
if (quoteStart == -1) quoteStart = charStart;
|
135
|
+
}
|
136
|
+
|
137
|
+
/**
|
138
|
+
* When in a sequence of characters that can be copied directly,
|
139
|
+
* interrupts the sequence and copies it to the output buffer.
|
140
|
+
*
|
141
|
+
* @param endPos The offset until which the direct character quoting should
|
142
|
+
* occur. You may pass {@link #pos} to quote until the most
|
143
|
+
* recently read character, or {@link #charStart} to quote
|
144
|
+
* until the character before it.
|
145
|
+
*/
|
146
|
+
protected void quoteStop(int endPos) {
|
147
|
+
if (quoteStart != -1) {
|
148
|
+
out.append(src, quoteStart, endPos - quoteStart);
|
149
|
+
quoteStart = -1;
|
150
|
+
}
|
151
|
+
}
|
152
|
+
|
153
|
+
protected void append(int b) {
|
154
|
+
out.append(b);
|
155
|
+
}
|
156
|
+
|
157
|
+
protected void append(byte[] origin, int start, int length) {
|
158
|
+
out.append(origin, start, length);
|
159
|
+
}
|
160
|
+
|
161
|
+
|
162
|
+
protected abstract RaiseException invalidUtf8();
|
163
|
+
|
164
|
+
protected RaiseException incompleteUtf8() {
|
165
|
+
return invalidUtf8();
|
166
|
+
}
|
167
|
+
}
|
@@ -0,0 +1,441 @@
|
|
1
|
+
/*
|
2
|
+
* This code is copyrighted work by Daniel Luz <dev at mernen dot com>.
|
3
|
+
*
|
4
|
+
* Distributed under the Ruby and GPLv2 licenses; see COPYING and GPL files
|
5
|
+
* for details.
|
6
|
+
*/
|
7
|
+
package json.ext;
|
8
|
+
|
9
|
+
import org.jruby.Ruby;
|
10
|
+
import org.jruby.RubyArray;
|
11
|
+
import org.jruby.RubyBignum;
|
12
|
+
import org.jruby.RubyBoolean;
|
13
|
+
import org.jruby.RubyClass;
|
14
|
+
import org.jruby.RubyFixnum;
|
15
|
+
import org.jruby.RubyFloat;
|
16
|
+
import org.jruby.RubyHash;
|
17
|
+
import org.jruby.RubyNumeric;
|
18
|
+
import org.jruby.RubyString;
|
19
|
+
import org.jruby.runtime.ThreadContext;
|
20
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
21
|
+
import org.jruby.util.ByteList;
|
22
|
+
|
23
|
+
public final class Generator {
|
24
|
+
private Generator() {
|
25
|
+
throw new RuntimeException();
|
26
|
+
}
|
27
|
+
|
28
|
+
/**
|
29
|
+
* Encodes the given object as a JSON string, using the given handler.
|
30
|
+
*/
|
31
|
+
static <T extends IRubyObject> RubyString
|
32
|
+
generateJson(ThreadContext context, T object,
|
33
|
+
Handler<? super T> handler, IRubyObject[] args) {
|
34
|
+
Session session = new Session(context, args.length > 0 ? args[0]
|
35
|
+
: null);
|
36
|
+
return session.infect(handler.generateNew(session, object));
|
37
|
+
}
|
38
|
+
|
39
|
+
/**
|
40
|
+
* Encodes the given object as a JSON string, detecting the appropriate handler
|
41
|
+
* for the given object.
|
42
|
+
*/
|
43
|
+
static <T extends IRubyObject> RubyString
|
44
|
+
generateJson(ThreadContext context, T object, IRubyObject[] args) {
|
45
|
+
Handler<? super T> handler = getHandlerFor(context.getRuntime(), object);
|
46
|
+
return generateJson(context, object, handler, args);
|
47
|
+
}
|
48
|
+
|
49
|
+
/**
|
50
|
+
* Encodes the given object as a JSON string, using the appropriate
|
51
|
+
* handler if one is found or calling #to_json if not.
|
52
|
+
*/
|
53
|
+
public static <T extends IRubyObject> RubyString
|
54
|
+
generateJson(ThreadContext context, T object,
|
55
|
+
GeneratorState config) {
|
56
|
+
Session session = new Session(context, config);
|
57
|
+
Handler<? super T> handler = getHandlerFor(context.getRuntime(), object);
|
58
|
+
return handler.generateNew(session, object);
|
59
|
+
}
|
60
|
+
|
61
|
+
/**
|
62
|
+
* Returns the best serialization handler for the given object.
|
63
|
+
*/
|
64
|
+
// Java's generics can't handle this satisfactorily, so I'll just leave
|
65
|
+
// the best I could get and ignore the warnings
|
66
|
+
@SuppressWarnings("unchecked")
|
67
|
+
private static <T extends IRubyObject>
|
68
|
+
Handler<? super T> getHandlerFor(Ruby runtime, T object) {
|
69
|
+
RubyClass metaClass = object.getMetaClass();
|
70
|
+
if (metaClass == runtime.getString()) return (Handler)STRING_HANDLER;
|
71
|
+
if (metaClass == runtime.getFixnum()) return (Handler)FIXNUM_HANDLER;
|
72
|
+
if (metaClass == runtime.getHash()) return (Handler)HASH_HANDLER;
|
73
|
+
if (metaClass == runtime.getArray()) return (Handler)ARRAY_HANDLER;
|
74
|
+
if (object.isNil()) return (Handler)NIL_HANDLER;
|
75
|
+
if (object == runtime.getTrue()) return (Handler)TRUE_HANDLER;
|
76
|
+
if (object == runtime.getFalse()) return (Handler)FALSE_HANDLER;
|
77
|
+
if (metaClass == runtime.getFloat()) return (Handler)FLOAT_HANDLER;
|
78
|
+
if (metaClass == runtime.getBignum()) return (Handler)BIGNUM_HANDLER;
|
79
|
+
return GENERIC_HANDLER;
|
80
|
+
}
|
81
|
+
|
82
|
+
|
83
|
+
/* Generator context */
|
84
|
+
|
85
|
+
/**
|
86
|
+
* A class that concentrates all the information that is shared by
|
87
|
+
* generators working on a single session.
|
88
|
+
*
|
89
|
+
* <p>A session is defined as the process of serializing a single root
|
90
|
+
* object; any handler directly called by container handlers (arrays and
|
91
|
+
* hashes/objects) shares this object with its caller.
|
92
|
+
*
|
93
|
+
* <p>Note that anything called indirectly (via {@link GENERIC_HANDLER})
|
94
|
+
* won't be part of the session.
|
95
|
+
*/
|
96
|
+
static class Session {
|
97
|
+
private final ThreadContext context;
|
98
|
+
private GeneratorState state;
|
99
|
+
private IRubyObject possibleState;
|
100
|
+
private RuntimeInfo info;
|
101
|
+
private StringEncoder stringEncoder;
|
102
|
+
|
103
|
+
private boolean tainted = false;
|
104
|
+
private boolean untrusted = false;
|
105
|
+
|
106
|
+
Session(ThreadContext context, GeneratorState state) {
|
107
|
+
this.context = context;
|
108
|
+
this.state = state;
|
109
|
+
}
|
110
|
+
|
111
|
+
Session(ThreadContext context, IRubyObject possibleState) {
|
112
|
+
this.context = context;
|
113
|
+
this.possibleState = possibleState == null || possibleState.isNil()
|
114
|
+
? null : possibleState;
|
115
|
+
}
|
116
|
+
|
117
|
+
public ThreadContext getContext() {
|
118
|
+
return context;
|
119
|
+
}
|
120
|
+
|
121
|
+
public Ruby getRuntime() {
|
122
|
+
return context.getRuntime();
|
123
|
+
}
|
124
|
+
|
125
|
+
public GeneratorState getState() {
|
126
|
+
if (state == null) {
|
127
|
+
state = GeneratorState.fromState(context, getInfo(), possibleState);
|
128
|
+
}
|
129
|
+
return state;
|
130
|
+
}
|
131
|
+
|
132
|
+
public RuntimeInfo getInfo() {
|
133
|
+
if (info == null) info = RuntimeInfo.forRuntime(getRuntime());
|
134
|
+
return info;
|
135
|
+
}
|
136
|
+
|
137
|
+
public StringEncoder getStringEncoder() {
|
138
|
+
if (stringEncoder == null) {
|
139
|
+
stringEncoder = new StringEncoder(context, getState().asciiOnly());
|
140
|
+
}
|
141
|
+
return stringEncoder;
|
142
|
+
}
|
143
|
+
|
144
|
+
public void infectBy(IRubyObject object) {
|
145
|
+
if (object.isTaint()) tainted = true;
|
146
|
+
if (object.isUntrusted()) untrusted = true;
|
147
|
+
}
|
148
|
+
|
149
|
+
public <T extends IRubyObject> T infect(T object) {
|
150
|
+
if (tainted) object.setTaint(true);
|
151
|
+
if (untrusted) object.setUntrusted(true);
|
152
|
+
return object;
|
153
|
+
}
|
154
|
+
}
|
155
|
+
|
156
|
+
|
157
|
+
/* Handler base classes */
|
158
|
+
|
159
|
+
private static abstract class Handler<T extends IRubyObject> {
|
160
|
+
/**
|
161
|
+
* Returns an estimative of how much space the serialization of the
|
162
|
+
* given object will take. Used for allocating enough buffer space
|
163
|
+
* before invoking other methods.
|
164
|
+
*/
|
165
|
+
int guessSize(Session session, T object) {
|
166
|
+
return 4;
|
167
|
+
}
|
168
|
+
|
169
|
+
RubyString generateNew(Session session, T object) {
|
170
|
+
ByteList buffer = new ByteList(guessSize(session, object));
|
171
|
+
generate(session, object, buffer);
|
172
|
+
return RubyString.newString(session.getRuntime(), buffer);
|
173
|
+
}
|
174
|
+
|
175
|
+
abstract void generate(Session session, T object, ByteList buffer);
|
176
|
+
}
|
177
|
+
|
178
|
+
/**
|
179
|
+
* A handler that returns a fixed keyword regardless of the passed object.
|
180
|
+
*/
|
181
|
+
private static class KeywordHandler<T extends IRubyObject>
|
182
|
+
extends Handler<T> {
|
183
|
+
private final ByteList keyword;
|
184
|
+
|
185
|
+
private KeywordHandler(String keyword) {
|
186
|
+
this.keyword = new ByteList(ByteList.plain(keyword), false);
|
187
|
+
}
|
188
|
+
|
189
|
+
@Override
|
190
|
+
int guessSize(Session session, T object) {
|
191
|
+
return keyword.length();
|
192
|
+
}
|
193
|
+
|
194
|
+
@Override
|
195
|
+
RubyString generateNew(Session session, T object) {
|
196
|
+
return RubyString.newStringShared(session.getRuntime(), keyword);
|
197
|
+
}
|
198
|
+
|
199
|
+
@Override
|
200
|
+
void generate(Session session, T object, ByteList buffer) {
|
201
|
+
buffer.append(keyword);
|
202
|
+
}
|
203
|
+
}
|
204
|
+
|
205
|
+
|
206
|
+
/* Handlers */
|
207
|
+
|
208
|
+
static final Handler<RubyBignum> BIGNUM_HANDLER =
|
209
|
+
new Handler<RubyBignum>() {
|
210
|
+
@Override
|
211
|
+
void generate(Session session, RubyBignum object, ByteList buffer) {
|
212
|
+
// JRUBY-4751: RubyBignum.to_s() returns generic object
|
213
|
+
// representation (fixed in 1.5, but we maintain backwards
|
214
|
+
// compatibility; call to_s(IRubyObject[]) then
|
215
|
+
buffer.append(((RubyString)object.to_s(IRubyObject.NULL_ARRAY)).getByteList());
|
216
|
+
}
|
217
|
+
};
|
218
|
+
|
219
|
+
static final Handler<RubyFixnum> FIXNUM_HANDLER =
|
220
|
+
new Handler<RubyFixnum>() {
|
221
|
+
@Override
|
222
|
+
void generate(Session session, RubyFixnum object, ByteList buffer) {
|
223
|
+
buffer.append(object.to_s().getByteList());
|
224
|
+
}
|
225
|
+
};
|
226
|
+
|
227
|
+
static final Handler<RubyFloat> FLOAT_HANDLER =
|
228
|
+
new Handler<RubyFloat>() {
|
229
|
+
@Override
|
230
|
+
void generate(Session session, RubyFloat object, ByteList buffer) {
|
231
|
+
double value = RubyFloat.num2dbl(object);
|
232
|
+
|
233
|
+
if (Double.isInfinite(value) || Double.isNaN(value)) {
|
234
|
+
if (!session.getState().allowNaN()) {
|
235
|
+
throw Utils.newException(session.getContext(),
|
236
|
+
Utils.M_GENERATOR_ERROR,
|
237
|
+
object + " not allowed in JSON");
|
238
|
+
}
|
239
|
+
}
|
240
|
+
buffer.append(((RubyString)object.to_s()).getByteList());
|
241
|
+
}
|
242
|
+
};
|
243
|
+
|
244
|
+
static final Handler<RubyArray> ARRAY_HANDLER =
|
245
|
+
new Handler<RubyArray>() {
|
246
|
+
@Override
|
247
|
+
int guessSize(Session session, RubyArray object) {
|
248
|
+
GeneratorState state = session.getState();
|
249
|
+
int depth = state.getDepth();
|
250
|
+
int perItem =
|
251
|
+
4 // prealloc
|
252
|
+
+ (depth + 1) * state.getIndent().length() // indent
|
253
|
+
+ 1 + state.getArrayNl().length(); // ',' arrayNl
|
254
|
+
return 2 + object.size() * perItem;
|
255
|
+
}
|
256
|
+
|
257
|
+
@Override
|
258
|
+
void generate(Session session, RubyArray object, ByteList buffer) {
|
259
|
+
ThreadContext context = session.getContext();
|
260
|
+
Ruby runtime = context.getRuntime();
|
261
|
+
GeneratorState state = session.getState();
|
262
|
+
int depth = state.increaseDepth();
|
263
|
+
|
264
|
+
ByteList indentUnit = state.getIndent();
|
265
|
+
byte[] shift = Utils.repeat(indentUnit, depth);
|
266
|
+
|
267
|
+
ByteList arrayNl = state.getArrayNl();
|
268
|
+
byte[] delim = new byte[1 + arrayNl.length()];
|
269
|
+
delim[0] = ',';
|
270
|
+
System.arraycopy(arrayNl.unsafeBytes(), arrayNl.begin(), delim, 1,
|
271
|
+
arrayNl.length());
|
272
|
+
|
273
|
+
session.infectBy(object);
|
274
|
+
|
275
|
+
buffer.append((byte)'[');
|
276
|
+
buffer.append(arrayNl);
|
277
|
+
boolean firstItem = true;
|
278
|
+
for (int i = 0, t = object.getLength(); i < t; i++) {
|
279
|
+
IRubyObject element = object.eltInternal(i);
|
280
|
+
session.infectBy(element);
|
281
|
+
if (firstItem) {
|
282
|
+
firstItem = false;
|
283
|
+
} else {
|
284
|
+
buffer.append(delim);
|
285
|
+
}
|
286
|
+
buffer.append(shift);
|
287
|
+
Handler<IRubyObject> handler = getHandlerFor(runtime, element);
|
288
|
+
handler.generate(session, element, buffer);
|
289
|
+
}
|
290
|
+
|
291
|
+
state.decreaseDepth();
|
292
|
+
if (arrayNl.length() != 0) {
|
293
|
+
buffer.append(arrayNl);
|
294
|
+
buffer.append(shift, 0, state.getDepth() * indentUnit.length());
|
295
|
+
}
|
296
|
+
|
297
|
+
buffer.append((byte)']');
|
298
|
+
}
|
299
|
+
};
|
300
|
+
|
301
|
+
static final Handler<RubyHash> HASH_HANDLER =
|
302
|
+
new Handler<RubyHash>() {
|
303
|
+
@Override
|
304
|
+
int guessSize(Session session, RubyHash object) {
|
305
|
+
GeneratorState state = session.getState();
|
306
|
+
int perItem =
|
307
|
+
12 // key, colon, comma
|
308
|
+
+ (state.getDepth() + 1) * state.getIndent().length()
|
309
|
+
+ state.getSpaceBefore().length()
|
310
|
+
+ state.getSpace().length();
|
311
|
+
return 2 + object.size() * perItem;
|
312
|
+
}
|
313
|
+
|
314
|
+
@Override
|
315
|
+
void generate(final Session session, RubyHash object,
|
316
|
+
final ByteList buffer) {
|
317
|
+
ThreadContext context = session.getContext();
|
318
|
+
final Ruby runtime = context.getRuntime();
|
319
|
+
final GeneratorState state = session.getState();
|
320
|
+
final int depth = state.increaseDepth();
|
321
|
+
|
322
|
+
final ByteList objectNl = state.getObjectNl();
|
323
|
+
final byte[] indent = Utils.repeat(state.getIndent(), depth);
|
324
|
+
final ByteList spaceBefore = state.getSpaceBefore();
|
325
|
+
final ByteList space = state.getSpace();
|
326
|
+
|
327
|
+
buffer.append((byte)'{');
|
328
|
+
buffer.append(objectNl);
|
329
|
+
object.visitAll(new RubyHash.Visitor() {
|
330
|
+
private boolean firstPair = true;
|
331
|
+
|
332
|
+
@Override
|
333
|
+
public void visit(IRubyObject key, IRubyObject value) {
|
334
|
+
if (firstPair) {
|
335
|
+
firstPair = false;
|
336
|
+
} else {
|
337
|
+
buffer.append((byte)',');
|
338
|
+
buffer.append(objectNl);
|
339
|
+
}
|
340
|
+
if (objectNl.length() != 0) buffer.append(indent);
|
341
|
+
|
342
|
+
STRING_HANDLER.generate(session, key.asString(), buffer);
|
343
|
+
session.infectBy(key);
|
344
|
+
|
345
|
+
buffer.append(spaceBefore);
|
346
|
+
buffer.append((byte)':');
|
347
|
+
buffer.append(space);
|
348
|
+
|
349
|
+
Handler<IRubyObject> valueHandler = getHandlerFor(runtime, value);
|
350
|
+
valueHandler.generate(session, value, buffer);
|
351
|
+
session.infectBy(value);
|
352
|
+
}
|
353
|
+
});
|
354
|
+
state.decreaseDepth();
|
355
|
+
if (objectNl.length() != 0) {
|
356
|
+
buffer.append(objectNl);
|
357
|
+
if (indent.length != 0) {
|
358
|
+
for (int i = 0; i < state.getDepth(); i++) {
|
359
|
+
buffer.append(indent);
|
360
|
+
}
|
361
|
+
}
|
362
|
+
}
|
363
|
+
buffer.append((byte)'}');
|
364
|
+
}
|
365
|
+
};
|
366
|
+
|
367
|
+
static final Handler<RubyString> STRING_HANDLER =
|
368
|
+
new Handler<RubyString>() {
|
369
|
+
@Override
|
370
|
+
int guessSize(Session session, RubyString object) {
|
371
|
+
// for most applications, most strings will be just a set of
|
372
|
+
// printable ASCII characters without any escaping, so let's
|
373
|
+
// just allocate enough space for that + the quotes
|
374
|
+
return 2 + object.getByteList().length();
|
375
|
+
}
|
376
|
+
|
377
|
+
@Override
|
378
|
+
void generate(Session session, RubyString object, ByteList buffer) {
|
379
|
+
RuntimeInfo info = session.getInfo();
|
380
|
+
RubyString src;
|
381
|
+
|
382
|
+
if (info.encodingsSupported() &&
|
383
|
+
object.encoding(session.getContext()) != info.utf8) {
|
384
|
+
src = (RubyString)object.encode(session.getContext(),
|
385
|
+
info.utf8);
|
386
|
+
} else {
|
387
|
+
src = object;
|
388
|
+
}
|
389
|
+
|
390
|
+
session.getStringEncoder().encode(src.getByteList(), buffer);
|
391
|
+
}
|
392
|
+
};
|
393
|
+
|
394
|
+
static final Handler<RubyBoolean> TRUE_HANDLER =
|
395
|
+
new KeywordHandler<RubyBoolean>("true");
|
396
|
+
static final Handler<RubyBoolean> FALSE_HANDLER =
|
397
|
+
new KeywordHandler<RubyBoolean>("false");
|
398
|
+
static final Handler<IRubyObject> NIL_HANDLER =
|
399
|
+
new KeywordHandler<IRubyObject>("null");
|
400
|
+
|
401
|
+
/**
|
402
|
+
* The default handler (<code>Object#to_json</code>): coerces the object
|
403
|
+
* to string using <code>#to_s</code>, and serializes that string.
|
404
|
+
*/
|
405
|
+
static final Handler<IRubyObject> OBJECT_HANDLER =
|
406
|
+
new Handler<IRubyObject>() {
|
407
|
+
@Override
|
408
|
+
RubyString generateNew(Session session, IRubyObject object) {
|
409
|
+
RubyString str = object.asString();
|
410
|
+
return STRING_HANDLER.generateNew(session, str);
|
411
|
+
}
|
412
|
+
|
413
|
+
@Override
|
414
|
+
void generate(Session session, IRubyObject object, ByteList buffer) {
|
415
|
+
RubyString str = object.asString();
|
416
|
+
STRING_HANDLER.generate(session, str, buffer);
|
417
|
+
}
|
418
|
+
};
|
419
|
+
|
420
|
+
/**
|
421
|
+
* A handler that simply calls <code>#to_json(state)</code> on the
|
422
|
+
* given object.
|
423
|
+
*/
|
424
|
+
static final Handler<IRubyObject> GENERIC_HANDLER =
|
425
|
+
new Handler<IRubyObject>() {
|
426
|
+
@Override
|
427
|
+
RubyString generateNew(Session session, IRubyObject object) {
|
428
|
+
IRubyObject result =
|
429
|
+
object.callMethod(session.getContext(), "to_json",
|
430
|
+
new IRubyObject[] {session.getState()});
|
431
|
+
if (result instanceof RubyString) return (RubyString)result;
|
432
|
+
throw session.getRuntime().newTypeError("to_json must return a String");
|
433
|
+
}
|
434
|
+
|
435
|
+
@Override
|
436
|
+
void generate(Session session, IRubyObject object, ByteList buffer) {
|
437
|
+
RubyString result = generateNew(session, object);
|
438
|
+
buffer.append(result.getByteList());
|
439
|
+
}
|
440
|
+
};
|
441
|
+
}
|