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