hessian2 2.0.2 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +10 -10
  3. data/README.md +197 -197
  4. data/hessian2.gemspec +0 -2
  5. data/lib/hessian2/client.rb +57 -50
  6. data/lib/hessian2/constants.rb +164 -164
  7. data/lib/hessian2/fault.rb +3 -3
  8. data/lib/hessian2/handler.rb +18 -18
  9. data/lib/hessian2/hessian_client.rb +3 -3
  10. data/lib/hessian2/parser.rb +619 -619
  11. data/lib/hessian2/type_wrapper.rb +49 -49
  12. data/lib/hessian2/version.rb +3 -3
  13. data/lib/hessian2/writer.rb +499 -499
  14. data/lib/hessian2.rb +14 -14
  15. data/spec/binary_spec.rb +51 -51
  16. data/spec/boolean_spec.rb +26 -26
  17. data/spec/class_wrapper_spec.rb +52 -52
  18. data/spec/create_monkeys.rb +14 -14
  19. data/spec/date_spec.rb +45 -45
  20. data/spec/double_spec.rb +78 -78
  21. data/spec/int_spec.rb +54 -54
  22. data/spec/list_spec.rb +66 -66
  23. data/spec/long_spec.rb +68 -68
  24. data/spec/map_spec.rb +36 -36
  25. data/spec/null_spec.rb +17 -17
  26. data/spec/object_spec.rb +78 -78
  27. data/spec/ref_spec.rb +43 -43
  28. data/spec/spec_helper.rb +23 -23
  29. data/spec/string_spec.rb +61 -61
  30. data/spec/struct_wrapper_spec.rb +47 -47
  31. data/spec/type_wrapper_spec.rb +102 -102
  32. data/test/app.rb +24 -24
  33. data/test/async/em_http_asleep.rb +25 -25
  34. data/test/async/em_http_sleep.rb +25 -25
  35. data/test/async/monkey.asleep.rb +27 -27
  36. data/test/async/mysql2_aquery.rb +37 -37
  37. data/test/fault/monkey.undefined_method.rb +5 -5
  38. data/test/fault/monkey.wrong_arguments.rb +5 -5
  39. data/test/fiber_concurrency/em_http_asleep.rb +17 -17
  40. data/test/fiber_concurrency/em_http_sleep.rb +17 -17
  41. data/test/fiber_concurrency/monkey.asleep.fiber_aware.rb +18 -18
  42. data/test/fiber_concurrency/mysql2_query.rb +29 -29
  43. data/test/fiber_concurrency/net_http_asleep.rb +19 -19
  44. data/test/fiber_concurrency/net_http_sleep.rb +19 -19
  45. data/test/fibered_rainbows/Gemfile +15 -15
  46. data/test/fibered_rainbows/config.ru +11 -11
  47. data/test/fibered_rainbows/rainbows.rb +13 -13
  48. data/test/monkey_service.rb +16 -16
  49. data/test/prepare.rb +7 -7
  50. data/test/thread_concurrency/active_record_execute.rb +29 -29
  51. data/test/thread_concurrency/monkey.asleep.rb +22 -22
  52. data/test/thread_concurrency/net_http_asleep.rb +24 -24
  53. data/test/thread_concurrency/net_http_sleep.rb +24 -24
  54. data/test/threaded_rainbows/Gemfile +13 -13
  55. data/test/threaded_rainbows/config.ru +9 -9
  56. data/test/threaded_rainbows/rainbows.rb +13 -13
  57. metadata +4 -74
