File Coverage

blib/lib/Object/InsideOut/Cumulative.pm
Criterion Covered Total %
statement 168 193 87.0
branch 33 44 75.0
condition 19 29 65.5
subroutine 23 24 95.8
pod 0 2 0.0
total 243 292 83.2


line stmt bran cond sub pod time code
1             package Object::InsideOut; {
2              
3 9     12   81 use strict;
  9         17  
  9         317  
4 9     9   44 use warnings;
  9         17  
  9         298  
5 9     9   54 no warnings 'redefine';
  9         20  
  9         7176  
6              
7             my $GBL = {};
8              
9             sub generate_CUMULATIVE :Sub(Private)
10             {
11 8     8 0 23 ($GBL) = @_;
12 8         21 my $g_cu = $$GBL{'sub'}{'cumu'};
13 8   100     32 my $cumu_td = $$g_cu{'new'}{'td'} || [];
14 8   100     42 my $cumu_bu = $$g_cu{'new'}{'bu'} || [];
15 8         23 delete($$g_cu{'new'});
16 8 50       38 if (! exists($$g_cu{'td'})) {
17 8         38 $$GBL{'sub'}{'cumu'} = {
18             td => {}, # 'Top down'
19             bu => {}, # 'Bottom up'
20             restrict => {}, # :Restricted
21             };
22 8         19 $g_cu = $$GBL{'sub'}{'cumu'};
23             }
24 8         15 my $cu_td = $$g_cu{'td'};
25 8         17 my $cu_bu = $$g_cu{'bu'};
26 8         15 my $cu_restr = $$g_cu{'restrict'};
27              
28             # Get names for :CUMULATIVE methods
29 8         14 my (%cum_loc);
30 8         15 while (my $info = shift(@{$cumu_td})) {
  41         97  
31 33   66     124 $$info{'name'} ||= sub_name($$info{'code'}, ':CUMULATIVE', $$info{'loc'});
32 33         58 my $package = $$info{'pkg'};
33 33         48 my $name = $$info{'name'};
34              
35 33         69 $cum_loc{$name}{$package} = $$info{'loc'};
36              
37 33         55 $$cu_td{$name}{$package} = $$info{'wrap'};
38 33 100       94 if (exists($$info{'exempt'})) {
39 4         38 push(@{$$cu_restr{$package}{$name}},
40 4   100     5 sort grep {$_} split(/[,'\s]+/, $$info{'exempt'} || ''));
  2         10  
41             }
42             }
43              
44             # Get names for :CUMULATIVE(BOTTOM UP) methods
45 8         17 while (my $info = shift(@{$cumu_bu})) {
  16         45  
46 8   33     34 $$info{'name'} ||= sub_name($$info{'code'}, ':CUMULATIVE(BOTTOM UP)', $$info{'loc'});
47 8         15 my $package = $$info{'pkg'};
48 8         17 my $name = $$info{'name'};
49              
50             # Check for conflicting definitions of 'name'
51 8 50       18 if ($$cu_td{$name}) {
52 0         0 foreach my $other_package (keys(%{$$cu_td{$name}})) {
  0         0  
53 0 0 0     0 if ($other_package->isa($package) ||
54             $package->isa($other_package))
55             {
56 0         0 my ($pkg, $file, $line) = @{$cum_loc{$name}{$other_package}};
  0         0  
57 0         0 my ($pkg2, $file2, $line2) = @{$$info{'loc'}};
  0         0  
58             OIO::Attribute->die(
59 0         0 'location' => $$info{'loc'},
60             'message' => "Conflicting definitions for cumulative method '$name'",
61             'Info' => "Declared as :CUMULATIVE in class '$pkg' (file '$file', line $line), but declared as :CUMULATIVE(BOTTOM UP) in class '$pkg2' (file '$file2' line $line2)");
62             }
63             }
64             }
65              
66 8         13 $$cu_bu{$name}{$package} = $$info{'wrap'};
67 8 50       38 if (exists($$info{'exempt'})) {
68 0         0 push(@{$$cu_restr{$package}{$name}},
69 0   0     0 sort grep {$_} split(/[,'\s]+/, $$info{'exempt'} || ''));
  0         0  
70             }
71             }
72              
73             # Propagate restrictions
74 8         18 my $reapply = 1;
75 8         16 my $trees = $$GBL{'tree'}{'td'};
76 8         24 while ($reapply) {
77 9         16 $reapply = 0;
78 9         17 foreach my $pkg (keys(%{$cu_restr})) {
  9         30  
79 11         13 foreach my $class (keys(%{$trees})) {
  11         25  
80 84 100       113 next if (! grep { $_ eq $pkg } @{$$trees{$class}});
  221         410  
  84         126  
81 33         41 foreach my $p (@{$$trees{$class}}) {
  33         59  
82 142         171 foreach my $n (keys(%{$$cu_restr{$pkg}})) {
  142         243  
83 142 100       230 if (exists($$cu_restr{$p}{$n})) {
84 136 100       319 next if ($$cu_restr{$p}{$n} == $$cu_restr{$pkg}{$n});
85 2         15 my $equal = (@{$$cu_restr{$p}{$n}} == @{$$cu_restr{$pkg}{$n}});
  2         12  
  2         6  
86 2 50       5 if ($equal) {
87 2         3 for (1..@{$$cu_restr{$p}{$n}}) {
  2         7  
88 0 0       0 if ($$cu_restr{$pkg}{$n}[$_-1] ne $$cu_restr{$p}{$n}[$_-1]) {
89 0         0 $equal = 0;
90 0         0 last;
91             }
92             }
93             }
94 2 50       6 if (! $equal) {
95 0         0 my %restr = map { $_ => 1 } @{$$cu_restr{$p}{$n}}, @{$$cu_restr{$pkg}{$n}};
  0         0  
  0         0  
  0         0  
96 0         0 $$cu_restr{$pkg}{$n} = [ sort(keys(%restr)) ];
97 0         0 $reapply = 1;
98             }
99             } else {
100 6         9 $reapply = 1;
101             }
102 8         19 $$cu_restr{$p}{$n} = $$cu_restr{$pkg}{$n};
103             }
104             }
105             }
106             }
107             }
108              
109 9     9   74 no warnings 'redefine';
  9         18  
  9         429  
110 9     9   60 no strict 'refs';
  9         17  
  9         3248  
111              
112             # Implement :CUMULATIVE methods
113 8         17 foreach my $name (keys(%{$cu_td})) {
  8         22  
114 8         27 my $code = create_CUMULATIVE($name, $trees, $$cu_td{$name});
115 8         16 foreach my $package (keys(%{$$cu_td{$name}})) {
  8         27  
116 33         75 *{$package.'::'.$name} = $code;
  33         138  
117 33         109 add_meta($package, $name, 'kind', 'cumulative');
118 33 100       86 if (exists($$cu_restr{$package}{$name})) {
119 9         20 add_meta($package, $name, 'restrict', 1);
120             }
121             }
122             }
123              
124             # Implement :CUMULATIVE(BOTTOM UP) methods
125 8         21 foreach my $name (keys(%{$cu_bu})) {
  8         48  
126 2         6 my $code = create_CUMULATIVE($name, $$GBL{'tree'}{'bu'}, $$cu_bu{$name});
127 2         7 foreach my $package (keys(%{$$cu_bu{$name}})) {
  2         12  
128 8         16 *{$package.'::'.$name} = $code;
  8         28  
129 8         25 add_meta($package, $name, 'kind', 'cumulative (bottom up)');
130 8 50       32 if (exists($$cu_restr{$package}{$name})) {
131 0         0 add_meta($package, $name, 'restrict', 1);
132             }
133             }
134             }
135 9     9   79 }
  9         19  
  9         59  
136              
137              
138             # Returns a closure back to initialize() that is used to setup CUMULATIVE
139             # and CUMULATIVE(BOTTOM UP) methods for a particular method name.
140             sub create_CUMULATIVE :Sub(Private)
141             {
142             # $name - method name
143             # $tree - either $GBL{'tree'}{'td'} or $GBL{'tree'}{'bu'}
144             # $code_refs - hash ref by package of code refs for a particular method name
145 15     12 0 37 my ($name, $tree, $code_refs) = @_;
146              
147             return sub {
148 24   33 21   2541 my $class = ref($_[0]) || $_[0];
149 24 50       66 if (! $class) {
150 0         0 OIO::Method->die('message' => "Must call '$name' as a method");
151             }
152 24         45 my $list_context = wantarray;
153 24         47 my (@results, @classes);
154              
155             # Caller must be in class hierarchy
156 24         66 my $restr = $$GBL{'sub'}{'cumu'}{'restrict'};
157 24 100 100     126 if ($restr && exists($$restr{$class}{$name})) {
158 4         11 my $caller = caller();
159 4 100 100     6 if (! ((grep { $_ eq $caller } @{$$restr{$class}{$name}}) ||
  3   100     41  
  4         15  
160             $caller->isa($class) ||
161             $class->isa($caller)))
162             {
163 1         20 OIO::Method->die('message' => "Can't call restricted method '$class->$name' from class '$caller'");
164             }
165             }
166              
167             # Accumulate results
168 23         44 foreach my $pkg (@{$$tree{$class}}) {
  23         62  
169 94 100       5654 if (my $code = $$code_refs{$pkg}) {
170 81         254 local $SIG{'__DIE__'} = 'OIO::trap';
171 81         171 my @args = @_;
172 81 100       162 if (defined($list_context)) {
173 75         183 push(@classes, $pkg);
174 75 100       128 if ($list_context) {
175             # List context
176 22         64 push(@results, $code->(@args));
177             } else {
178             # Scalar context
179 53         143 push(@results, scalar($code->(@args)));
180             }
181             } else {
182             # void context
183 6         19 $code->(@args);
184             }
185             }
186             }
187              
188             # Return results
189 23 100       10533 if (defined($list_context)) {
190 19 100       47 if ($list_context) {
191             # List context
192 6         27 return (@results);
193             }
194             # Scalar context - returns object
195 13         58 return (Object::InsideOut::Results->new('VALUES' => \@results,
196             'CLASSES' => \@classes));
197             }
198 15         120 };
199 9     9   4442 }
  9         23  
  9         44  
200              
201             } # End of package's lexical scope
202              
203              
204             package Object::InsideOut::Results; {
205              
206 9     9   1113 use strict;
  9         21  
  9         245  
207 9     9   46 use warnings;
  9         28  
  9         588  
208              
209             our $VERSION = '4.05';
210             $VERSION = eval $VERSION;
211              
212 9     9   61 use Object::InsideOut 4.05;
  9         212  
  9         77  
213 9     9   63 use Object::InsideOut::Metadata 4.05;
  9         179  
  9         58  
214              
215             my @VALUES :Field :Arg(VALUES);
216             my @CLASSES :Field :Arg(CLASSES);
217             my @HASHES :Field;
218              
219             sub as_string :Stringify
220             {
221 4     4   8 return (join('', grep(defined, @{$VALUES[${$_[0]}]})));
  4         6  
  4         40  
222 9     9   65 }
  9         18  
  9         37  
223              
224             sub count :Numerify
225             {
226 4     4   20 return (scalar(@{$VALUES[${$_[0]}]}));
  4         162  
  4         29  
227 9     9   1420 }
  9         25  
  9         35  
228              
229             sub have_any :Boolify
230             {
231 0     0   0 return (@{$VALUES[${$_[0]}]} > 0);
  0         0  
  0         0  
232 9     9   1312 }
  9         29  
  9         39  
233              
234             sub values :Arrayify
235             {
236 12     12   20 return ($VALUES[${$_[0]}]);
  12         64  
237 9     9   1184 }
  9         18  
  9         47  
238              
239             sub as_hash :Hashify
240             {
241 41     41   75 my $self = $_[0];
242              
243 41 100       130 if (! defined($HASHES[$$self])) {
244 5         10 my %hash;
245 5         8 @hash{@{$CLASSES[$$self]}} = @{$VALUES[$$self]};
  5         20  
  5         11  
246 5         21 $self->set(\@HASHES, \%hash);
247             }
248              
249 41         231 return ($HASHES[$$self]);
250 9     9   1878 }
  9         20  
  9         42  
251              
252             # Our metadata
253             add_meta('Object::InsideOut::Results', {
254             'new' => {'hidden' => 1},
255             'create_field' => {'hidden' => 1},
256             'add_class' => {'hidden' => 1},
257             });
258              
259             } # End of package's lexical scope
260              
261              
262             # Ensure correct versioning
263             ($Object::InsideOut::VERSION eq '4.05')
264             or die("Version mismatch\n");
265              
266             # EOF