1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2014 Google Inc. All rights reserved.
3 // https://developers.google.com/protocol-buffers/
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions are
7 // met:
8 //
9 // * Redistributions of source code must retain the above copyright
10 // notice, this list of conditions and the following disclaimer.
11 // * Redistributions in binary form must reproduce the above
12 // copyright notice, this list of conditions and the following disclaimer
13 // in the documentation and/or other materials provided with the
14 // distribution.
15 // * Neither the name of Google Inc. nor the names of its
16 // contributors may be used to endorse or promote products derived from
17 // this software without specific prior written permission.
18 //
19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31 #include "protobuf.h"
32
33 // -----------------------------------------------------------------------------
34 // Basic map operations on top of upb's strtable.
35 //
36 // Note that we roll our own `Map` container here because, as for
37 // `RepeatedField`, we want a strongly-typed container. This is so that any user
38 // errors due to incorrect map key or value types are raised as close as
39 // possible to the error site, rather than at some deferred point (e.g.,
40 // serialization).
41 //
42 // We build our `Map` on top of upb_strtable so that we're able to take
43 // advantage of the native_slot storage abstraction, as RepeatedField does.
44 // (This is not quite a perfect mapping -- see the key conversions below -- but
45 // gives us full support and error-checking for all value types for free.)
46 // -----------------------------------------------------------------------------
47
48 // Map values are stored using the native_slot abstraction (as with repeated
49 // field values), but keys are a bit special. Since we use a strtable, we need
50 // to store keys as sequences of bytes such that equality of those bytes maps
51 // one-to-one to equality of keys. We store strings directly (i.e., they map to
52 // their own bytes) and integers as native integers (using the native_slot
53 // abstraction).
54
55 // Note that there is another tradeoff here in keeping string keys as native
56 // strings rather than Ruby strings: traversing the Map requires conversion to
57 // Ruby string values on every traversal, potentially creating more garbage. We
58 // should consider ways to cache a Ruby version of the key if this becomes an
59 // issue later.
60
61 // Forms a key to use with the underlying strtable from a Ruby key value. |buf|
62 // must point to TABLE_KEY_BUF_LENGTH bytes of temporary space, used to
63 // construct a key byte sequence if needed. |out_key| and |out_length| provide
64 // the resulting key data/length.
65 #define TABLE_KEY_BUF_LENGTH 8 // sizeof(uint64_t)
table_key(Map * self,VALUE key,char * buf,const char ** out_key,size_t * out_length)66 static VALUE table_key(Map* self, VALUE key,
67 char* buf,
68 const char** out_key,
69 size_t* out_length) {
70 switch (self->key_type) {
71 case UPB_TYPE_BYTES:
72 case UPB_TYPE_STRING:
73 // Strings: use string content directly.
74 Check_Type(key, T_STRING);
75 key = native_slot_encode_and_freeze_string(self->key_type, key);
76 *out_key = RSTRING_PTR(key);
77 *out_length = RSTRING_LEN(key);
78 break;
79
80 case UPB_TYPE_BOOL:
81 case UPB_TYPE_INT32:
82 case UPB_TYPE_INT64:
83 case UPB_TYPE_UINT32:
84 case UPB_TYPE_UINT64:
85 native_slot_set(self->key_type, Qnil, buf, key);
86 *out_key = buf;
87 *out_length = native_slot_size(self->key_type);
88 break;
89
90 default:
91 // Map constructor should not allow a Map with another key type to be
92 // constructed.
93 assert(false);
94 break;
95 }
96
97 return key;
98 }
99
table_key_to_ruby(Map * self,const char * buf,size_t length)100 static VALUE table_key_to_ruby(Map* self, const char* buf, size_t length) {
101 switch (self->key_type) {
102 case UPB_TYPE_BYTES:
103 case UPB_TYPE_STRING: {
104 VALUE ret = rb_str_new(buf, length);
105 rb_enc_associate(ret,
106 (self->key_type == UPB_TYPE_BYTES) ?
107 kRubyString8bitEncoding : kRubyStringUtf8Encoding);
108 return ret;
109 }
110
111 case UPB_TYPE_BOOL:
112 case UPB_TYPE_INT32:
113 case UPB_TYPE_INT64:
114 case UPB_TYPE_UINT32:
115 case UPB_TYPE_UINT64:
116 return native_slot_get(self->key_type, Qnil, buf);
117
118 default:
119 assert(false);
120 return Qnil;
121 }
122 }
123
value_memory(upb_value * v)124 static void* value_memory(upb_value* v) {
125 return (void*)(&v->val);
126 }
127
128 // -----------------------------------------------------------------------------
129 // Map container type.
130 // -----------------------------------------------------------------------------
131
132 const rb_data_type_t Map_type = {
133 "Google::Protobuf::Map",
134 { Map_mark, Map_free, NULL },
135 };
136
137 VALUE cMap;
138
ruby_to_Map(VALUE _self)139 Map* ruby_to_Map(VALUE _self) {
140 Map* self;
141 TypedData_Get_Struct(_self, Map, &Map_type, self);
142 return self;
143 }
144
Map_mark(void * _self)145 void Map_mark(void* _self) {
146 Map* self = _self;
147
148 rb_gc_mark(self->value_type_class);
149
150 if (self->value_type == UPB_TYPE_STRING ||
151 self->value_type == UPB_TYPE_BYTES ||
152 self->value_type == UPB_TYPE_MESSAGE) {
153 upb_strtable_iter it;
154 for (upb_strtable_begin(&it, &self->table);
155 !upb_strtable_done(&it);
156 upb_strtable_next(&it)) {
157 upb_value v = upb_strtable_iter_value(&it);
158 void* mem = value_memory(&v);
159 native_slot_mark(self->value_type, mem);
160 }
161 }
162 }
163
Map_free(void * _self)164 void Map_free(void* _self) {
165 Map* self = _self;
166 upb_strtable_uninit(&self->table);
167 xfree(self);
168 }
169
Map_alloc(VALUE klass)170 VALUE Map_alloc(VALUE klass) {
171 Map* self = ALLOC(Map);
172 memset(self, 0, sizeof(Map));
173 self->value_type_class = Qnil;
174 return TypedData_Wrap_Struct(klass, &Map_type, self);
175 }
176
needs_typeclass(upb_fieldtype_t type)177 static bool needs_typeclass(upb_fieldtype_t type) {
178 switch (type) {
179 case UPB_TYPE_MESSAGE:
180 case UPB_TYPE_ENUM:
181 return true;
182 default:
183 return false;
184 }
185 }
186
187 /*
188 * call-seq:
189 * Map.new(key_type, value_type, value_typeclass = nil, init_hashmap = {})
190 * => new map
191 *
192 * Allocates a new Map container. This constructor may be called with 2, 3, or 4
193 * arguments. The first two arguments are always present and are symbols (taking
194 * on the same values as field-type symbols in message descriptors) that
195 * indicate the type of the map key and value fields.
196 *
197 * The supported key types are: :int32, :int64, :uint32, :uint64, :bool,
198 * :string, :bytes.
199 *
200 * The supported value types are: :int32, :int64, :uint32, :uint64, :bool,
201 * :string, :bytes, :enum, :message.
202 *
203 * The third argument, value_typeclass, must be present if value_type is :enum
204 * or :message. As in RepeatedField#new, this argument must be a message class
205 * (for :message) or enum module (for :enum).
206 *
207 * The last argument, if present, provides initial content for map. Note that
208 * this may be an ordinary Ruby hashmap or another Map instance with identical
209 * key and value types. Also note that this argument may be present whether or
210 * not value_typeclass is present (and it is unambiguously separate from
211 * value_typeclass because value_typeclass's presence is strictly determined by
212 * value_type). The contents of this initial hashmap or Map instance are
213 * shallow-copied into the new Map: the original map is unmodified, but
214 * references to underlying objects will be shared if the value type is a
215 * message type.
216 */
Map_init(int argc,VALUE * argv,VALUE _self)217 VALUE Map_init(int argc, VALUE* argv, VALUE _self) {
218 Map* self = ruby_to_Map(_self);
219 int init_value_arg;
220
221 // We take either two args (:key_type, :value_type), three args (:key_type,
222 // :value_type, "ValueMessageType"), or four args (the above plus an initial
223 // hashmap).
224 if (argc < 2 || argc > 4) {
225 rb_raise(rb_eArgError, "Map constructor expects 2, 3 or 4 arguments.");
226 }
227
228 self->key_type = ruby_to_fieldtype(argv[0]);
229 self->value_type = ruby_to_fieldtype(argv[1]);
230
231 // Check that the key type is an allowed type.
232 switch (self->key_type) {
233 case UPB_TYPE_INT32:
234 case UPB_TYPE_INT64:
235 case UPB_TYPE_UINT32:
236 case UPB_TYPE_UINT64:
237 case UPB_TYPE_BOOL:
238 case UPB_TYPE_STRING:
239 case UPB_TYPE_BYTES:
240 // These are OK.
241 break;
242 default:
243 rb_raise(rb_eArgError, "Invalid key type for map.");
244 }
245
246 init_value_arg = 2;
247 if (needs_typeclass(self->value_type) && argc > 2) {
248 self->value_type_class = argv[2];
249 validate_type_class(self->value_type, self->value_type_class);
250 init_value_arg = 3;
251 }
252
253 // Table value type is always UINT64: this ensures enough space to store the
254 // native_slot value.
255 if (!upb_strtable_init(&self->table, UPB_CTYPE_UINT64)) {
256 rb_raise(rb_eRuntimeError, "Could not allocate table.");
257 }
258
259 if (argc > init_value_arg) {
260 Map_merge_into_self(_self, argv[init_value_arg]);
261 }
262
263 return Qnil;
264 }
265
266 /*
267 * call-seq:
268 * Map.each(&block)
269 *
270 * Invokes &block on each |key, value| pair in the map, in unspecified order.
271 * Note that Map also includes Enumerable; map thus acts like a normal Ruby
272 * sequence.
273 */
Map_each(VALUE _self)274 VALUE Map_each(VALUE _self) {
275 Map* self = ruby_to_Map(_self);
276
277 upb_strtable_iter it;
278 for (upb_strtable_begin(&it, &self->table);
279 !upb_strtable_done(&it);
280 upb_strtable_next(&it)) {
281
282 VALUE key = table_key_to_ruby(
283 self, upb_strtable_iter_key(&it), upb_strtable_iter_keylength(&it));
284
285 upb_value v = upb_strtable_iter_value(&it);
286 void* mem = value_memory(&v);
287 VALUE value = native_slot_get(self->value_type,
288 self->value_type_class,
289 mem);
290
291 rb_yield_values(2, key, value);
292 }
293
294 return Qnil;
295 }
296
297 /*
298 * call-seq:
299 * Map.keys => [list_of_keys]
300 *
301 * Returns the list of keys contained in the map, in unspecified order.
302 */
Map_keys(VALUE _self)303 VALUE Map_keys(VALUE _self) {
304 Map* self = ruby_to_Map(_self);
305
306 VALUE ret = rb_ary_new();
307 upb_strtable_iter it;
308 for (upb_strtable_begin(&it, &self->table);
309 !upb_strtable_done(&it);
310 upb_strtable_next(&it)) {
311
312 VALUE key = table_key_to_ruby(
313 self, upb_strtable_iter_key(&it), upb_strtable_iter_keylength(&it));
314
315 rb_ary_push(ret, key);
316 }
317
318 return ret;
319 }
320
321 /*
322 * call-seq:
323 * Map.values => [list_of_values]
324 *
325 * Returns the list of values contained in the map, in unspecified order.
326 */
Map_values(VALUE _self)327 VALUE Map_values(VALUE _self) {
328 Map* self = ruby_to_Map(_self);
329
330 VALUE ret = rb_ary_new();
331 upb_strtable_iter it;
332 for (upb_strtable_begin(&it, &self->table);
333 !upb_strtable_done(&it);
334 upb_strtable_next(&it)) {
335
336 upb_value v = upb_strtable_iter_value(&it);
337 void* mem = value_memory(&v);
338 VALUE value = native_slot_get(self->value_type,
339 self->value_type_class,
340 mem);
341
342 rb_ary_push(ret, value);
343 }
344
345 return ret;
346 }
347
348 /*
349 * call-seq:
350 * Map.[](key) => value
351 *
352 * Accesses the element at the given key. Throws an exception if the key type is
353 * incorrect. Returns nil when the key is not present in the map.
354 */
Map_index(VALUE _self,VALUE key)355 VALUE Map_index(VALUE _self, VALUE key) {
356 Map* self = ruby_to_Map(_self);
357
358 char keybuf[TABLE_KEY_BUF_LENGTH];
359 const char* keyval = NULL;
360 size_t length = 0;
361 upb_value v;
362 key = table_key(self, key, keybuf, &keyval, &length);
363
364 if (upb_strtable_lookup2(&self->table, keyval, length, &v)) {
365 void* mem = value_memory(&v);
366 return native_slot_get(self->value_type, self->value_type_class, mem);
367 } else {
368 return Qnil;
369 }
370 }
371
372 /*
373 * call-seq:
374 * Map.[]=(key, value) => value
375 *
376 * Inserts or overwrites the value at the given key with the given new value.
377 * Throws an exception if the key type is incorrect. Returns the new value that
378 * was just inserted.
379 */
Map_index_set(VALUE _self,VALUE key,VALUE value)380 VALUE Map_index_set(VALUE _self, VALUE key, VALUE value) {
381 Map* self = ruby_to_Map(_self);
382
383 char keybuf[TABLE_KEY_BUF_LENGTH];
384 const char* keyval = NULL;
385 size_t length = 0;
386 upb_value v;
387 void* mem;
388 key = table_key(self, key, keybuf, &keyval, &length);
389
390 mem = value_memory(&v);
391 native_slot_set(self->value_type, self->value_type_class, mem, value);
392
393 // Replace any existing value by issuing a 'remove' operation first.
394 upb_strtable_remove2(&self->table, keyval, length, NULL);
395 if (!upb_strtable_insert2(&self->table, keyval, length, v)) {
396 rb_raise(rb_eRuntimeError, "Could not insert into table");
397 }
398
399 // Ruby hashmap's :[]= method also returns the inserted value.
400 return value;
401 }
402
403 /*
404 * call-seq:
405 * Map.has_key?(key) => bool
406 *
407 * Returns true if the given key is present in the map. Throws an exception if
408 * the key has the wrong type.
409 */
Map_has_key(VALUE _self,VALUE key)410 VALUE Map_has_key(VALUE _self, VALUE key) {
411 Map* self = ruby_to_Map(_self);
412
413 char keybuf[TABLE_KEY_BUF_LENGTH];
414 const char* keyval = NULL;
415 size_t length = 0;
416 key = table_key(self, key, keybuf, &keyval, &length);
417
418 if (upb_strtable_lookup2(&self->table, keyval, length, NULL)) {
419 return Qtrue;
420 } else {
421 return Qfalse;
422 }
423 }
424
425 /*
426 * call-seq:
427 * Map.delete(key) => old_value
428 *
429 * Deletes the value at the given key, if any, returning either the old value or
430 * nil if none was present. Throws an exception if the key is of the wrong type.
431 */
Map_delete(VALUE _self,VALUE key)432 VALUE Map_delete(VALUE _self, VALUE key) {
433 Map* self = ruby_to_Map(_self);
434
435 char keybuf[TABLE_KEY_BUF_LENGTH];
436 const char* keyval = NULL;
437 size_t length = 0;
438 upb_value v;
439 key = table_key(self, key, keybuf, &keyval, &length);
440
441 if (upb_strtable_remove2(&self->table, keyval, length, &v)) {
442 void* mem = value_memory(&v);
443 return native_slot_get(self->value_type, self->value_type_class, mem);
444 } else {
445 return Qnil;
446 }
447 }
448
449 /*
450 * call-seq:
451 * Map.clear
452 *
453 * Removes all entries from the map.
454 */
Map_clear(VALUE _self)455 VALUE Map_clear(VALUE _self) {
456 Map* self = ruby_to_Map(_self);
457
458 // Uninit and reinit the table -- this is faster than iterating and doing a
459 // delete-lookup on each key.
460 upb_strtable_uninit(&self->table);
461 if (!upb_strtable_init(&self->table, UPB_CTYPE_INT64)) {
462 rb_raise(rb_eRuntimeError, "Unable to re-initialize table");
463 }
464 return Qnil;
465 }
466
467 /*
468 * call-seq:
469 * Map.length
470 *
471 * Returns the number of entries (key-value pairs) in the map.
472 */
Map_length(VALUE _self)473 VALUE Map_length(VALUE _self) {
474 Map* self = ruby_to_Map(_self);
475 return ULL2NUM(upb_strtable_count(&self->table));
476 }
477
Map_new_this_type(VALUE _self)478 static VALUE Map_new_this_type(VALUE _self) {
479 Map* self = ruby_to_Map(_self);
480 VALUE new_map = Qnil;
481 VALUE key_type = fieldtype_to_ruby(self->key_type);
482 VALUE value_type = fieldtype_to_ruby(self->value_type);
483 if (self->value_type_class != Qnil) {
484 new_map = rb_funcall(CLASS_OF(_self), rb_intern("new"), 3,
485 key_type, value_type, self->value_type_class);
486 } else {
487 new_map = rb_funcall(CLASS_OF(_self), rb_intern("new"), 2,
488 key_type, value_type);
489 }
490 return new_map;
491 }
492
493 /*
494 * call-seq:
495 * Map.dup => new_map
496 *
497 * Duplicates this map with a shallow copy. References to all non-primitive
498 * element objects (e.g., submessages) are shared.
499 */
Map_dup(VALUE _self)500 VALUE Map_dup(VALUE _self) {
501 Map* self = ruby_to_Map(_self);
502 VALUE new_map = Map_new_this_type(_self);
503 Map* new_self = ruby_to_Map(new_map);
504
505 upb_strtable_iter it;
506 for (upb_strtable_begin(&it, &self->table);
507 !upb_strtable_done(&it);
508 upb_strtable_next(&it)) {
509
510 upb_value v = upb_strtable_iter_value(&it);
511 void* mem = value_memory(&v);
512 upb_value dup;
513 void* dup_mem = value_memory(&dup);
514 native_slot_dup(self->value_type, dup_mem, mem);
515
516 if (!upb_strtable_insert2(&new_self->table,
517 upb_strtable_iter_key(&it),
518 upb_strtable_iter_keylength(&it),
519 dup)) {
520 rb_raise(rb_eRuntimeError, "Error inserting value into new table");
521 }
522 }
523
524 return new_map;
525 }
526
527 // Used by Google::Protobuf.deep_copy but not exposed directly.
Map_deep_copy(VALUE _self)528 VALUE Map_deep_copy(VALUE _self) {
529 Map* self = ruby_to_Map(_self);
530 VALUE new_map = Map_new_this_type(_self);
531 Map* new_self = ruby_to_Map(new_map);
532
533 upb_strtable_iter it;
534 for (upb_strtable_begin(&it, &self->table);
535 !upb_strtable_done(&it);
536 upb_strtable_next(&it)) {
537
538 upb_value v = upb_strtable_iter_value(&it);
539 void* mem = value_memory(&v);
540 upb_value dup;
541 void* dup_mem = value_memory(&dup);
542 native_slot_deep_copy(self->value_type, dup_mem, mem);
543
544 if (!upb_strtable_insert2(&new_self->table,
545 upb_strtable_iter_key(&it),
546 upb_strtable_iter_keylength(&it),
547 dup)) {
548 rb_raise(rb_eRuntimeError, "Error inserting value into new table");
549 }
550 }
551
552 return new_map;
553 }
554
555 /*
556 * call-seq:
557 * Map.==(other) => boolean
558 *
559 * Compares this map to another. Maps are equal if they have identical key sets,
560 * and for each key, the values in both maps compare equal. Elements are
561 * compared as per normal Ruby semantics, by calling their :== methods (or
562 * performing a more efficient comparison for primitive types).
563 *
564 * Maps with dissimilar key types or value types/typeclasses are never equal,
565 * even if value comparison (for example, between integers and floats) would
566 * have otherwise indicated that every element has equal value.
567 */
Map_eq(VALUE _self,VALUE _other)568 VALUE Map_eq(VALUE _self, VALUE _other) {
569 Map* self = ruby_to_Map(_self);
570 Map* other;
571 upb_strtable_iter it;
572
573 // Allow comparisons to Ruby hashmaps by converting to a temporary Map
574 // instance. Slow, but workable.
575 if (TYPE(_other) == T_HASH) {
576 VALUE other_map = Map_new_this_type(_self);
577 Map_merge_into_self(other_map, _other);
578 _other = other_map;
579 }
580
581 other = ruby_to_Map(_other);
582
583 if (self == other) {
584 return Qtrue;
585 }
586 if (self->key_type != other->key_type ||
587 self->value_type != other->value_type ||
588 self->value_type_class != other->value_type_class) {
589 return Qfalse;
590 }
591 if (upb_strtable_count(&self->table) != upb_strtable_count(&other->table)) {
592 return Qfalse;
593 }
594
595 // For each member of self, check that an equal member exists at the same key
596 // in other.
597 for (upb_strtable_begin(&it, &self->table);
598 !upb_strtable_done(&it);
599 upb_strtable_next(&it)) {
600
601 upb_value v = upb_strtable_iter_value(&it);
602 void* mem = value_memory(&v);
603 upb_value other_v;
604 void* other_mem = value_memory(&other_v);
605
606 if (!upb_strtable_lookup2(&other->table,
607 upb_strtable_iter_key(&it),
608 upb_strtable_iter_keylength(&it),
609 &other_v)) {
610 // Not present in other map.
611 return Qfalse;
612 }
613
614 if (!native_slot_eq(self->value_type, mem, other_mem)) {
615 // Present, but value not equal.
616 return Qfalse;
617 }
618 }
619
620 return Qtrue;
621 }
622
623 /*
624 * call-seq:
625 * Map.hash => hash_value
626 *
627 * Returns a hash value based on this map's contents.
628 */
Map_hash(VALUE _self)629 VALUE Map_hash(VALUE _self) {
630 Map* self = ruby_to_Map(_self);
631
632 st_index_t h = rb_hash_start(0);
633 VALUE hash_sym = rb_intern("hash");
634
635 upb_strtable_iter it;
636 for (upb_strtable_begin(&it, &self->table);
637 !upb_strtable_done(&it);
638 upb_strtable_next(&it)) {
639 VALUE key = table_key_to_ruby(
640 self, upb_strtable_iter_key(&it), upb_strtable_iter_keylength(&it));
641
642 upb_value v = upb_strtable_iter_value(&it);
643 void* mem = value_memory(&v);
644 VALUE value = native_slot_get(self->value_type,
645 self->value_type_class,
646 mem);
647
648 h = rb_hash_uint(h, NUM2LONG(rb_funcall(key, hash_sym, 0)));
649 h = rb_hash_uint(h, NUM2LONG(rb_funcall(value, hash_sym, 0)));
650 }
651
652 return INT2FIX(h);
653 }
654
655 /*
656 * call-seq:
657 * Map.inspect => string
658 *
659 * Returns a string representing this map's elements. It will be formatted as
660 * "{key => value, key => value, ...}", with each key and value string
661 * representation computed by its own #inspect method.
662 */
Map_inspect(VALUE _self)663 VALUE Map_inspect(VALUE _self) {
664 Map* self = ruby_to_Map(_self);
665
666 VALUE str = rb_str_new2("{");
667
668 bool first = true;
669 VALUE inspect_sym = rb_intern("inspect");
670
671 upb_strtable_iter it;
672 for (upb_strtable_begin(&it, &self->table);
673 !upb_strtable_done(&it);
674 upb_strtable_next(&it)) {
675 VALUE key = table_key_to_ruby(
676 self, upb_strtable_iter_key(&it), upb_strtable_iter_keylength(&it));
677
678 upb_value v = upb_strtable_iter_value(&it);
679 void* mem = value_memory(&v);
680 VALUE value = native_slot_get(self->value_type,
681 self->value_type_class,
682 mem);
683
684 if (!first) {
685 str = rb_str_cat2(str, ", ");
686 } else {
687 first = false;
688 }
689 str = rb_str_append(str, rb_funcall(key, inspect_sym, 0));
690 str = rb_str_cat2(str, "=>");
691 str = rb_str_append(str, rb_funcall(value, inspect_sym, 0));
692 }
693
694 str = rb_str_cat2(str, "}");
695 return str;
696 }
697
698 /*
699 * call-seq:
700 * Map.merge(other_map) => map
701 *
702 * Copies key/value pairs from other_map into a copy of this map. If a key is
703 * set in other_map and this map, the value from other_map overwrites the value
704 * in the new copy of this map. Returns the new copy of this map with merged
705 * contents.
706 */
Map_merge(VALUE _self,VALUE hashmap)707 VALUE Map_merge(VALUE _self, VALUE hashmap) {
708 VALUE dupped = Map_dup(_self);
709 return Map_merge_into_self(dupped, hashmap);
710 }
711
merge_into_self_callback(VALUE key,VALUE value,VALUE self)712 static int merge_into_self_callback(VALUE key, VALUE value, VALUE self) {
713 Map_index_set(self, key, value);
714 return ST_CONTINUE;
715 }
716
717 // Used only internally -- shared by #merge and #initialize.
Map_merge_into_self(VALUE _self,VALUE hashmap)718 VALUE Map_merge_into_self(VALUE _self, VALUE hashmap) {
719 if (TYPE(hashmap) == T_HASH) {
720 rb_hash_foreach(hashmap, merge_into_self_callback, _self);
721 } else if (RB_TYPE_P(hashmap, T_DATA) && RTYPEDDATA_P(hashmap) &&
722 RTYPEDDATA_TYPE(hashmap) == &Map_type) {
723
724 Map* self = ruby_to_Map(_self);
725 Map* other = ruby_to_Map(hashmap);
726 upb_strtable_iter it;
727
728 if (self->key_type != other->key_type ||
729 self->value_type != other->value_type ||
730 self->value_type_class != other->value_type_class) {
731 rb_raise(rb_eArgError, "Attempt to merge Map with mismatching types");
732 }
733
734 for (upb_strtable_begin(&it, &other->table);
735 !upb_strtable_done(&it);
736 upb_strtable_next(&it)) {
737
738 // Replace any existing value by issuing a 'remove' operation first.
739 upb_value v;
740 upb_value oldv;
741 upb_strtable_remove2(&self->table,
742 upb_strtable_iter_key(&it),
743 upb_strtable_iter_keylength(&it),
744 &oldv);
745
746 v = upb_strtable_iter_value(&it);
747 upb_strtable_insert2(&self->table,
748 upb_strtable_iter_key(&it),
749 upb_strtable_iter_keylength(&it),
750 v);
751 }
752 } else {
753 rb_raise(rb_eArgError, "Unknown type merging into Map");
754 }
755 return _self;
756 }
757
758 // Internal method: map iterator initialization (used for serialization).
Map_begin(VALUE _self,Map_iter * iter)759 void Map_begin(VALUE _self, Map_iter* iter) {
760 Map* self = ruby_to_Map(_self);
761 iter->self = self;
762 upb_strtable_begin(&iter->it, &self->table);
763 }
764
Map_next(Map_iter * iter)765 void Map_next(Map_iter* iter) {
766 upb_strtable_next(&iter->it);
767 }
768
Map_done(Map_iter * iter)769 bool Map_done(Map_iter* iter) {
770 return upb_strtable_done(&iter->it);
771 }
772
Map_iter_key(Map_iter * iter)773 VALUE Map_iter_key(Map_iter* iter) {
774 return table_key_to_ruby(
775 iter->self,
776 upb_strtable_iter_key(&iter->it),
777 upb_strtable_iter_keylength(&iter->it));
778 }
779
Map_iter_value(Map_iter * iter)780 VALUE Map_iter_value(Map_iter* iter) {
781 upb_value v = upb_strtable_iter_value(&iter->it);
782 void* mem = value_memory(&v);
783 return native_slot_get(iter->self->value_type,
784 iter->self->value_type_class,
785 mem);
786 }
787
Map_register(VALUE module)788 void Map_register(VALUE module) {
789 VALUE klass = rb_define_class_under(module, "Map", rb_cObject);
790 rb_define_alloc_func(klass, Map_alloc);
791 cMap = klass;
792 rb_gc_register_address(&cMap);
793
794 rb_define_method(klass, "initialize", Map_init, -1);
795 rb_define_method(klass, "each", Map_each, 0);
796 rb_define_method(klass, "keys", Map_keys, 0);
797 rb_define_method(klass, "values", Map_values, 0);
798 rb_define_method(klass, "[]", Map_index, 1);
799 rb_define_method(klass, "[]=", Map_index_set, 2);
800 rb_define_method(klass, "has_key?", Map_has_key, 1);
801 rb_define_method(klass, "delete", Map_delete, 1);
802 rb_define_method(klass, "clear", Map_clear, 0);
803 rb_define_method(klass, "length", Map_length, 0);
804 rb_define_method(klass, "dup", Map_dup, 0);
805 rb_define_method(klass, "==", Map_eq, 1);
806 rb_define_method(klass, "hash", Map_hash, 0);
807 rb_define_method(klass, "inspect", Map_inspect, 0);
808 rb_define_method(klass, "merge", Map_merge, 1);
809 rb_include_module(klass, rb_mEnumerable);
810 }
811