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 58
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 359
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: nil, value: nil, map: nil) click to toggle source

Constructor that uses the type information from the given FieldDescriptor to configure the new Map instance. @param field [FieldDescriptor] Type information for the new Map @param arena [Arena] Owning message’s arena @param value [Hash|Map] Initial value @param map [::FFI::Pointer] Existing upb_Map

# File lib/google/protobuf/ffi/map.rb, line 401
def self.construct_for_field(field, arena: nil, 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 423
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 415
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 214
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 104
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 119
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 149
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 138
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 197
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 291
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

Freezes the map object. We have to intercept this so we can freeze the underlying representation, not just the Ruby wrapper. Returns self.

Calls superclass method
# File lib/google/protobuf/ffi/map.rb, line 177
def freeze
  if method(:frozen?).super_method.call
    unless Google::Protobuf::FFI.map_frozen? @map_ptr
      raise RuntimeError.new "Underlying representation of map still mutable despite frozen wrapper"
    end
    return self
  end
  unless Google::Protobuf::FFI.map_frozen? @map_ptr
    mini_table = (value_type == :message) ? Google::Protobuf::FFI.get_mini_table(@descriptor) : nil
    Google::Protobuf::FFI.map_freeze(@map_ptr, mini_table)
  end
  super
end
frozen?() click to toggle source

Is this object frozen? Returns true if either this Ruby wrapper or the underlying representation are frozen. Freezes the wrapper if the underlying representation is already frozen but this wrapper isn’t.

Calls superclass method
# File lib/google/protobuf/ffi/map.rb, line 165
def frozen?
  unless Google::Protobuf::FFI.map_frozen? @map_ptr
    raise RuntimeError.new "Ruby frozen Map with mutable representation" if super
    return false
  end
  method(:freeze).super_method.call unless super
  true
end
has_key?(key) click to toggle source
# File lib/google/protobuf/ffi/map.rb, line 127
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 232
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 257
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 75
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 155
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 280
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 246
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 89
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 314
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 322
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 305
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 355
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 331
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