@@ -1,499 +1,499 @@
1
- require 'hessian2/constants'
2
- require 'bigdecimal'
3
- require 'active_record'
4
-
5
- module Hessian2
6
- module Writer
7
- include Constants
8
-
9
- def call(method, args)
10
- refs, crefs, trefs = {}, {}, {}
11
- out = [ 'H', '2', '0', 'C' ].pack('ahha')
12
- out << write_string(method)
13
- out << write_int(args.size)
14
- args.each{|arg| out << write(arg, refs, crefs, trefs) }
15
-
16
- out
17
- end
18
-
19
-
20
- def reply(val)
21
- [ 'H', '2', '0', 'R' ].pack('ahha') << write(val)
22
- end
23
-
24
-
25
- def write_fault(e)
26
- val = {
27
- code: e.class.to_s,
28
- message: e.message,
29
- detail: e.backtrace }
30
- [ 'F' ].pack('a') << write_hash(val)
31
- end
32
-
33
-
34
- def write(val, refs = {}, crefs = {}, trefs = {})
35
- case val
36
- when StructWrapper # object to values-array
37
- write_struct_wrapper(val, refs, crefs, trefs)
38
- when ClassWrapper # class definition for statically typed languages
39
- write_class_wrapper(val, refs, crefs, trefs)
40
- when TypeWrapper
41
- write_type_wrapper(val, refs, crefs, trefs)
42
- when TrueClass
43
- [ BC_TRUE ].pack('C')
44
- when FalseClass
45
- [ BC_FALSE ].pack('C')
46
- when Time
47
- if val.usec == 0 && val.sec == 0 # date in minutes
48
- [ BC_DATE_MINUTE, val.to_i / 60 ].pack('CL>')
49
- else
50
- [ BC_DATE, val.to_i * 1000 + val.usec / 1000 ].pack('CQ>') # date
51
- end
52
- when Float, BigDecimal
53
- write_float(val)
54
- when Fixnum
55
- write_int(val)
56
- when Array, ActiveRecord::Relation
57
- write_array(val, refs, crefs, trefs)
58
- when Bignum
59
- if val >= -0x80_000_000 && val <= 0x7f_fff_fff # four octet longs
60
- [ BC_LONG_INT, val ].pack('Cl>')
61
- else # long
62
- [ BC_LONG, val ].pack('Cq>')
63
- end
64
- when Hash
65
- write_hash(val, refs, crefs, trefs)
66
- when NilClass
67
- write_nil
68
- when String
69
- write_string(val)
70
- when Symbol
71
- write_string(val.to_s)
72
- else
73
- write_object(val, refs, crefs, trefs)
74
- end
75
- end
76
-
77
-
78
- def print_string(str)
79
- return str.b if String.method_defined?(:b)
80
-
81
- arr, i = Array.new(str.bytesize), 0
82
- str.unpack('U*').each do |c|
83
- if c < 0x80 # 0xxxxxxx
84
- arr[i] = c
85
- elsif c < 0x800 # 110xxxxx 10xxxxxx
86
- arr[i] = 0xc0 + ((c >> 6) & 0x1f)
87
- arr[i += 1] = 0x80 + (c & 0x3f)
88
- else # 1110xxxx 10xxxxxx 10xxxxxx
89
- arr[i] = 0xe0 + ((c >> 12) & 0xf)
90
- arr[i += 1] = 0x80 + ((c >> 6) & 0x3f)
91
- arr[i += 1] = 0x80 + (c & 0x3f)
92
- end
93
- i += 1
94
- end
95
-
96
- arr.pack('C*')
97
- end
98
-
99
-
100
- def write_array(arr, refs = {}, crefs = {}, trefs = {})
101
- idx = refs[arr.object_id]
102
- return write_ref(idx) if idx
103
-
104
- refs[arr.object_id] = refs.size
105
- len = arr.size
106
- if len <= LIST_DIRECT_MAX # [x78-7f] value*
107
- str = [ BC_LIST_DIRECT_UNTYPED + len ].pack('C')
108
- else # x58 int value*
109
- str = [ BC_LIST_FIXED_UNTYPED ].pack('C') << write_int(len)
110
- end
111
-
112
- arr.each do |ele|
113
- str << write(ele, refs, crefs, trefs)
114
- end
115
-
116
- str
117
- end
118
-
119
-
120
- def write_binary(str)
121
- chunks, i, len = [], 0, str.size
122
- while len > 0x8000
123
- chunks << [ BC_BINARY_CHUNK, 0x8000 ].pack('Cn') << str[i...(i += 0x8000)]
124
- len -= 0x8000
125
- end
126
-
127
- final = str[i..-1]
128
- if len <= BINARY_DIRECT_MAX
129
- chunks << [ BC_BINARY_DIRECT + len ].pack('C') << final
130
- elsif len <= BINARY_SHORT_MAX
131
- chunks << [ BC_BINARY_SHORT + (len >> 8), len ].pack('CC') << final
132
- else
133
- chunks << [ BC_BINARY, len ].pack('Cn') << final
134
- end
135
-
136
- chunks.join
137
- end
138
-
139
-
140
- def write_class_wrapper(val, refs, crefs, trefs)
141
- return write_nil unless val.values
142
-
143
- idx = refs[val.object_id]
144
- return write_ref(idx) if idx
145
-
146
- refs[val.object_id] = refs.size
147
-
148
- if val.is_multi?
149
- type = '[' << val.klass
150
- if trefs.include?(type)
151
- tstr = write_int(trefs[type])
152
- else
153
- trefs[type] = trefs.size # store a type
154
- tstr = write_string(type)
155
- end
156
- return [ BC_LIST_DIRECT ].pack('C') << tstr if val.values.size == 0
157
- end
158
-
159
- cref = crefs[val.klass]
160
- if cref
161
- cidx = cref.first
162
- fields = cref.last
163
- str = ''
164
- else
165
- fstr = val.fields.map{|f| write_string(f) }.join
166
-
167
- str = [ BC_OBJECT_DEF ].pack('C') << write_string(val.klass) << write_int(val.fields.size) << fstr
168
- cidx = crefs.size
169
- crefs[val.klass] = [cidx, val.fields] # store a class definition
170
- end
171
-
172
- if cidx <= OBJECT_DIRECT_MAX
173
- cstr = [ BC_OBJECT_DIRECT + cidx ].pack('C')
174
- else
175
- cstr = [ BC_OBJECT ].pack('C') << write_int(cidx)
176
- end
177
-
178
- if val.is_multi?
179
- len = val.values.size
180
- if len <= LIST_DIRECT_MAX # [x70-77] type value*
181
- str << [ BC_LIST_DIRECT + len ].pack('C') << tstr
182
- else # 'V' type int value*
183
- str << [ BC_LIST_FIXED ].pack('C') << tstr << write_int(len)
184
- end
185
-
186
- val.values.each do |ele|
187
- if ele
188
- ele_idx = refs[ele.object_id]
189
- if ele_idx
190
- str << (cstr + write_ref(ele_idx))
191
- else
192
- refs[ele.object_id] = refs.size
193
- str << (cstr + ele.map{|v| write(v, refs, crefs, trefs)}.join)
194
- end
195
- else
196
- str << write_nil
197
- end
198
- end
199
- else
200
- str << (cstr + val.values.map{|v| write(v, refs, crefs, trefs)}.join)
201
- end
202
-
203
- str
204
- end
205
-
206
-
207
- def write_float(val)
208
- case val.infinite?
209
- when 1
210
- return [ BC_DOUBLE, Float::INFINITY ].pack('CG')
211
- when -1
212
- return [ BC_DOUBLE, -Float::INFINITY ].pack('CG')
213
- else
214
- return [ BC_DOUBLE, Float::NAN ].pack('CG') if val.nan?
215
- return [ BC_DOUBLE_ZERO ].pack('C') if val.zero? # double zero
216
- return [ BC_DOUBLE_ONE ].pack('C') if val == 1 # double one
217
-
218
- ival = val.to_i
219
- if ival == val
220
- return [ BC_DOUBLE_BYTE, ival ].pack('Cc') if ival >= -0x80 && ival <= 0x7f # double octet
221
- return [ BC_DOUBLE_SHORT, (ival >> 8), ival ].pack('Ccc') if ival >= -0x8000 && ival <= 0x7fff # double short
222
- end
223
-
224
- mval = val * 1000
225
- if mval.finite?
226
- mills = mval.to_i
227
- if mills >= -0x80_000_000 && mills <= 0x7f_fff_fff && 0.001 * mills == val
228
- return [ BC_DOUBLE_MILL, mills ].pack('Cl>') # double mill
229
- end
230
- end
231
-
232
- [ BC_DOUBLE, val ].pack('CG') # double
233
- end
234
- end
235
-
236
-
237
- def write_hash(hash, refs = {}, crefs = {}, trefs = {})
238
- idx = refs[hash.object_id]
239
- return write_ref(idx) if idx
240
-
241
- refs[hash.object_id] = refs.size
242
- str = [ BC_MAP_UNTYPED ].pack('C')
243
- hash.each do |k, v|
244
- str << write(k, refs, crefs, trefs)
245
- str << write(v, refs, crefs, trefs)
246
- end
247
-
248
- str << [ BC_END ].pack('C')
249
- end
250
-
251
-
252
- def write_int(val)
253
- if val >= INT_DIRECT_MIN && val <= INT_DIRECT_MAX # single octet integers
254
- [ BC_INT_ZERO + val ].pack('c')
255
- elsif val >= INT_BYTE_MIN && val <= INT_BYTE_MAX # two octet integers
256
- [ BC_INT_BYTE_ZERO + (val >> 8), val ].pack('cc')
257
- elsif val >= INT_SHORT_MIN && val <= INT_SHORT_MAX # three octet integers
258
- [ BC_INT_SHORT_ZERO + (val >> 16), (val >> 8), val].pack('ccc')
259
- elsif val >= -0x80_000_000 && val <= 0x7f_fff_fff # integer
260
- [ BC_INT, val ].pack('Cl>')
261
- else
262
- [ BC_LONG, val ].pack('Cq>')
263
- end
264
- end
265
-
266
-
267
- def write_long(val)
268
- if val >= LONG_DIRECT_MIN && val <= LONG_DIRECT_MAX # single octet longs
269
- [ BC_LONG_ZERO + val ].pack('c')
270
- elsif val >= LONG_BYTE_MIN && val <= LONG_BYTE_MAX # two octet longs
271
- [ BC_LONG_BYTE_ZERO + (val >> 8), val ].pack('cc')
272
- elsif val >= LONG_SHORT_MIN && val <= LONG_SHORT_MAX # three octet longs
273
- [ BC_LONG_SHORT_ZERO + (val >> 16), (val >> 8), val ].pack('ccc')
274
- elsif val >= -0x80_000_000 && val <= 0x7f_fff_fff # four octet longs
275
- [ BC_LONG_INT, val ].pack('Cl>')
276
- else
277
- [ BC_LONG, val ].pack('Cq>')
278
- end
279
- end
280
-
281
-
282
- def write_nil
283
- [ BC_NULL ].pack('C')
284
- end
285
-
286
-
287
- def write_object(object, refs = {}, crefs = {}, trefs = {})
288
- return write_nil unless object
289
-
290
- idx = refs[object.object_id]
291
- return write_ref(idx) if idx
292
-
293
- refs[object.object_id] = refs.size
294
-
295
- klass = object.class.to_s
296
- cref = crefs[klass]
297
- if cref
298
- cidx = cref.first
299
- fields = cref.last
300
- str = ''
301
- else
302
- fields = get_fields(object)
303
- fstr = fields.map{|f| write_string(f) }.join
304
- cidx = crefs.size
305
- crefs[klass] = [cidx, fields]
306
-
307
- str = [ BC_OBJECT_DEF ].pack('C') << write_string(klass) << write_int(fields.size) << fstr
308
- end
309
-
310
- if cidx <= OBJECT_DIRECT_MAX
311
- cstr = [ BC_OBJECT_DIRECT + cidx ].pack('C')
312
- else
313
- cstr = [ BC_OBJECT ].pack('C') << write_int(cidx)
314
- end
315
-
316
- str << write_values(object, cstr, fields, refs, crefs, trefs)
317
-
318
- str
319
- end
320
-
321
-
322
- def write_ref(val)
323
- [ BC_REF ].pack('C') << write_int(val)
324
- end
325
-
326
-
327
- def write_struct_wrapper(val, refs, crefs, trefs)
328
- return write_nil unless val.values
329
-
330
- idx = refs[val.object_id]
331
- return write_ref(idx) if idx
332
-
333
- refs[val.object_id] = refs.size
334
-
335
- write_array(val.values, refs, crefs, trefs)
336
- end
337
-
338
-
339
- def write_string(str)
340
- chunks, i, len = '', 0, str.size
341
- while len > 0x8000
342
- chunks << [ BC_STRING_CHUNK, 0x8000 ].pack('Cn') << print_string(str[i...(i += 0x8000)])
343
- len -= 0x8000
344
- end
345
-
346
- final = str[i..-1]
347
- chunks << if len <= STRING_DIRECT_MAX
348
- [ BC_STRING_DIRECT + len ].pack('C')
349
- elsif len <= STRING_SHORT_MAX
350
- [ BC_STRING_SHORT + (len >> 8), len ].pack('CC')
351
- else
352
- [ BC_STRING, len ].pack('Cn')
353
- end
354
-
355
- chunks << print_string(final)
356
- end
357
-
358
-
359
- def write_type_wrapped_array(arr, tstr, eletype, refs = {}, crefs = {}, trefs = {})
360
- len = arr.size
361
- return [ BC_LIST_DIRECT ].pack('C') << tstr if len == 0
362
-
363
- if len <= LIST_DIRECT_MAX # [x70-77] type value*
364
- str = [ BC_LIST_DIRECT + len ].pack('C') << tstr
365
- else # 'V' type int value*
366
- str = [ BC_LIST_FIXED ].pack('C') << tstr << write_int(len)
367
- end
368
-
369
- case eletype
370
- when 'L'
371
- arr.each do |ele|
372
- str << write_long(Integer(ele))
373
- end
374
- when 'I'
375
- arr.each do |ele|
376
- str << write_int(Integer(ele))
377
- end
378
- when 'B'
379
- arr.each do |ele|
380
- str << write_binary(ele)
381
- end
382
- else
383
- arr.each do |ele|
384
- idx = refs[ele.object_id]
385
- if idx
386
- str << write_ref(idx)
387
- else
388
- refs[ele.object_id] = refs.size
389
- str << write_type_wrapped_object(ele, tstr, refs, crefs, trefs)
390
- end
391
- end
392
- end
393
-
394
- str
395
- end
396
-
397
-
398
- def write_type_wrapped_object(object, tstr, refs = {}, crefs = {}, trefs = {})
399
- return write_nil unless object
400
-
401
- str = [ BC_MAP ].pack('C') << tstr
402
-
403
- if object.is_a?(Hash)
404
- object.each do |k, v|
405
- str << write(k, refs, crefs, trefs)
406
- str << write(v, refs, crefs, trefs)
407
- end
408
- elsif object.instance_variable_get(:@attributes).is_a?(Hash)
409
- object.attributes.each do |k, v|
410
- str << write(k, refs, crefs, trefs)
411
- str << write(v, refs, crefs, trefs)
412
- end
413
- elsif object.is_a?(ClassWrapper)
414
- object.fields.each_with_index do |f, i|
415
- str << write(f, refs, crefs, trefs)
416
- str << write(object.values[i], refs, crefs, trefs)
417
- end
418
- elsif object.is_a?(TypeWrapper)
419
- object.object.each do |k, v|
420
- str << write(k, refs, crefs, trefs)
421
- str << write(v, refs, crefs, trefs)
422
- end
423
- else
424
- object.instance_variables.each do |var|
425
- str << write(var[1..-1], refs, crefs, trefs)
426
- str << write(object.instance_variable_get(var), refs, crefs, trefs)
427
- end
428
- end
429
-
430
- str << [ BC_END ].pack('C')
431
- end
432
-
433
-
434
- def write_type_wrapper(val, refs, crefs, trefs)
435
- return write_nil unless val.object
436
-
437
- idx = refs[val.object_id]
438
- return write_ref(idx) if idx
439
-
440
- refs[val.object_id] = refs.size
441
-
442
- type = val.is_multi? ? ('[' << val.hessian_type) : val.hessian_type
443
- if trefs.include?(type)
444
- tstr = write_int(trefs[type])
445
- else
446
- trefs[type] = trefs.size
447
- tstr = write_string(type)
448
- end
449
-
450
- if val.is_multi?
451
- write_type_wrapped_array(val.object, tstr, val.hessian_type, refs, crefs, trefs)
452
- else
453
- case val.hessian_type
454
- when 'L'
455
- write_long(Integer(val.object))
456
- when 'I'
457
- write_int(Integer(val.object))
458
- when 'B'
459
- write_binary(val.object)
460
- else
461
- write_type_wrapped_object(val.object, tstr, refs, crefs, trefs)
462
- end
463
- end
464
- end
465
-
466
-
467
- private
468
-
469
- def get_fields(object)
470
- fields = if object.is_a?(Hash)
471
- object.keys.map{|k| k.to_sym }
472
- elsif object.instance_variable_get(:@attributes).is_a?(Hash)
473
- object.attributes.keys.map{|k| k.to_sym }
474
- else
475
- object.instance_variables.map{|k| k[1..-1].to_sym }
476
- end
477
-
478
- raise "fields should not be empty: #{object.inspect}" if fields.empty?
479
-
480
- fields
481
- end
482
-
483
-
484
- def write_values(object, cstr, fields, refs, crefs, trefs)
485
- return write_nil unless object
486
-
487
- vstr = if object.is_a?(Hash)
488
- fields.map{|f| write(object[f] || object[f.to_s], refs, crefs, trefs) }.join
489
- elsif object.instance_variable_get(:@attributes).is_a?(Hash)
490
- fields.map{|f| write(object.attributes[f.to_s], refs, crefs, trefs) }.join
491
- else
492
- fields.map{|f| write(object.instance_variable_get(f.to_s.prepend('@')), refs, crefs, trefs) }.join
493
- end
494
-
495
- cstr + vstr
496
- end
497
-
498
- end
499
- end
1
+ require 'hessian2/constants'
2
+ require 'bigdecimal'
3
+ require 'active_record'
4
+
5
+ module Hessian2
6
+ module Writer
7
+ include Constants
8
+
9
+ def call(method, args)
10
+ refs, crefs, trefs = {}, {}, {}
11
+ out = [ 'H', '2', '0', 'C' ].pack('ahha')
12
+ out << write_string(method)
13
+ out << write_int(args.size)
14
+ args.each{|arg| out << write(arg, refs, crefs, trefs) }
15
+
16
+ out
17
+ end
18
+
19
+
20
+ def reply(val)
21
+ [ 'H', '2', '0', 'R' ].pack('ahha') << write(val)
22
+ end
23
+
24
+
25
+ def write_fault(e)
26
+ val = {
27
+ code: e.class.to_s,
28
+ message: e.message,
29
+ detail: e.backtrace }
30
+ [ 'F' ].pack('a') << write_hash(val)
31
+ end
32
+
33
+
34
+ def write(val, refs = {}, crefs = {}, trefs = {})
35
+ case val
36
+ when StructWrapper # object to values-array
37
+ write_struct_wrapper(val, refs, crefs, trefs)
38
+ when ClassWrapper # class definition for statically typed languages
39
+ write_class_wrapper(val, refs, crefs, trefs)
40
+ when TypeWrapper
41
+ write_type_wrapper(val, refs, crefs, trefs)
42
+ when TrueClass
43
+ [ BC_TRUE ].pack('C')
44
+ when FalseClass
45
+ [ BC_FALSE ].pack('C')
46
+ when Time
47
+ if val.usec == 0 && val.sec == 0 # date in minutes
48
+ [ BC_DATE_MINUTE, val.to_i / 60 ].pack('CL>')
49
+ else
50
+ [ BC_DATE, val.to_i * 1000 + val.usec / 1000 ].pack('CQ>') # date
51
+ end
52
+ when Float, BigDecimal
53
+ write_float(val)
54
+ when Fixnum
55
+ write_int(val)
56
+ when Array, ActiveRecord::Relation
57
+ write_array(val, refs, crefs, trefs)
58
+ when Bignum
59
+ if val >= -0x80_000_000 && val <= 0x7f_fff_fff # four octet longs
60
+ [ BC_LONG_INT, val ].pack('Cl>')
61
+ else # long
62
+ [ BC_LONG, val ].pack('Cq>')
63
+ end
64
+ when Hash
65
+ write_hash(val, refs, crefs, trefs)
66
+ when NilClass
67
+ write_nil
68
+ when String
69
+ write_string(val)
70
+ when Symbol
71
+ write_string(val.to_s)
72
+ else
73
+ write_object(val, refs, crefs, trefs)
74
+ end
75
+ end
76
+
77
+
78
+ def print_string(str)
79
+ return str.b if String.method_defined?(:b)
80
+
81
+ arr, i = Array.new(str.bytesize), 0
82
+ str.unpack('U*').each do |c|
83
+ if c < 0x80 # 0xxxxxxx
84
+ arr[i] = c
85
+ elsif c < 0x800 # 110xxxxx 10xxxxxx
86
+ arr[i] = 0xc0 + ((c >> 6) & 0x1f)
87
+ arr[i += 1] = 0x80 + (c & 0x3f)
88
+ else # 1110xxxx 10xxxxxx 10xxxxxx
89
+ arr[i] = 0xe0 + ((c >> 12) & 0xf)
90
+ arr[i += 1] = 0x80 + ((c >> 6) & 0x3f)
91
+ arr[i += 1] = 0x80 + (c & 0x3f)
92
+ end
93
+ i += 1
94
+ end
95
+
96
+ arr.pack('C*')
97
+ end
98
+
99
+
100
+ def write_array(arr, refs = {}, crefs = {}, trefs = {})
101
+ idx = refs[arr.object_id]
102
+ return write_ref(idx) if idx
103
+
104
+ refs[arr.object_id] = refs.size
105
+ len = arr.size
106
+ if len <= LIST_DIRECT_MAX # [x78-7f] value*
107
+ str = [ BC_LIST_DIRECT_UNTYPED + len ].pack('C')
108
+ else # x58 int value*
109
+ str = [ BC_LIST_FIXED_UNTYPED ].pack('C') << write_int(len)
110
+ end
111
+
112
+ arr.each do |ele|
113
+ str << write(ele, refs, crefs, trefs)
114
+ end
115
+
116
+ str
117
+ end
118
+
119
+
120
+ def write_binary(str)
121
+ chunks, i, len = [], 0, str.size
122
+ while len > 0x8000
123
+ chunks << [ BC_BINARY_CHUNK, 0x8000 ].pack('Cn') << str[i...(i += 0x8000)]
124
+ len -= 0x8000
125
+ end
126
+
127
+ final = str[i..-1]
128
+ if len <= BINARY_DIRECT_MAX
129
+ chunks << [ BC_BINARY_DIRECT + len ].pack('C') << final
130
+ elsif len <= BINARY_SHORT_MAX
131
+ chunks << [ BC_BINARY_SHORT + (len >> 8), len ].pack('CC') << final
132
+ else
133
+ chunks << [ BC_BINARY, len ].pack('Cn') << final
134
+ end
135
+
136
+ chunks.join
137
+ end
138
+
139
+
140
+ def write_class_wrapper(val, refs, crefs, trefs)
141
+ return write_nil unless val.values
142
+
143
+ idx = refs[val.object_id]
144
+ return write_ref(idx) if idx
145
+
146
+ refs[val.object_id] = refs.size
147
+
148
+ if val.is_multi?
149
+ type = '[' << val.klass
150
+ if trefs.include?(type)
151
+ tstr = write_int(trefs[type])
152
+ else
153
+ trefs[type] = trefs.size # store a type
154
+ tstr = write_string(type)
155
+ end
156
+ return [ BC_LIST_DIRECT ].pack('C') << tstr if val.values.size == 0
157
+ end
158
+
159
+ cref = crefs[val.klass]
160
+ if cref
161
+ cidx = cref.first
162
+ fields = cref.last
163
+ str = ''
164
+ else
165
+ fstr = val.fields.map{|f| write_string(f) }.join
166
+
167
+ str = [ BC_OBJECT_DEF ].pack('C') << write_string(val.klass) << write_int(val.fields.size) << fstr
168
+ cidx = crefs.size
169
+ crefs[val.klass] = [cidx, val.fields] # store a class definition
170
+ end
171
+
172
+ if cidx <= OBJECT_DIRECT_MAX
173
+ cstr = [ BC_OBJECT_DIRECT + cidx ].pack('C')
174
+ else
175
+ cstr = [ BC_OBJECT ].pack('C') << write_int(cidx)
176
+ end
177
+
178
+ if val.is_multi?
179
+ len = val.values.size
180
+ if len <= LIST_DIRECT_MAX # [x70-77] type value*
181
+ str << [ BC_LIST_DIRECT + len ].pack('C') << tstr
182
+ else # 'V' type int value*
183
+ str << [ BC_LIST_FIXED ].pack('C') << tstr << write_int(len)
184
+ end
185
+
186
+ val.values.each do |ele|
187
+ if ele
188
+ ele_idx = refs[ele.object_id]
189
+ if ele_idx
190
+ str << (cstr + write_ref(ele_idx))
191
+ else
192
+ refs[ele.object_id] = refs.size
193
+ str << (cstr + ele.map{|v| write(v, refs, crefs, trefs)}.join)
194
+ end
195
+ else
196
+ str << write_nil
197
+ end
198
+ end
199
+ else
200
+ str << (cstr + val.values.map{|v| write(v, refs, crefs, trefs)}.join)
201
+ end
202
+
203
+ str
204
+ end
205
+
206
+
207
+ def write_float(val)
208
+ case val.infinite?
209
+ when 1
210
+ return [ BC_DOUBLE, Float::INFINITY ].pack('CG')
211
+ when -1
212
+ return [ BC_DOUBLE, -Float::INFINITY ].pack('CG')
213
+ else
214
+ return [ BC_DOUBLE, Float::NAN ].pack('CG') if val.nan?
215
+ return [ BC_DOUBLE_ZERO ].pack('C') if val.zero? # double zero
216
+ return [ BC_DOUBLE_ONE ].pack('C') if val == 1 # double one
217
+
218
+ ival = val.to_i
219
+ if ival == val
220
+ return [ BC_DOUBLE_BYTE, ival ].pack('Cc') if ival >= -0x80 && ival <= 0x7f # double octet
221
+ return [ BC_DOUBLE_SHORT, (ival >> 8), ival ].pack('Ccc') if ival >= -0x8000 && ival <= 0x7fff # double short
222
+ end
223
+
224
+ mval = val * 1000
225
+ if mval.finite?
226
+ mills = mval.to_i
227
+ if mills >= -0x80_000_000 && mills <= 0x7f_fff_fff && 0.001 * mills == val
228
+ return [ BC_DOUBLE_MILL, mills ].pack('Cl>') # double mill
229
+ end
230
+ end
231
+
232
+ [ BC_DOUBLE, val ].pack('CG') # double
233
+ end
234
+ end
235
+
236
+
237
+ def write_hash(hash, refs = {}, crefs = {}, trefs = {})
238
+ idx = refs[hash.object_id]
239
+ return write_ref(idx) if idx
240
+
241
+ refs[hash.object_id] = refs.size
242
+ str = [ BC_MAP_UNTYPED ].pack('C')
243
+ hash.each do |k, v|
244
+ str << write(k, refs, crefs, trefs)
245
+ str << write(v, refs, crefs, trefs)
246
+ end
247
+
248
+ str << [ BC_END ].pack('C')
249
+ end
250
+
251
+
252
+ def write_int(val)
253
+ if val >= INT_DIRECT_MIN && val <= INT_DIRECT_MAX # single octet integers
254
+ [ BC_INT_ZERO + val ].pack('c')
255
+ elsif val >= INT_BYTE_MIN && val <= INT_BYTE_MAX # two octet integers
256
+ [ BC_INT_BYTE_ZERO + (val >> 8), val ].pack('cc')
257
+ elsif val >= INT_SHORT_MIN && val <= INT_SHORT_MAX # three octet integers
258
+ [ BC_INT_SHORT_ZERO + (val >> 16), (val >> 8), val].pack('ccc')
259
+ elsif val >= -0x80_000_000 && val <= 0x7f_fff_fff # integer
260
+ [ BC_INT, val ].pack('Cl>')
261
+ else
262
+ [ BC_LONG, val ].pack('Cq>')
263
+ end
264
+ end
265
+
266
+
267
+ def write_long(val)
268
+ if val >= LONG_DIRECT_MIN && val <= LONG_DIRECT_MAX # single octet longs
269
+ [ BC_LONG_ZERO + val ].pack('c')
270
+ elsif val >= LONG_BYTE_MIN && val <= LONG_BYTE_MAX # two octet longs
271
+ [ BC_LONG_BYTE_ZERO + (val >> 8), val ].pack('cc')
272
+ elsif val >= LONG_SHORT_MIN && val <= LONG_SHORT_MAX # three octet longs
273
+ [ BC_LONG_SHORT_ZERO + (val >> 16), (val >> 8), val ].pack('ccc')
274
+ elsif val >= -0x80_000_000 && val <= 0x7f_fff_fff # four octet longs
275
+ [ BC_LONG_INT, val ].pack('Cl>')
276
+ else
277
+ [ BC_LONG, val ].pack('Cq>')
278
+ end
279
+ end
280
+
281
+
282
+ def write_nil
283
+ [ BC_NULL ].pack('C')
284
+ end
285
+
286
+
287
+ def write_object(object, refs = {}, crefs = {}, trefs = {})
288
+ return write_nil unless object
289
+
290
+ idx = refs[object.object_id]
291
+ return write_ref(idx) if idx
292
+
293
+ refs[object.object_id] = refs.size
294
+
295
+ klass = object.class.to_s
296
+ cref = crefs[klass]
297
+ if cref
298
+ cidx = cref.first
299
+ fields = cref.last
300
+ str = ''
301
+ else
302
+ fields = get_fields(object)
303
+ fstr = fields.map{|f| write_string(f) }.join
304
+ cidx = crefs.size
305
+ crefs[klass] = [cidx, fields]
306
+
307
+ str = [ BC_OBJECT_DEF ].pack('C') << write_string(klass) << write_int(fields.size) << fstr
308
+ end
309
+
310
+ if cidx <= OBJECT_DIRECT_MAX
311
+ cstr = [ BC_OBJECT_DIRECT + cidx ].pack('C')
312
+ else
313
+ cstr = [ BC_OBJECT ].pack('C') << write_int(cidx)
314
+ end
315
+
316
+ str << write_values(object, cstr, fields, refs, crefs, trefs)
317
+
318
+ str
319
+ end
320
+
321
+
322
+ def write_ref(val)
323
+ [ BC_REF ].pack('C') << write_int(val)
324
+ end
325
+
326
+
327
+ def write_struct_wrapper(val, refs, crefs, trefs)
328
+ return write_nil unless val.values
329
+
330
+ idx = refs[val.object_id]
331
+ return write_ref(idx) if idx
332
+
333
+ refs[val.object_id] = refs.size
334
+
335
+ write_array(val.values, refs, crefs, trefs)
336
+ end
337
+
338
+
339
+ def write_string(str)
340
+ chunks, i, len = '', 0, str.size
341
+ while len > 0x8000
342
+ chunks << [ BC_STRING_CHUNK, 0x8000 ].pack('Cn') << print_string(str[i...(i += 0x8000)])
343
+ len -= 0x8000
344
+ end
345
+
346
+ final = str[i..-1]
347
+ chunks << if len <= STRING_DIRECT_MAX
348
+ [ BC_STRING_DIRECT + len ].pack('C')
349
+ elsif len <= STRING_SHORT_MAX
350
+ [ BC_STRING_SHORT + (len >> 8), len ].pack('CC')
351
+ else
352
+ [ BC_STRING, len ].pack('Cn')
353
+ end
354
+
355
+ chunks << print_string(final)
356
+ end
357
+
358
+
359
+ def write_type_wrapped_array(arr, tstr, eletype, refs = {}, crefs = {}, trefs = {})
360
+ len = arr.size
361
+ return [ BC_LIST_DIRECT ].pack('C') << tstr if len == 0
362
+
363
+ if len <= LIST_DIRECT_MAX # [x70-77] type value*
364
+ str = [ BC_LIST_DIRECT + len ].pack('C') << tstr
365
+ else # 'V' type int value*
366
+ str = [ BC_LIST_FIXED ].pack('C') << tstr << write_int(len)
367
+ end
368
+
369
+ case eletype
370
+ when 'L'
371
+ arr.each do |ele|
372
+ str << write_long(Integer(ele))
373
+ end
374
+ when 'I'
375
+ arr.each do |ele|
376
+ str << write_int(Integer(ele))
377
+ end
378
+ when 'B'
379
+ arr.each do |ele|
380
+ str << write_binary(ele)
381
+ end
382
+ else
383
+ arr.each do |ele|
384
+ idx = refs[ele.object_id]
385
+ if idx
386
+ str << write_ref(idx)
387
+ else
388
+ refs[ele.object_id] = refs.size
389
+ str << write_type_wrapped_object(ele, tstr, refs, crefs, trefs)
390
+ end
391
+ end
392
+ end
393
+
394
+ str
395
+ end
396
+
397
+
398
+ def write_type_wrapped_object(object, tstr, refs = {}, crefs = {}, trefs = {})
399
+ return write_nil unless object
400
+
401
+ str = [ BC_MAP ].pack('C') << tstr
402
+
403
+ if object.is_a?(Hash)
404
+ object.each do |k, v|
405
+ str << write(k, refs, crefs, trefs)
406
+ str << write(v, refs, crefs, trefs)
407
+ end
408
+ elsif object.instance_variable_get(:@attributes).is_a?(Hash)
409
+ object.attributes.each do |k, v|
410
+ str << write(k, refs, crefs, trefs)
411
+ str << write(v, refs, crefs, trefs)
412
+ end
413
+ elsif object.is_a?(ClassWrapper)
414
+ object.fields.each_with_index do |f, i|
415
+ str << write(f, refs, crefs, trefs)
416
+ str << write(object.values[i], refs, crefs, trefs)
417
+ end
418
+ elsif object.is_a?(TypeWrapper)
419
+ object.object.each do |k, v|
420
+ str << write(k, refs, crefs, trefs)
421
+ str << write(v, refs, crefs, trefs)
422
+ end
423
+ else
424
+ object.instance_variables.each do |var|
425
+ str << write(var[1..-1], refs, crefs, trefs)
426
+ str << write(object.instance_variable_get(var), refs, crefs, trefs)
427
+ end
428
+ end
429
+
430
+ str << [ BC_END ].pack('C')
431
+ end
432
+
433
+
434
+ def write_type_wrapper(val, refs, crefs, trefs)
435
+ return write_nil unless val.object
436
+
437
+ idx = refs[val.object_id]
438
+ return write_ref(idx) if idx
439
+
440
+ refs[val.object_id] = refs.size
441
+
442
+ type = val.is_multi? ? ('[' << val.hessian_type) : val.hessian_type
443
+ if trefs.include?(type)
444
+ tstr = write_int(trefs[type])
445
+ else
446
+ trefs[type] = trefs.size
447
+ tstr = write_string(type)
448
+ end
449
+
450
+ if val.is_multi?
451
+ write_type_wrapped_array(val.object, tstr, val.hessian_type, refs, crefs, trefs)
452
+ else
453
+ case val.hessian_type
454
+ when 'L'
455
+ write_long(Integer(val.object))
456
+ when 'I'
457
+ write_int(Integer(val.object))
458
+ when 'B'
459
+ write_binary(val.object)
460
+ else
461
+ write_type_wrapped_object(val.object, tstr, refs, crefs, trefs)
462
+ end
463
+ end
464
+ end
465
+
466
+
467
+ private
468
+
469
+ def get_fields(object)
470
+ fields = if object.is_a?(Hash)
471
+ object.keys.map{|k| k.to_sym }
472
+ elsif object.instance_variable_get(:@attributes).is_a?(Hash)
473
+ object.attributes.keys.map{|k| k.to_sym }
474
+ else
475
+ object.instance_variables.map{|k| k[1..-1].to_sym }
476
+ end
477
+
478
+ raise "fields should not be empty: #{object.inspect}" if fields.empty?
479
+
480
+ fields
481
+ end
482
+
483
+
484
+ def write_values(object, cstr, fields, refs, crefs, trefs)
485
+ return write_nil unless object
486
+
487
+ vstr = if object.is_a?(Hash)
488
+ fields.map{|f| write(object[f] || object[f.to_s], refs, crefs, trefs) }.join
489
+ elsif object.instance_variable_get(:@attributes).is_a?(Hash)
490
+ fields.map{|f| write(object.attributes[f.to_s], refs, crefs, trefs) }.join
491
+ else
492
+ fields.map{|f| write(object.instance_variable_get(f.to_s.prepend('@')), refs, crefs, trefs) }.join
493
+ end
494
+
495
+ cstr + vstr
496
+ end
497
+
498
+ end
499
+ end