lib/rocketamf/pure/serializer.rb in mrpin-rocketamf-1.0.4 vs lib/rocketamf/pure/serializer.rb in mrpin-rocketamf-2.0.0

- old
+ new

@@ -1,368 +1,270 @@ -require 'rocketamf/pure/io_helpers' +require 'rocketamf/pure/helpers/io_helper_write' +require 'rocketamf/pure/helpers/object_cache' +require 'rocketamf/pure/helpers/string_cache' module RocketAMF module Pure - # Pure ruby serializer for AMF0 and AMF3 + # Pure ruby serializer for AMF3 class Serializer - attr_reader :stream, :version + # + # Modules + # + + private + include RocketAMF::Pure::IOHelperWrite + + # + # Properties + # + + public + attr_reader :stream + # Pass in the class mapper instance to use when serializing. This enables # better caching behavior in the class mapper and allows one to change # mappings between serialization attempts. - def initialize class_mapper + public + def initialize(class_mapper) @class_mapper = class_mapper - @stream = "" - @depth = 0 + @stream = '' + @depth = 0 end - # Serialize the given object using AMF0 or AMF3. Can be called from inside - # encode_amf, but make sure to pass in the proper version or it may not be - # possible to decode. Use the serializer version attribute for this. - def serialize version, obj - raise ArgumentError, "unsupported version #{version}" unless [0,3].include?(version) - @version = version - + # Serialize the given object using AMF3. Can be called from inside + # encode_amf. + def serialize(obj) # Initialize caches if @depth == 0 - if @version == 0 - @ref_cache = SerializerCache.new :object - else - @string_cache = SerializerCache.new :string - @object_cache = SerializerCache.new :object - @trait_cache = SerializerCache.new :string - end + @string_cache = StringCache.new + @object_cache = ObjectCache.new + @trait_cache = StringCache.new end @depth += 1 # Perform serialization - if @version == 0 - amf0_serialize(obj) - else - amf3_serialize(obj) - end + amf3_serialize(obj) # Cleanup @depth -= 1 if @depth == 0 - @ref_cache = nil + @ref_cache = nil @string_cache = nil @object_cache = nil - @trait_cache = nil + @trait_cache = nil end - return @stream + @stream end - # Helper for writing arrays inside encode_amf. It uses the current AMF - # version to write the array. - def write_array arr - if @version == 0 - amf0_write_array arr - else - amf3_write_array arr - end + # Helper for writing arrays inside encode_amf. + def write_array(value) + amf3_write_array value end - # Helper for writing objects inside encode_amf. It uses the current AMF - # version to write the object. If you pass in a property hash, it will use - # it rather than having the class mapper determine properties. For AMF3, - # you can also specify a traits hash, which can be used to reduce serialized + # Helper for writing objects inside encode_amf. If you pass in a property hash, it will use + # it rather than having the class mapper determine properties. + # You can also specify a traits hash, which can be used to reduce serialized # data size or serialize things as externalizable. - def write_object obj, props=nil, traits=nil - if @version == 0 - amf0_write_object obj, props - else - amf3_write_object obj, props, traits - end + def write_object(obj, props = nil, traits = nil) + amf3_write_object(obj, props, traits) end - private - include RocketAMF::Pure::WriteIOHelpers - def amf0_serialize obj - if @ref_cache[obj] != nil - amf0_write_reference @ref_cache[obj] - elsif obj.respond_to?(:encode_amf) - obj.encode_amf(self) - elsif obj.is_a?(NilClass) - amf0_write_null - elsif obj.is_a?(TrueClass) || obj.is_a?(FalseClass) - amf0_write_boolean obj - elsif obj.is_a?(Numeric) - amf0_write_number obj - elsif obj.is_a?(Symbol) || obj.is_a?(String) - amf0_write_string obj.to_s - elsif obj.is_a?(Time) - amf0_write_time obj - elsif obj.is_a?(Date) - amf0_write_date obj - elsif obj.is_a?(Array) - amf0_write_array obj - elsif obj.is_a?(Hash) ||obj.is_a?(Object) - amf0_write_object obj - end - end - - def amf0_write_null - @stream << AMF0_NULL_MARKER - end - - def amf0_write_boolean bool - @stream << AMF0_BOOLEAN_MARKER - @stream << pack_int8(bool ? 1 : 0) - end - - def amf0_write_number num - @stream << AMF0_NUMBER_MARKER - @stream << pack_double(num) - end - - def amf0_write_string str - str = str.encode("UTF-8").force_encoding("ASCII-8BIT") if str.respond_to?(:encode) - len = str.bytesize - if len > 2**16-1 - @stream << AMF0_LONG_STRING_MARKER - @stream << pack_word32_network(len) - else - @stream << AMF0_STRING_MARKER - @stream << pack_int16_network(len) - end - @stream << str - end - - def amf0_write_time time - @stream << AMF0_DATE_MARKER - - time = time.getutc # Dup and convert to UTC - milli = (time.to_f * 1000).to_i - @stream << pack_double(milli) - - @stream << pack_int16_network(0) # Time zone - end - - def amf0_write_date date - @stream << AMF0_DATE_MARKER - @stream << pack_double(date.strftime("%Q").to_i) - @stream << pack_int16_network(0) # Time zone - end - - def amf0_write_reference index - @stream << AMF0_REFERENCE_MARKER - @stream << pack_int16_network(index) - end - - def amf0_write_array array - @ref_cache.add_obj array - @stream << AMF0_STRICT_ARRAY_MARKER - @stream << pack_word32_network(array.length) - array.each do |elem| - amf0_serialize elem - end - end - - def amf0_write_object obj, props=nil - @ref_cache.add_obj obj - - props = @class_mapper.props_for_serialization obj if props.nil? - - # Is it a typed object? - class_name = @class_mapper.get_as_class_name obj - if class_name - class_name = class_name.encode("UTF-8").force_encoding("ASCII-8BIT") if class_name.respond_to?(:encode) - @stream << AMF0_TYPED_OBJECT_MARKER - @stream << pack_int16_network(class_name.bytesize) - @stream << class_name - else - @stream << AMF0_OBJECT_MARKER - end - - # Write prop list - props.sort.each do |key, value| # Sort keys before writing - key = key.encode("UTF-8").force_encoding("ASCII-8BIT") if key.respond_to?(:encode) - @stream << pack_int16_network(key.bytesize) - @stream << key - amf0_serialize value - end - - # Write end - @stream << pack_int16_network(0) - @stream << AMF0_OBJECT_END_MARKER - end - - def amf3_serialize obj + private + def amf3_serialize(obj) if obj.respond_to?(:encode_amf) obj.encode_amf(self) elsif obj.is_a?(NilClass) amf3_write_null elsif obj.is_a?(TrueClass) amf3_write_true elsif obj.is_a?(FalseClass) amf3_write_false elsif obj.is_a?(Numeric) - amf3_write_numeric obj + amf3_write_numeric(obj) elsif obj.is_a?(Symbol) || obj.is_a?(String) - amf3_write_string obj.to_s + amf3_write_string(obj.to_s) elsif obj.is_a?(Time) amf3_write_time obj elsif obj.is_a?(Date) - amf3_write_date obj + amf3_write_date(obj) elsif obj.is_a?(StringIO) - amf3_write_byte_array obj + amf3_write_byte_array(obj) elsif obj.is_a?(Array) - amf3_write_array obj + amf3_write_array(obj) elsif obj.is_a?(Hash) || obj.is_a?(Object) - amf3_write_object obj + amf3_write_object(obj) end end - def amf3_write_reference index + private + def amf3_write_reference(index) header = index << 1 # shift value left to leave a low bit of 0 @stream << pack_integer(header) end + private def amf3_write_null @stream << AMF3_NULL_MARKER end + private def amf3_write_true @stream << AMF3_TRUE_MARKER end + private def amf3_write_false @stream << AMF3_FALSE_MARKER end - def amf3_write_numeric num - if !num.integer? || num < MIN_INTEGER || num > MAX_INTEGER # Check valid range for 29 bits + private + def amf3_write_numeric(value) + if !value.integer? || value < MIN_INTEGER || value > MAX_INTEGER # Check valid range for 29 bits @stream << AMF3_DOUBLE_MARKER - @stream << pack_double(num) + @stream << pack_double(value) else @stream << AMF3_INTEGER_MARKER - @stream << pack_integer(num) + @stream << pack_integer(value) end end - def amf3_write_string str + private + def amf3_write_string(value) @stream << AMF3_STRING_MARKER - amf3_write_utf8_vr str + amf3_write_utf8_vr value end - def amf3_write_time time + private + def amf3_write_time(value) @stream << AMF3_DATE_MARKER - if @object_cache[time] != nil - amf3_write_reference @object_cache[time] + + if @object_cache[value] != nil + amf3_write_reference(@object_cache[value]) else # Cache time - @object_cache.add_obj time + @object_cache.add_obj(value) # Build AMF string - time = time.getutc # Dup and convert to UTC - milli = (time.to_f * 1000).to_i + value = value.getutc # Dup and convert to UTC + milli = (value.to_f * 1000).to_i @stream << AMF3_NULL_MARKER @stream << pack_double(milli) end end - def amf3_write_date date + private + def amf3_write_date(value) @stream << AMF3_DATE_MARKER - if @object_cache[date] != nil - amf3_write_reference @object_cache[date] + + if @object_cache[value] != nil + amf3_write_reference(@object_cache[value]) else # Cache date - @object_cache.add_obj date + @object_cache.add_obj(value) # Build AMF string @stream << AMF3_NULL_MARKER - @stream << pack_double(date.strftime("%Q").to_i) + @stream << pack_double(value.strftime('%Q').to_i) end end - def amf3_write_byte_array array + private + def amf3_write_byte_array(value) @stream << AMF3_BYTE_ARRAY_MARKER - if @object_cache[array] != nil - amf3_write_reference @object_cache[array] + + if @object_cache[value] != nil + amf3_write_reference(@object_cache[value]) else - @object_cache.add_obj array - str = array.string + @object_cache.add_obj(value) + str = value.string @stream << pack_integer(str.bytesize << 1 | 1) @stream << str end end - def amf3_write_array array + private + def amf3_write_array(value) # Is it an array collection? - is_ac = false - if array.respond_to?(:is_array_collection?) - is_ac = array.is_array_collection? + is_array_collection = false + + if value.respond_to?(:is_array_collection?) + is_array_collection = value.is_array_collection? else - is_ac = @class_mapper.use_array_collection + is_array_collection = @class_mapper.use_array_collection end # Write type marker - @stream << (is_ac ? AMF3_OBJECT_MARKER : AMF3_ARRAY_MARKER) + @stream << (is_array_collection ? AMF3_OBJECT_MARKER : AMF3_ARRAY_MARKER) # Write reference or cache array - if @object_cache[array] != nil - amf3_write_reference @object_cache[array] + if @object_cache[value] != nil + amf3_write_reference(@object_cache[value]) return else - @object_cache.add_obj array - @object_cache.add_obj nil if is_ac # The array collection source array + @object_cache.add_obj(value) + @object_cache.add_obj(nil) if is_array_collection # The array collection source array end # Write out traits and array marker if it's an array collection - if is_ac - class_name = "flex.messaging.io.ArrayCollection" + if is_array_collection + class_name = 'flex.messaging.io.ArrayCollection' + if @trait_cache[class_name] != nil @stream << pack_integer(@trait_cache[class_name] << 2 | 0x01) else - @trait_cache.add_obj class_name + @trait_cache.add_obj(class_name) @stream << "\a" # Externalizable, non-dynamic amf3_write_utf8_vr(class_name) end @stream << AMF3_ARRAY_MARKER end # Build AMF string for array - header = array.length << 1 # make room for a low bit of 1 + header = value.length << 1 # make room for a low bit of 1 header = header | 1 # set the low bit to 1 @stream << pack_integer(header) @stream << AMF3_CLOSE_DYNAMIC_ARRAY - array.each do |elem| + value.each do |elem| amf3_serialize elem end end - def amf3_write_object obj, props=nil, traits=nil + private + def amf3_write_object(obj, properties = nil, traits = nil) @stream << AMF3_OBJECT_MARKER # Caching... if @object_cache[obj] != nil - amf3_write_reference @object_cache[obj] + amf3_write_reference(@object_cache[obj]) return end - @object_cache.add_obj obj + @object_cache.add_obj(obj) + # Calculate traits if not given is_default = false if traits.nil? - traits = { - :class_name => @class_mapper.get_as_class_name(obj), - :members => [], - :externalizable => false, - :dynamic => true - } + traits = + { + class_name: @class_mapper.get_as_class_name(obj), + members: [], + externalizable: false, + dynamic: true + } is_default = true unless traits[:class_name] end - class_name = is_default ? "__default__" : traits[:class_name] + class_name = is_default ? '__default__' : traits[:class_name] + # Write out traits if class_name && @trait_cache[class_name] != nil @stream << pack_integer(@trait_cache[class_name] << 2 | 0x01) else - @trait_cache.add_obj class_name if class_name + @trait_cache.add_obj(class_name) if class_name # Write out trait header header = 0x03 # Not object ref and not trait ref header |= 0x02 << 2 if traits[:dynamic] header |= 0x01 << 2 if traits[:externalizable] @@ -375,100 +277,65 @@ else amf3_write_utf8_vr(class_name.to_s) end # Write out members - traits[:members].each {|m| amf3_write_utf8_vr(m)} + traits[:members].each { |m| amf3_write_utf8_vr(m) } end # If externalizable, take externalized data shortcut if traits[:externalizable] obj.write_external(self) return end # Extract properties if not given - props = @class_mapper.props_for_serialization(obj) if props.nil? + properties = @class_mapper.props_for_serialization(obj) if properties.nil? # Write out sealed properties traits[:members].each do |m| - amf3_serialize props[m] - props.delete(m) + amf3_serialize(properties[m]) + properties.delete(m) end # Write out dynamic properties if traits[:dynamic] # Write out dynamic properties - props.sort.each do |key, val| # Sort props until Ruby 1.9 becomes common - amf3_write_utf8_vr key.to_s - amf3_serialize val + properties.sort.each do |key, val| # Sort props until Ruby 1.9 becomes common + amf3_write_utf8_vr(key.to_s) + amf3_serialize(val) end # Write close @stream << AMF3_CLOSE_DYNAMIC_OBJECT end end - def amf3_write_utf8_vr str, encode=true - if str.respond_to?(:encode) + private + def amf3_write_utf8_vr(value, encode = true) + if value.respond_to?(:encode) if encode - str = str.encode("UTF-8") + value = value.encode('UTF-8') else - str = str.dup if str.frozen? + value = value.dup if value.frozen? end - str.force_encoding("ASCII-8BIT") + value.force_encoding('ASCII-8BIT') end - if str == '' + if value == '' @stream << AMF3_EMPTY_STRING - elsif @string_cache[str] != nil - amf3_write_reference @string_cache[str] + elsif @string_cache[value] != nil + amf3_write_reference(@string_cache[value]) else # Cache string - @string_cache.add_obj str + @string_cache.add_obj(value) # Build AMF string - @stream << pack_integer(str.bytesize << 1 | 1) - @stream << str + @stream << pack_integer(value.bytesize << 1 | 1) + @stream << value end end - end - class SerializerCache #:nodoc: - def self.new type - if type == :string - StringCache.new - elsif type == :object - ObjectCache.new - end - end + end # Serializer - class StringCache < Hash #:nodoc: - def initialize - @cache_index = 0 - end - - def add_obj str - self[str] = @cache_index - @cache_index += 1 - end - end - - class ObjectCache < Hash #:nodoc: - def initialize - @cache_index = 0 - @obj_references = [] - end - - def [] obj - super(obj.object_id) - end - - def add_obj obj - @obj_references << obj - self[obj.object_id] = @cache_index - @cache_index += 1 - end - end - end - end -end + end #Pure +end #RocketAMF