ruby_sol 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 (75) hide show
  1. checksums.yaml +7 -0
  2. data/README.rdoc +90 -0
  3. data/Rakefile +43 -0
  4. data/lib/ruby_sol.rb +113 -0
  5. data/lib/ruby_sol/class_mapping.rb +250 -0
  6. data/lib/ruby_sol/constants.rb +50 -0
  7. data/lib/ruby_sol/extensions.rb +22 -0
  8. data/lib/ruby_sol/pure/deserializer.rb +461 -0
  9. data/lib/ruby_sol/pure/io_helpers.rb +94 -0
  10. data/lib/ruby_sol/pure/serializer.rb +490 -0
  11. data/spec/class_mapping_spec.rb +110 -0
  12. data/spec/deserializer_spec.rb +449 -0
  13. data/spec/fixtures/objects/amf0-boolean.bin +1 -0
  14. data/spec/fixtures/objects/amf0-complex-encoded-string.bin +0 -0
  15. data/spec/fixtures/objects/amf0-date.bin +0 -0
  16. data/spec/fixtures/objects/amf0-ecma-ordinal-array.bin +0 -0
  17. data/spec/fixtures/objects/amf0-hash.bin +0 -0
  18. data/spec/fixtures/objects/amf0-null.bin +1 -0
  19. data/spec/fixtures/objects/amf0-number.bin +0 -0
  20. data/spec/fixtures/objects/amf0-object.bin +0 -0
  21. data/spec/fixtures/objects/amf0-ref-test.bin +0 -0
  22. data/spec/fixtures/objects/amf0-strict-array.bin +0 -0
  23. data/spec/fixtures/objects/amf0-string.bin +0 -0
  24. data/spec/fixtures/objects/amf0-time.bin +0 -0
  25. data/spec/fixtures/objects/amf0-typed-object.bin +0 -0
  26. data/spec/fixtures/objects/amf0-undefined.bin +1 -0
  27. data/spec/fixtures/objects/amf0-untyped-object.bin +0 -0
  28. data/spec/fixtures/objects/amf0-xml-doc.bin +0 -0
  29. data/spec/fixtures/objects/amf3-0.bin +0 -0
  30. data/spec/fixtures/objects/amf3-array-collection.bin +2 -0
  31. data/spec/fixtures/objects/amf3-array-ref.bin +1 -0
  32. data/spec/fixtures/objects/amf3-associative-array.bin +1 -0
  33. data/spec/fixtures/objects/amf3-bigNum.bin +0 -0
  34. data/spec/fixtures/objects/amf3-byte-array-ref.bin +1 -0
  35. data/spec/fixtures/objects/amf3-byte-array.bin +0 -0
  36. data/spec/fixtures/objects/amf3-complex-array-collection.bin +6 -0
  37. data/spec/fixtures/objects/amf3-complex-encoded-string-array.bin +1 -0
  38. data/spec/fixtures/objects/amf3-date-ref.bin +0 -0
  39. data/spec/fixtures/objects/amf3-date.bin +0 -0
  40. data/spec/fixtures/objects/amf3-dictionary.bin +0 -0
  41. data/spec/fixtures/objects/amf3-dynamic-object.bin +2 -0
  42. data/spec/fixtures/objects/amf3-empty-array-ref.bin +1 -0
  43. data/spec/fixtures/objects/amf3-empty-array.bin +1 -0
  44. data/spec/fixtures/objects/amf3-empty-dictionary.bin +0 -0
  45. data/spec/fixtures/objects/amf3-empty-string-ref.bin +1 -0
  46. data/spec/fixtures/objects/amf3-encoded-string-ref.bin +0 -0
  47. data/spec/fixtures/objects/amf3-externalizable.bin +0 -0
  48. data/spec/fixtures/objects/amf3-false.bin +1 -0
  49. data/spec/fixtures/objects/amf3-float.bin +0 -0
  50. data/spec/fixtures/objects/amf3-graph-member.bin +0 -0
  51. data/spec/fixtures/objects/amf3-hash.bin +2 -0
  52. data/spec/fixtures/objects/amf3-large-max.bin +0 -0
  53. data/spec/fixtures/objects/amf3-large-min.bin +0 -0
  54. data/spec/fixtures/objects/amf3-max.bin +1 -0
  55. data/spec/fixtures/objects/amf3-min.bin +0 -0
  56. data/spec/fixtures/objects/amf3-mixed-array.bin +10 -0
  57. data/spec/fixtures/objects/amf3-null.bin +1 -0
  58. data/spec/fixtures/objects/amf3-object-ref.bin +0 -0
  59. data/spec/fixtures/objects/amf3-primitive-array.bin +1 -0
  60. data/spec/fixtures/objects/amf3-string-ref.bin +0 -0
  61. data/spec/fixtures/objects/amf3-string.bin +1 -0
  62. data/spec/fixtures/objects/amf3-symbol.bin +1 -0
  63. data/spec/fixtures/objects/amf3-trait-ref.bin +3 -0
  64. data/spec/fixtures/objects/amf3-true.bin +1 -0
  65. data/spec/fixtures/objects/amf3-typed-object.bin +2 -0
  66. data/spec/fixtures/objects/amf3-vector-double.bin +0 -0
  67. data/spec/fixtures/objects/amf3-vector-int.bin +0 -0
  68. data/spec/fixtures/objects/amf3-vector-object.bin +0 -0
  69. data/spec/fixtures/objects/amf3-vector-uint.bin +0 -0
  70. data/spec/fixtures/objects/amf3-xml-doc.bin +1 -0
  71. data/spec/fixtures/objects/amf3-xml-ref.bin +1 -0
  72. data/spec/fixtures/objects/amf3-xml.bin +1 -0
  73. data/spec/serializer_spec.rb +503 -0
  74. data/spec/spec_helper.rb +59 -0
  75. metadata +128 -0
