Protocol Buffers - Google's data interchange format (grpc依赖)
https://developers.google.com/protocol-buffers/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
411 lines
15 KiB
411 lines
15 KiB
# Protocol Buffers - Google's data interchange format |
|
# Copyright 2008 Google Inc. All rights reserved. |
|
# |
|
# Use of this source code is governed by a BSD-style |
|
# license that can be found in the LICENSE file or at |
|
# https://developers.google.com/open-source/licenses/bsd |
|
|
|
# |
|
# This class makes RepeatedField act (almost-) like a Ruby Array. |
|
# It has convenience methods that extend the core C or Java based |
|
# methods. |
|
# |
|
# This is a best-effort to mirror Array behavior. Two comments: |
|
# 1) patches always welcome :) |
|
# 2) if performance is an issue, feel free to rewrite the method |
|
# in C. The source code has plenty of examples |
|
# |
|
# KNOWN ISSUES |
|
# - #[]= doesn't allow less used approaches such as `arr[1, 2] = 'fizz'` |
|
# - #concat should return the orig array |
|
# - #push should accept multiple arguments and push them all at the same time |
|
# |
|
module Google |
|
module Protobuf |
|
class FFI |
|
# Array |
|
attach_function :append_array, :upb_Array_Append, [:Array, MessageValue.by_value, Internal::Arena], :bool |
|
attach_function :get_msgval_at, :upb_Array_Get, [:Array, :size_t], MessageValue.by_value |
|
attach_function :create_array, :upb_Array_New, [Internal::Arena, CType], :Array |
|
attach_function :array_resize, :upb_Array_Resize, [:Array, :size_t, Internal::Arena], :bool |
|
attach_function :array_set, :upb_Array_Set, [:Array, :size_t, MessageValue.by_value], :void |
|
attach_function :array_size, :upb_Array_Size, [:Array], :size_t |
|
attach_function :array_freeze, :upb_Array_Freeze, [:Array, MiniTable.by_ref], :void |
|
attach_function :array_frozen?, :upb_Array_IsFrozen, [:Array], :bool |
|
end |
|
|
|
class RepeatedField |
|
include Enumerable |
|
|
|
## |
|
# call-seq: |
|
# RepeatedField.new(type, type_class = nil, initial_values = []) |
|
# |
|
# Creates a new repeated field. The provided type must be a Ruby symbol, and |
|
# an take on the same values as those accepted by FieldDescriptor#type=. If |
|
# the type is :message or :enum, type_class must be non-nil, and must be the |
|
# Ruby class or module returned by Descriptor#msgclass or |
|
# EnumDescriptor#enummodule, respectively. An initial list of elements may also |
|
# be provided. |
|
def self.new(type, type_class = nil, initial_values = []) |
|
instance = allocate |
|
# TODO This argument mangling doesn't agree with the type signature in the comments |
|
# but is required to make unit tests pass; |
|
if type_class.is_a?(Enumerable) and initial_values.empty? and ![:enum, :message].include?(type) |
|
initial_values = type_class |
|
type_class = nil |
|
end |
|
instance.send(:initialize, type, type_class: type_class, initial_values: initial_values) |
|
instance |
|
end |
|
|
|
## |
|
# call-seq: |
|
# RepeatedField.each(&block) |
|
# |
|
# Invokes the block once for each element of the repeated field. RepeatedField |
|
# also includes Enumerable; combined with this method, the repeated field thus |
|
# acts like an ordinary Ruby sequence. |
|
def each &block |
|
each_msg_val do |element| |
|
yield(convert_upb_to_ruby(element, type, descriptor, arena)) |
|
end |
|
self |
|
end |
|
|
|
def [](*args) |
|
count = length |
|
if args.size < 1 |
|
raise ArgumentError.new "Index or range is a required argument." |
|
end |
|
if args[0].is_a? Range |
|
if args.size > 1 |
|
raise ArgumentError.new "Expected 1 when passing Range argument, but got #{args.size}" |
|
end |
|
range = args[0] |
|
# Handle begin-less and/or endless ranges, when supported. |
|
index_of_first = range.respond_to?(:begin) ? range.begin : range.last |
|
index_of_first = 0 if index_of_first.nil? |
|
end_of_range = range.respond_to?(:end) ? range.end : range.last |
|
index_of_last = end_of_range.nil? ? -1 : end_of_range |
|
|
|
if index_of_last < 0 |
|
index_of_last += count |
|
end |
|
unless range.exclude_end? and !end_of_range.nil? |
|
index_of_last += 1 |
|
end |
|
index_of_first += count if index_of_first < 0 |
|
length = index_of_last - index_of_first |
|
return [] if length.zero? |
|
elsif args[0].is_a? Integer |
|
index_of_first = args[0] |
|
index_of_first += count if index_of_first < 0 |
|
if args.size > 2 |
|
raise ArgumentError.new "Expected 1 or 2 arguments, but got #{args.size}" |
|
end |
|
if args.size == 1 # No length specified, return one element |
|
if array.null? or index_of_first < 0 or index_of_first >= count |
|
return nil |
|
else |
|
return convert_upb_to_ruby(Google::Protobuf::FFI.get_msgval_at(array, index_of_first), type, descriptor, arena) |
|
end |
|
else |
|
length = [args[1],count].min |
|
end |
|
else |
|
raise NotImplementedError |
|
end |
|
|
|
if array.null? or index_of_first < 0 or index_of_first >= count |
|
nil |
|
else |
|
if index_of_first + length > count |
|
length = count - index_of_first |
|
end |
|
if length < 0 |
|
nil |
|
else |
|
subarray(index_of_first, length) |
|
end |
|
end |
|
end |
|
alias at [] |
|
|
|
|
|
def []=(index, value) |
|
raise FrozenError if frozen? |
|
count = length |
|
index += count if index < 0 |
|
return nil if index < 0 |
|
if index >= count |
|
resize(index+1) |
|
empty_message_value = Google::Protobuf::FFI::MessageValue.new # Implicitly clear |
|
count.upto(index-1) do |i| |
|
Google::Protobuf::FFI.array_set(array, i, empty_message_value) |
|
end |
|
end |
|
Google::Protobuf::FFI.array_set(array, index, convert_ruby_to_upb(value, arena, type, descriptor)) |
|
nil |
|
end |
|
|
|
def push(*elements) |
|
raise FrozenError if frozen? |
|
internal_push(*elements) |
|
end |
|
|
|
def <<(element) |
|
raise FrozenError if frozen? |
|
push element |
|
end |
|
|
|
def replace(replacements) |
|
raise FrozenError if frozen? |
|
clear |
|
push(*replacements) |
|
end |
|
|
|
def clear |
|
raise FrozenError if frozen? |
|
resize 0 |
|
self |
|
end |
|
|
|
def length |
|
array.null? ? 0 : Google::Protobuf::FFI.array_size(array) |
|
end |
|
alias size :length |
|
|
|
## |
|
# 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. |
|
def frozen? |
|
unless Google::Protobuf::FFI.array_frozen? array |
|
raise RuntimeError.new "Ruby frozen RepeatedField with mutable representation" if super |
|
return false |
|
end |
|
method(:freeze).super_method.call unless super |
|
true |
|
end |
|
|
|
## |
|
# Freezes the RepeatedField object. We have to intercept this so we can |
|
# freeze the underlying representation, not just the Ruby wrapper. Returns |
|
# self. |
|
def freeze |
|
if method(:frozen?).super_method.call |
|
unless Google::Protobuf::FFI.array_frozen? array |
|
raise RuntimeError.new "Underlying representation of repeated field still mutable despite frozen wrapper" |
|
end |
|
return self |
|
end |
|
unless Google::Protobuf::FFI.array_frozen? array |
|
mini_table = (type == :message) ? Google::Protobuf::FFI.get_mini_table(@descriptor) : nil |
|
Google::Protobuf::FFI.array_freeze(array, mini_table) |
|
end |
|
super |
|
end |
|
|
|
def dup |
|
instance = self.class.allocate |
|
instance.send(:initialize, type, descriptor: descriptor, arena: arena) |
|
each_msg_val do |element| |
|
instance.send(:append_msg_val, element) |
|
end |
|
instance |
|
end |
|
alias clone dup |
|
|
|
def ==(other) |
|
return true if other.object_id == object_id |
|
if other.is_a? RepeatedField |
|
return false unless other.length == length |
|
each_msg_val_with_index do |msg_val, i| |
|
other_msg_val = Google::Protobuf::FFI.get_msgval_at(other.send(:array), i) |
|
unless Google::Protobuf::FFI.message_value_equal(msg_val, other_msg_val, type, descriptor) |
|
return false |
|
end |
|
end |
|
return true |
|
elsif other.is_a? Enumerable |
|
return to_ary == other.to_a |
|
end |
|
false |
|
end |
|
|
|
## |
|
# call-seq: |
|
# RepeatedField.to_ary => array |
|
# |
|
# Used when converted implicitly into array, e.g. compared to an Array. |
|
# Also called as a fallback of Object#to_a |
|
def to_ary |
|
return_value = [] |
|
each do |element| |
|
return_value << element |
|
end |
|
return_value |
|
end |
|
|
|
def hash |
|
return_value = 0 |
|
each_msg_val do |msg_val| |
|
return_value = Google::Protobuf::FFI.message_value_hash(msg_val, type, descriptor, return_value) |
|
end |
|
return_value |
|
end |
|
|
|
def +(other) |
|
if other.is_a? RepeatedField |
|
if type != other.instance_variable_get(:@type) or descriptor != other.instance_variable_get(:@descriptor) |
|
raise ArgumentError.new "Attempt to append RepeatedField with different element type." |
|
end |
|
fuse_arena(other.send(:arena)) |
|
super_set = dup |
|
other.send(:each_msg_val) do |msg_val| |
|
super_set.send(:append_msg_val, msg_val) |
|
end |
|
super_set |
|
elsif other.is_a? Enumerable |
|
super_set = dup |
|
super_set.push(*other.to_a) |
|
else |
|
raise ArgumentError.new "Unknown type appending to RepeatedField" |
|
end |
|
end |
|
|
|
def concat(other) |
|
raise ArgumentError.new "Expected Enumerable, but got #{other.class}" unless other.is_a? Enumerable |
|
push(*other.to_a) |
|
end |
|
|
|
private |
|
include Google::Protobuf::Internal::Convert |
|
|
|
attr :name, :arena, :array, :type, :descriptor |
|
|
|
def internal_push(*elements) |
|
elements.each do |element| |
|
append_msg_val convert_ruby_to_upb(element, arena, type, descriptor) |
|
end |
|
self |
|
end |
|
|
|
def pop_one |
|
raise FrozenError if frozen? |
|
count = length |
|
return nil if length.zero? |
|
last_element = Google::Protobuf::FFI.get_msgval_at(array, count-1) |
|
return_value = convert_upb_to_ruby(last_element, type, descriptor, arena) |
|
resize(count-1) |
|
return_value |
|
end |
|
|
|
def subarray(start, length) |
|
return_result = [] |
|
(start..(start + length - 1)).each do |i| |
|
element = Google::Protobuf::FFI.get_msgval_at(array, i) |
|
return_result << convert_upb_to_ruby(element, type, descriptor, arena) |
|
end |
|
return_result |
|
end |
|
|
|
def each_msg_val_with_index &block |
|
n = array.null? ? 0 : Google::Protobuf::FFI.array_size(array) |
|
0.upto(n-1) do |i| |
|
yield Google::Protobuf::FFI.get_msgval_at(array, i), i |
|
end |
|
end |
|
|
|
def each_msg_val &block |
|
each_msg_val_with_index do |msg_val, _| |
|
yield msg_val |
|
end |
|
end |
|
|
|
# @param msg_val [Google::Protobuf::FFI::MessageValue] Value to append |
|
def append_msg_val(msg_val) |
|
unless Google::Protobuf::FFI.append_array(array, msg_val, arena) |
|
raise NoMemoryError.new "Could not allocate room for #{msg_val} in Arena" |
|
end |
|
end |
|
|
|
# @param new_size [Integer] New size of the array |
|
def resize(new_size) |
|
unless Google::Protobuf::FFI.array_resize(array, new_size, arena) |
|
raise NoMemoryError.new "Array resize to #{new_size} failed!" |
|
end |
|
end |
|
|
|
def initialize(type, type_class: nil, initial_values: nil, name: nil, arena: nil, array: nil, descriptor: nil) |
|
@name = name || 'RepeatedField' |
|
raise ArgumentError.new "Expected argument type to be a Symbol" unless type.is_a? Symbol |
|
field_number = Google::Protobuf::FFI::FieldType[type] |
|
raise ArgumentError.new "Unsupported type '#{type}'" if field_number.nil? |
|
if !descriptor.nil? |
|
@descriptor = descriptor |
|
elsif [:message, :enum].include? type |
|
raise ArgumentError.new "Expected at least 2 arguments for message/enum." if type_class.nil? |
|
descriptor = type_class.respond_to?(:descriptor) ? type_class.descriptor : nil |
|
raise ArgumentError.new "Type class #{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 |
|
@type = type |
|
|
|
@arena = arena || Google::Protobuf::FFI.create_arena |
|
@array = array || Google::Protobuf::FFI.create_array(@arena, @type) |
|
unless initial_values.nil? |
|
unless initial_values.is_a? Enumerable |
|
raise ArgumentError.new "Expected array as initializer value for repeated field '#{name}' (given #{initial_values.class})." |
|
end |
|
internal_push(*initial_values) |
|
end |
|
|
|
# Should always be the last expression of the initializer to avoid |
|
# leaking references to this object before construction is complete. |
|
OBJECT_CACHE.try_add(@array.address, self) |
|
end |
|
|
|
## |
|
# Constructor that uses the type information from the given |
|
# FieldDescriptor to configure the new RepeatedField instance. |
|
# @param field [FieldDescriptor] Type information for the new RepeatedField |
|
# @param arena [Arena] Owning message's arena |
|
# @param values [Enumerable] Initial values |
|
# @param array [::FFI::Pointer] Existing upb_Array |
|
def self.construct_for_field(field, arena: nil, values: nil, array: nil) |
|
instance = allocate |
|
options = {initial_values: values, name: field.name, arena: arena, array: array} |
|
if [:enum, :message].include? field.type |
|
options[:descriptor] = field.subtype |
|
end |
|
instance.send(:initialize, field.type, **options) |
|
instance |
|
end |
|
|
|
def fuse_arena(arena) |
|
arena.fuse(arena) |
|
end |
|
|
|
extend Google::Protobuf::Internal::Convert |
|
|
|
def self.deep_copy(repeated_field) |
|
instance = allocate |
|
instance.send(:initialize, repeated_field.send(:type), descriptor: repeated_field.send(:descriptor)) |
|
instance.send(:resize, repeated_field.length) |
|
new_array = instance.send(:array) |
|
repeated_field.send(:each_msg_val_with_index) do |element, i| |
|
Google::Protobuf::FFI.array_set(new_array, i, message_value_deep_copy(element, repeated_field.send(:type), repeated_field.send(:descriptor), instance.send(:arena))) |
|
end |
|
instance |
|
end |
|
|
|
end |
|
end |
|
end |
|
|
|
require 'google/protobuf/repeated_field'
|
|
|