Muriel-Salvan/ruby-serial

View on GitHub
lib/ruby-serial/versions/1/serializer.rb

Summary

Maintainability
C
1 day
Test Coverage
module RubySerial

  class Serializer

    module Versions

      module Version1

        # Serializer version 1
        class Serializer

          # Get data
          #
          # Parameters::
          # * *obj* (_Object_): Object to pack
          # Result::
          # * _String_: The serialized data
          def pack_data(obj)
            # First look for shared objects
            # Set of objects parsed, per object_id
            @objs = {}
            # Set of shared object_id, with a boolean indicating whether they have been serialized already or not
            @shared_objs = {}
            gather_ids_rec(obj)
            @shared_objs_to_store = {}
            @shared_objs.each do |object_id, false_value|
              @shared_objs_to_store[object_id] = [
                @objs[object_id].class.name,
                get_msgpack_compatible_rec(@objs[object_id], false)
              ]
            end
            # puts "Found #{@shared_objs_to_store.size} shared objects to be stored"
            {
              'obj' => get_msgpack_compatible_rec(obj),
              'shared_objs' => @shared_objs_to_store
            }.to_msgpack.force_encoding(Encoding::BINARY)
          end

          private

          # Gather all shared objects in @shared_objs, tracking which ones have already been parsed in @objs.
          # Called recursively on all objects contained in obj.
          #
          # Parameters::
          # * *obj* (_Object_): The object to inspect
          def gather_ids_rec(obj)
            if !obj.is_a?(Fixnum) &&
               !obj.is_a?(Bignum) &&
               !obj.is_a?(Float) &&
               !obj.is_a?(Symbol) &&
               !obj.is_a?(Encoding) &&
               !obj.nil? &&
               obj != true &&
               obj != false
              # Check if obj id is shared
              if @objs[obj.object_id].nil?
                # First time we encounter this object
                @objs[obj.object_id] = obj
                # See other references
                if obj.is_a?(Array)
                  obj.each_with_index do |item, idx|
                    gather_ids_rec(item)
                  end
                elsif obj.is_a?(Hash)
                  obj.each do |key, value|
                    gather_ids_rec(value)
                    gather_ids_rec(key)
                  end
                else
                  # Handle other objects
                  obj.instance_vars_to_be_rubyserialized.each do |var_name, var|
                    gather_ids_rec(var)
                  end
                end
              else
                # This object is shared.
                @shared_objs[obj.object_id] = false
              end
            end
          end

          # Convert the object (and all its descendants) to be serializable using to_msgpack without loosing information.
          #
          # Parameters::
          # * *obj* (_Object_): Object to convert
          # * *check_shared* (_Boolean_): Do we check whether this object is shared? [default = true]
          # Result::
          # * _Object_: The object ready to be serialized
          def get_msgpack_compatible_rec(obj, check_shared = true)
            if obj.is_a?(Fixnum) ||
               obj.is_a?(Bignum) ||
               obj.is_a?(Float) ||
               obj.nil? ||
               obj == true ||
               obj == false
              return obj
            #
            # First handle objects that are not shareable
            #
            elsif obj.is_a?(Symbol)
              return {
                OBJECT_CLASSNAME_REFERENCE => CLASS_ID_SYMBOL,
                OBJECT_CONTENT_REFERENCE => obj.to_s
              }
            elsif obj.is_a?(Encoding)
              return {
                OBJECT_CLASSNAME_REFERENCE => CLASS_ID_ENCODING,
                OBJECT_CONTENT_REFERENCE => obj.name
              }
            #
            # Handle shared objects
            #
            elsif check_shared &&
                  !@shared_objs[obj.object_id].nil?
              # This object is shared: store its object_id only
              return {
                OBJECT_ID_REFERENCE => obj.object_id
              }
            #
            # Handle shareable objects
            #
            elsif obj.is_a?(Array)
              # First serialize its items
              return obj.map { |item| get_msgpack_compatible_rec(item) }
            elsif obj.is_a?(Hash)
              # First serialize its items
              hash_to_store = {}
              obj.each do |key, value|
                hash_to_store[get_msgpack_compatible_rec(key)] = get_msgpack_compatible_rec(value)
              end
              return hash_to_store
            elsif obj.is_a?(String)
              return obj
            elsif obj.is_a?(Range)
              return {
                OBJECT_CLASSNAME_REFERENCE => CLASS_ID_RANGE,
                OBJECT_CONTENT_REFERENCE => [get_msgpack_compatible_rec(obj.first), get_msgpack_compatible_rec(obj.last), obj.exclude_end?]
              }
            else
              # Handle other objects
              # If there is an ondump callback, call it
              obj.rubyserial_ondump if obj.respond_to?(:rubyserial_ondump)
              serialized_instance_vars = {}
              obj.instance_vars_to_be_rubyserialized.each do |var_name, value|
                serialized_instance_vars[var_name] = get_msgpack_compatible_rec(value)
              end
              return {
                OBJECT_CLASSNAME_REFERENCE => obj.class.name,
                OBJECT_CONTENT_REFERENCE => serialized_instance_vars
              }
            end
          end

        end

      end

    end

  end

end