class Google::Protobuf::Map

Attributes

arena[RW]

Public Class Methods

new(key_type, value_type, value_typeclass = nil, init_hashmap = {}) click to toggle source
→ 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.

# File lib/google/protobuf/ffi/map.rb, line 56
def self.new(key_type, value_type, value_typeclass = nil, init_hashmap = {})
  instance = allocate
  # TODO This argument mangling doesn't agree with the type signature,
  # but does align with the text of the comments and is required to make unit tests pass.
  if init_hashmap.empty? and ![:enum, :message].include?(value_type)
    init_hashmap = value_typeclass
    value_typeclass = nil
  end
  instance.send(:initialize, key_type, value_type, value_type_class: value_typeclass, initial_values: init_hashmap)
  instance
end
new(key_type, value_type, value_type_class: nil, initial_values: nil, arena: nil, map: nil, descriptor: nil, name: nil) click to toggle source
# File lib/google/protobuf/ffi/map.rb, line 339
def initialize(key_type, value_type, value_type_class: nil, initial_values: nil, arena: nil, map: nil, descriptor: nil, name: nil)
  @name = name || 'Map'

  unless [:int32, :int64, :uint32, :uint64, :bool, :string, :bytes].include? key_type
    raise ArgumentError.new "Invalid key type for map." #TODO improve error message to include what type was passed
  end
  @key_type = key_type

  unless [:int32, :int64, :uint32, :uint64, :bool, :string, :bytes, :enum, :message].include? value_type
    raise ArgumentError.new "Invalid value type for map." #TODO improve error message to include what type was passed
  end
  @value_type = value_type

  if !descriptor.nil?
    raise ArgumentError "Expected descriptor to be a Descriptor or EnumDescriptor" unless [EnumDescriptor, Descriptor].include? descriptor.class
    @descriptor = descriptor
  elsif [:message, :enum].include? value_type
    raise ArgumentError.new "Expected at least 3 arguments for message/enum." if value_type_class.nil?
    descriptor = value_type_class.respond_to?(:descriptor) ? value_type_class.descriptor : nil
    raise ArgumentError.new "Type class #{value_type_class} has no descriptor. Please pass a class or enum as returned by the DescriptorPool." if descriptor.nil?
    @descriptor = descriptor
  else
    @descriptor = nil
  end

  @arena = arena || Google::Protobuf::FFI.create_arena
  @map_ptr = map || Google::Protobuf::FFI.create_map(@arena, @key_type, @value_type)

  internal_merge_into_self(initial_values) unless initial_values.nil?

  # Should always be the last expression of the initializer to avoid
  # leaking references to this object before construction is complete.
  OBJECT_CACHE.try_add(@map_ptr.address, self)
end

Private Class Methods

construct_for_field(field, arena, value: nil, map: nil) click to toggle source

@param field [FieldDescriptor] Descriptor of the field where the RepeatedField will be assigned @param values [Hash|Map] Initial value; may be nil or empty @param arena [Arena] Owning message’s arena

# File lib/google/protobuf/ffi/map.rb, line 377
def self.construct_for_field(field, arena, value: nil, map: nil)
  raise ArgumentError.new "Expected Hash object as initializer value for map field '#{field.name}' (given #{value.class})." unless value.nil? or value.is_a? Hash
  instance = allocate
  raise ArgumentError.new "Expected field with type :message, instead got #{field.class}" unless field.type == :message
  message_descriptor = field.send(:subtype)
  key_field_def = Google::Protobuf::FFI.get_field_by_number(message_descriptor, 1)
  key_field_type = Google::Protobuf::FFI.get_type(key_field_def)

  value_field_def = Google::Protobuf::FFI.get_field_by_number(message_descriptor, 2)
  value_field_type = Google::Protobuf::FFI.get_type(value_field_def)
  instance.send(:initialize, key_field_type, value_field_type, initial_values: value, name: field.name, arena: arena, map: map, descriptor: value_field_def.subtype)
  instance
end
deep_copy(map) click to toggle source
# File lib/google/protobuf/ffi/map.rb, line 399
def self.deep_copy(map)
  instance = allocate
  instance.send(:initialize, map.send(:key_type), map.send(:value_type), descriptor: map.send(:descriptor))
  map.send(:each_msg_val) do |key_message_value, value_message_value|
    Google::Protobuf::FFI.map_set(instance.send(:map_ptr), key_message_value, message_value_deep_copy(value_message_value, map.send(:value_type), map.send(:descriptor), instance.send(:arena)), instance.send(:arena))
  end
  instance
end
private_constructor(key_type, value_type, descriptor, initial_values: nil, arena: nil) click to toggle source
# File lib/google/protobuf/ffi/map.rb, line 391
def self.private_constructor(key_type, value_type, descriptor, initial_values: nil, arena: nil)
  instance = allocate
  instance.send(:initialize, key_type, value_type, descriptor: descriptor, initial_values: initial_values, arena: arena)
  instance
end

Public Instance Methods

