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