| Kevin Clark | 2319375 | 2008-06-18 01:18:07 +0000 | [diff] [blame] | 1 | require 'thrift/types' |
| Kevin Clark | 41c0a02 | 2008-06-18 01:09:28 +0000 | [diff] [blame] | 2 | require 'set' |
| 3 | |
| Kevin Clark | 97d2166 | 2008-06-18 00:53:28 +0000 | [diff] [blame] | 4 | module Thrift |
| 5 | module Struct |
| 6 | def initialize(d={}) |
| Kevin Clark | 38a2ce6 | 2008-08-25 21:34:19 +0000 | [diff] [blame] | 7 | # get a copy of the default values to work on, removing defaults in favor of arguments |
| 8 | fields_with_defaults = fields_with_default_values.dup |
| 9 | d.each_key do |name| |
| 10 | fields_with_defaults.delete(name.to_s) |
| 11 | end |
| 12 | |
| 13 | # assign all the user-specified arguments |
| 14 | d.each do |name, value| |
| 15 | unless name_to_id(name.to_s) |
| 16 | raise Exception, "Unknown key given to #{self.class}.new: #{name}" |
| 17 | end |
| 18 | Thrift.check_type(value, struct_fields[name_to_id(name.to_s)], name) if Thrift.type_checking |
| Kevin Clark | 2319375 | 2008-06-18 01:18:07 +0000 | [diff] [blame] | 19 | instance_variable_set("@#{name}", value) |
| Kevin Clark | 97d2166 | 2008-06-18 00:53:28 +0000 | [diff] [blame] | 20 | end |
| Kevin Clark | 38a2ce6 | 2008-08-25 21:34:19 +0000 | [diff] [blame] | 21 | |
| 22 | # assign all the default values |
| 23 | fields_with_defaults.each do |name, default_value| |
| 24 | instance_variable_set("@#{name}", (default_value.dup rescue default_value)) |
| 25 | end |
| 26 | end |
| 27 | |
| 28 | def fields_with_default_values |
| 29 | fields_with_default_values = self.class.instance_variable_get("@fields_with_default_values") |
| 30 | unless fields_with_default_values |
| 31 | fields_with_default_values = {} |
| 32 | struct_fields.each do |fid, field_def| |
| 33 | if field_def[:default] |
| 34 | fields_with_default_values[field_def[:name]] = field_def[:default] |
| 35 | end |
| 36 | end |
| 37 | self.class.instance_variable_set("@fields_with_default_values", fields_with_default_values) |
| 38 | end |
| 39 | fields_with_default_values |
| 40 | end |
| 41 | |
| 42 | def name_to_id(name) |
| 43 | names_to_ids = self.class.instance_variable_get("@names_to_ids") |
| 44 | unless names_to_ids |
| 45 | names_to_ids = {} |
| 46 | struct_fields.each do |fid, field_def| |
| 47 | names_to_ids[field_def[:name]] = fid |
| 48 | end |
| 49 | self.class.instance_variable_set("@names_to_ids", names_to_ids) |
| 50 | end |
| 51 | names_to_ids[name] |
| Kevin Clark | 9bf3362 | 2008-06-18 00:53:07 +0000 | [diff] [blame] | 52 | end |
| Kevin Clark | 9bf3362 | 2008-06-18 00:53:07 +0000 | [diff] [blame] | 53 | |
| Kevin Clark | 97d2166 | 2008-06-18 00:53:28 +0000 | [diff] [blame] | 54 | def struct_fields |
| 55 | self.class.const_get(:FIELDS) |
| Kevin Clark | 9bf3362 | 2008-06-18 00:53:07 +0000 | [diff] [blame] | 56 | end |
| Kevin Clark | 9bf3362 | 2008-06-18 00:53:07 +0000 | [diff] [blame] | 57 | |
| Kevin Clark | 97d2166 | 2008-06-18 00:53:28 +0000 | [diff] [blame] | 58 | def each_field |
| 59 | struct_fields.each do |fid, data| |
| Kevin Clark | 5ad6d4a | 2008-08-26 20:02:07 +0000 | [diff] [blame] | 60 | yield fid, data[:type], data[:name], data[:default], data[:optional] |
| Kevin Clark | 97d2166 | 2008-06-18 00:53:28 +0000 | [diff] [blame] | 61 | end |
| Kevin Clark | 9bf3362 | 2008-06-18 00:53:07 +0000 | [diff] [blame] | 62 | end |
| Kevin Clark | 9bf3362 | 2008-06-18 00:53:07 +0000 | [diff] [blame] | 63 | |
| Kevin Clark | 5ad6d4a | 2008-08-26 20:02:07 +0000 | [diff] [blame] | 64 | def inspect(skip_optional_nulls = true) |
| 65 | fields = [] |
| 66 | each_field do |fid, type, name, default, optional| |
| 67 | value = instance_variable_get("@#{name}") |
| 68 | unless skip_optional_nulls && optional && value.nil? |
| 69 | fields << "#{name}:#{value.inspect}" |
| 70 | end |
| 71 | end |
| 72 | "<#{self.class} #{fields.join(", ")}>" |
| 73 | end |
| 74 | |
| Kevin Clark | 97d2166 | 2008-06-18 00:53:28 +0000 | [diff] [blame] | 75 | def read(iprot) |
| Kevin Clark | 4bd8916 | 2008-07-08 00:47:49 +0000 | [diff] [blame] | 76 | # TODO(kevinclark): Make sure transport is C readable |
| 77 | if iprot.respond_to?(:decode_binary) |
| 78 | iprot.decode_binary(self, iprot.trans) |
| 79 | else |
| 80 | iprot.read_struct_begin |
| 81 | loop do |
| 82 | fname, ftype, fid = iprot.read_field_begin |
| 83 | break if (ftype == Types::STOP) |
| 84 | handle_message(iprot, fid, ftype) |
| 85 | iprot.read_field_end |
| 86 | end |
| 87 | iprot.read_struct_end |
| Kevin Clark | 97d2166 | 2008-06-18 00:53:28 +0000 | [diff] [blame] | 88 | end |
| Kevin Clark | 97d2166 | 2008-06-18 00:53:28 +0000 | [diff] [blame] | 89 | end |
| 90 | |
| 91 | def write(oprot) |
| Kevin Clark | 4bd8916 | 2008-07-08 00:47:49 +0000 | [diff] [blame] | 92 | if oprot.respond_to?(:encode_binary) |
| 93 | # TODO(kevinclark): Clean this so I don't have to access the transport. |
| 94 | oprot.trans.write oprot.encode_binary(self) |
| 95 | else |
| 96 | oprot.write_struct_begin(self.class.name) |
| 97 | each_field do |fid, type, name| |
| 98 | unless (value = instance_variable_get("@#{name}")).nil? |
| 99 | if is_container? type |
| 100 | oprot.write_field_begin(name, type, fid) |
| 101 | write_container(oprot, value, struct_fields[fid]) |
| 102 | oprot.write_field_end |
| 103 | else |
| 104 | oprot.write_field(name, type, fid, value) |
| 105 | end |
| Kevin Clark | 97d2166 | 2008-06-18 00:53:28 +0000 | [diff] [blame] | 106 | end |
| Kevin Clark | 9bf3362 | 2008-06-18 00:53:07 +0000 | [diff] [blame] | 107 | end |
| Kevin Clark | 4bd8916 | 2008-07-08 00:47:49 +0000 | [diff] [blame] | 108 | oprot.write_field_stop |
| 109 | oprot.write_struct_end |
| Kevin Clark | 9bf3362 | 2008-06-18 00:53:07 +0000 | [diff] [blame] | 110 | end |
| 111 | end |
| Kevin Clark | 9bf3362 | 2008-06-18 00:53:07 +0000 | [diff] [blame] | 112 | |
| Kevin Clark | 8d79e3f | 2008-06-18 01:09:15 +0000 | [diff] [blame] | 113 | def ==(other) |
| 114 | return false unless other.is_a?(self.class) |
| 115 | each_field do |fid, type, name, default| |
| 116 | return false unless self.instance_variable_get("@#{name}") == other.instance_variable_get("@#{name}") |
| 117 | end |
| 118 | true |
| 119 | end |
| 120 | |
| Kevin Clark | 2319375 | 2008-06-18 01:18:07 +0000 | [diff] [blame] | 121 | def self.field_accessor(klass, *fields) |
| 122 | fields.each do |field| |
| 123 | klass.send :attr_reader, field |
| 124 | klass.send :define_method, "#{field}=" do |value| |
| Kevin Clark | a2693c1 | 2008-08-01 22:04:09 +0000 | [diff] [blame] | 125 | Thrift.check_type(value, klass::FIELDS.values.find { |f| f[:name].to_s == field.to_s }, field) if Thrift.type_checking |
| Kevin Clark | 2319375 | 2008-06-18 01:18:07 +0000 | [diff] [blame] | 126 | instance_variable_set("@#{field}", value) |
| 127 | end |
| 128 | end |
| 129 | end |
| 130 | |
| Kevin Clark | 97d2166 | 2008-06-18 00:53:28 +0000 | [diff] [blame] | 131 | protected |
| Kevin Clark | 9bf3362 | 2008-06-18 00:53:07 +0000 | [diff] [blame] | 132 | |
| Kevin Clark | 3af9287 | 2008-07-28 22:20:36 +0000 | [diff] [blame] | 133 | def self.append_features(mod) |
| 134 | if mod.ancestors.include? ::Exception |
| 135 | mod.send :class_variable_set, :'@@__thrift_struct_real_initialize', mod.instance_method(:initialize) |
| 136 | super |
| 137 | # set up our custom initializer so `raise Xception, 'message'` works |
| 138 | mod.send :define_method, :struct_initialize, mod.instance_method(:initialize) |
| 139 | mod.send :define_method, :initialize, mod.instance_method(:exception_initialize) |
| 140 | else |
| 141 | super |
| 142 | end |
| 143 | end |
| 144 | |
| 145 | def exception_initialize(*args, &block) |
| 146 | if args.size == 1 and args.first.is_a? Hash |
| 147 | # looks like it's a regular Struct initialize |
| 148 | method(:struct_initialize).call(args.first) |
| 149 | else |
| 150 | # call the Struct initializer first with no args |
| 151 | # this will set our field default values |
| 152 | method(:struct_initialize).call() |
| 153 | # now give it to the exception |
| 154 | self.class.send(:class_variable_get, :'@@__thrift_struct_real_initialize').bind(self).call(*args, &block) |
| 155 | # self.class.instance_method(:initialize).bind(self).call(*args, &block) |
| 156 | end |
| 157 | end |
| 158 | |
| Kevin Clark | 97d2166 | 2008-06-18 00:53:28 +0000 | [diff] [blame] | 159 | def handle_message(iprot, fid, ftype) |
| 160 | field = struct_fields[fid] |
| Kevin Clark | 5a2d0ad | 2008-06-18 01:14:48 +0000 | [diff] [blame] | 161 | if field and field[:type] == ftype |
| Kevin Clark | 97d2166 | 2008-06-18 00:53:28 +0000 | [diff] [blame] | 162 | value = read_field(iprot, field) |
| 163 | instance_variable_set("@#{field[:name]}", value) |
| 164 | else |
| 165 | iprot.skip(ftype) |
| 166 | end |
| Kevin Clark | 9bf3362 | 2008-06-18 00:53:07 +0000 | [diff] [blame] | 167 | end |
| Kevin Clark | 9bf3362 | 2008-06-18 00:53:07 +0000 | [diff] [blame] | 168 | |
| Kevin Clark | 97d2166 | 2008-06-18 00:53:28 +0000 | [diff] [blame] | 169 | def read_field(iprot, field = {}) |
| Kevin Clark | 5a2d0ad | 2008-06-18 01:14:48 +0000 | [diff] [blame] | 170 | case field[:type] |
| 171 | when Types::STRUCT |
| Kevin Clark | 97d2166 | 2008-06-18 00:53:28 +0000 | [diff] [blame] | 172 | value = field[:class].new |
| 173 | value.read(iprot) |
| Kevin Clark | 5a2d0ad | 2008-06-18 01:14:48 +0000 | [diff] [blame] | 174 | when Types::MAP |
| Kevin Clark | 0d45617 | 2008-06-18 00:59:37 +0000 | [diff] [blame] | 175 | key_type, val_type, size = iprot.read_map_begin |
| Kevin Clark | 97d2166 | 2008-06-18 00:53:28 +0000 | [diff] [blame] | 176 | value = {} |
| 177 | size.times do |
| 178 | k = read_field(iprot, field_info(field[:key])) |
| 179 | v = read_field(iprot, field_info(field[:value])) |
| 180 | value[k] = v |
| 181 | end |
| Kevin Clark | 0d45617 | 2008-06-18 00:59:37 +0000 | [diff] [blame] | 182 | iprot.read_map_end |
| Kevin Clark | 5a2d0ad | 2008-06-18 01:14:48 +0000 | [diff] [blame] | 183 | when Types::LIST |
| Kevin Clark | 0d45617 | 2008-06-18 00:59:37 +0000 | [diff] [blame] | 184 | e_type, size = iprot.read_list_begin |
| Kevin Clark | 97d2166 | 2008-06-18 00:53:28 +0000 | [diff] [blame] | 185 | value = Array.new(size) do |n| |
| 186 | read_field(iprot, field_info(field[:element])) |
| 187 | end |
| Kevin Clark | 0d45617 | 2008-06-18 00:59:37 +0000 | [diff] [blame] | 188 | iprot.read_list_end |
| Kevin Clark | 5a2d0ad | 2008-06-18 01:14:48 +0000 | [diff] [blame] | 189 | when Types::SET |
| Kevin Clark | 0d45617 | 2008-06-18 00:59:37 +0000 | [diff] [blame] | 190 | e_type, size = iprot.read_set_begin |
| Kevin Clark | 8d79e3f | 2008-06-18 01:09:15 +0000 | [diff] [blame] | 191 | value = Set.new |
| Kevin Clark | 97d2166 | 2008-06-18 00:53:28 +0000 | [diff] [blame] | 192 | size.times do |
| 193 | element = read_field(iprot, field_info(field[:element])) |
| Kevin Clark | 8d79e3f | 2008-06-18 01:09:15 +0000 | [diff] [blame] | 194 | value << element |
| Kevin Clark | 97d2166 | 2008-06-18 00:53:28 +0000 | [diff] [blame] | 195 | end |
| Kevin Clark | 0d45617 | 2008-06-18 00:59:37 +0000 | [diff] [blame] | 196 | iprot.read_set_end |
| Kevin Clark | 97d2166 | 2008-06-18 00:53:28 +0000 | [diff] [blame] | 197 | else |
| 198 | value = iprot.read_type(field[:type]) |
| Kevin Clark | 9bf3362 | 2008-06-18 00:53:07 +0000 | [diff] [blame] | 199 | end |
| Kevin Clark | 97d2166 | 2008-06-18 00:53:28 +0000 | [diff] [blame] | 200 | value |
| Kevin Clark | 9bf3362 | 2008-06-18 00:53:07 +0000 | [diff] [blame] | 201 | end |
| Kevin Clark | 9bf3362 | 2008-06-18 00:53:07 +0000 | [diff] [blame] | 202 | |
| Kevin Clark | 97d2166 | 2008-06-18 00:53:28 +0000 | [diff] [blame] | 203 | def write_data(oprot, value, field) |
| 204 | if is_container? field[:type] |
| 205 | write_container(oprot, value, field) |
| 206 | else |
| 207 | oprot.write_type(field[:type], value) |
| 208 | end |
| Kevin Clark | 9bf3362 | 2008-06-18 00:53:07 +0000 | [diff] [blame] | 209 | end |
| Kevin Clark | 9bf3362 | 2008-06-18 00:53:07 +0000 | [diff] [blame] | 210 | |
| Kevin Clark | 97d2166 | 2008-06-18 00:53:28 +0000 | [diff] [blame] | 211 | def write_container(oprot, value, field = {}) |
| Kevin Clark | 5a2d0ad | 2008-06-18 01:14:48 +0000 | [diff] [blame] | 212 | case field[:type] |
| 213 | when Types::MAP |
| Kevin Clark | b8a7ad7 | 2008-06-18 00:58:23 +0000 | [diff] [blame] | 214 | oprot.write_map_begin(field[:key][:type], field[:value][:type], value.size) |
| Kevin Clark | 97d2166 | 2008-06-18 00:53:28 +0000 | [diff] [blame] | 215 | value.each do |k, v| |
| 216 | write_data(oprot, k, field[:key]) |
| 217 | write_data(oprot, v, field[:value]) |
| 218 | end |
| Kevin Clark | b8a7ad7 | 2008-06-18 00:58:23 +0000 | [diff] [blame] | 219 | oprot.write_map_end |
| Kevin Clark | 5a2d0ad | 2008-06-18 01:14:48 +0000 | [diff] [blame] | 220 | when Types::LIST |
| Kevin Clark | b8a7ad7 | 2008-06-18 00:58:23 +0000 | [diff] [blame] | 221 | oprot.write_list_begin(field[:element][:type], value.size) |
| Kevin Clark | 97d2166 | 2008-06-18 00:53:28 +0000 | [diff] [blame] | 222 | value.each do |elem| |
| 223 | write_data(oprot, elem, field[:element]) |
| 224 | end |
| Kevin Clark | b8a7ad7 | 2008-06-18 00:58:23 +0000 | [diff] [blame] | 225 | oprot.write_list_end |
| Kevin Clark | 5a2d0ad | 2008-06-18 01:14:48 +0000 | [diff] [blame] | 226 | when Types::SET |
| Kevin Clark | b8a7ad7 | 2008-06-18 00:58:23 +0000 | [diff] [blame] | 227 | oprot.write_set_begin(field[:element][:type], value.size) |
| Kevin Clark | 41c0a02 | 2008-06-18 01:09:28 +0000 | [diff] [blame] | 228 | value.each do |v,| # the , is to preserve compatibility with the old Hash-style sets |
| 229 | write_data(oprot, v, field[:element]) |
| Kevin Clark | 97d2166 | 2008-06-18 00:53:28 +0000 | [diff] [blame] | 230 | end |
| Kevin Clark | b8a7ad7 | 2008-06-18 00:58:23 +0000 | [diff] [blame] | 231 | oprot.write_set_end |
| Kevin Clark | 97d2166 | 2008-06-18 00:53:28 +0000 | [diff] [blame] | 232 | else |
| 233 | raise "Not a container type: #{field[:type]}" |
| Kevin Clark | 9bf3362 | 2008-06-18 00:53:07 +0000 | [diff] [blame] | 234 | end |
| Kevin Clark | 9bf3362 | 2008-06-18 00:53:07 +0000 | [diff] [blame] | 235 | end |
| Kevin Clark | 9bf3362 | 2008-06-18 00:53:07 +0000 | [diff] [blame] | 236 | |
| Kevin Clark | 97d2166 | 2008-06-18 00:53:28 +0000 | [diff] [blame] | 237 | def is_container?(type) |
| Kevin Clark | 2960044 | 2008-06-18 00:54:13 +0000 | [diff] [blame] | 238 | [Types::LIST, Types::MAP, Types::SET].include? type |
| Kevin Clark | 97d2166 | 2008-06-18 00:53:28 +0000 | [diff] [blame] | 239 | end |
| Kevin Clark | 9bf3362 | 2008-06-18 00:53:07 +0000 | [diff] [blame] | 240 | |
| Kevin Clark | 97d2166 | 2008-06-18 00:53:28 +0000 | [diff] [blame] | 241 | def field_info(field) |
| 242 | { :type => field[:type], |
| 243 | :class => field[:class], |
| 244 | :key => field[:key], |
| 245 | :value => field[:value], |
| 246 | :element => field[:element] } |
| 247 | end |
| Kevin Clark | 9bf3362 | 2008-06-18 00:53:07 +0000 | [diff] [blame] | 248 | end |
| Kevin Clark | fe897d3 | 2008-06-18 01:02:31 +0000 | [diff] [blame] | 249 | deprecate_module! :ThriftStruct => Struct |
| Kevin Clark | 9bf3362 | 2008-06-18 00:53:07 +0000 | [diff] [blame] | 250 | end |