==(other) → boolean click to toggle source

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.

# File lib/google/protobuf/ffi/map.rb, line 194
def ==(other)
  if other.is_a? Hash
    other = self.class.send(:private_constructor, key_type, value_type, descriptor, initial_values: other)
  elsif !other.is_a? Google::Protobuf::Map
    return false
  end

  return true if object_id == other.object_id
  return false if key_type != other.send(:key_type) or value_type != other.send(:value_type) or descriptor != other.send(:descriptor) or length != other.length
  other_map_ptr = other.send(:map_ptr)
  each_msg_val do |key_message_value, value_message_value|
    other_value = Google::Protobuf::FFI::MessageValue.new
    return false unless Google::Protobuf::FFI.map_get(other_map_ptr, key_message_value, other_value)
    return false unless Google::Protobuf::FFI.message_value_equal(value_message_value, other_value, value_type, descriptor)
  end
  true
end
[](key) → value click to toggle source

Accesses the element at the given key. Throws an exception if the key type is incorrect. Returns nil when the key is not present in the map.

# File lib/google/protobuf/ffi/map.rb, line 102
def [](key)
  value = Google::Protobuf::FFI::MessageValue.new
  key_message_value = convert_ruby_to_upb(key, arena, key_type, nil)
  if Google::Protobuf::FFI.map_get(@map_ptr, key_message_value, value)
     convert_upb_to_ruby(value, value_type, descriptor, arena)
  end
end
[]=(key, value) → value click to toggle source

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.

# File lib/google/protobuf/ffi/map.rb, line 117
def []=(key, value)
  raise FrozenError.new "can't modify frozen #{self.class}" if frozen?
  key_message_value = convert_ruby_to_upb(key, arena, key_type, nil)
  value_message_value = convert_ruby_to_upb(value, arena, value_type, descriptor)
  Google::Protobuf::FFI.map_set(@map_ptr, key_message_value, value_message_value, arena)
  value
end
clear() click to toggle source
# File lib/google/protobuf/ffi/map.rb, line 147
def clear
  raise FrozenError.new "can't modify frozen #{self.class}" if frozen?
  Google::Protobuf::FFI.map_clear(@map_ptr)
  nil
end
clone()
Alias for: dup
delete(key) → old_value click to toggle source

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.

# File lib/google/protobuf/ffi/map.rb, line 136
def delete(key)
  raise FrozenError.new "can't modify frozen #{self.class}" if frozen?
  value = Google::Protobuf::FFI::MessageValue.new
  key_message_value = convert_ruby_to_upb(key, arena, key_type, nil)
  if Google::Protobuf::FFI.map_delete(@map_ptr, key_message_value, value)
    convert_upb_to_ruby(value, value_type, descriptor, arena)
  else
    nil
  end
end
dup → new_map click to toggle source

Duplicates this map with a shallow copy. References to all non-primitive element objects (e.g., submessages) are shared.

# File lib/google/protobuf/ffi/map.rb, line 177
def dup
  internal_dup
end
Also aliased as: clone
each(&block) click to toggle source

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.

# File lib/google/protobuf/ffi/map.rb, line 271
def each &block
  each_msg_val do |key_message_value, value_message_value|
    key_value = convert_upb_to_ruby(key_message_value, key_type)
    value_value = convert_upb_to_ruby(value_message_value, value_type, descriptor, arena)
    yield key_value, value_value
  end
  nil
end
freeze() click to toggle source
Calls superclass method
# File lib/google/protobuf/ffi/map.rb, line 158
def freeze
  return self if frozen?
  super
  @arena.pin self
  if value_type == :message
    internal_iterator do |iterator|
      value_message_value = Google::Protobuf::FFI.map_value(@map_ptr, iterator)
      convert_upb_to_ruby(value_message_value, value_type, descriptor, arena).freeze
    end
  end
  self
end
has_key?(key) click to toggle source
# File lib/google/protobuf/ffi/map.rb, line 125
def has_key?(key)
  key_message_value = convert_ruby_to_upb(key, arena, key_type, nil)
  Google::Protobuf::FFI.map_get(@map_ptr, key_message_value, nil)
end
hash() click to toggle source
# File lib/google/protobuf/ffi/map.rb, line 212
def hash
  return_value = 0
  each_msg_val do |key_message_value, value_message_value|
    return_value = Google::Protobuf::FFI.message_value_hash(key_message_value, key_type, nil, return_value)
    return_value = Google::Protobuf::FFI.message_value_hash(value_message_value, value_type, descriptor, return_value)
  end
  return_value
end
inspect() click to toggle source
# File lib/google/protobuf/ffi/map.rb, line 237
def inspect
  key_value_pairs = []
  each_msg_val do |key_message_value, value_message_value|
    key_string = convert_upb_to_ruby(key_message_value, key_type).inspect
    if value_type == :message
      sub_msg_descriptor = Google::Protobuf::FFI.get_subtype_as_message(descriptor)
      value_string = sub_msg_descriptor.msgclass.send(:inspect_internal, value_message_value[:msg_val])
    else
      value_string = convert_upb_to_ruby(value_message_value, value_type, descriptor).inspect
    end
    key_value_pairs << "#{key_string}=>#{value_string}"
  end
  "{#{key_value_pairs.join(", ")}}"
