postmodern/ffi-msgpack

View on GitHub
lib/ffi/msgpack/msg_object.rb

Summary

Maintainability
A
2 hrs
Test Coverage
require 'ffi/msgpack/types'
require 'ffi/msgpack/msg_object_union'

module FFI
  module MsgPack
    class MsgObject < FFI::Struct

      layout :type, :msgpack_object_type,
             :values, MsgObjectUnion

      #
      # Initializes a new Msg Object.
      #
      # @param [FFI::Pointer] ptr
      #   An optional pointer to an existing Msg Object.
      #
      def initialize(ptr=nil)
        if ptr
          super(ptr)
        else
          super()
        end

        @object = nil
        @memory = nil
      end

      #
      # Creates a new `nil` Msg Object.
      #
      # @param [FFI::Pointer] ptr
      #   Optional pointer to create the Msg Object at.
      #
      # @return [MsgObject]
      #   A new Msg Object that represents the `nil` value.
      #
      def MsgObject.new_nil(ptr=nil)
        obj = MsgObject.new(ptr)
        obj.set_nil!
        return obj
      end

      #
      # Creates a new boolean Msg Object.
      #
      # @param [Boolean] value
      #   The boolean value.
      #
      # @param [FFI::Pointer] ptr
      #   Optional pointer to create the Msg Object at.
      #
      # @return [MsgObject]
      #   The new boolean Msg Object.
      #
      def MsgObject.new_boolean(value,ptr=nil)
        obj = MsgObject.new(ptr)
        obj.set_boolean!(value)
        return obj
      end

      #
      # Creates a new integer Msg Object.
      #
      # @param [Integer] value
      #   The integer value.
      #
      # @param [FFI::Pointer] ptr
      #   Optional pointer to create the Msg Object at.
      #
      # @return [MsgObject]
      #   The new integer Msg Object.
      #
      def MsgObject.new_integer(value,ptr=nil)
        obj = MsgObject.new(ptr)
        obj.set_integer!(value)
        return obj
      end

      #
      # Creates a new floating-point Msg Object.
      #
      # @param [Double, Float] value
      #   The floating-point value.
      #
      # @param [FFI::Pointer] ptr
      #   Optional pointer to create the Msg Object at.
      #
      # @return [MsgObject]
      #   The floating-point Msg Object.
      #
      def MsgObject.new_double(value,ptr=nil)
        obj = MsgObject.new(ptr)
        obj.set_double!(value)
        return obj
      end

      #
      # @see MsgObject.new_double
      #
      def MsgObject.new_float(value,ptr=nil)
        obj = MsgObject.new(ptr)
        obj.set_double!(value)
        return obj
      end

      #
      # Creates a new raw Msg Object.
      #
      # @param [String] data
      #   The raw data.
      #
      # @param [FFI::Pointer] ptr
      #   Optional pointer to create the Msg Object at.
      #
      # @return [MsgObject]
      #   The raw Msg Object.
      #
      def MsgObject.new_raw(data,ptr=nil)
        obj = MsgObject.new(ptr)
        obj.set_raw!(data)
        return obj
      end

      #
      # Creates a new array Msg Object.
      #
      # @param [Array] values
      #   The values for the array.
      #
      # @param [FFI::Pointer] ptr
      #   Optional pointer to create the Msg Object at.
      #
      # @return [MsgObject]
      #   The array Msg Object.
      #
      def MsgObject.new_array(values,ptr=nil)
        obj = MsgObject.new(ptr)
        obj.set_array!(values)
        return obj
      end

      #
      # Creates a new map Msg Object.
      #
      # @param [Hash] values
      #   The values for the map.
      #
      # @param [FFI::Pointer] ptr
      #   Optional pointer to create the Msg Object at.
      #
      # @return [MsgObject]
      #   The map Msg Object.
      #
      def MsgObject.new_map(values,ptr=nil)
        obj = MsgObject.new(ptr)
        obj.set_map!(values)
        return obj
      end

      #
      # Creates a Msg Object from a given Ruby object.
      #
      # @param [Object] value
      #   The Ruby object.
      #
      # @param [FFI::Pointer] ptr
      #   Optional pointer to create the Msg Object at.
      #
      # @return [MsgObject]
      #   The new Msg Object.
      #
      def MsgObject.new_object(value,ptr=nil)
        obj = MsgObject.new(ptr)
        obj.set!(value)
        return obj
      end

      #
      # Sets the Msg Objects value to nil.
      #
      # @since 0.2.0
      #
      def set_nil!
        @objects = nil
        @memory = nil

        self[:type] = :nil
      end

      #
      # Sets the Msg Objects value to a Boolean value.
      #
      # @param [Boolean] value
      #   The Boolean value.
      #
      # @since 0.2.0
      #
      def set_boolean!(value)
        @objects = nil
        @memory = nil

        self[:type] = :boolean
        self[:values][:boolean] = if value
                                   1
                                 else
                                   0
                                 end
      end

      #
      # Sets the Msg Objects value to an Integer value.
      #
      # @param [Integer] value
      #   The Integer value.
      #
      # @since 0.2.0
      #
      def set_integer!(value)
        @objects = nil
        @memory = nil

        if value < 0
          self[:type] = :negative_integer
          self[:values][:i64] = value
        else
          self[:type] = :positive_integer
          self[:values][:u64] = value
        end
      end

      #
      # Sets the Msg Objects value to a Floating Point value.
      #
      # @param [Float] value
      #   The Floating Point value.
      #
      # @since 0.2.0
      #
      def set_double!(value)
        @objects = nil
        @memory = nil

        self[:type] = :double
        self[:values][:dec] = value
      end

      alias set_float! set_double!

      #
      # Sets the Msg Objects value to a String value.
      #
      # @param [String, Symbol] value
      #   The String value.
      #
      # @since 0.2.0
      #
      def set_raw!(value)
        value = value.to_s

        @objects = nil
        @memory = FFI::MemoryPointer.new(:uchar, value.length) 
        @memory.write_bytes(value)

        self[:type] = :raw

        raw = self[:values][:raw]
        raw[:size] = value.length
        raw[:ptr] = @memory
      end

      #
      # Sets the Msg Objects value to an Array value.
      #
      # @param [Array] values
      #   The Array value.
      #
      # @since 0.2.0
      #
      def set_array!(values)
        @objects = []
        @memory = FFI::MemoryPointer.new(MsgObject,values.length)

        values.each_with_index do |value,index|
          @objects << MsgObject.new_object(value,@memory[index])
        end

        self[:type] = :array

        array = self[:values][:array]
        array[:size] = values.length
        array[:ptr] = @memory
      end

      #
      # Sets the Msg Objects value to a Map value.
      #
      # @param [Hash] values
      #   The Map value.
      #
      # @since 0.2.0
      #
      def set_map!(values)
        @objects = {}
        @memory = FFI::MemoryPointer.new(MsgKeyValue,values.length)

        values.each_with_index do |(key,value),index|
          pair = MsgKeyValue.new(@memory[index])

          key_obj = MsgObject.new_object(key,pair[:key].to_ptr)
          value_obj = MsgObject.new_object(value,pair[:value].to_ptr)

          @objects[key_obj] = value_obj
        end

        self[:type] = :map

        map = self[:values][:map]
        map[:size] = values.length
        map[:ptr] = @memory
      end

      #
      # Sets the contents of the Msg Object using a Ruby Object.
      #
      # @param [Object] value
      #   The Ruby object.
      #
      # @return [MsgObject]
      #   The new Msg Object.
      #
      # @raise [TypeError]
      #   The object was not a {Hash}, {Array}, {String}, {Symbol}, {Float},
      #   {Integer}, `true`, `false` or `nil`.
      #
      # @since 0.2.0
      #
      def set!(value)
        case value
        when Hash
          set_map!(value)
        when Array
          set_array!(value)
        when String, Symbol
          set_raw!(value)
        when Float
          set_double!(value)
        when Integer
          set_integer!(value)
        when TrueClass, FalseClass
          set_boolean!(value)
        when NilClass
          set_nil!
        else
          raise(TypeError,"ambigious object to create MsgObject from: #{value.inspect}")
        end

        return value
      end

      #
      # The type of the Msg Object.
      #
      # @return [Symbol]
      #   The type of the Msg Object.
      #
      def type
        self[:type]
      end

      #
      # The union of values for the Msg Object.
      #
      # @return [MsgObjectUnion]
      #   The union of values.
      #
      def values
        self[:values]
      end

      #
      # The value of the Msg Object.
      #
      # @return [Object]
      #   The value of the Msg Object, expressed as a Ruby primitive.
      #
      # @raise [RuntimeError]
      #   The type of the Msg Object was unknown.
      #
      def to_ruby
        case self[:type]
        when :nil
          nil
        when :boolean
          if self[:values][:boolean] == 0
            false
          else
            true
          end
        when :positive_integer
          self[:values][:u64]
        when :negative_integer
          self[:values][:i64]
        when :double
          self[:values][:dec]
        when :raw
          self[:values][:raw].to_s
        when :array
          self[:values][:array].to_a
        when :map
          self[:values][:map].to_hash
        else
          raise("unknown msgpack object type")
        end
      end

    end
  end
end