json_pure 1.4.6 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
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
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
+ }