end
keys → [list_of_keys] click to toggle source

Returns the list of keys contained in the map, in unspecified order.

# File lib/google/protobuf/ffi/map.rb, line 73
def keys
  return_value = []
  internal_iterator do |iterator|
    key_message_value = Google::Protobuf::FFI.map_key(@map_ptr, iterator)
    return_value << convert_upb_to_ruby(key_message_value, key_type)
  end
  return_value
end
length() click to toggle source
# File lib/google/protobuf/ffi/map.rb, line 153
def length
  Google::Protobuf::FFI.map_size(@map_ptr)
end
Also aliased as: size
merge(other_map) → map click to toggle source

Copies key/value pairs from other_map into a copy of this map. If a key is set in other_map and this map, the value from other_map overwrites the value in the new copy of this map. Returns the new copy of this map with merged contents.

# File lib/google/protobuf/ffi/map.rb, line 260
def merge(other)
  internal_merge(other)
end
size()
Alias for: length
to_h → {} click to toggle source

Returns a Ruby Hash object containing all the values within the map

# File lib/google/protobuf/ffi/map.rb, line 226
def to_h
  return {} if map_ptr.nil? or map_ptr.null?
  return_value = {}
  each_msg_val do |key_message_value, value_message_value|
    hash_key = convert_upb_to_ruby(key_message_value, key_type)
    hash_value = scalar_create_hash(value_message_value, value_type, msg_or_enum_descriptor: descriptor)
    return_value[hash_key] = hash_value
  end
  return_value
end
values → [list_of_values] click to toggle source

Returns the list of values contained in the map, in unspecified order.

# File lib/google/protobuf/ffi/map.rb, line 87
def values
  return_value = []
  internal_iterator do |iterator|
    value_message_value = Google::Protobuf::FFI.map_value(@map_ptr, iterator)
    return_value << convert_upb_to_ruby(value_message_value, value_type, descriptor, arena)
  end
  return_value
end

Private Instance Methods

each_msg_val() { |key_message_value, value_message_value| ... } click to toggle source
# File lib/google/protobuf/ffi/map.rb, line 294
def each_msg_val &block
  internal_iterator do |iterator|
    key_message_value = Google::Protobuf::FFI.map_key(@map_ptr, iterator)
    value_message_value = Google::Protobuf::FFI.map_value(@map_ptr, iterator)
    yield key_message_value, value_message_value
  end
end
internal_dup() click to toggle source
# File lib/google/protobuf/ffi/map.rb, line 302
def internal_dup
  instance = self.class.send(:private_constructor, key_type, value_type, descriptor, arena: arena)
  new_map_ptr = instance.send(:map_ptr)
  each_msg_val do |key_message_value, value_message_value|
    Google::Protobuf::FFI.map_set(new_map_ptr, key_message_value, value_message_value, arena)
  end
  instance
end
internal_iterator() { |iter_size_t| ... } click to toggle source
# File lib/google/protobuf/ffi/map.rb, line 285
def internal_iterator
  iter = ::FFI::MemoryPointer.new(:size_t, 1)
  iter.write(:size_t, Google::Protobuf::FFI::Upb_Map_Begin)
  while Google::Protobuf::FFI.map_next(@map_ptr, iter) do
    iter_size_t = iter.read(:size_t)
    yield iter_size_t
  end
end
internal_merge(other) click to toggle source
# File lib/google/protobuf/ffi/map.rb, line 335
def internal_merge(other)
  internal_dup.internal_merge_into_self(other)
end
internal_merge_into_self(other) click to toggle source
# File lib/google/protobuf/ffi/map.rb, line 311
def internal_merge_into_self(other)
  case other
  when Hash
    other.each do |key, value|
      key_message_value = convert_ruby_to_upb(key, arena, key_type, nil)
      value_message_value = convert_ruby_to_upb(value, arena, value_type, descriptor)
      Google::Protobuf::FFI.map_set(@map_ptr, key_message_value, value_message_value, arena)
    end
  when Google::Protobuf::Map
    unless key_type == other.send(:key_type) and value_type == other.send(:value_type) and descriptor == other.descriptor
      raise ArgumentError.new "Attempt to merge Map with mismatching types" #TODO Improve error message by adding type information
    end
    arena.fuse(other.send(:arena))
    iter = ::FFI::MemoryPointer.new(:size_t, 1)
    iter.write(:size_t, Google::Protobuf::FFI::Upb_Map_Begin)
    other.send(:each_msg_val) do |key_message_value, value_message_value|
      Google::Protobuf::FFI.map_set(@map_ptr, key_message_value, value_message_value, arena)
    end
  else
    raise ArgumentError.new "Unknown type merging into Map" #TODO improve this error message by including type information
  end
  self
end