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.

Files changed (48) hide show
  1. data/CHANGES +6 -0
  2. data/COPYING-json-jruby +57 -0
  3. data/README-json-jruby.markdown +33 -0
  4. data/Rakefile +224 -119
  5. data/VERSION +1 -1
  6. data/benchmarks/generator2_benchmark.rb +1 -1
  7. data/benchmarks/generator_benchmark.rb +1 -1
  8. data/ext/json/ext/generator/generator.c +20 -20
  9. data/ext/json/ext/generator/generator.h +7 -7
  10. data/ext/json/ext/parser/extconf.rb +1 -0
  11. data/ext/json/ext/parser/parser.c +122 -88
  12. data/ext/json/ext/parser/parser.h +7 -0
  13. data/ext/json/ext/parser/parser.rl +54 -20
  14. data/java/lib/bytelist-1.0.6.jar +0 -0
  15. data/java/lib/jcodings.jar +0 -0
  16. data/java/src/json/ext/ByteListTranscoder.java +167 -0
  17. data/java/src/json/ext/Generator.java +441 -0
  18. data/java/src/json/ext/GeneratorMethods.java +231 -0
  19. data/java/src/json/ext/GeneratorService.java +42 -0
  20. data/java/src/json/ext/GeneratorState.java +473 -0
  21. data/java/src/json/ext/OptionsReader.java +119 -0
  22. data/java/src/json/ext/Parser.java +2295 -0
  23. data/java/src/json/ext/Parser.rl +825 -0
  24. data/java/src/json/ext/ParserService.java +34 -0
  25. data/java/src/json/ext/RuntimeInfo.java +119 -0
  26. data/java/src/json/ext/StringDecoder.java +166 -0
  27. data/java/src/json/ext/StringEncoder.java +106 -0
  28. data/java/src/json/ext/Utils.java +89 -0
  29. data/json-java.gemspec +20 -0
  30. data/lib/json/add/core.rb +1 -2
  31. data/lib/json/add/rails.rb +4 -54
  32. data/lib/json/common.rb +36 -8
  33. data/lib/json/editor.rb +1 -3
  34. data/lib/json/ext.rb +2 -2
  35. data/lib/json/pure.rb +2 -64
  36. data/lib/json/pure/generator.rb +10 -8
  37. data/lib/json/pure/parser.rb +23 -12
  38. data/lib/json/version.rb +1 -1
  39. data/tests/setup_variant.rb +11 -0
  40. data/tests/test_json.rb +1 -5
  41. data/tests/test_json_addition.rb +14 -9
  42. data/tests/test_json_encoding.rb +9 -12
  43. data/tests/test_json_fixtures.rb +9 -8
  44. data/tests/test_json_generate.rb +3 -5
  45. data/tests/test_json_string_matching.rb +40 -0
  46. data/tests/test_json_unicode.rb +1 -5
  47. metadata +51 -13
  48. 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 (RTEST(json->create_id)) {
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
- fhold;
461
- fbreak;
462
- } else {
463
- FORCE_UTF8(*result);
464
- fexec p + 1;
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
- VALUE allow_nan = rb_hash_aref(opts, tmp);
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
- VALUE symbolize_names = rb_hash_aref(opts, tmp);
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
- VALUE create_additions = rb_hash_aref(opts, tmp);
650
- if (RTEST(create_additions)) {
651
- json->create_id = rb_funcall(mJSON, i_create_id, 0);
652
- } else {
653
- json->create_id = Qnil;
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
@@ -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
+ }