@@ -0,0 +1,94 @@
1
+ module RubySol
2
+ module Pure
3
+ module ReadIOHelpers #:nodoc:
4
+ def read_int8 source
5
+ source.read(1).unpack('c').first
6
+ end
7
+
8
+ def read_word8 source
9
+ source.read(1).unpack('C').first
10
+ end
11
+
12
+ def read_double source
13
+ source.read(8).unpack('G').first
14
+ end
15
+
16
+ def read_word16_network source
17
+ source.read(2).unpack('n').first
18
+ end
19
+
20
+ def read_int16_network source
21
+ str = source.read(2)
22
+ str.reverse! if byte_order_little? # swap bytes as native=little (and we want network)
23
+ str.unpack('s').first
24
+ end
25
+
26
+ def read_word32_network source
27
+ source.read(4).unpack('N').first
28
+ end
29
+
30
+ def byte_order
31
+ if [0x12345678].pack("L") == "\x12\x34\x56\x78"
32
+ :BigEndian
33
+ else
34
+ :LittleEndian
35
+ end
36
+ end
37
+
38
+ def byte_order_little?
39
+ (byte_order == :LittleEndian) ? true : false;
40
+ end
41
+ end
42
+
43
+ module WriteIOHelpers #:nodoc:
44
+ def pack_integer(integer)
45
+ integer = integer & 0x1fffffff
46
+ if(integer < 0x80)
47
+ [integer].pack('c')
48
+ elsif(integer < 0x4000)
49
+ [integer >> 7 & 0x7f | 0x80].pack('c')+
50
+ [integer & 0x7f].pack('c')
51
+ elsif(integer < 0x200000)
52
+ [integer >> 14 & 0x7f | 0x80].pack('c') +
53
+ [integer >> 7 & 0x7f | 0x80].pack('c') +
54
+ [integer & 0x7f].pack('c')
55
+ else
56
+ [integer >> 22 & 0x7f | 0x80].pack('c')+
57
+ [integer >> 15 & 0x7f | 0x80].pack('c')+
58
+ [integer >> 8 & 0x7f | 0x80].pack('c')+
59
+ [integer & 0xff].pack('c')
60
+ end
61
+ end
62
+
63
+ def pack_double(double)
64
+ [double].pack('G')
65
+ end
66
+
67
+ def pack_int8(val)
68
+ [val].pack('c')
69
+ end
70
+
71
+ def pack_int16_network(val)
72
+ [val].pack('n')
73
+ end
74
+
75
+ def pack_word32_network(val)
76
+ str = [val].pack('L')
77
+ str.reverse! if byte_order_little? # swap bytes as native=little (and we want network)
78
+ str
79
+ end
80
+
81
+ def byte_order
82
+ if [0x12345678].pack("L") == "\x12\x34\x56\x78"
83
+ :BigEndian
84
+ else
85
+ :LittleEndian
86
+ end
87
+ end
88
+
89
+ def byte_order_little?
90
+ (byte_order == :LittleEndian) ? true : false;
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,490 @@
1
+ require 'ruby_sol/pure/io_helpers'
2
+
3
+ module RubySol
4
+ module Pure
5
+ # Pure ruby serializer for AMF0 and AMF3
6
+ class Serializer
7
+ attr_reader :stream, :version
8
+
9
+ # Pass in the class mapper instance to use when serializing. This enables
10
+ # better caching behavior in the class mapper and allows one to change
11
+ # mappings between serialization attempts.
12
+ def initialize class_mapper
13
+ @class_mapper = class_mapper
14
+ @stream = ""
15
+ @depth = 0
16
+ reset_caches()
17
+ end
18
+
19
+ def reset_caches
20
+ @ref_cache = SerializerCache.new :object
21
+ @string_cache = SerializerCache.new :string
22
+ @object_cache = SerializerCache.new :object
23
+ @trait_cache = SerializerCache.new :string
24
+ end
25
+
26
+ # Serialize the given object using AMF0 or AMF3. Can be called from inside
27
+ # encode_amf, but make sure to pass in the proper version or it may not be
28
+ # possible to decode. Use the serializer version attribute for this.
29
+ def serialize version, obj
30
+ raise ArgumentError, "unsupported version #{version}" unless [0,3].include?(version)
31
+ @version = version
32
+
33
+ # Initialize caches
34
+ if @depth == 0
35
+ if @version == 0
36
+ @ref_cache = SerializerCache.new :object
37
+ else
38
+ @string_cache = SerializerCache.new :string
39
+ @object_cache = SerializerCache.new :object
40
+ @trait_cache = SerializerCache.new :string
41
+ end
42
+ end
43
+ @depth += 1
44
+
45
+ # Perform serialization
46
+ if @version == 0
47
+ amf0_serialize(obj)
48
+ else
49
+ amf3_serialize(obj)
50
+ end
51
+
52
+ # Cleanup
53
+ @depth -= 1
54
+ if @depth == 0
55
+ @ref_cache = nil
56
+ @string_cache = nil
57
+ @object_cache = nil
58
+ @trait_cache = nil
59
+ end
60
+
61
+ return @stream
62
+ end
63
+
64
+ # Helper for writing arrays inside encode_amf. It uses the current AMF
65
+ # version to write the array.
66
+ def write_array arr
67
+ if @version == 0
68
+ amf0_write_array arr
69
+ else
70
+ amf3_write_array arr
71
+ end
72
+ end
73
+
74
+ # Helper for writing objects inside encode_amf. It uses the current AMF
75
+ # version to write the object. If you pass in a property hash, it will use
76
+ # it rather than having the class mapper determine properties. For AMF3,
77
+ # you can also specify a traits hash, which can be used to reduce serialized
78
+ # data size or serialize things as externalizable.
79
+ def write_object obj, props=nil, traits=nil
80
+ if @version == 0
81
+ amf0_write_object obj, props
82
+ else
83
+ amf3_write_object obj, props, traits
84
+ end
85
+ end
86
+
87
+ # private
88
+ include RubySol::Pure::WriteIOHelpers
89
+
90
+ def amf0_serialize obj
91
+ if @ref_cache[obj] != nil
92
+ amf0_write_reference @ref_cache[obj]
93
+ elsif obj.respond_to?(:encode_amf)
94
+ obj.encode_amf(self)
95
+ elsif obj.is_a?(NilClass)
96
+ amf0_write_null
97
+ elsif obj.is_a?(TrueClass) || obj.is_a?(FalseClass)
98
+ amf0_write_boolean obj
99
+ elsif obj.is_a?(Numeric)
100
+ amf0_write_number obj
101
+ elsif obj.is_a?(Symbol) || obj.is_a?(String)
102
+ amf0_write_string obj.to_s
103
+ elsif obj.is_a?(Time)
104
+ amf0_write_time obj
105
+ elsif obj.is_a?(Date)
106
+ amf0_write_date obj
107
+ elsif obj.is_a?(Array)
108
+ amf0_write_array obj
109
+ elsif obj.is_a?(Hash) ||obj.is_a?(Object)
110
+ amf0_write_object obj
111
+ end
112
+ end
113
+
114
+ def amf0_write_null
115
+ @stream << AMF0_NULL_MARKER
116
+ end
117
+
118
+ def amf0_write_boolean bool
119
+ @stream << AMF0_BOOLEAN_MARKER
120
+ @stream << pack_int8(bool ? 1 : 0)
121
+ end
122
+
123
+ def amf0_write_number num
124
+ @stream << AMF0_NUMBER_MARKER
125
+ @stream << pack_double(num)
126
+ end
127
+
128
+ def amf0_write_string str
129
+ str = str.encode("UTF-8").force_encoding("ASCII-8BIT") if str.respond_to?(:encode)
130
+ len = str.bytesize
131
+ if len > 2**16-1
132
+ @stream << AMF0_LONG_STRING_MARKER
133
+ @stream << pack_word32_network(len)
134
+ else
135
+ @stream << AMF0_STRING_MARKER
136
+ @stream << pack_int16_network(len)
137
+ end
138
+ @stream << str
139
+ end
140
+
141
+ def amf0_write_string_wo_marker str
142
+ str = str.encode("UTF-8").force_encoding("ASCII-8BIT") if str.respond_to?(:encode)
143
+ len = str.bytesize
144
+ throw SOLError, 'too long string' if len > 2**16-1
145
+ @stream << pack_int16_network(len)
146
+ @stream << str
147
+ end
148
+
149
+ def amf0_write_time time
150
+ @stream << AMF0_DATE_MARKER
151
+
152
+ time = time.getutc # Dup and convert to UTC
153
+ milli = (time.to_f * 1000).to_i
154
+ @stream << pack_double(milli)
155
+
156
+ @stream << pack_int16_network(0) # Time zone
157
+ end
158
+
159
+ def amf0_write_date date
160
+ @stream << AMF0_DATE_MARKER
161
+ @stream << pack_double(date.strftime("%Q").to_i)
162
+ @stream << pack_int16_network(0) # Time zone
163
+ end
164
+
165
+ def amf0_write_reference index
166
+ @stream << AMF0_REFERENCE_MARKER
167
+ @stream << pack_int16_network(index)
168
+ end
169
+
170
+ def amf0_write_array array
171
+ @ref_cache.add_obj array
172
+ @stream << AMF0_STRICT_ARRAY_MARKER
173
+ @stream << pack_word32_network(array.length)
174
+ array.each do |elem|
175
+ amf0_serialize elem
176
+ end
177
+ end
178
+
179
+ def amf0_write_object obj, props=nil
180
+ @ref_cache.add_obj obj
181
+
182
+ props = @class_mapper.props_for_serialization obj if props.nil?
183
+
184
+ # Is it a typed object?
185
+ class_name = @class_mapper.get_as_class_name obj
186
+ if class_name
187
+ class_name = class_name.encode("UTF-8").force_encoding("ASCII-8BIT") if class_name.respond_to?(:encode)
188
+ @stream << AMF0_TYPED_OBJECT_MARKER
189
+ @stream << pack_int16_network(class_name.bytesize)
190
+ @stream << class_name
191
+ else
192
+ @stream << AMF0_OBJECT_MARKER
193
+ end
194
+
195
+ # Write prop list
196
+ props.sort.each do |key, value| # Sort keys before writing
197
+ key = key.encode("UTF-8").force_encoding("ASCII-8BIT") if key.respond_to?(:encode)
198
+ @stream << pack_int16_network(key.bytesize)
199
+ @stream << key
200
+ amf0_serialize value
201
+ end
202
+
203
+ # Write end
204
+ @stream << pack_int16_network(0)
205
+ @stream << AMF0_OBJECT_END_MARKER
206
+ end
207
+
208
+ def amf3_serialize obj
209
+ if obj.respond_to?(:encode_amf)
210
+ obj.encode_amf(self)
211
+ elsif obj.is_a?(NilClass)
212
+ amf3_write_null
213
+ elsif obj.is_a?(TrueClass)
214
+ amf3_write_true
215
+ elsif obj.is_a?(FalseClass)
216
+ amf3_write_false
217
+ elsif obj.is_a?(Numeric)
218
+ amf3_write_numeric obj
219
+ elsif obj.is_a?(Symbol) || obj.is_a?(String)
220
+ amf3_write_string obj.to_s
221
+ elsif obj.is_a?(Time)
222
+ amf3_write_time obj
223
+ elsif obj.is_a?(Date)
224
+ amf3_write_date obj
225
+ elsif obj.is_a?(StringIO)
226
+ amf3_write_byte_array obj
227
+ elsif obj.is_a?(Array)
228
+ amf3_write_array obj
229
+ elsif obj.is_a?(Hash) || obj.is_a?(Object)
230
+ amf3_write_object obj
231
+ end
232
+ end
233
+
234
+ def amf3_write_reference index
235
+ header = index << 1 # shift value left to leave a low bit of 0
236
+ @stream << pack_integer(header)
237
+ end
238
+
239
+ def amf3_write_null
240
+ @stream << AMF3_NULL_MARKER
241
+ end
242
+
243
+ def amf3_write_true
244
+ @stream << AMF3_TRUE_MARKER
245
+ end
246
+
247
+ def amf3_write_false
248
+ @stream << AMF3_FALSE_MARKER
249
+ end
250
+
251
+ def amf3_write_numeric num
252
+ if !num.integer? || num < MIN_INTEGER || num > MAX_INTEGER # Check valid range for 29 bits
253
+ @stream << AMF3_DOUBLE_MARKER
254
+ @stream << pack_double(num)
255
+ else
256
+ @stream << AMF3_INTEGER_MARKER
257
+ @stream << pack_integer(num)
258
+ end
259
+ end
260
+
261
+ def amf3_write_string str
262
+ @stream << AMF3_STRING_MARKER
263
+ amf3_write_utf8_vr str
264
+ end
265
+
266
+ def amf3_write_time time
267
+ @stream << AMF3_DATE_MARKER
268
+ if @object_cache[time] != nil
269
+ amf3_write_reference @object_cache[time]
270
+ else
271
+ # Cache time
272
+ @object_cache.add_obj time
273
+
274
+ # Build AMF string
275
+ time = time.getutc # Dup and convert to UTC
276
+ milli = (time.to_f * 1000).to_i
277
+ @stream << AMF3_NULL_MARKER
278
+ @stream << pack_double(milli)
279
+ end
280
+ end
281
+
282
+ def amf3_write_date date
283
+ @stream << AMF3_DATE_MARKER
284
+ if @object_cache[date] != nil
285
+ amf3_write_reference @object_cache[date]
286
+ else
287
+ # Cache date
288
+ @object_cache.add_obj date
289
+
290
+ # Build AMF string
291
+ @stream << AMF3_NULL_MARKER
292
+ @stream << pack_double(date.strftime("%Q").to_i)
293
+ end
294
+ end
295
+
296
+ def amf3_write_byte_array array
297
+ @stream << AMF3_BYTE_ARRAY_MARKER
298
+ if @object_cache[array] != nil
299
+ amf3_write_reference @object_cache[array]
300
+ else
301
+ @object_cache.add_obj array
302
+ str = array.string
303
+ @stream << pack_integer(str.bytesize << 1 | 1)
304
+ @stream << str
305
+ end
306
+ end
307
+
308
+ def amf3_write_array array
309
+ # Is it an array collection?
310
+ is_ac = false
311
+ if array.respond_to?(:is_array_collection?)
312
+ is_ac = array.is_array_collection?
313
+ else
314
+ is_ac = @class_mapper.use_array_collection
315
+ end
316
+
317
+ # Write type marker
318
+ @stream << (is_ac ? AMF3_OBJECT_MARKER : AMF3_ARRAY_MARKER)
319
+
320
+ # Write reference or cache array
321
+ if @object_cache[array] != nil
322
+ amf3_write_reference @object_cache[array]
323
+ return
324
+ else
325
+ @object_cache.add_obj array
326
+ @object_cache.add_obj nil if is_ac # The array collection source array
327
+ end
328
+
329
+ # Write out traits and array marker if it's an array collection
330
+ if is_ac
331
+ class_name = "flex.messaging.io.ArrayCollection"
332
+ if @trait_cache[class_name] != nil
333
+ @stream << pack_integer(@trait_cache[class_name] << 2 | 0x01)
334
+ else
335
+ @trait_cache.add_obj class_name
336
+ @stream << "\a" # Externalizable, non-dynamic
337
+ amf3_write_utf8_vr(class_name)
338
+ end
339
+ @stream << AMF3_ARRAY_MARKER
340
+ end
341
+
342
+ # Build AMF string for array
343
+ header = array.length << 1 # make room for a low bit of 1
344
+ header = header | 1 # set the low bit to 1
345
+ @stream << pack_integer(header)
346
+ @stream << AMF3_CLOSE_DYNAMIC_ARRAY
347
+ array.each do |elem|
348
+ amf3_serialize elem
349
+ end
350
+ end
351
+
352
+ def amf3_write_object obj, props=nil, traits=nil
353
+ @stream << AMF3_OBJECT_MARKER
354
+
355
+ # Caching...
356
+ if @object_cache[obj] != nil
357
+ amf3_write_reference @object_cache[obj]
358
+ return
359
+ end
360
+ @object_cache.add_obj obj
361
+
362
+ # Calculate traits if not given
363
+ is_default = false
364
+ if traits.nil?
365
+ traits = {
366
+ :class_name => @class_mapper.get_as_class_name(obj),
367
+ :members => [],
368
+ :externalizable => false,
369
+ :dynamic => true
370
+ }
371
+ is_default = true unless traits[:class_name]
372
+ end
373
+ class_name = is_default ? "__default__" : traits[:class_name]
374
+
375
+ # Write out traits
376
+ if (class_name && @trait_cache[class_name] != nil)
377
+ @stream << pack_integer(@trait_cache[class_name] << 2 | 0x01)
378
+ else
379
+ @trait_cache.add_obj class_name if class_name
380
+
381
+ # Write out trait header
382
+ header = 0x03 # Not object ref and not trait ref
383
+ header |= 0x02 << 2 if traits[:dynamic]
384
+ header |= 0x01 << 2 if traits[:externalizable]
385
+ header |= traits[:members].length << 4
386
+ @stream << pack_integer(header)
387
+
388
+ # Write out class name
389
+ if class_name == "__default__"
390
+ amf3_write_utf8_vr("")
391
+ else
392
+ amf3_write_utf8_vr(class_name.to_s)
393
+ end
394
+
395
+ # Write out members
396
+ traits[:members].each {|m| amf3_write_utf8_vr(m)}
397
+ end
398
+
399
+ # If externalizable, take externalized data shortcut
400
+ if traits[:externalizable]
401
+ obj.write_external(self)
402
+ return
403
+ end
404
+
405
+ # Extract properties if not given
406
+ props = @class_mapper.props_for_serialization(obj) if props.nil?
407
+
408
+ # Write out sealed properties
409
+ traits[:members].each do |m|
410
+ amf3_serialize props[m]
411
+ props.delete(m)
412
+ end
413
+
414
+ # Write out dynamic properties
415
+ if traits[:dynamic]
416
+ # Write out dynamic properties
417
+ props.sort.each do |key, val| # Sort props until Ruby 1.9 becomes common
418
+ amf3_write_utf8_vr key.to_s
419
+ amf3_serialize val
420
+ end
421
+
422
+ # Write close
423
+ @stream << AMF3_CLOSE_DYNAMIC_OBJECT
424
+ end
425
+ end
426
+
427
+ def amf3_write_utf8_vr str, encode=true
428
+ if str.respond_to?(:encode)
429
+ if encode
430
+ str = str.encode("UTF-8")
431
+ else
432
+ str = str.dup if str.frozen?
433
+ end
434
+ str.force_encoding("ASCII-8BIT")
435
+ end
436
+
437
+ if str == ''
438
+ @stream << AMF3_EMPTY_STRING
439
+ elsif @string_cache[str] != nil
440
+ amf3_write_reference @string_cache[str]
441
+ else
442
+ # Cache string
443
+ @string_cache.add_obj str
444
+
445
+ # Build AMF string
446
+ @stream << pack_integer(str.bytesize << 1 | 1)
447
+ @stream << str
448
+ end
449
+ end
450
+ end
451
+
452
+ class SerializerCache #:nodoc:
453
+ def self.new type
454
+ if type == :string
455
+ StringCache.new
456
+ elsif type == :object
457
+ ObjectCache.new
458
+ end
459
+ end
460
+
461
+ class StringCache < Hash #:nodoc:
462
+ def initialize
463
+ @cache_index = 0
464
+ end
465
+
466
+ def add_obj str
467
+ self[str] = @cache_index
468
+ @cache_index += 1
469
+ end
470
+ end
471
+
472
+ class ObjectCache < Hash #:nodoc:
473
+ def initialize
474
+ @cache_index = 0
475
+ @obj_references = []
476
+ end
477
+
478
+ def [] obj
479
+ super(obj.object_id)
480
+ end
481
+
482
+ def add_obj obj
483
+ @obj_references << obj
484
+ self[obj.object_id] = @cache_index
485
+ @cache_index += 1
486
+ end
487
+ end
488
+ end
489
+ end
490
+ end