hessian2 1.1.1 → 2.0.1

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