// Used by Google::Protobuf.deep_copy but not exposed directly. VALUE Map_deep_copy(VALUE _self) { Map* self = ruby_to_Map(_self); VALUE new_map = Map_new_this_type(_self); Map* new_self = ruby_to_Map(new_map); upb_strtable_iter it; for (upb_strtable_begin(&it, &self->table); !upb_strtable_done(&it); upb_strtable_next(&it)) { upb_value v = upb_strtable_iter_value(&it); void* mem = value_memory(&v); upb_value dup; void* dup_mem = value_memory(&dup); native_slot_deep_copy(self->value_type, dup_mem, mem); if (!upb_strtable_insert2(&new_self->table, upb_strtable_iter_key(&it), upb_strtable_iter_keylength(&it), dup)) { rb_raise(rb_eRuntimeError, "Error inserting value into new table"); } } return new_map; }
/* * call-seq: * Map.==(other) => boolean * * Compares this map to another. Maps are equal if they have identical key sets, * and for each key, the values in both maps compare equal. Elements are * compared as per normal Ruby semantics, by calling their :== methods (or * performing a more efficient comparison for primitive types). * * Maps with dissimilar key types or value types/typeclasses are never equal, * even if value comparison (for example, between integers and floats) would * have otherwise indicated that every element has equal value. */ VALUE Map_eq(VALUE _self, VALUE _other) { Map* self = ruby_to_Map(_self); Map* other; upb_strtable_iter it; // Allow comparisons to Ruby hashmaps by converting to a temporary Map // instance. Slow, but workable. if (TYPE(_other) == T_HASH) { VALUE other_map = Map_new_this_type(_self); Map_merge_into_self(other_map, _other); _other = other_map; } other = ruby_to_Map(_other); if (self == other) { return Qtrue; } if (self->key_type != other->key_type || self->value_type != other->value_type || self->value_type_class != other->value_type_class) { return Qfalse; } if (upb_strtable_count(&self->table) != upb_strtable_count(&other->table)) { return Qfalse; } // For each member of self, check that an equal member exists at the same key // in other. for (upb_strtable_begin(&it, &self->table); !upb_strtable_done(&it); upb_strtable_next(&it)) { upb_value v = upb_strtable_iter_value(&it); void* mem = value_memory(&v); upb_value other_v; void* other_mem = value_memory(&other_v); if (!upb_strtable_lookup2(&other->table, upb_strtable_iter_key(&it), upb_strtable_iter_keylength(&it), &other_v)) { // Not present in other map. return Qfalse; } if (!native_slot_eq(self->value_type, mem, other_mem)) { // Present, but value not equal. return Qfalse; } } return Qtrue; }
static void putmap(VALUE map, const upb_fielddef *f, upb_sink *sink, int depth) { if (map == Qnil) return; Map* self = ruby_to_Map(map); upb_sink subsink; upb_sink_startseq(sink, getsel(f, UPB_HANDLER_STARTSEQ), &subsink); assert(upb_fielddef_type(f) == UPB_TYPE_MESSAGE); const upb_fielddef* key_field = map_field_key(f); const upb_fielddef* value_field = map_field_value(f); Map_iter it; for (Map_begin(map, &it); !Map_done(&it); Map_next(&it)) { VALUE key = Map_iter_key(&it); VALUE value = Map_iter_value(&it); upb_sink entry_sink; upb_sink_startsubmsg(&subsink, getsel(f, UPB_HANDLER_STARTSUBMSG), &entry_sink); upb_sink_startmsg(&entry_sink); put_ruby_value(key, key_field, Qnil, depth + 1, &entry_sink); put_ruby_value(value, value_field, self->value_type_class, depth + 1, &entry_sink); upb_status status; upb_sink_endmsg(&entry_sink, &status); upb_sink_endsubmsg(&subsink, getsel(f, UPB_HANDLER_ENDSUBMSG)); } upb_sink_endseq(sink, getsel(f, UPB_HANDLER_ENDSEQ)); }
/* * call-seq: * Map.inspect => string * * Returns a string representing this map's elements. It will be formatted as * "{key => value, key => value, ...}", with each key and value string * representation computed by its own #inspect method. */ VALUE Map_inspect(VALUE _self) { Map* self = ruby_to_Map(_self); VALUE str = rb_str_new2("{"); bool first = true; VALUE inspect_sym = rb_intern("inspect"); upb_strtable_iter it; for (upb_strtable_begin(&it, &self->table); !upb_strtable_done(&it); upb_strtable_next(&it)) { VALUE key = table_key_to_ruby( self, upb_strtable_iter_key(&it), upb_strtable_iter_keylength(&it)); upb_value v = upb_strtable_iter_value(&it); void* mem = value_memory(&v); VALUE value = native_slot_get(self->value_type, self->value_type_class, mem); if (!first) { str = rb_str_cat2(str, ", "); } else { first = false; } str = rb_str_append(str, rb_funcall(key, inspect_sym, 0)); str = rb_str_cat2(str, "=>"); str = rb_str_append(str, rb_funcall(value, inspect_sym, 0)); } str = rb_str_cat2(str, "}"); return str; }
static void check_map_field_type(VALUE val, const upb_fielddef* field) { const upb_fielddef* key_field = map_field_key(field); const upb_fielddef* value_field = map_field_value(field); Map* self; if (!RB_TYPE_P(val, T_DATA) || !RTYPEDDATA_P(val) || RTYPEDDATA_TYPE(val) != &Map_type) { rb_raise(cTypeError, "Expected Map instance"); } self = ruby_to_Map(val); if (self->key_type != upb_fielddef_type(key_field)) { rb_raise(cTypeError, "Map key type does not match field's key type"); } if (self->value_type != upb_fielddef_type(value_field)) { rb_raise(cTypeError, "Map value type does not match field's value type"); } if (upb_fielddef_type(value_field) == UPB_TYPE_MESSAGE || upb_fielddef_type(value_field) == UPB_TYPE_ENUM) { if (self->value_type_class != get_def_obj(upb_fielddef_subdef(value_field))) { rb_raise(cTypeError, "Map value type has wrong message/enum class"); } } }
/* * call-seq: * Map.hash => hash_value * * Returns a hash value based on this map's contents. */ VALUE Map_hash(VALUE _self) { Map* self = ruby_to_Map(_self); st_index_t h = rb_hash_start(0); VALUE hash_sym = rb_intern("hash"); upb_strtable_iter it; for (upb_strtable_begin(&it, &self->table); !upb_strtable_done(&it); upb_strtable_next(&it)) { VALUE key = table_key_to_ruby( self, upb_strtable_iter_key(&it), upb_strtable_iter_keylength(&it)); upb_value v = upb_strtable_iter_value(&it); void* mem = value_memory(&v); VALUE value = native_slot_get(self->value_type, self->value_type_class, mem); h = rb_hash_uint(h, NUM2LONG(rb_funcall(key, hash_sym, 0))); h = rb_hash_uint(h, NUM2LONG(rb_funcall(value, hash_sym, 0))); } return INT2FIX(h); }
/* * call-seq: * Map.clear * * Removes all entries from the map. */ VALUE Map_clear(VALUE _self) { Map* self = ruby_to_Map(_self); // Uninit and reinit the table -- this is faster than iterating and doing a // delete-lookup on each key. upb_strtable_uninit(&self->table); if (!upb_strtable_init(&self->table, UPB_CTYPE_INT64)) { rb_raise(rb_eRuntimeError, "Unable to re-initialize table"); } return Qnil; }
// Used only internally -- shared by #merge and #initialize. VALUE Map_merge_into_self(VALUE _self, VALUE hashmap) { if (TYPE(hashmap) == T_HASH) { rb_hash_foreach(hashmap, merge_into_self_callback, _self); } else if (RB_TYPE_P(hashmap, T_DATA) && RTYPEDDATA_P(hashmap) && RTYPEDDATA_TYPE(hashmap) == &Map_type) { Map* self = ruby_to_Map(_self); Map* other = ruby_to_Map(hashmap); upb_strtable_iter it; if (self->key_type != other->key_type || self->value_type != other->value_type || self->value_type_class != other->value_type_class) { rb_raise(rb_eArgError, "Attempt to merge Map with mismatching types"); } for (upb_strtable_begin(&it, &other->table); !upb_strtable_done(&it); upb_strtable_next(&it)) { // Replace any existing value by issuing a 'remove' operation first. upb_value v; upb_value oldv; upb_strtable_remove2(&self->table, upb_strtable_iter_key(&it), upb_strtable_iter_keylength(&it), &oldv); v = upb_strtable_iter_value(&it); upb_strtable_insert2(&self->table, upb_strtable_iter_key(&it), upb_strtable_iter_keylength(&it), v); } } else { rb_raise(rb_eArgError, "Unknown type merging into Map"); } return _self; }
static VALUE Map_new_this_type(VALUE _self) { Map* self = ruby_to_Map(_self); VALUE new_map = Qnil; VALUE key_type = fieldtype_to_ruby(self->key_type); VALUE value_type = fieldtype_to_ruby(self->value_type); if (self->value_type_class != Qnil) { new_map = rb_funcall(CLASS_OF(_self), rb_intern("new"), 3, key_type, value_type, self->value_type_class); } else { new_map = rb_funcall(CLASS_OF(_self), rb_intern("new"), 2, key_type, value_type); } return new_map; }
/* * call-seq: * Map.has_key?(key) => bool * * Returns true if the given key is present in the map. Throws an exception if * the key has the wrong type. */ VALUE Map_has_key(VALUE _self, VALUE key) { Map* self = ruby_to_Map(_self); char keybuf[TABLE_KEY_BUF_LENGTH]; const char* keyval = NULL; size_t length = 0; key = table_key(self, key, keybuf, &keyval, &length); if (upb_strtable_lookup2(&self->table, keyval, length, NULL)) { return Qtrue; } else { return Qfalse; } }
/* * call-seq: * Map.new(key_type, value_type, value_typeclass = nil, init_hashmap = {}) * => new map * * Allocates a new Map container. This constructor may be called with 2, 3, or 4 * arguments. The first two arguments are always present and are symbols (taking * on the same values as field-type symbols in message descriptors) that * indicate the type of the map key and value fields. * * The supported key types are: :int32, :int64, :uint32, :uint64, :bool, * :string, :bytes. * * The supported value types are: :int32, :int64, :uint32, :uint64, :bool, * :string, :bytes, :enum, :message. * * The third argument, value_typeclass, must be present if value_type is :enum * or :message. As in RepeatedField#new, this argument must be a message class * (for :message) or enum module (for :enum). * * The last argument, if present, provides initial content for map. Note that * this may be an ordinary Ruby hashmap or another Map instance with identical * key and value types. Also note that this argument may be present whether or * not value_typeclass is present (and it is unambiguously separate from * value_typeclass because value_typeclass's presence is strictly determined by * value_type). The contents of this initial hashmap or Map instance are * shallow-copied into the new Map: the original map is unmodified, but * references to underlying objects will be shared if the value type is a * message type. */ VALUE Map_init(int argc, VALUE* argv, VALUE _self) { Map* self = ruby_to_Map(_self); int init_value_arg; // We take either two args (:key_type, :value_type), three args (:key_type, // :value_type, "ValueMessageType"), or four args (the above plus an initial // hashmap). if (argc < 2 || argc > 4) { rb_raise(rb_eArgError, "Map constructor expects 2, 3 or 4 arguments."); } self->key_type = ruby_to_fieldtype(argv[0]); self->value_type = ruby_to_fieldtype(argv[1]); // Check that the key type is an allowed type. switch (self->key_type) { case UPB_TYPE_INT32: case UPB_TYPE_INT64: case UPB_TYPE_UINT32: case UPB_TYPE_UINT64: case UPB_TYPE_BOOL: case UPB_TYPE_STRING: case UPB_TYPE_BYTES: // These are OK. break; default: rb_raise(rb_eArgError, "Invalid key type for map."); } init_value_arg = 2; if (needs_typeclass(self->value_type) && argc > 2) { self->value_type_class = argv[2]; validate_type_class(self->value_type, self->value_type_class); init_value_arg = 3; } // Table value type is always UINT64: this ensures enough space to store the // native_slot value. if (!upb_strtable_init(&self->table, UPB_CTYPE_UINT64)) { rb_raise(rb_eRuntimeError, "Could not allocate table."); } if (argc > init_value_arg) { Map_merge_into_self(_self, argv[init_value_arg]); } return Qnil; }
/* * call-seq: * Map.delete(key) => old_value * * Deletes the value at the given key, if any, returning either the old value or * nil if none was present. Throws an exception if the key is of the wrong type. */ VALUE Map_delete(VALUE _self, VALUE key) { Map* self = ruby_to_Map(_self); char keybuf[TABLE_KEY_BUF_LENGTH]; const char* keyval = NULL; size_t length = 0; upb_value v; key = table_key(self, key, keybuf, &keyval, &length); if (upb_strtable_remove2(&self->table, keyval, length, &v)) { void* mem = value_memory(&v); return native_slot_get(self->value_type, self->value_type_class, mem); } else { return Qnil; } }
/* * call-seq: * Map.keys => [list_of_keys] * * Returns the list of keys contained in the map, in unspecified order. */ VALUE Map_keys(VALUE _self) { Map* self = ruby_to_Map(_self); VALUE ret = rb_ary_new(); upb_strtable_iter it; for (upb_strtable_begin(&it, &self->table); !upb_strtable_done(&it); upb_strtable_next(&it)) { VALUE key = table_key_to_ruby( self, upb_strtable_iter_key(&it), upb_strtable_iter_keylength(&it)); rb_ary_push(ret, key); } return ret; }
/* * call-seq: * Map.values => [list_of_values] * * Returns the list of values contained in the map, in unspecified order. */ VALUE Map_values(VALUE _self) { Map* self = ruby_to_Map(_self); VALUE ret = rb_ary_new(); upb_strtable_iter it; for (upb_strtable_begin(&it, &self->table); !upb_strtable_done(&it); upb_strtable_next(&it)) { upb_value v = upb_strtable_iter_value(&it); void* mem = value_memory(&v); VALUE value = native_slot_get(self->value_type, self->value_type_class, mem); rb_ary_push(ret, value); } return ret; }
/* * call-seq: * Map.[]=(key, value) => value * * Inserts or overwrites the value at the given key with the given new value. * Throws an exception if the key type is incorrect. Returns the new value that * was just inserted. */ VALUE Map_index_set(VALUE _self, VALUE key, VALUE value) { Map* self = ruby_to_Map(_self); char keybuf[TABLE_KEY_BUF_LENGTH]; const char* keyval = NULL; size_t length = 0; table_key(self, key, keybuf, &keyval, &length); upb_value v; void* mem = value_memory(&v); native_slot_set(self->value_type, self->value_type_class, mem, value); // Replace any existing value by issuing a 'remove' operation first. upb_strtable_remove2(&self->table, keyval, length, NULL); if (!upb_strtable_insert2(&self->table, keyval, length, v)) { rb_raise(rb_eRuntimeError, "Could not insert into table"); } // Ruby hashmap's :[]= method also returns the inserted value. return value; }
/* * call-seq: * Map.each(&block) * * Invokes &block on each |key, value| pair in the map, in unspecified order. * Note that Map also includes Enumerable; map thus acts like a normal Ruby * sequence. */ VALUE Map_each(VALUE _self) { Map* self = ruby_to_Map(_self); upb_strtable_iter it; for (upb_strtable_begin(&it, &self->table); !upb_strtable_done(&it); upb_strtable_next(&it)) { VALUE key = table_key_to_ruby( self, upb_strtable_iter_key(&it), upb_strtable_iter_keylength(&it)); upb_value v = upb_strtable_iter_value(&it); void* mem = value_memory(&v); VALUE value = native_slot_get(self->value_type, self->value_type_class, mem); rb_yield_values(2, key, value); } return Qnil; }
/* * call-seq: * Map.to_h => {} * * Returns a Ruby Hash object containing all the values within the map */ VALUE Map_to_h(VALUE _self) { Map* self = ruby_to_Map(_self); VALUE hash = rb_hash_new(); upb_strtable_iter it; for (upb_strtable_begin(&it, &self->table); !upb_strtable_done(&it); upb_strtable_next(&it)) { VALUE key = table_key_to_ruby( self, upb_strtable_iter_key(&it), upb_strtable_iter_keylength(&it)); upb_value v = upb_strtable_iter_value(&it); void* mem = value_memory(&v); VALUE value = native_slot_get(self->value_type, self->value_type_class, mem); if (self->value_type == UPB_TYPE_MESSAGE) { value = Message_to_h(value); } rb_hash_aset(hash, key, value); } return hash; }
// Internal method: map iterator initialization (used for serialization). void Map_begin(VALUE _self, Map_iter* iter) { Map* self = ruby_to_Map(_self); iter->self = self; upb_strtable_begin(&iter->it, &self->table); }
/* * call-seq: * Map.length * * Returns the number of entries (key-value pairs) in the map. */ VALUE Map_length(VALUE _self) { Map* self = ruby_to_Map(_self); return ULL2NUM(upb_strtable_count(&self->table)); }