File Coverage

blib/lib/Object/InsideOut/Chained.pm
Criterion Covered Total %
statement 128 138 92.7
branch 29 34 85.2
condition 20 29 68.9
subroutine 10 10 100.0
pod 0 2 0.0
total 187 213 87.7


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