blob: 494a8f3206ae5ce70d6b0fa24f8765111012ca3a [file] [log] [blame]
#
# 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.
#
use Scalar::Util qw(reftype looks_like_number);
=pod
=head1 NAME
qpid::proton::Data
=head1 DESCRIPTION
The B<Data> class provides an interface for decoding, extract, creating and
encoding arbitrary AMQP data. A B<Data> object contains a tree of AMQP values.
Leaf nodes in this tree correspond to scalars in the AMQP type system such as
B<INT> or B<STRING>. Integerior nodes in this tree correspond to compound values
in the AMQP type system such as B<LIST>, B<MAP>, B<ARRAY> or B<DESCRIBED>. The
root node of the tree is the B<Data> object itself and can have an arbitrary
number of children.
A B<Data> object maintains the notion of the current sibling node and a current
parent node. Siblings are ordered within their parent. Values are accessed
and/or added by using the B<next>, B<prev>, B<enter> and B<exit> methods to
navigate to the desired location in the tree and using the supplied variety of
mutator and accessor methods to access or add a value of the desired type.
The mutator methods will always add a vlaue I<after> the current node in the
tree. If the current node has a next sibling the mutaor method will overwrite
the value on this node. If there is no current node or the current node has no
next sibling then one will be added. The accessor methods always set the
add/modified node to the current node. The accessor methods read the value of
the current node and do not change which node is current.
=cut
package qpid::proton::Data;
=pod
=head1 CONSTRUCTOR
Creates a new instance with the specified capacity.
=over
=item my $data = qpid::proton::Data->new( CAPACITY );
=back
=cut
sub new {
my ($class) = @_;
my ($self) = {};
my $capacity = $_[1] || 16;
my $impl = $capacity;
$self->{_free} = 0;
if($capacity) {
if (::looks_like_number($capacity)) {
$impl = cproton_perl::pn_data($capacity);
$self->{_free} = 1;
}
}
$self->{_impl} = $impl;
bless $self, $class;
return $self;
}
sub DESTROY {
my ($self) = @_;
my $impl = $self->{_impl};
cproton_perl::pn_data_free($impl) if $self->{_free};
}
=pod
=head1 ACTIONS
Clear all content for the data object.
=over
=item my $data->clear();
=back
=cut
sub clear {
my ($self) = @_;
my $impl = $self->{_impl};
cproton_perl::pn_data_clear($impl);
}
=pod
=head1 NAVIGATION
The following methods allow for navigating through the nodes in the tree.
=cut
=pod
=over
=item $doc->enter;
=item if ($doc->enter()) { do_something_with_children; }
Sets the parent node to the current node and clears the current node.
Clearing the current node sets it I<before> the first child.
=item $doc->exit;
=item if ($doc->exit()) { do_something_with_parent; }
Sets the current node to the parent node, and the parent node to its own parent.
=item $doc->next;
=item $doc->prev;
Moves to the next/previous sibling and returns its type. If there is no next or
previous sibling then the current node remains unchanged.
=item $doc->rewind;
Clears the current node and sets the parent to the root node.
=back
=cut
sub enter {
my ($self) = @_;
my $impl = $self->{_impl};
cproton_perl::pn_data_enter($impl);
}
sub exit {
my ($self) = @_;
my $impl = $self->{_impl};
cproton_perl::pn_data_exit($impl);
}
sub rewind {
my ($self) = @_;
my $impl = $self->{_impl};
cproton_perl::pn_data_rewind($impl);
}
=pod
=over
=item $doc->next;
=item if ($doc->next()) { do_something; }
Advances the current node to its next sibling and returns its type.
If there is no next sibling then the current node remains unchanged and
B<undef> is returned.
=item $doc->prev;
=item if ($doc->prev()) { do_something; }
Advances the current node to its previous sibling and returns its type.
If there is no previous sibling then the current node remains unchanged and
undef is returned.
=back
=cut
sub next {
my ($self) = @_;
my $impl = $self->{_impl};
return cproton_perl::pn_data_next($impl);
}
sub prev {
my ($self) = @_;
my $impl = $self->{_impl};
return cproton_perl::pn_data_prev($impl);
}
=pod
=head1 SUPPORTED TYPES
The following methods allow for inserting the various node types into the
tree.
=head2 NODE TYPE
You can retrieve the type of the current node.
=over
=item $type = $doc->get_type;
=back
=cut
sub get_type {
my ($self) = @_;
my $impl = $self->{_impl};
my $type = cproton_perl::pn_data_type($impl);
return qpid::proton::Mapping->find_by_type_value($type);
}
=pod
=head2 SCALAR TYPES
=cut
=pod
=head3 NULL
=over
=item $doc->put_null;
Inserts a null node.
=item $doc->is_null;
Returns true if the current node is null.
=back
=cut
sub put_null() {
my ($self) = @_;
my $impl = $self->{_impl};
cproton_perl::pn_data_put_null($impl);
}
sub is_null {
my ($self) = @_;
my $impl = $self->{_impl};
cproton_perl::pn_data_is_null($impl);
}
sub check {
my ($self) = @_;
my $impl = $self->{_impl};
my $err = $_[1];
# if we got a null then just exit
return $err if !defined($err);
if($err < 0) {
die DataException->new("[$err]: " . cproton_perl::pn_data_error($impl));
} else {
return $err;
}
}
=pod
=head3 BOOL
Handles a boolean (B<true>/B<false>) node.
=over
=item $doc->put_bool( VALUE );
=item $doc->get_bool;
=back
=cut
sub put_bool {
my ($self) = @_;
my $impl = $self->{_impl};
my $value = $_[1] || 0;
cproton_perl::pn_data_put_bool($impl, $value);
}
sub get_bool {
my ($self) = @_;
my $impl = $self->{_impl};
cproton_perl::pn_data_get_bool($impl);
}
=pod
=head3 UBYTE
Handles an unsigned byte node.
=over
=item $data->put_ubyte( VALUE );
=item $data->get_ubyte;
=back
=cut
sub put_ubyte {
my ($self) = @_;
my $impl = $self->{_impl};
my $value = $_[1];
die "ubyte must be defined" if !defined($value);
die "ubyte must be non-negative" if $value < 0;
check(cproton_perl::pn_data_put_ubyte($impl, int($value)));
}
sub get_ubyte {
my ($self) = @_;
my $impl = $self->{_impl};
cproton_perl::pn_data_get_ubyte($impl);
}
=pod
=head3 BYTE
Handles a signed byte node.
=over
=item $data->put_byte( VALUE );
=item $data->get_byte;
=back
=cut
sub put_byte {
my ($self) = @_;
my $impl = $self->{_impl};
my $value = $_[1];
die "byte must be defined" if !defined($value);
check(cproton_perl::pn_data_put_byte($impl, int($value)));
}
sub get_byte {
my ($self) = @_;
my $impl = $self->{_impl};
cproton_perl::pn_data_get_byte($impl);
}
=pod
=head3 USHORT
Handles an unsigned short node.
=over
=item $data->put_ushort( VALUE );
=item $data->get_ushort;
=back
=cut
sub put_ushort {
my ($self) = @_;
my $impl = $self->{_impl};
my $value = $_[1];
die "ushort must be defined" if !defined($value);
check(cproton_perl::pn_data_put_ushort($impl, int($value)));
}
sub get_ushort {
my ($self) = @_;
my $impl = $self->{_impl};
cproton_perl::pn_data_get_ushort($impl);
}
=pod
=head3 SHORT
Handles a signed short node.
=over
=item $data->put_short( VALUE );
=item $data->get_short;
=back
=cut
sub put_short {
my ($self) = @_;
my $impl = $self->{_impl};
my $value = $_[1];
die "short must be defined" if !defined($value);
check(cproton_perl::pn_data_put_short($impl, int($value)));
}
sub get_short {
my ($self) = @_;
my $impl = $self->{_impl};
cproton_perl::pn_data_get_short($impl);
}
=pod
=head3 UINT
Handles an unsigned integer node.
=over
=item $data->put_uint( VALUE );
=item $data->get_uint;
=back
=cut
sub put_uint {
my ($self) = @_;
my $impl = $self->{_impl};
my $value = $_[1];
die "uint must be defined" if !defined($value);
check(cproton_perl::pn_data_put_uint($impl, int($value)));
}
sub get_uint {
my ($self) = @_;
my $impl = $self->{_impl};
cproton_perl::pn_data_get_uint($impl);
}
=pod
=head3 INT
Handles an integer node.
=over
=item $data->put_int( VALUE );
=item $data->get_int;
=back
=cut
sub put_int {
my ($self) = @_;
my $impl = $self->{_impl};
my $value = $_[1];
die "int must be defined" if !defined($value);
check(cproton_perl::pn_data_put_int($impl, int($value)));
}
sub get_int {
my ($self) = @_;
my $impl = $self->{_impl};
cproton_perl::pn_data_get_int($impl);
}
=pod
=head3 CHAR
Handles a character node.
=over
=item $data->put_char( VALUE );
=item $data->get_char;
=back
=cut
sub put_char {
my ($self) = @_;
my $impl = $self->{_impl};
my $value = $_[1];
die "char must be defined" if !defined($value);
check(cproton_perl::pn_data_put_char($impl, int($value)));
}
sub get_char {
my ($self) = @_;
my $impl = $self->{_impl};
cproton_perl::pn_data_get_char($impl);
}
=pod
=head3 ULONG
Handles an unsigned long node.
=over
=item $data->set_ulong( VALUE );
=item $data->get_ulong;
=back
=cut
sub put_ulong {
my ($self) = @_;
my $impl = $self->{_impl};
my $value = $_[1];
die "ulong must be defined" if !defined($value);
check(cproton_perl::pn_data_put_ulong($impl, $value));
}
sub get_ulong {
my ($self) = @_;
my $impl = $self->{_impl};
cproton_perl::pn_data_get_ulong($impl);
}
=pod
=head3 LONG
Handles a signed long node.
=over
=item $data->put_long( VALUE );
=item $data->get_long;
=back
=cut
sub put_long {
my ($self) = @_;
my $impl = $self->{_impl};
my $value = $_[1];
die "long must be defined" if !defined($value);
cproton_perl::pn_data_put_long($impl, int($value));
}
sub get_long {
my ($self) = @_;
my $impl = $self->{_impl};
cproton_perl::pn_data_get_long($impl);
}
=pod
=head3 TIMESTAMP
Handles a timestamp node.
=over
=item $data->put_timestamp( VALUE );
=item $data->get_timestamp;
=back
=cut
sub put_timestamp {
my ($self) = @_;
my $impl = $self->{_impl};
my $value = $_[1];
die "timestamp must be defined" if !defined($value);
check(cproton_perl::pn_data_put_timestamp($impl, int($value)));
}
sub get_timestamp {
my ($self) = @_;
my $impl = $self->{_impl};
cproton_perl::pn_data_get_timestamp($impl);
}
=pod
=head3 FLOAT
Handles a floating point node.
=over
=item $data->put_float( VALUE );
=item $data->get_float;
=back
=cut
sub put_float {
my ($self) = @_;
my $impl = $self->{_impl};
my $value = $_[1];
die "float must be defined" if !defined($value);
check(cproton_perl::pn_data_put_float($impl, $value));
}
sub get_float {
my ($self) = @_;
my $impl = $self->{_impl};
my $value = cproton_perl::pn_data_get_float($impl);
cproton_perl::pn_data_get_float($impl);
}
=pod
=head3 DOUBLE
Handles a double node.
=over
=item $data->put_double( VALUE );
=item $data->get_double;
=back
=cut
sub put_double {
my ($self) = @_;
my $impl = $self->{_impl};
my $value = $_[1];
die "double must be defined" if !defined($value);
check(cproton_perl::pn_data_put_double($impl, $value));
}
sub get_double {
my ($self) = @_;
my $impl = $self->{_impl};
cproton_perl::pn_data_get_double($impl);
}
=pod
=head3 DECIMAL32
Handles a decimal32 node.
=over
=item $data->put_decimal32( VALUE );
=item $data->get_decimal32;
=back
=cut
sub put_decimal32 {
my ($self) = @_;
my $impl = $self->{_impl};
my $value = $_[1];
die "decimal32 must be defined" if !defined($value);
check(cproton_perl::pn_data_put_decimal32($impl, $value));
}
sub get_decimal32 {
my ($self) = @_;
my $impl = $self->{_impl};
cproton_perl::pn_data_get_decimal32($impl);
}
=pod
=head3 DECIMAL64
Handles a decimal64 node.
=over
=item $data->put_decimal64( VALUE );
=item $data->get_decimal64;
=back
=cut
sub put_decimal64 {
my ($self) = @_;
my $impl = $self->{_impl};
my $value = $_[1];
die "decimal64 must be defined" if !defined($value);
check(cproton_perl::pn_data_put_decimal64($impl, $value));
}
sub get_decimal64 {
my ($self) = @_;
my $impl = $self->{_impl};
cproton_perl::pn_data_get_decimal64($impl);
}
=pod
=head3 DECIMAL128
Handles a decimal128 node.
=over
=item $data->put_decimal128( VALUE );
=item $data->get_decimal128;
=back
=cut
sub put_decimal128 {
my ($self) = @_;
my $impl = $self->{_impl};
my $value = $_[1];
die "decimal128 must be defined" if !defined($value);
my @binary = split //, pack("H[32]", sprintf("%032x", $value));
my @bytes = ();
foreach $char (@binary) {
push(@bytes, ord($char));
}
check(cproton_perl::pn_data_put_decimal128($impl, \@bytes));
}
sub get_decimal128 {
my ($self) = @_;
my $impl = $self->{_impl};
my $bytes = cproton_perl::pn_data_get_decimal128($impl);
my $value = hex(unpack("H[32]", $bytes));
return $value;
}
=pod
=head3 UUID
Handles setting a UUID value. UUID values can be set using a 128-bit integer
value or else a well-formed string.
=over
=item $data->put_uuid( VALUE );
=item $data->get_uuid;
=back
=cut
use Data::Dumper;
sub put_uuid {
my ($self) = @_;
my $impl = $self->{_impl};
my $value = $_[1];
die "uuid must be defined" if !defined($value);
if($value =~ /[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}/) {
$value =~ s/-//g;
my @binary = split //, pack("H[32]", $value);
my @bytes = ();
foreach $char (@binary) {
push(@bytes, ord($char));
}
check(cproton_perl::pn_data_put_uuid($impl, \@bytes));
} else {
die "uuid is malformed: $value";
}
}
sub get_uuid {
my ($self) = @_;
my $impl = $self->{_impl};
my $bytes = cproton_perl::pn_data_get_uuid($impl);
my $value = unpack("H[32]", $bytes);
$value = substr($value, 0, 8) . "-" .
substr($value, 8, 4) . "-" .
substr($value, 12, 4) . "-" .
substr($value, 16, 4) . "-" .
substr($value, 20);
return $value;
}
=pod
=head3 BINARY
Handles a binary data node.
=over
=item $data->put_binary( VALUE );
=item $data->get_binary;
=back
=cut
sub put_binary {
my ($self) = @_;
my $impl = $self->{_impl};
my $value = $_[1];
die "binary must be defined" if !defined($value);
check(cproton_perl::pn_data_put_binary($impl, $value)) if defined($value);
}
sub get_binary {
my ($self) = @_;
my $impl = $self->{_impl};
cproton_perl::pn_data_get_binary($impl);
}
=pod
=head3 STRING
Handles a string node.
=over
=item $data->put_string( VALUE );
=item $data->get_string;
=back
=cut
sub put_string {
my ($self) = @_;
my $impl = $self->{_impl};
my $value = $_[1];
die "string must be defined" if !defined($value);
check(cproton_perl::pn_data_put_string($impl, $value));
}
sub get_string {
my ($self) = @_;
my $impl = $self->{_impl};
cproton_perl::pn_data_get_string($impl);
}
=pod
=head3 SYMBOL
Handles a symbol value.
=over
=item $data->put_symbol( VALUE );
=item $data->get_symbol;
=back
=cut
sub put_symbol {
my ($self) = @_;
my $impl = $self->{_impl};
my $value = $_[1];
die "symbol must be defined" if !defined($value);
check(cproton_perl::pn_data_put_symbol($impl, $value));
}
sub get_symbol {
my ($self) = @_;
my $impl = $self->{_impl};
cproton_perl::pn_data_get_symbol($impl);
}
=pod
=head3 DESCRIBED VALUE
A described node has two children: the descriptor and the value.
These are specified by entering the node and putting the
described values.
=over
=item $data->put_described;
=item $data->is_described;
=back
=cut
sub put_described {
my ($self) = @_;
my $impl = $self->{_impl};
cproton_perl::pn_data_put_described($impl);
}
sub is_described {
my ($self) = @_;
my $impl = $self->{_impl};
cproton_perl::pn_data_is_described($impl);
}
=pod
=head3 ARRAYS
Puts an array value.
Elements may be filled by entering the array node and putting the element values.
The values must all be of the specified array element type.
If an array is B<described> then the first child value of the array is the
descriptor and may be of any type.
=over
B<DESCRIBED> specifies whether the array is described or not.
B<TYPE> specifies the type of elements in the array.
=back
=over
=item $data->put_array( DESCRIBED, TYPE )
=item my ($count, $described, $array_type) = item $data->get_array
=back
=cut
sub put_array {
my ($self) = @_;
my $impl = $self->{_impl};
my $described = $_[1] || 0;
my $array_type = $_[2];
die "array type must be defined" if !defined($array_type);
check(cproton_perl::pn_data_put_array($impl,
$described,
$array_type->get_type_value));
}
sub get_array {
my ($self) = @_;
my $impl = $self->{_impl};
my $count = cproton_perl::pn_data_get_array($impl);
my $described = cproton_perl::pn_data_is_array_described($impl);
my $type_value = cproton_perl::pn_data_get_array_type($impl);
$type_value = qpid::proton::Mapping->find_by_type_value($type_value);
return ($count, $described, $type_value);
}
sub get_array_type {
my ($self) = @_;
my $impl = $self->{_impl};
cproton_perl::pn_data_get_array_type($impl);
}
=pod
=head3 LIST
Puts a list value.
Elements may be filled in by entering the list and putting element values.
=over
=item $data->put_list;
=item my $count = $data->get_list
=back
=cut
sub put_list {
my ($self) = @_;
my $impl = $self->{_impl};
check(cproton_perl::pn_data_put_list($impl));
}
sub get_list {
my ($self) = @_;
my $impl = $self->{_impl};
cproton_perl::pn_data_get_list($impl);
}
=pod
head3 MAP
Puts a map value.
Elements may be filled by entering the map node and putting alternating
key/value pairs.
=over
=item $data->put_map;
=item my $count = $data->get_map;
=back
=cut
sub put_map {
my ($self) = @_;
my $impl = $self->{_impl};
check(cproton_perl::pn_data_put_map($impl));
}
sub get_map {
my ($self) = @_;
my $impl = $self->{_impl};
cproton_perl::pn_data_get_map($impl);
}
sub put_list_helper {
my ($self) = @_;
my ($array) = $_[1];
$self->put_list;
$self->enter;
for my $value (@{$array}) {
if (qpid::proton::utils::is_num($value)) {
if (qpid::proton::utils::is_float($value)) {
$self->put_float($value);
} else {
$self->put_int($value);
}
} elsif (!defined($value)) {
$self->put_null;
} elsif ($value eq '') {
$self->put_string($value);
} elsif (ref($value) eq 'HASH') {
$self->put_map_helper($value);
} elsif (ref($value) eq 'ARRAY') {
$self->put_list_helper($value);
} else {
$self->put_string($value);
}
}
$self->exit;
}
sub get_list_helper {
my ($self) = @_;
my $result = [];
my $type = $self->get_type;
if ($cproton_perl::PN_LIST == $type->get_type_value) {
my $size = $self->get_list;
$self->enter;
for(my $count = 0; $count < $size; $count++) {
if ($self->next) {
my $value_type = $self->get_type;
my $value = $value_type->get($self);
push(@{$result}, $value);
}
}
$self->exit;
}
return $result;
}
sub put_map_helper {
my ($self) = @_;
my $hash = $_[1];
$self->put_map;
$self->enter;
foreach(keys %{$hash}) {
my $key = $_;
my $value = $hash->{$key};
my $keytype = ::reftype($key);
my $valtype = ::reftype($value);
if ($keytype eq ARRAY) {
$self->put_list_helper($key);
} elsif ($keytype eq "HASH") {
$self->put_map_helper($key);
} else {
$self->put_string("$key");
}
if (::reftype($value) eq HASH) {
$self->put_map_helper($value);
} elsif (::reftype($value) eq ARRAY) {
$self->put_list_helper($value);
} else {
$self->put_string("$value");
}
}
$self->exit;
}
sub get_map_helper {
my ($self) = @_;
my $result = {};
my $type = $self->get_type;
if ($cproton_perl::PN_MAP == $type->get_type_value) {
my $size = $self->get_map;
$self->enter;
for($count = 0; $count < $size; $count++) {
if($self->next) {
my $key = $self->get_type->get($self);
if($self->next) {
my $value = $self->get_type->get($self);
$result->{$key} = $value;
}
}
}
$self->exit;
}
return $result;
}
1;