]>
Commit | Line | Data |
---|---|---|
f67539c2 TL |
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 | ||
20 | require 'set' | |
21 | ||
22 | module 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 | |
237 | end |