File Coverage

blib/lib/Object/InsideOut/Chained.pm
Criterion Covered Total %
statement 128 138 92.7
branch 29 34 85.2
condition 21 29 72.4
subroutine 10 10 100.0
pod 0 2 0.0
total 188 213 88.2


line stmt bran cond sub pod time code
1             package Object::InsideOut; {
2              
3 6     10   54 use strict;
  6         13  
  6         544  
4 6     6   32 use warnings;
  6         12  
  6         196  
5 6     6   30 no warnings 'redefine';
  6         11  
  6         4864  
6              
7             my $GBL = {};
8              
9             sub generate_CHAINED :Sub(Private)
10             {
11 5     5 0 17 ($GBL) = @_;
12 5         15 my $g_ch = $$GBL{'sub'}{'chain'};
13 5   100     22 my $chain_td = $$g_ch{'new'}{'td'} || [];
14 5   100     22 my $chain_bu = $$g_ch{'new'}{'bu'} || [];
15 5         15 delete($$g_ch{'new'});
16 5 50       16 if (! exists($$g_ch{'td'})) {
17 5         17 $$GBL{'sub'}{'chain'} = {
18             td => {}, # 'Top down'
19             bu => {}, # 'Bottom up'
20             restrict => {}, # :Restricted
21             };
22 5         16 $g_ch = $$GBL{'sub'}{'chain'};
23             }
24 5         10 my $ch_td = $$g_ch{'td'};
25 5         8 my $ch_bu = $$g_ch{'bu'};
26 5         7 my $ch_restr = $$g_ch{'restrict'};
27              
28             # Get names for :CHAINED methods
29 5         10 my (%chain_loc);
30 5         8 while (my $info = shift(@{$chain_td})) {
  23         54  
31 18   66     49 $$info{'name'} ||= sub_name($$info{'code'}, ':CHAINED', $$info{'loc'});
32 18         28 my $package = $$info{'pkg'};
33 18         27 my $name = $$info{'name'};
34              
35 18         30 $chain_loc{$name}{$package} = $$info{'loc'};
36              
37 18         32 $$ch_td{$name}{$package} = $$info{'wrap'};
38 18 100       47 if (exists($$info{'exempt'})) {
39 2         11 push(@{$$ch_restr{$package}{$name}},
40 2   50     3 sort grep {$_} split(/[,'\s]+/, $$info{'exempt'} || ''));
  3         13  
41             }
42             }
43              
44             # Get names for :CHAINED(BOTTOM UP) methods
45 5         10 while (my $info = shift(@{$chain_bu})) {
  16         41  
46 11   66     39 $$info{'name'} ||= sub_name($$info{'code'}, ':CHAINED(BOTTOM UP)', $$info{'loc'});
47 11         21 my $package = $$info{'pkg'};
48 11         17 my $name = $$info{'name'};
49              
50             # Check for conflicting definitions of 'name'
51 11 50       21 if ($$ch_td{$name}) {
52 0         0 foreach my $other_package (keys(%{$$ch_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) = @{$chain_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 chained method '$name'",
61             'Info' => "Declared as :CHAINED in class '$pkg' (file '$file', line $line), but declared as :CHAINED(BOTTOM UP) in class '$pkg2' (file '$file2' line $line2)");
62             }
63             }
64             }
65              
66 11         21 $$ch_bu{$name}{$package} = $$info{'wrap'};
67 11 100       34 if (exists($$info{'exempt'})) {
68 3         25 push(@{$$ch_restr{$package}{$name}},
69 3   50     5 sort grep {$_} split(/[,'\s]+/, $$info{'exempt'} || ''));
  0         0  
70             }
71             }
72              
73             # Propagate restrictions
74 5         9 my $reapply = 1;
75 5         10 my $trees = $$GBL{'tree'}{'td'};
76 5         15 while ($reapply) {
77 6         9 $reapply = 0;
78 6         12 foreach my $pkg (keys(%{$ch_restr})) {
  6         21  
79 12         14 foreach my $class (keys(%{$trees})) {
  12         30  
80 84 100       108 next if (! grep { $_ eq $pkg } @{$$trees{$class}});
  237         430  
  84         135  
81 37         48 foreach my $p (@{$$trees{$class}}) {
  37         80  
82 157         196 foreach my $n (keys(%{$$ch_restr{$pkg}})) {
  157         279  
83 157 100       284 if (exists($$ch_restr{$p}{$n})) {
84 152 100       368 next if ($$ch_restr{$p}{$n} == $$ch_restr{$pkg}{$n});
85 9         14 my $equal = (@{$$ch_restr{$p}{$n}} == @{$$ch_restr{$pkg}{$n}});
  9         14  
  9         16  
86 9 100       17 if ($equal) {
87 6         10 for (1..@{$$ch_restr{$p}{$n}}) {
  6         14  
88 7 100       22 if ($$ch_restr{$pkg}{$n}[$_-1] ne $$ch_restr{$p}{$n}[$_-1]) {
89 1         2 $equal = 0;
90 1         1 last;
91             }
92             }
93             }
94 9 100       17 if (! $equal) {
95 4         6 my %restr = map { $_ => 1 } @{$$ch_restr{$p}{$n}}, @{$$ch_restr{$pkg}{$n}};
  11         21  
  4         6  
  4         9  
96 4         17 $$ch_restr{$pkg}{$n} = [ sort(keys(%restr)) ];
97 4         10 $reapply = 1;
98             }
99             } else {
100 5         7 $reapply = 1;
101             }
102 14         31 $$ch_restr{$p}{$n} = $$ch_restr{$pkg}{$n};
103             }
104             }
105             }
106             }
107             }
108              
109 6     6   49 no warnings 'redefine';
  6         13  
  6         256  
110 6     6   36 no strict 'refs';
  6         20  
  6         2240  
111              
112             # Implement :CHAINED methods
113 5         9 foreach my $name (keys(%{$ch_td})) {
  5         13  
114 3         10 my $code = create_CHAINED($name, $trees, $$ch_td{$name});
115 3         8 foreach my $package (keys(%{$$ch_td{$name}})) {
  3         9  
116 18         26 *{$package.'::'.$name} = $code;
  18         72  
117 18         56 add_meta($package, $name, 'kind', 'chained');
118 18 100       57 if (exists($$ch_restr{$package}{$name})) {
119 6         13 add_meta($package, $name, 'restricted', 1);
120             }
121             }
122             }
123              
124             # Implement :CHAINED(BOTTOM UP) methods
125 5         10 foreach my $name (keys(%{$ch_bu})) {
  5         23  
126 3         19 my $code = create_CHAINED($name, $$GBL{'tree'}{'bu'}, $$ch_bu{$name});
127 3         15 foreach my $package (keys(%{$$ch_bu{$name}})) {
  3         11  
128 11         16 *{$package.'::'.$name} = $code;
  11         42  
129 11         33 add_meta($package, $name, 'kind', 'chained (bottom up)');
130 11 100       40 if (exists($$ch_restr{$package}{$name})) {
131 3         5 add_meta($package, $name, 'restricted', 1);
132             }
133             }
134             }
135 6     6   44 }
  6         11  
  6         34  
136              
137              
138             # Returns a closure back to initialize() that is used to setup CHAINED
139             # and CHAINED(BOTTOM UP) methods for a particular method name.
140             sub create_CHAINED :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 9     9 0 20 my ($name, $tree, $code_refs) = @_;
146              
147             return sub {
148 13     13   678 my $thing = shift;
149 13   66     57 my $class = ref($thing) || $thing;
150 13 50       41 if (! $class) {
151 0         0 OIO::Method->die('message' => "Must call '$name' as a method");
152             }
153 13         28 my @args = @_;
154              
155             # Caller must be in class hierarchy
156 13         32 my $restr = $$GBL{'sub'}{'chain'}{'restrict'};
157 13 100 100     64 if ($restr && exists($$restr{$class}{$name})) {
158 7         16 my $caller = caller();
159 7 100 100     11 if (! ((grep { $_ eq $caller } @{$$restr{$class}{$name}}) ||
  6   100     32  
  7         36  
160             $caller->isa($class) ||
161             $class->isa($caller)))
162             {
163 1         14 OIO::Method->die('message' => "Can't call restricted method '$class->$name' from class '$caller'");
164             }
165             }
166              
167             # Chain results together
168 12         25 foreach my $pkg (@{$$tree{$class}}) {
  12         29  
169 53 100       409 if (my $code = $$code_refs{$pkg}) {
170 45         140 local $SIG{'__DIE__'} = 'OIO::trap';
171 45         126 @args = $thing->$code(@args);
172             }
173             }
174              
175             # Return results
176 12         221 return (@args);
177 9         53 };
178 6     6   2313 }
  6         14  
  6         23  
179              
180             } # End of package's lexical scope
181              
182              
183             # Ensure correct versioning
184             ($Object::InsideOut::VERSION eq '4.05')
185             or die("Version mismatch\n");
186              
187             # EOF