File Coverage

blib/lib/Farly/Object/Aggregate.pm
Criterion Covered Total %
statement 92 93 98.9
branch 25 30 83.3
condition n/a
subroutine 16 16 100.0
pod 8 9 88.8
total 141 148 95.2


line stmt bran cond sub pod time code
1             package Farly::Object::Aggregate;
2 16     16   318 use 5.008008;
  16         53  
  16         869  
3 16     16   90 use strict;
  16         29  
  16         538  
4 16     16   90 use warnings;
  16         38  
  16         478  
5 16     16   80 use Carp;
  16         31  
  16         21263  
6             require Exporter;
7             require Farly::Object::List;
8            
9             our @ISA = qw(Exporter);
10             our @EXPORT_OK = qw(NEXTVAL);
11            
12             our $VERSION = '0.26';
13            
14             sub new {
15 8     8 1 29 my ( $class, $container ) = @_;
16            
17 8 50       35 confess "container object required"
18             unless ( defined($container) );
19            
20 8 50       52 confess "Farly::Object::List object required"
21             unless ( $container->isa('Farly::Object::List') );
22            
23 8         38 my $self = {
24             CONTAINER => $container, # input
25             GROUPED => undef, # @array of objects grouped by identity
26             };
27            
28 8         26 bless $self, $class;
29            
30 8         33 return $self;
31             }
32            
33             sub container {
34 10     10 0 84 return $_[0]->{'CONTAINER'};
35             }
36            
37             sub iter {
38 66     66 1 85 return @{ $_[0]->{'GROUPED'} };
  66         836  
39             }
40            
41 11     11 1 399 sub NEXTVAL { $_[0]->() }
42            
43             # iterator over the aggregate identities
44             sub id_iterator {
45 2     2 1 6 my ($self) = @_;
46            
47 2         10 my @arr = $self->iter();
48 2         4 my $i = 0;
49            
50             # the iterator code ref
51             return sub {
52 7 100   7   93 return undef if ( $i == scalar(@arr) );
53            
54 5         16 my $object = Farly::Object->new();
55            
56 5         19 foreach my $property ( $arr[$i]->get_keys() ) {
57 14 100       39 if ( $property ne '__AGG__' ) {
58 9         30 $object->set( $property, $arr[$i]->get($property) );
59             }
60             }
61            
62 5         11 $i++;
63            
64 5         16 return $object;
65             }
66 2         16 }
67            
68             sub list_iterator {
69 1     1 1 3 my ($self) = @_;
70            
71 1         2 my @arr = $self->iter();
72 1         2 my $i = 0;
73            
74             # the iterator code ref
75             return sub {
76 4 100   4   12 return undef if ( $i == scalar(@arr) );
77 3         14 my $set = $arr[$i]->get('__AGG__');
78 3         93 $i++;
79 3         6 return $set;
80             }
81 1         7 }
82            
83             # CONTAINER objects which have defined all keys
84             # return \@array
85             sub _has_defined_keys {
86 10     10   24 my ( $self, $keys ) = @_;
87            
88 10         14 my @r;
89            
90 10         39 foreach my $obj ( $self->container->iter() ) {
91            
92 357         389 my $all_keys_defined = 1;
93            
94 357         551 foreach my $key (@$keys) {
95 735 100       1770 if ( !$obj->has_defined($key) ) {
96 14         23 $all_keys_defined = undef;
97 14         27 last;
98             }
99 721 100       1660 if ( !$obj->get($key)->can('compare') ) {
100            
101             #warn "$self skipped ", $obj->dump(), " in groupby\n";
102 5         14 $all_keys_defined = undef;
103 5         14 last;
104             }
105             }
106            
107 357 100       727 if ($all_keys_defined) {
108 338         764 push @r, $obj;
109             }
110             }
111            
112 10         77 return \@r;
113             }
114            
115             # [ { KEY1 => value object,
116             # KEY2 => value object,
117             # __AGG__ => Farly::Object::List }, ]
118             # __AGG__ is a set of all objects sharing the
119             # common identity formed by KEY1 and KEY2,
120             # i.e $obj1->{KEY1} equals $obj2->{KEY1}
121             # and $obj1->{KEY2} equals $obj2->{KEY2}
122             # for all objects in __AGG__
123            
124             sub groupby {
125 10     10 1 37 my ($self) = shift;
126 10         42 my @keys = @_;
127            
128 10 50       39 confess "a list of keys is required"
129             unless ( scalar(@keys) > 0 );
130            
131             # $list will include objects that have defined all @keys
132 10         44 my $list = $self->_has_defined_keys( \@keys );
133            
134             # check list size?
135            
136 979         1193 my @sorted = sort {
137            
138 10         77 my $r;
139 979         1181 foreach my $key (@keys) {
140 1724         4500 $r = $a->get($key)->compare( $b->get($key) );
141 1724 100       10550 return $r if ( $r != 0 );
142             }
143 301         456 return $r;
144            
145             } @$list;
146            
147 10         27 my @grouped;
148            
149 10         53 for ( my $i = 0 ; $i != scalar(@sorted) ; $i++ ) {
150            
151 138         421 my $root = Farly::Object->new();
152            
153 138         244 foreach my $key (@keys) {
154 290         797 $root->set( $key, $sorted[$i]->get($key) );
155             }
156            
157 138         507 my $result = Farly::Object::List->new();
158            
159 138         186 my $j = $i;
160            
161 138         418 while ( $sorted[$j]->matches($root) ) {
162            
163 338         950 $result->add( $sorted[$j] );
164            
165 338         348 $j++;
166            
167 338 100       1296 last() if $j == scalar(@sorted);
168             }
169            
170 138         265 $i = $j - 1;
171            
172 138         354 $root->set( '__AGG__', $result );
173            
174 138         497 push @grouped, $root;
175             }
176            
177 10         127 $self->{'GROUPED'} = \@grouped;
178             }
179            
180             # input = search object
181             # return the __AGG__ object on first match
182             sub matches {
183 58     58 1 96 my ( $self, $search ) = @_;
184            
185 58         146 foreach my $object ( $self->iter() ) {
186 475 100       1294 if ( $object->matches($search) ) {
187 57         174 return $object->get('__AGG__');
188             }
189             }
190            
191             #return an empty List on no match
192 1         9 return Farly::Object::List->new();
193             }
194            
195             # input = search object and new __AGG__
196             sub update {
197 2     2 1 9 my ( $self, $search, $list ) = @_;
198            
199 2 50       8 confess "Farly::Object::List required"
200             unless defined($list);
201            
202 2 50       10 confess "Farly::Object::List required"
203             unless $list->isa('Farly::Object::List');
204            
205 2         6 foreach my $object ( $self->iter() ) {
206 11 100       29 if ( $object->matches($search) ) {
207 2         8 $object->set( '__AGG__', $list );
208 2         4 return;
209             }
210             }
211            
212 0           confess $search->dump(), " not found";
213             }
214            
215             1;
216             __END__