ruby_sol 0.1

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