File Coverage

blib/lib/Thrift/Parser/Type/Container.pm
Criterion Covered Total %
statement 145 160 90.6
branch 72 94 76.6
condition 16 33 48.4
subroutine 16 16 100.0
pod 10 10 100.0
total 259 313 82.7


line stmt bran cond sub pod time code
1             package Thrift::Parser::Type::Container;
2              
3             =head1 NAME
4              
5             Thrift::Parser::Type::Container - Container base class
6              
7             =head1 DESCRIPTION
8              
9             This class implements common behavior for L, L and L.
10              
11             =cut
12              
13 6     6   32 use strict;
  6         13  
  6         206  
14 6     6   31 use warnings;
  6         14  
  6         145  
15 6     6   33 use Data::Dumper;
  6         10  
  6         338  
16 6     6   326 use Scalar::Util qw(blessed);
  6         14  
  6         303  
17 6     6   31 use base qw(Thrift::Parser::Type);
  6         18  
  6         13693  
18             __PACKAGE__->mk_accessors(qw(val_type));
19              
20             =head1 METHODS
21              
22             This class inherits from L; see docs there for inherited methods.
23              
24             =head2 compose
25              
26             $map_class->define(..)->compose({ key => $value, ... });
27              
28             $map_class->define(..)->compose([ $key => $value, ... ]);
29              
30             $list_set_class->define(..)->compose([ $item, $item ]);
31              
32             Call L first before using this method. If map, call with array or hashref. For set and list, call with arrayref. Creates a new object in this class. Throws L.
33              
34             =cut
35              
36             sub compose {
37 36     36 1 2300 my ($self, $value) = @_;
38 36         39 my $class;
39 36 100       78 if (! ref $self) {
40 2         5 $class = $self;
41 2         28 $self = $class->new();
42             }
43             else {
44 34         98 $class = ref $self;
45             }
46              
47             # If compose() has been called on an already existant container, copy myself to a
48             # newly blessed object (sans value) so we can inherit the same val/key_type_class/define values
49 36 100       152 if (defined $self->{value}) {
50 4         27 $self = bless { %$self, value => [] }, $class;
51             }
52              
53 36         64 my $val_type = $self->{val_type_class};
54 36 100       77 if (! defined $val_type) {
55 2 100       18 if ($class->idl) {
56             # We should be able to infer the define call for this compose() automatically
57 1         14 return $class->compose_with_idl($class->idl->type, $value);
58             }
59 1         37 Thrift::Parser::InvalidArgument->throw("Must call define() on $class before compose()");
60             }
61              
62 34 100       89 if ($self->{val_type_define}) {
63 7         12 $val_type = $self->{val_type_class}->define(@{ $self->{val_type_define} });
  7         29  
64             }
65              
66 34         42 my $key_type;
67 34 100       216 if ($class->can('key_type')) {
68 8         11 $key_type = $self->{key_type_class};
69 8 50       16 if (! defined $key_type) {
70 0         0 Thrift::Parser::InvalidArgument->throw("Must call define() on $class before compose() for both key and val types");
71             }
72              
73 8 50       18 if ($self->{key_type_define}) {
74 0         0 $key_type = $self->{key_type_class}->define(@{ $self->{key_type_define} });
  0         0  
75             }
76             }
77              
78 34 100       99 if (blessed $value) {
79 6 50       54 if (! $value->isa($class)) {
80 0         0 Thrift::Parser::InvalidArgument->throw("$class compose() can't take a value of ".ref($value));
81             }
82             #print Dumper({ self => $self, new_value => $value });
83 6         19 foreach my $key (qw(val_type val_type_class val_type_define key_type key_type_class key_type_define)) {
84 23 100       185 next unless defined $self->{$key};
85 12         27 my ($value_a, $value_b) = ($self->{$key}, $value->{$key});
86 12 100       39 if ($key =~ m{_define$}) {
    100          
87             # Deep check of similarity
88 2 100       19 if (Dumper($value_a) ne Dumper($value_b)) {
89 1         132 Thrift::Parser::InvalidArgument->throw("$class compose() invalid typed object passed; $key expected '".Dumper($value_a)."', got '".Dumper($value_b)."'");
90             }
91             }
92             elsif ($value_a ne $value_b) {
93 2         27 Thrift::Parser::InvalidArgument->throw("$class compose() invalid typed object passed; $key expected '$value_a', got '$value_b'");
94             }
95             }
96 3         26 return $value;
97             }
98              
99 28         32 my @values;
100 28 100       52 if ($key_type) {
101 8 100 66     56 if (! ref $value || (ref $value ne 'HASH' && ref $value ne 'ARRAY')) {
      66        
102 1         10 Thrift::Parser::InvalidArgument->throw("Composing a $class requires a HASHREF or ARRAYREF");
103             }
104 7 100       27 my @args = ( ref $value eq 'HASH' ? %$value : @$value );
105 7 100       17 if (int @args % 2 != 0) {
106 1         8 Thrift::Parser::InvalidArgument->throw("Composing containers with key's requires a hash; I see an odd number of pairs here");
107             }
108 6         18 for (my $i = 0; $i <= $#args; $i += 2) {
109 7         34 my ($key, $val) = @args[$i .. $i + 1];
110 7         37 push @values, [
111             $key_type->compose($key),
112             $val_type->compose($val),
113             ];
114             }
115             }
116             else {
117 20 100 66     113 if (! ref $value || ref $value ne 'ARRAY') {
118 1         11 Thrift::Parser::InvalidArgument->throw("Composing a $class requires an ARRAYREF");
119             }
120 19         37 foreach my $val (@$value) {
121 53         477 push @values, $val_type->compose($val);
122             }
123             }
124              
125 22         288 $self->value(\@values);
126              
127 22         344 return $self;
128             }
129              
130             =head2 define
131              
132             my $list = $list_class->define('::string')->compose([ ... ]);
133              
134             my $nested_list = $list_class->define('::list' => [ '::string' ])->compose([ ... ]);
135              
136             my $map = $map_class->define('::i32', '::string')->compose([ ... ]);
137              
138             Call define with a list of class names that define the structure of this container. In the case of map, pass two values, $key_class and $val_class. For set and list, just $val_class. If either the key or the value class are themselves containers, the next argument is expected to be an arrayref of arguments for the nested C call. Returns a new object which is ready for L to be called.
139              
140             =cut
141              
142             sub define {
143 38     38 1 48445 my ($class, @args) = @_;
144              
145 38         327 my $self = $class->new();
146              
147 38         433 foreach my $key (grep { $self->can($_) } qw(key_type val_type)) {
  76         512  
148 47         70 my $type = shift @args;
149 47 100       130 if (! defined $type) {
150 2         17 Thrift::Parser::InvalidArgument->throw("$class define() requires type for '$key'");
151             }
152 45 100       99 if (ref $type) {
153 1         11 Thrift::Parser::InvalidArgument->throw("$class define() invalid type $type");
154             }
155 44 100       166 if ($type =~ m{^::}) {
156 43         132 $type = 'Thrift::Parser::Type' . $type;
157             }
158 44 100       552 if (! $type->can('type_id')) {
159 1         9 Thrift::Parser::InvalidArgument->throw("$type doesn't support type_id()");
160             }
161 43         147 $self->$key( $type->type_id );
162 43         646 $self->{$key . '_class'} = $type;
163              
164 43 50 66     542 if ($type->can('val_type') && defined $args[0] && ref $args[0] && ref $args[0] eq 'ARRAY') {
      66        
      33        
165 8         30 $self->{$key . '_define'} = shift @args;
166             }
167             }
168              
169 34 100       102 if (int @args) {
170 1         12 Thrift::Parser::InvalidArgument->throw("$class define() unexpected number of args (".int(@args).") left over");
171             }
172              
173 33         177 return $self;
174             }
175              
176             sub compose_with_idl {
177 1     1 1 27 my ($class, $type, $value) = @_;
178              
179 1 50 33     9 if (blessed $value && $value->isa($class)) {
180             # Thrift::Parser::Unimplemented->throw("TODO: determine if this typed value adheres to the IDL type");
181 0         0 return $value;
182             }
183              
184 1         11 my @define = $class->_recursive_define_resolve($type);
185              
186 1         11 return $class->define(@define)->compose($value);
187             }
188              
189             sub _recursive_define_resolve {
190 1     1   2 my ($class, $type) = @_;
191              
192 1         3 my @define;
193 1         3 foreach my $key (grep { $class->can($_) } qw(key_type val_type)) {
  2         37  
194             # If the type of the container is a typedef, and we're in a custom class, resolve it via the object's idl
195             # TODO: there's no guarantee the first referenced type is a container type; may need to recurse
196 1 0 33     16 if ($type->isa('Thrift::IDL::Type::Custom') && $class->can('idl') && $class->idl->isa('Thrift::IDL::TypeDef')) {
      33        
197 0         0 $type = $class->idl->type;
198             }
199 1 50       7 next if ! $type->can($key);
200 1         4 my $idl_type = $type->$key;
201              
202 1 50       17 if ($idl_type->isa('Thrift::IDL::Type::Custom')) {
    0          
203 1         8 my $namespace = $idl_type->{header}->namespace('perl');
204 1 50       8 push @define, join ('::', (defined $namespace ? ($namespace) : ()), $idl_type->name);
205             }
206             elsif ($idl_type->can('val_type')) {
207 0         0 push @define, '::' . $idl_type->name;
208 0         0 push @define, [ _recursive_define_resolve($class, $idl_type) ];
209             }
210             else {
211 0         0 push @define, '::' . $idl_type->name;
212             }
213             }
214 1         13 return @define;
215             }
216              
217             =head2 keys
218              
219             Valid only for map types. Returns a list of the keys.
220              
221             =cut
222              
223             sub keys {
224 1     1 1 2 my $self = shift;
225 1 50       7 if (! $self->can('key_type')) {
226 0         0 Thrift::Parser::Exception->throw("Can't call 'keys()' on ".ref($self));
227             }
228 1         3 my @keys;
229 1         2 foreach my $pair (@{ $self->{value} }) {
  1         5  
230 2         6 push @keys, $pair->[0];
231             }
232 1         10 return @keys;
233             }
234              
235             =head2 values
236              
237             Returns a list of the values.
238              
239             =cut
240              
241             sub values {
242 2     2 1 7 my $self = shift;
243 2 100       11 my $is_map = $self->can('key_type') ? 1 : 0;
244              
245 2         4 my @values;
246 2         3 foreach my $value (@{ $self->{value} }) {
  2         6  
247 7 100       16 push @values, $is_map ? $value->[1] : $value;
248             }
249 2         14 return @values;
250             }
251              
252             sub value_plain {
253 2     2 1 6 my ($self) = @_;
254              
255 2 100       10 if ($self->can('key_type')) {
256 1         3 my %hash;
257 1         3 foreach my $pair (@{ $self->{value} }) {
  1         4  
258 2         12 my ($key, $value) = @$pair;
259 2 50       25 if (! blessed $key) {
    50          
260 0         0 die "Key is not blessed: " . Dumper($key);
261             }
262             elsif (! blessed $value) {
263 0         0 die "Value is not blessed: " . Dumper($value);
264             }
265 2         9 $hash{ $key->value_plain } = $value->value_plain;
266             }
267 1         17 return \%hash;
268             }
269             else {
270 1         3 my @array;
271 1         2 foreach my $value (@{ $self->{value} }) {
  1         3  
272 5         38 push @array, $value->value_plain;
273             }
274 1         11 return \@array;
275             }
276             }
277              
278             =head2 size
279              
280             Returns the number of values or key/value pairs (in the case of a map).
281              
282             =cut
283              
284             sub size {
285 11     11 1 1431 my $self = shift;
286 11         14 return int @{ $self->{value} };
  11         57  
287             }
288              
289             =head2 each
290              
291             Sets up an iterator over all the elements of this object and returns the next value or key/value pair (as list). Returns undef or an empty list (in the case of a map). Does not auto-reset; call L to reset the iterator.
292              
293             =cut
294              
295             sub each {
296 3     3 1 7 my $self = shift;
297 3 50       13 my $is_map = $self->can('key_type') ? 1 : 0;
298              
299 3         5 my $idx = $self->{_each_idx};
300 3 100       10 $idx = 0 unless defined $idx;
301              
302 3 50       3 if ($idx > $#{ $self->{value} }) {
  3         10  
303 0         0 $self->{_each_idx} = undef;
304 0 0       0 return $is_map ? () : undef;
305             }
306              
307 3         6 $self->{_each_idx} = $idx + 1;
308 3 50       17 return $is_map ? @{ $self->{value}[$idx] } : $self->{value}[$idx];
  0         0  
309             }
310              
311             =head2 each_reset
312              
313             Resets the L iterator.
314              
315             =cut
316              
317             sub each_reset {
318 1     1 1 2 my $self = shift;
319 1         4 $self->{_each_idx} = undef;
320             }
321              
322             =head2 index
323              
324             my $value = $list->index(0);
325             my ($key, $value) = $map->index(0);
326              
327             Returns the value at the index given (zero starting). Returns a list if a map type. Returns undef or () if not present. Throws L.
328              
329             =cut
330              
331             sub index {
332 7     7 1 13 my ($self, $idx) = @_;
333 7 50 33     76 Thrift::Parser::InvalidArgument->throw("Pass an index number")
      33        
334             unless defined $idx && ! ref $idx && $idx =~ m{^\d+$};
335 7 100       30 my $is_map = $self->can('key_type') ? 1 : 0;
336 7 50       11 return ($is_map ? () : undef) if $idx > $#{ $self->{value} };
  7 100       31  
337 6 100       30 return $is_map ? @{ $self->{value}[$idx] } : $self->{value}[$idx];
  1         8  
338             }
339              
340             =head1 COPYRIGHT
341              
342             Copyright (c) 2009 Eric Waters and XMission LLC (http://www.xmission.com/). All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
343              
344             The full text of the license can be found in the LICENSE file included with this module.
345              
346             =head1 AUTHOR
347              
348             Eric Waters
349              
350             =cut
351              
352             1;