require 'set' require 'protobuf/field' require 'protobuf/enum' require 'protobuf/exceptions' require 'protobuf/message/decoder' module Protobuf class Message ## # Constants # STRING_ENCODING = "ASCII-8BIT".freeze ## # Class Methods # def self.all_fields @all_fields ||= begin all_fields_array = [] max_fields = fields.size > extension_fields.size ? fields.size : extension_fields.size max_fields.times do |field_number| all_fields_array << (fields[field_number] || extension_fields[field_number]) end all_fields_array.compact! all_fields_array end end # Define a field. Don't use this method directly. def self.define_field(rule, type, fname, tag, options) field_array = options[:extension] ? extension_fields : fields field_name_hash = options[:extension] ? extension_field_name_to_tag : field_name_to_tag if field_array[tag] raise TagCollisionError, %!{Field number #{tag} has already been used in "#{self.name}" by field "#{fname}".! end field_definition = ::Protobuf::Field.build(self, rule, type, fname, tag, options) field_name_hash[fname] = tag field_array[tag] = field_definition end # Reserve field numbers for extensions. Don't use this method directly. def self.extensions(range) extension_fields.add_range(range) end def self.extension_field_name_to_tag @extension_fields_by_name ||= {} end # An extension field object. def self.extension_fields @extension_fields ||= ::Protobuf::Field::ExtensionFields.new end def self.extension_tag?(tag) extension_fields.include_tag?(tag) end # A collection of field object. def self.fields @fields ||= [] end def self.field_name_to_tag @field_name_to_tag ||= {} end def self.get_ext_field_by_name(name) # Check if the name has been used before, if not then set it to the sym value extension_fields[extension_field_name_to_tag[name.to_sym]] rescue TypeError, NoMethodError => e name = 'nil' if name.nil? raise FieldNotDefinedError.new("Field '#{name}' is not defined on message '#{self.name}'") end def self.get_ext_field_by_tag(tag) extension_fields[tag] end # Find a field object by +name+. def self.get_field_by_name(name) # Check if the name has been used before, if not then set it to the sym value fields[field_name_to_tag[name.to_sym]] rescue TypeError, NoMethodError => e name = 'nil' if name.nil? raise FieldNotDefinedError.new("Field '#{name}' is not defined on message '#{self.name}'") end # Find a field object by +tag+ number. def self.get_field_by_tag(tag) fields[tag] rescue TypeError => e tag = tag.nil? ? 'nil' : tag.to_s raise FieldNotDefinedError.new("Tag '#{tag}' does not reference a message field for '#{self.name}'") end # Define a optional field. Don't use this method directly. def self.optional(type, name, tag, options = {}) define_field(:optional, type, name, tag, options) end # Define a repeated field. Don't use this method directly. def self.repeated(type, name, tag, options = {}) define_field(:repeated, type, name, tag, options) end # Define a required field. Don't use this method directly. def self.required(type, name, tag, options = {}) define_field(:required, type, name, tag, options) end ## # Constructor # def initialize(values = {}) @values = {} values = values.to_hash values.each { |name, val| self[name] = val unless val.nil? } end ## # Public Instance Methods # def all_fields self.class.all_fields end def clear! @values.delete_if do |_, value| if value.is_a?(Field::FieldArray) value.clear false else true end end self end def clone copy_to(super, :clone) end def dup copy_to(super, :dup) end # Iterate over a field collection. # message.each_field do |field_object, value| # # do something # end def each_field all_fields.each do |field| value = __send__(field.name) yield(field, value) end end def each_field_for_serialization all_fields.each do |field| next unless _field_must_be_serialized?(field) value = @values[field.name] if value.present? || [true, false].include?(value) yield(field, value) else # Only way you can get here is if you are required and not present raise ::Protobuf::SerializationError, "#{field.name} is required on #{field.message_class}" end end end # Returns extension fields. See Message#fields method. def extension_fields self.class.extension_fields end def fields self.class.fields end def get_ext_field_by_name(name) # :nodoc: self.class.get_ext_field_by_name(name) end def get_ext_field_by_tag(tag) # :nodoc: self.class.get_ext_field_by_tag(tag) end # Returns field object or +nil+. def get_field_by_name(name) self.class.get_field_by_name(name) end # Returns field object or +nil+. def get_field_by_tag(tag) self.class.get_field_by_tag(tag) end def has_field?(name) @values.has_key?(name) end def inspect to_hash.inspect end def parse_from(stream) Decoder.decode(stream, self) end def parse_from_string(string) parse_from(StringIO.new(string)) end def respond_to_has?(key) self.respond_to?(key) && self.has_field?(key) end def respond_to_has_and_present?(key) self.respond_to_has?(key) && (self.__send__(key).present? || [true, false].include?(self.__send__(key))) end def serialize_to(stream) each_field_for_serialization do |field, value| if field.repeated? if field.packed? key = (field.tag << 3) | ::Protobuf::WireType::LENGTH_DELIMITED packed_value = value.map { |val| field.encode(val) }.join stream.write(::Protobuf::Field::VarintField.encode(key)) stream.write(::Protobuf::Field::VarintField.encode(packed_value.size)) stream.write(packed_value) else value.each { |val| write_pair(stream, field, val) } end else write_pair(stream, field, value) end end end def serialize_to_string(string = '') io = StringIO.new(string) serialize_to(io) result = io.string result.force_encoding(::Protobuf::Message::STRING_ENCODING) if result.respond_to?(:force_encoding) result end def set_field(tag, bytes) field = (get_field_by_tag(tag) || get_ext_field_by_tag(tag)) field.set(self, bytes) if field end # Return a hash-representation of the given fields for this message type. def to_hash result = Hash.new @values.keys.each do |field_name| value = __send__(field_name) hashed_value = value.respond_to?(:to_hash_value) ? value.to_hash_value : value result.merge!(field_name => hashed_value) end return result end def to_json to_hash.to_json end def ==(obj) return false unless obj.is_a?(self.class) each_field do |field, value| return false unless value == obj.__send__(field.name) end true end def [](name) if field = get_field_by_name(name) || get_ext_field_by_name(name) __send__(field.name) else raise NoMethodError, "No such field: #{name.inspect}" end end def []=(name, value) if field = get_field_by_name(name) || get_ext_field_by_name(name) __send__(field.setter_method_name, value) else raise NoMethodError, "No such field: #{name.inspect}" end end ## # Instance Aliases # alias_method :to_hash_value, :to_hash alias_method :to_s, :serialize_to_string alias_method :bytes, :serialize_to_string alias_method :serialize, :serialize_to_string alias_method :responds_to_has?, :respond_to_has? alias_method :respond_to_and_has?, :respond_to_has? alias_method :responds_to_and_has?, :respond_to_has? alias_method :respond_to_has_present?, :respond_to_has_and_present? alias_method :respond_to_and_has_present?, :respond_to_has_and_present? alias_method :respond_to_and_has_and_present?, :respond_to_has_and_present? alias_method :responds_to_has_present?, :respond_to_has_and_present? alias_method :responds_to_and_has_present?, :respond_to_has_and_present? alias_method :responds_to_and_has_and_present?, :respond_to_has_and_present? ## # Private Instance Methods # private def copy_to(object, method) duplicate = proc { |obj| case obj when Message, String then obj.__send__(method) else obj end } object.__send__(:initialize) @values.each do |name, value| if value.is_a?(Field::FieldArray) object.__send__(name).replace(value.map {|v| duplicate.call(v)}) else object.__send__("#{name}=", duplicate.call(value)) end end object end def _field_must_be_serialized?(field) return true if field.required? if field.repeated? && has_field?(field.name) @values[field.name].compact! @values.delete(field.name) if @values[field.name].empty? end return has_field?(field.name) end # Encode key and value, and write to +stream+. def write_pair(stream, field, value) key = (field.tag << 3) | field.wire_type key_bytes = ::Protobuf::Field::VarintField.encode(key) stream.write(key_bytes) bytes = field.encode(value) stream.write(bytes) end end end