lib/gorgon_amq-protocol/lib/gorgon_amq/protocol/table_value_encoder.rb
# encoding: binary
require "gorgon_amq/protocol/client"
require "gorgon_amq/protocol/type_constants"
require "gorgon_amq/protocol/table"
require "date"
require "gorgon_amq/protocol/float_32bit"
module GorgonAMQ
module Protocol
class TableValueEncoder
#
# Behaviors
#
include TypeConstants
#
# API
#
def self.encode(value)
accumulator = String.new
case value
when String then
accumulator << TYPE_STRING
accumulator << [value.bytesize].pack(PACK_UINT32)
accumulator << value
when Symbol then
v = value.to_s
accumulator << TYPE_STRING
accumulator << [v.bytesize].pack(PACK_UINT32)
accumulator << v
when Integer then
accumulator << TYPE_INTEGER
accumulator << [value].pack(PACK_UINT32)
when GorgonAMQ::Protocol::Float32Bit then
accumulator << TYPE_32BIT_FLOAT
accumulator << [value.value].pack(PACK_32BIT_FLOAT)
when Float then
accumulator << TYPE_64BIT_FLOAT
accumulator << [value].pack(PACK_64BIT_FLOAT)
when true, false then
accumulator << TYPE_BOOLEAN
accumulator << (value ? BOOLEAN_TRUE : BOOLEAN_FALSE)
when Time then
accumulator << TYPE_TIME
accumulator << [value.to_i].pack(PACK_INT64).reverse # FIXME: there has to be a more efficient way
when nil then
accumulator << TYPE_VOID
when Array then
accumulator << TYPE_ARRAY
accumulator << [self.array_size(value)].pack(PACK_UINT32)
value.each { |v| accumulator << self.encode(v) }
when Hash then
accumulator << TYPE_HASH
accumulator << GorgonAMQ::Protocol::Table.encode(value)
else
# We don't want to require these libraries.
if defined?(BigDecimal) && value.is_a?(BigDecimal)
accumulator << TYPE_DECIMAL
if value.exponent < 0
decimals = -value.exponent
raw = (value * (decimals ** 10)).to_i
accumulator << [decimals + 1, raw].pack(PACK_UCHAR_UINT32) # somewhat like floating point
else
# per spec, the "decimals" octet is unsigned (!)
accumulator << [0, value.to_i].pack(PACK_UCHAR_UINT32)
end
else
raise ArgumentError.new("Unsupported value #{value.inspect} of type #{value.class.name}")
end # if
end # case
accumulator
end # self.encode(value)
def self.field_value_size(value)
# the type tag takes 1 byte
acc = 1
case value
when String then
acc += (value.bytesize + 4)
when Integer then
acc += 4
when Float then
acc += 8
when Time, DateTime then
acc += 8
when true, false then
acc += 1
when nil then
# nothing, type tag alone is enough
when Hash then
acc += (4 + Table.hash_size(value))
when Array then
acc += (4 + self.array_size(value))
end
acc
end # self.field_value_size(value)
def self.array_size(value)
acc = 0
value.each { |v| acc += self.field_value_size(v) }
acc
end # self.array_size(value)
end # TableValueEncoder
end # Protocol
end # AMQ