/
table_value_encoder.rb
123 lines (101 loc) · 3.32 KB
/
table_value_encoder.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# encoding: binary
require "amq/protocol/client"
require "amq/protocol/type_constants"
require "amq/protocol/table"
require "date"
require "amq/protocol/float_32bit"
module AMQ
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 AMQ::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 << AMQ::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