summaryrefslogtreecommitdiff
path: root/lib/rb/lib/thrift/struct_union.rb
blob: 4e0afcfe68c5916266cacc9f163e5f5e61e72e7c (plain)
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# 
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
# 
#   http://www.apache.org/licenses/LICENSE-2.0
# 
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
require 'set'

module Thrift
  module Struct_Union
    def name_to_id(name)
      names_to_ids = self.class.instance_variable_get(:@names_to_ids)
      unless names_to_ids
        names_to_ids = {}
        struct_fields.each do |fid, field_def|
          names_to_ids[field_def[:name]] = fid
        end
        self.class.instance_variable_set(:@names_to_ids, names_to_ids)
      end
      names_to_ids[name]
    end

    def sorted_field_ids
      sorted_field_ids = self.class.instance_variable_get(:@sorted_field_ids)
      unless sorted_field_ids
        sorted_field_ids = struct_fields.keys.sort
        self.class.instance_variable_set(:@sorted_field_ids, sorted_field_ids)
      end
      sorted_field_ids
    end

    def each_field
      sorted_field_ids.each do |fid|
        data = struct_fields[fid]
        yield fid, data
      end
    end

    def read_field(iprot, field = {})
      case field[:type]
      when Types::STRUCT
        value = field[:class].new
        value.read(iprot)
      when Types::MAP
        key_type, val_type, size = iprot.read_map_begin
        # Skip the map contents if the declared key or value types don't match the expected ones.
        if (size != 0 && (key_type != field[:key][:type] || val_type != field[:value][:type]))
          size.times do
            iprot.skip(key_type)
            iprot.skip(val_type)
          end
          value = nil
        else
          value = {}
          size.times do
            k = read_field(iprot, field_info(field[:key]))
            v = read_field(iprot, field_info(field[:value]))
            value[k] = v
          end
        end
        iprot.read_map_end
      when Types::LIST
        e_type, size = iprot.read_list_begin
        # Skip the list contents if the declared element type doesn't match the expected one.
        if (e_type != field[:element][:type])
          size.times do
            iprot.skip(e_type)
          end
          value = nil
        else
          value = Array.new(size) do |n|
            read_field(iprot, field_info(field[:element]))
          end
        end
        iprot.read_list_end
      when Types::SET
        e_type, size = iprot.read_set_begin
        # Skip the set contents if the declared element type doesn't match the expected one.
        if (e_type != field[:element][:type])
          size.times do
            iprot.skip(e_type)
          end
        else
          value = Set.new
          size.times do
            element = read_field(iprot, field_info(field[:element]))
            value << element
          end
        end
        iprot.read_set_end
      else
        value = iprot.read_type(field[:type])
      end
      value
    end

    def write_data(oprot, value, field)
      if is_container? field[:type]
        write_container(oprot, value, field)
      else
        oprot.write_type(field[:type], value)
      end
    end

    def write_container(oprot, value, field = {})
      case field[:type]
      when Types::MAP
        oprot.write_map_begin(field[:key][:type], field[:value][:type], value.size)
        value.each do |k, v|
          write_data(oprot, k, field[:key])
          write_data(oprot, v, field[:value])
        end
        oprot.write_map_end
      when Types::LIST
        oprot.write_list_begin(field[:element][:type], value.size)
        value.each do |elem|
          write_data(oprot, elem, field[:element])
        end
        oprot.write_list_end
      when Types::SET
        oprot.write_set_begin(field[:element][:type], value.size)
        value.each do |v,| # the , is to preserve compatibility with the old Hash-style sets
          write_data(oprot, v, field[:element])
        end
        oprot.write_set_end
      else
        raise "Not a container type: #{field[:type]}"
      end
    end

    CONTAINER_TYPES = []
    CONTAINER_TYPES[Types::LIST] = true
    CONTAINER_TYPES[Types::MAP] = true
    CONTAINER_TYPES[Types::SET] = true
    def is_container?(type)
      CONTAINER_TYPES[type]
    end

    def field_info(field)
      { :type => field[:type],
        :class => field[:class],
        :key => field[:key],
        :value => field[:value],
        :element => field[:element] }
    end

    def inspect_field(value, field_info)
      if enum_class = field_info[:enum_class]
        "#{enum_class.const_get(:VALUE_MAP)[value]} (#{value})"
      elsif value.is_a? Hash 
        if field_info[:type] == Types::MAP
          map_buf = []
          value.each do |k, v|
            map_buf << inspect_field(k, field_info[:key]) + ": " + inspect_field(v, field_info[:value])
          end
          "{" + map_buf.join(", ") + "}"
        else
          # old-style set
          inspect_collection(value.keys, field_info)
        end
      elsif value.is_a? Array
        inspect_collection(value, field_info)
      elsif value.is_a? Set
        inspect_collection(value, field_info)
      elsif value.is_a?(String) && field_info[:binary]
        value.unpack("H*").first
      else
        value.inspect
      end
    end
    
    def inspect_collection(collection, field_info)
      buf = []
      collection.each do |k|
        buf << inspect_field(k, field_info[:element])
      end
      "[" + buf.join(", ") + "]"      
    end
  end
end