1# 2# Licensed to the Apache Software Foundation (ASF) under one 3# or more contributor license agreements. See the NOTICE file 4# distributed with this work for additional information 5# regarding copyright ownership. The ASF licenses this file 6# to you under the Apache License, Version 2.0 (the 7# "License"); you may not use this file except in compliance 8# with the License. You may obtain a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, 13# software distributed under the License is distributed on an 14# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15# KIND, either express or implied. See the License for the 16# specific language governing permissions and limitations 17# under the License. 18# 19 20require 'set' 21 22module Thrift 23 module Struct 24 def initialize(d={}, &block) 25 # get a copy of the default values to work on, removing defaults in favor of arguments 26 fields_with_defaults = fields_with_default_values.dup 27 28 # check if the defaults is empty, or if there are no parameters for this 29 # instantiation, and if so, don't bother overriding defaults. 30 unless fields_with_defaults.empty? || d.empty? 31 d.each_key do |name| 32 fields_with_defaults.delete(name.to_s) 33 end 34 end 35 36 # assign all the user-specified arguments 37 unless d.empty? 38 d.each do |name, value| 39 unless name_to_id(name.to_s) 40 raise Exception, "Unknown key given to #{self.class}.new: #{name}" 41 end 42 Thrift.check_type(value, struct_fields[name_to_id(name.to_s)], name) if Thrift.type_checking 43 instance_variable_set("@#{name}", value) 44 end 45 end 46 47 # assign all the default values 48 unless fields_with_defaults.empty? 49 fields_with_defaults.each do |name, default_value| 50 instance_variable_set("@#{name}", (default_value.dup rescue default_value)) 51 end 52 end 53 54 yield self if block_given? 55 end 56 57 def fields_with_default_values 58 fields_with_default_values = self.class.instance_variable_get(:@fields_with_default_values) 59 unless fields_with_default_values 60 fields_with_default_values = {} 61 struct_fields.each do |fid, field_def| 62 unless field_def[:default].nil? 63 fields_with_default_values[field_def[:name]] = field_def[:default] 64 end 65 end 66 self.class.instance_variable_set(:@fields_with_default_values, fields_with_default_values) 67 end 68 fields_with_default_values 69 end 70 71 def inspect(skip_optional_nulls = true) 72 fields = [] 73 each_field do |fid, field_info| 74 name = field_info[:name] 75 value = instance_variable_get("@#{name}") 76 unless skip_optional_nulls && field_info[:optional] && value.nil? 77 fields << "#{name}:#{inspect_field(value, field_info)}" 78 end 79 end 80 "<#{self.class} #{fields.join(", ")}>" 81 end 82 83 def read(iprot) 84 iprot.read_struct_begin 85 loop do 86 fname, ftype, fid = iprot.read_field_begin 87 break if (ftype == Types::STOP) 88 handle_message(iprot, fid, ftype) 89 iprot.read_field_end 90 end 91 iprot.read_struct_end 92 validate 93 end 94 95 def write(oprot) 96 validate 97 oprot.write_struct_begin(self.class.name) 98 each_field do |fid, field_info| 99 name = field_info[:name] 100 type = field_info[:type] 101 value = instance_variable_get("@#{name}") 102 unless value.nil? 103 if is_container? type 104 oprot.write_field_begin(name, type, fid) 105 write_container(oprot, value, field_info) 106 oprot.write_field_end 107 else 108 oprot.write_field(field_info, fid, value) 109 end 110 end 111 end 112 oprot.write_field_stop 113 oprot.write_struct_end 114 end 115 116 def ==(other) 117 return false if other.nil? 118 each_field do |fid, field_info| 119 name = field_info[:name] 120 return false unless other.respond_to?(name) && self.send(name) == other.send(name) 121 end 122 true 123 end 124 125 def eql?(other) 126 self.class == other.class && self == other 127 end 128 129 # This implementation of hash() is inspired by Apache's Java HashCodeBuilder class. 130 def hash 131 total = 17 132 each_field do |fid, field_info| 133 name = field_info[:name] 134 value = self.send(name) 135 total = (total * 37 + value.hash) & 0xffffffff 136 end 137 total 138 end 139 140 def differences(other) 141 diffs = [] 142 unless other.is_a?(self.class) 143 diffs << "Different class!" 144 else 145 each_field do |fid, field_info| 146 name = field_info[:name] 147 diffs << "#{name} differs!" unless self.instance_variable_get("@#{name}") == other.instance_variable_get("@#{name}") 148 end 149 end 150 diffs 151 end 152 153 def self.field_accessor(klass, field_info) 154 field_name_sym = field_info[:name].to_sym 155 klass.send :attr_reader, field_name_sym 156 klass.send :define_method, "#{field_info[:name]}=" do |value| 157 Thrift.check_type(value, field_info, field_info[:name]) if Thrift.type_checking 158 instance_variable_set("@#{field_name_sym}", value) 159 end 160 end 161 162 def self.generate_accessors(klass) 163 klass::FIELDS.values.each do |field_info| 164 field_accessor(klass, field_info) 165 qmark_isset_method(klass, field_info) 166 end 167 end 168 169 def self.qmark_isset_method(klass, field_info) 170 klass.send :define_method, "#{field_info[:name]}?" do 171 !self.send(field_info[:name].to_sym).nil? 172 end 173 end 174 175 def <=>(other) 176 if self.class == other.class 177 each_field do |fid, field_info| 178 v1 = self.send(field_info[:name]) 179 v1_set = !v1.nil? 180 v2 = other.send(field_info[:name]) 181 v2_set = !v2.nil? 182 if v1_set && !v2_set 183 return -1 184 elsif !v1_set && v2_set 185 return 1 186 elsif v1_set && v2_set 187 cmp = v1 <=> v2 188 if cmp != 0 189 return cmp 190 end 191 end 192 end 193 0 194 else 195 self.class <=> other.class 196 end 197 end 198 199 protected 200 201 def self.append_features(mod) 202 if mod.ancestors.include? ::Exception 203 mod.send :class_variable_set, :'@@__thrift_struct_real_initialize', mod.instance_method(:initialize) 204 super 205 # set up our custom initializer so `raise Xception, 'message'` works 206 mod.send :define_method, :struct_initialize, mod.instance_method(:initialize) 207 mod.send :define_method, :initialize, mod.instance_method(:exception_initialize) 208 else 209 super 210 end 211 end 212 213 def exception_initialize(*args, &block) 214 if args.size == 1 and args.first.is_a? Hash 215 # looks like it's a regular Struct initialize 216 method(:struct_initialize).call(args.first) 217 else 218 # call the Struct initializer first with no args 219 # this will set our field default values 220 method(:struct_initialize).call() 221 # now give it to the exception 222 self.class.send(:class_variable_get, :'@@__thrift_struct_real_initialize').bind(self).call(*args, &block) if args.size > 0 223 # self.class.instance_method(:initialize).bind(self).call(*args, &block) 224 end 225 end 226 227 def handle_message(iprot, fid, ftype) 228 field = struct_fields[fid] 229 if field and field[:type] == ftype 230 value = read_field(iprot, field) 231 instance_variable_set("@#{field[:name]}", value) 232 else 233 iprot.skip(ftype) 234 end 235 end 236 end 237end 238