File Coverage

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


line stmt bran cond sub pod time code
1             package Object::InsideOut; {
2              
3 9     12   40 use strict;
  9         11  
  9         241  
4 9     9   27 use warnings;
  9         97  
  9         215  
5 9     9   74 no warnings 'redefine';
  9         8  
  9         5069  
6              
7             my $GBL = {};
8              
9             sub generate_CUMULATIVE :Sub(Private)
10             {
11 8     8 0 12 ($GBL) = @_;
12 8         15 my $g_cu = $$GBL{'sub'}{'cumu'};
13 8   100     28 my $cumu_td = $$g_cu{'new'}{'td'} || [];
14 8   100     31 my $cumu_bu = $$g_cu{'new'}{'bu'} || [];
15 8         12 delete($$g_cu{'new'});
16 8 50       19 if (! exists($$g_cu{'td'})) {
17 8         19 $$GBL{'sub'}{'cumu'} = {
18             td => {}, # 'Top down'
19             bu => {}, # 'Bottom up'
20             restrict => {}, # :Restricted
21             };
22 8         12 $g_cu = $$GBL{'sub'}{'cumu'};
23             }
24 8         8 my $cu_td = $$g_cu{'td'};
25 8         9 my $cu_bu = $$g_cu{'bu'};
26 8         7 my $cu_restr = $$g_cu{'restrict'};
27              
28             # Get names for :CUMULATIVE methods
29 8         7 my (%cum_loc);
30 8         9 while (my $info = shift(@{$cumu_td})) {
  41         75  
31 33   66     81 $$info{'name'} ||= sub_name($$info{'code'}, ':CUMULATIVE', $$info{'loc'});
32 33         31 my $package = $$info{'pkg'};
33 33         23 my $name = $$info{'name'};
34              
35 33         36 $cum_loc{$name}{$package} = $$info{'loc'};
36              
37 33         30 $$cu_td{$name}{$package} = $$info{'wrap'};
38 33 100       58 if (exists($$info{'exempt'})) {
39 4         22 push(@{$$cu_restr{$package}{$name}},
40 4   100     4 sort grep {$_} split(/[,'\s]+/, $$info{'exempt'} || ''));
  2         4  
41             }
42             }
43              
44             # Get names for :CUMULATIVE(BOTTOM UP) methods
45 8         9 while (my $info = shift(@{$cumu_bu})) {
  16         34  
46 8   33     24 $$info{'name'} ||= sub_name($$info{'code'}, ':CUMULATIVE(BOTTOM UP)', $$info{'loc'});
47 8         8 my $package = $$info{'pkg'};
48 8         7 my $name = $$info{'name'};
49              
50             # Check for conflicting definitions of 'name'
51 8 50       12 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         10 $$cu_bu{$name}{$package} = $$info{'wrap'};
67 8 50       21 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         13 my $reapply = 1;
75 8         11 my $trees = $$GBL{'tree'}{'td'};
76 8         17 while ($reapply) {
77 9         9 $reapply = 0;
78 9         9 foreach my $pkg (keys(%{$cu_restr})) {
  9         24  
79 11         8 foreach my $class (keys(%{$trees})) {
  11         15  
80 84 100       42 next if (! grep { $_ eq $pkg } @{$$trees{$class}});
  221         234  
  84         65  
81 33         22 foreach my $p (@{$$trees{$class}}) {
  33         30  
82 142         73 foreach my $n (keys(%{$$cu_restr{$pkg}})) {
  142         135  
83 142 100       130 if (exists($$cu_restr{$p}{$n})) {
84 136 100       238 next if ($$cu_restr{$p}{$n} == $$cu_restr{$pkg}{$n});
85 2         1 my $equal = (@{$$cu_restr{$p}{$n}} == @{$$cu_restr{$pkg}{$n}});
  2         2  
  2         2  
86 2 50       3 if ($equal) {
87 2         2 for (1..@{$$cu_restr{$p}{$n}}) {
  2         4  
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       4 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         6 $reapply = 1;
101             }
102 8         10 $$cu_restr{$p}{$n} = $$cu_restr{$pkg}{$n};
103             }
104             }
105             }
106             }
107             }
108              
109 9     9   43 no warnings 'redefine';
  9         7  
  9         261  
110 9     9   32 no strict 'refs';
  9         8  
  9         2287  
111              
112             # Implement :CUMULATIVE methods
113 8         15 foreach my $name (keys(%{$cu_td})) {
  8         17  
114 8         23 my $code = create_CUMULATIVE($name, $trees, $$cu_td{$name});
115 8         9 foreach my $package (keys(%{$$cu_td{$name}})) {
  8         18  
116 33         22 *{$package.'::'.$name} = $code;
  33         85  
117 33         56 add_meta($package, $name, 'kind', 'cumulative');
118 33 100       58 if (exists($$cu_restr{$package}{$name})) {
119 9         11 add_meta($package, $name, 'restrict', 1);
120             }
121             }
122             }
123              
124             # Implement :CUMULATIVE(BOTTOM UP) methods
125 8         9 foreach my $name (keys(%{$cu_bu})) {
  8         33  
126 2         3 my $code = create_CUMULATIVE($name, $$GBL{'tree'}{'bu'}, $$cu_bu{$name});
127 2         2 foreach my $package (keys(%{$$cu_bu{$name}})) {
  2         5  
128 8         2 *{$package.'::'.$name} = $code;
  8         20  
129 8         13 add_meta($package, $name, 'kind', 'cumulative (bottom up)');
130 8 50       24 if (exists($$cu_restr{$package}{$name})) {
131 0         0 add_meta($package, $name, 'restrict', 1);
132             }
133             }
134             }
135 9     9   38 }
  9         12  
  9         40  
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 23 my ($name, $tree, $code_refs) = @_;
146              
147             return sub {
148 24   33 21   1722 my $class = ref($_[0]) || $_[0];
149 24 50       44 if (! $class) {
150 0         0 OIO::Method->die('message' => "Must call '$name' as a method");
151             }
152 24         26 my $list_context = wantarray;
153 24         33 my (@results, @classes);
154              
155             # Caller must be in class hierarchy
156 24         42 my $restr = $$GBL{'sub'}{'cumu'}{'restrict'};
157 24 100 66     91 if ($restr && exists($$restr{$class}{$name})) {
158 4         10 my $caller = caller();
159 4 100 100     4 if (! ((grep { $_ eq $caller } @{$$restr{$class}{$name}}) ||
  3   100     24  
  4         12  
160             $caller->isa($class) ||
161             $class->isa($caller)))
162             {
163 1         12 OIO::Method->die('message' => "Can't call restricted method '$class->$name' from class '$caller'");
164             }
165             }
166              
167             # Accumulate results
168 23         23 foreach my $pkg (@{$$tree{$class}}) {
  23         48  
169 94 100       2727 if (my $code = $$code_refs{$pkg}) {
170 81         161 local $SIG{'__DIE__'} = 'OIO::trap';
171 81         211 my @args = @_;
172 81 100       103 if (defined($list_context)) {
173 75         68 push(@classes, $pkg);
174 75 100       77 if ($list_context) {
175             # List context
176 22         46 push(@results, $code->(@args));
177             } else {
178             # Scalar context
179 53         89 push(@results, scalar($code->(@args)));
180             }
181             } else {
182             # void context
183 6         15 $code->(@args);
184             }
185             }
186             }
187              
188             # Return results
189 23 100       5013 if (defined($list_context)) {
190 19 100       40 if ($list_context) {
191             # List context
192 6         19 return (@results);
193             }
194             # Scalar context - returns object
195 13         41 return (Object::InsideOut::Results->new('VALUES' => \@results,
196             'CLASSES' => \@classes));
197             }
198 15         77 };
199 9     9   2995 }
  9         9  
  9         27  
200              
201             } # End of package's lexical scope
202              
203              
204             package Object::InsideOut::Results; {
205              
206 9     9   764 use strict;
  9         9  
  9         152  
207 9     9   24 use warnings;
  9         7  
  9         377  
208              
209             our $VERSION = '4.04';
210             $VERSION = eval $VERSION;
211              
212 9     9   28 use Object::InsideOut 4.04;
  9         153  
  9         38  
213 9     9   44 use Object::InsideOut::Metadata 4.04;
  9         122  
  9         39  
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   5 return (join('', grep(defined, @{$VALUES[${$_[0]}]})));
  4         3  
  4         25  
222 9     9   34 }
  9         10  
  9         26  
223              
224             sub count :Numerify
225             {
226 4     4   4 return (scalar(@{$VALUES[${$_[0]}]}));
  4         5  
  4         20  
227 9     9   1304 }
  9         9  
  9         34  
228              
229             sub have_any :Boolify
230             {
231 0     0   0 return (@{$VALUES[${$_[0]}]} > 0);
  0         0  
  0         0  
232 9     9   1006 }
  9         19  
  9         24  
233              
234             sub values :Arrayify
235             {
236 12     12   14 return ($VALUES[${$_[0]}]);
  12         86  
237 9     9   939 }
  9         10  
  9         28  
238              
239             sub as_hash :Hashify
240             {
241 41     41   41 my $self = $_[0];
242              
243 41 100       97 if (! defined($HASHES[$$self])) {
244 5         5 my %hash;
245 5         5 @hash{@{$CLASSES[$$self]}} = @{$VALUES[$$self]};
  5         17  
  5         9  
246 5         16 $self->set(\@HASHES, \%hash);
247             }
248              
249 41         122 return ($HASHES[$$self]);
250 9     9   1378 }
  9         10  
  9         28  
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.04')
264             or die("Version mismatch\n");
265              
266             # EOF