| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| |
| module Qpid::Proton |
| # @private |
| module Codec |
| |
| DataError = ::TypeError |
| |
| # @private wrapper for pn_data_t* |
| # Raises TypeError for invalid conversions |
| class Data |
| |
| # @private |
| PROTON_METHOD_PREFIX = "pn_data" |
| # @private |
| include Util::Wrapper |
| |
| # @private |
| # Convert a pn_data_t* containing a single value to a ruby object. |
| # @return [Object, nil] The ruby value extracted from +impl+ or nil if impl is empty |
| def self.to_object(impl) |
| if (Cproton.pn_data_size(impl) > 0) |
| d = Data.new(impl) |
| d.rewind |
| d.next_object |
| end |
| end |
| |
| # @private |
| # Convert a pn_data_t* containing an AMQP "multiple" field to an Array or nil. |
| # A "multiple" field can be encoded as an array or a single value - always return Array. |
| # @return [Array, nil] The ruby Array extracted from +impl+ or nil if impl is empty |
| def self.to_multiple(impl) |
| o = self.to_object(impl) |
| Array(o) if o |
| end |
| |
| # @private |
| # Clear a pn_data_t* and convert a ruby object into it. If x==nil leave it empty. |
| def self.from_object(impl, x) |
| d = Data.new(impl) |
| d.clear |
| d.object = x if x |
| nil |
| end |
| |
| # @overload initialize(capacity) |
| # @param capacity [Integer] capacity for the new data instance. |
| # @overload instance(impl) |
| # @param impl [SWIG::pn_data_t*] wrap the C impl pointer. |
| def initialize(capacity = 16) |
| if capacity.is_a?(Integer) |
| @impl = Cproton.pn_data(capacity.to_i) |
| @free = true |
| else |
| # Assume non-integer capacity is a SWIG::pn_data_t* |
| @impl = capacity |
| @free = false |
| end |
| |
| # destructor |
| ObjectSpace.define_finalizer(self, self.class.finalize!(@impl, @free)) |
| end |
| |
| # @private |
| def self.finalize!(impl, free) |
| proc { |
| Cproton.pn_data_free(impl) if free |
| } |
| end |
| |
| proton_caller :clear, :rewind, :next, :prev, :enter, :exit |
| |
| def enter_exit() |
| enter |
| yield self |
| ensure |
| exit |
| end |
| |
| def code() Cproton.pn_data_type(@impl); end |
| |
| def type() Mapping.for_code(Cproton.pn_data_type(@impl)); end |
| |
| # Returns a representation of the data encoded in AMQP format. |
| def encode |
| buffer = "\0"*1024 |
| loop do |
| cd = Cproton.pn_data_encode(@impl, buffer, buffer.length) |
| if cd == Cproton::PN_OVERFLOW |
| buffer *= 2 |
| elsif cd >= 0 |
| return buffer[0...cd] |
| else |
| check(cd) |
| end |
| end |
| end |
| |
| # Decodes the first value from supplied AMQP data and returns the number |
| # of bytes consumed. |
| # |
| # @param encoded [String] The encoded data. |
| def decode(encoded) |
| check(Cproton.pn_data_decode(@impl, encoded, encoded.length)) |
| end |
| |
| proton_is :described, :array_described |
| proton_caller :put_described |
| proton_caller :put_list, :get_list, :put_map, :get_map |
| proton_get :array_type |
| proton_caller :put_array |
| def get_array() [Cproton.pn_data_get_array(@impl), array_described?, array_type]; end |
| |
| def expect(code) |
| unless code == self.code |
| raise TypeError, "expected #{Cproton.pn_type_name(code)}, got #{Cproton.pn_type_name(self.code)}" |
| end |
| end |
| |
| def described |
| expect Cproton::PN_DESCRIBED |
| enter_exit { Types::Described.new(self.next_object, self.next_object) } |
| end |
| |
| def described= d |
| put_described |
| enter_exit { self << d.descriptor << d.value } |
| end |
| |
| def fill(a, count, what) |
| a << self.object while self.next |
| raise TypeError, "#{what} expected #{count} elements, got #{a.size}" unless a.size == count |
| a |
| end |
| |
| def list |
| return array if code == Cproton::PN_ARRAY |
| expect Cproton::PN_LIST |
| count = get_list |
| a = [] |
| enter_exit { fill(a, count, __method__) } |
| end |
| |
| def list=(a) |
| put_list |
| enter_exit { a.each { |x| self << x } } |
| end |
| |
| def array |
| return list if code == Cproton::PN_LIST |
| expect Cproton::PN_ARRAY |
| count, d, t = get_array |
| enter_exit do |
| desc = next_object if d |
| a = Types::UniformArray.new(t, nil, desc) |
| fill(a, count, "array") |
| end |
| end |
| |
| def array=(a) |
| t = a.type if a.respond_to? :type |
| d = a.descriptor if a.respond_to? :descriptor |
| if (h = a.instance_variable_get(:@proton_array_header)) |
| t ||= h.type |
| d ||= h.descriptor |
| end |
| raise TypeError, "no type when converting #{a.class} to an array" unless t |
| put_array(!d.nil?, t.code) |
| m = Mapping[t] |
| enter_exit do |
| self << d unless d.nil? |
| a.each { |e| m.put(self, e); } |
| end |
| end |
| |
| def map |
| expect Cproton::PN_MAP |
| count = self.get_map |
| raise TypeError, "invalid map, total of keys and values is odd" if count.odd? |
| enter_exit do |
| m = {} |
| m[object] = next_object while self.next |
| raise TypeError, "map expected #{count/2} entries, got #{m.size}" unless m.size == count/2 |
| m |
| end |
| end |
| |
| def map= m |
| put_map |
| enter_exit { m.each_pair { |k,v| self << k << v } } |
| end |
| |
| # Return nil if vallue is null, raise exception otherwise. |
| def null() raise TypeError, "expected null, got #{type || 'empty'}" unless null?; end |
| |
| # Set the current value to null |
| def null=(dummy=nil) check(Cproton.pn_data_put_null(@impl)); end |
| |
| # Puts an arbitrary object type. |
| # |
| # The Data instance will determine which AMQP type is appropriate and will |
| # use that to encode the object. |
| # |
| # @param object [Object] The value. |
| # |
| def object=(object) |
| Mapping.for_class(object.class).put(self, object) |
| object |
| end |
| |
| # Add an arbitrary data value using object=, return self |
| def <<(x) self.object=x; self; end |
| |
| # Move forward to the next value and return it |
| def next_object |
| self.next or raise TypeError, "not enough data" |
| self.object |
| end |
| |
| # Gets the current node, based on how it was encoded. |
| # |
| # @return [Object] The current node. |
| # |
| def object |
| self.type.get(self) if self.type |
| end |
| |
| # Checks if the current node is null. |
| # |
| # @return [Boolean] True if the node is null. |
| # |
| def null? |
| Cproton.pn_data_is_null(@impl) |
| end |
| |
| # Puts a boolean value. |
| # |
| # @param value [Boolean] The boolean value. |
| # |
| def bool=(value) |
| check(Cproton.pn_data_put_bool(@impl, value)) |
| end |
| |
| # If the current node is a boolean, then it returns the value. Otherwise, |
| # it returns false. |
| # |
| # @return [Boolean] The boolean value. |
| # |
| def bool |
| Cproton.pn_data_get_bool(@impl) |
| end |
| |
| # Puts an unsigned byte value. |
| # |
| # @param value [Integer] The unsigned byte value. |
| # |
| def ubyte=(value) |
| check(Cproton.pn_data_put_ubyte(@impl, value)) |
| end |
| |
| # If the current node is an unsigned byte, returns its value. Otherwise, |
| # it returns 0. |
| # |
| # @return [Integer] The unsigned byte value. |
| # |
| def ubyte |
| Cproton.pn_data_get_ubyte(@impl) |
| end |
| |
| # Puts a byte value. |
| # |
| # @param value [Integer] The byte value. |
| # |
| def byte=(value) |
| check(Cproton.pn_data_put_byte(@impl, value)) |
| end |
| |
| # If the current node is an byte, returns its value. Otherwise, |
| # it returns 0. |
| # |
| # @return [Integer] The byte value. |
| # |
| def byte |
| Cproton.pn_data_get_byte(@impl) |
| end |
| |
| # Puts an unsigned short value. |
| # |
| # @param value [Integer] The unsigned short value |
| # |
| def ushort=(value) |
| check(Cproton.pn_data_put_ushort(@impl, value)) |
| end |
| |
| # If the current node is an unsigned short, returns its value. Otherwise, |
| # it returns 0. |
| # |
| # @return [Integer] The unsigned short value. |
| # |
| def ushort |
| Cproton.pn_data_get_ushort(@impl) |
| end |
| |
| # Puts a short value. |
| # |
| # @param value [Integer] The short value. |
| # |
| def short=(value) |
| check(Cproton.pn_data_put_short(@impl, value)) |
| end |
| |
| # If the current node is a short, returns its value. Otherwise, |
| # returns a 0. |
| # |
| # @return [Integer] The short value. |
| # |
| def short |
| Cproton.pn_data_get_short(@impl) |
| end |
| |
| # Puts an unsigned integer value. |
| # |
| # @param value [Integer] the unsigned integer value |
| # |
| def uint=(value) |
| raise TypeError if value.nil? |
| raise RangeError, "invalid uint: #{value}" if value < 0 |
| check(Cproton.pn_data_put_uint(@impl, value)) |
| end |
| |
| # If the current node is an unsigned int, returns its value. Otherwise, |
| # returns 0. |
| # |
| # @return [Integer] The unsigned integer value. |
| # |
| def uint |
| Cproton.pn_data_get_uint(@impl) |
| end |
| |
| # Puts an integer value. |
| # |
| # ==== Options |
| # |
| # * value - the integer value |
| def int=(value) |
| check(Cproton.pn_data_put_int(@impl, value)) |
| end |
| |
| # If the current node is an integer, returns its value. Otherwise, |
| # returns 0. |
| # |
| # @return [Integer] The integer value. |
| # |
| def int |
| Cproton.pn_data_get_int(@impl) |
| end |
| |
| # Puts a character value. |
| # |
| # @param value [Integer] The character value. |
| # |
| def char=(value) |
| check(Cproton.pn_data_put_char(@impl, value)) |
| end |
| |
| # If the current node is a character, returns its value. Otherwise, |
| # returns 0. |
| # |
| # @return [Integer] The character value. |
| # |
| def char |
| Cproton.pn_data_get_char(@impl) |
| end |
| |
| # Puts an unsigned long value. |
| # |
| # @param value [Integer] The unsigned long value. |
| # |
| def ulong=(value) |
| raise TypeError if value.nil? |
| raise RangeError, "invalid ulong: #{value}" if value < 0 |
| check(Cproton.pn_data_put_ulong(@impl, value)) |
| end |
| |
| # If the current node is an unsigned long, returns its value. Otherwise, |
| # returns 0. |
| # |
| # @return [Integer] The unsigned long value. |
| # |
| def ulong |
| Cproton.pn_data_get_ulong(@impl) |
| end |
| |
| # Puts a long value. |
| # |
| # @param value [Integer] The long value. |
| # |
| def long=(value) |
| check(Cproton.pn_data_put_long(@impl, value)) |
| end |
| |
| # If the current node is a long, returns its value. Otherwise, returns 0. |
| # |
| # @return [Integer] The long value. |
| def long |
| Cproton.pn_data_get_long(@impl) |
| end |
| |
| # Puts a timestamp value. |
| # |
| # @param value [Integer] The timestamp value. |
| # |
| def timestamp=(value) |
| value = value.to_i if (!value.nil? && value.is_a?(Time)) |
| check(Cproton.pn_data_put_timestamp(@impl, value)) |
| end |
| |
| # If the current node is a timestamp, returns its value. Otherwise, |
| # returns 0. |
| # |
| # @return [Integer] The timestamp value. |
| # |
| def timestamp |
| Cproton.pn_data_get_timestamp(@impl) |
| end |
| |
| # Puts a float value. |
| # |
| # @param value [Float] The floating point value. |
| # |
| def float=(value) |
| check(Cproton.pn_data_put_float(@impl, value)) |
| end |
| |
| # If the current node is a float, returns its value. Otherwise, |
| # returns 0. |
| # |
| # @return [Float] The floating point value. |
| # |
| def float |
| Cproton.pn_data_get_float(@impl) |
| end |
| |
| # Puts a double value. |
| # |
| # @param value [Float] The double precision floating point value. |
| # |
| def double=(value) |
| check(Cproton.pn_data_put_double(@impl, value)) |
| end |
| |
| # If the current node is a double, returns its value. Otherwise, |
| # returns 0. |
| # |
| # @return [Float] The double precision floating point value. |
| # |
| def double |
| Cproton.pn_data_get_double(@impl) |
| end |
| |
| # Puts a decimal32 value. |
| # |
| # @param value [Integer] The decimal32 value. |
| # |
| def decimal32=(value) |
| check(Cproton.pn_data_put_decimal32(@impl, value)) |
| end |
| |
| # If the current node is a decimal32, returns its value. Otherwise, |
| # returns 0. |
| # |
| # @return [Integer] The decimal32 value. |
| # |
| def decimal32 |
| Cproton.pn_data_get_decimal32(@impl) |
| end |
| |
| # Puts a decimal64 value. |
| # |
| # @param value [Integer] The decimal64 value. |
| # |
| def decimal64=(value) |
| check(Cproton.pn_data_put_decimal64(@impl, value)) |
| end |
| |
| # If the current node is a decimal64, returns its value. Otherwise, |
| # it returns 0. |
| # |
| # @return [Integer] The decimal64 value. |
| # |
| def decimal64 |
| Cproton.pn_data_get_decimal64(@impl) |
| end |
| |
| # Puts a decimal128 value. |
| # |
| # @param value [Integer] The decimal128 value. |
| # |
| def decimal128=(value) |
| raise TypeError, "invalid decimal128 value: #{value}" if value.nil? |
| value = value.to_s(16).rjust(32, "0") |
| bytes = [] |
| value.scan(/(..)/) {|v| bytes << v[0].to_i(16)} |
| check(Cproton.pn_data_put_decimal128(@impl, bytes)) |
| end |
| |
| # If the current node is a decimal128, returns its value. Otherwise, |
| # returns 0. |
| # |
| # @return [Integer] The decimal128 value. |
| # |
| def decimal128 |
| value = "" |
| Cproton.pn_data_get_decimal128(@impl).each{|val| value += ("%02x" % val)} |
| value.to_i(16) |
| end |
| |
| # Puts a +UUID+ value. |
| # |
| # The UUID is expected to be in the format of a string or else a 128-bit |
| # integer value. |
| # |
| # @param value [String, Numeric] A string or numeric representation of the UUID. |
| # |
| # @example |
| # |
| # # set a uuid value from a string value |
| # require 'securerandom' |
| # @impl.uuid = SecureRandom.uuid |
| # |
| # # or |
| # @impl.uuid = "fd0289a5-8eec-4a08-9283-81d02c9d2fff" |
| # |
| # # set a uuid value from a 128-bit value |
| # @impl.uuid = 0 # sets to 00000000-0000-0000-0000-000000000000 |
| # |
| def uuid=(value) |
| raise ::ArgumentError, "invalid uuid: #{value}" if value.nil? |
| |
| # if the uuid that was submitted was numeric value, then translated |
| # it into a hex string, otherwise assume it was a string represtation |
| # and attempt to decode it |
| if value.is_a? Numeric |
| value = "%032x" % value |
| else |
| raise ::ArgumentError, "invalid uuid: #{value}" if !valid_uuid?(value) |
| |
| value = (value[0, 8] + |
| value[9, 4] + |
| value[14, 4] + |
| value[19, 4] + |
| value[24, 12]) |
| end |
| bytes = [] |
| value.scan(/(..)/) {|v| bytes << v[0].to_i(16)} |
| check(Cproton.pn_data_put_uuid(@impl, bytes)) |
| end |
| |
| # If the current value is a +UUID+, returns its value. Otherwise, |
| # it returns nil. |
| # |
| # @return [String] The string representation of the UUID. |
| # |
| def uuid |
| value = "" |
| Cproton.pn_data_get_uuid(@impl).each{|val| value += ("%02x" % val)} |
| value.insert(8, "-").insert(13, "-").insert(18, "-").insert(23, "-") |
| end |
| |
| # Puts a binary value. |
| # |
| # A binary string is encoded as an ASCII 8-bit string value. This is in |
| # contranst to other strings, which are treated as UTF-8 encoded. |
| # |
| # @param value [String] An arbitrary string value. |
| # |
| # @see #string= |
| # |
| def binary=(value) |
| check(Cproton.pn_data_put_binary(@impl, value)) |
| end |
| |
| # If the current node is binary, returns its value. Otherwise, it returns |
| # an empty string (""). |
| # |
| # @return [String] The binary string. |
| # |
| # @see #string |
| # |
| def binary |
| Qpid::Proton::Types::BinaryString.new(Cproton.pn_data_get_binary(@impl)) |
| end |
| |
| # Puts a UTF-8 encoded string value. |
| # |
| # *NOTE:* A nil value is stored as an empty string rather than as a nil. |
| # |
| # @param value [String] The UTF-8 encoded string value. |
| # |
| # @see #binary= |
| # |
| def string=(value) |
| check(Cproton.pn_data_put_string(@impl, value)) |
| end |
| |
| # If the current node is a string, returns its value. Otherwise, it |
| # returns an empty string (""). |
| # |
| # @return [String] The UTF-8 encoded string. |
| # |
| # @see #binary |
| # |
| def string |
| Qpid::Proton::Types::UTFString.new(Cproton.pn_data_get_string(@impl)) |
| end |
| |
| # Puts a symbolic value. |
| # |
| # @param value [String|Symbol] The symbolic string value. |
| # |
| def symbol=(value) |
| check(Cproton.pn_data_put_symbol(@impl, value.to_s)) |
| end |
| |
| # If the current node is a symbol, returns its value. Otherwise, it |
| # returns an empty string (""). |
| # |
| # @return [Symbol] The symbol value. |
| # |
| def symbol |
| Cproton.pn_data_get_symbol(@impl).to_sym |
| end |
| |
| # Get the current value as a single object. |
| # |
| # @return [Object] The current node's object. |
| # |
| # @see #type_code |
| # @see #type |
| # |
| def get |
| type.get(self); |
| end |
| |
| # Puts a new value with the given type into the current node. |
| # |
| # @param value [Object] The value. |
| # @param type_code [Mapping] The value's type. |
| # |
| # @private |
| # |
| def put(value, type_code); |
| type_code.put(self, value); |
| end |
| |
| private |
| |
| def valid_uuid?(value) |
| # ensure that the UUID is in the right format |
| # xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx |
| value =~ /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/ |
| end |
| |
| # @private |
| def check(err) |
| if err < 0 |
| raise TypeError, "[#{err}]: #{Cproton.pn_data_error(@impl)}" |
| else |
| return err |
| end |
| end |
| end |
| end |
| end |