File Coverage

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


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