File Coverage

blib/lib/Class/Delegation.pm
Criterion Covered Total %
statement 180 199 90.4
branch 56 92 60.8
condition 14 24 58.3
subroutine 43 50 86.0
pod 1 1 100.0
total 294 366 80.3


line stmt bran cond sub pod time code
1             package Class::Delegation;
2              
3             our $VERSION = '1.9.0';
4              
5 2     2   15248 use strict;
  2         5  
  2         65  
6 2     2   9 use Carp;
  2         2  
  2         364  
7              
8 73     73   107 sub ::DEBUG { 0 };
9              
10             my %mappings;
11              
12             sub import {
13 3     3   16 my $class = shift;
14 3         5 my $caller = caller();
15 3         562 while (@_) {
16 18         9 push @{$mappings{$caller}}, Class::Delegation::->_new(\@_);
  18         35  
17             }
18             }
19              
20             INIT {
21 2     2   9 foreach my $class (keys %mappings) {
22 1         2 _install_delegation_for($class);
23             }
24             }
25              
26             sub _install_delegation_for {
27 2     2   8 use vars '$AUTOLOAD';
  2         5  
  2         90  
28 2     2   6 no strict 'refs';
  2         2  
  2         328  
29 1     1   2 my ($class) = @_;
30 1         2 my $symbol = "${class}::AUTOLOAD";
31 1 50       3 print STDERR "Installing $symbol\n" if ::DEBUG;
32             my $real_AUTOLOAD = *{$symbol}{CODE}
33 1   50 0   2 || sub {croak "Could not delegate $AUTOLOAD"};
  0         0  
34            
35 1     0   7 local $SIG{__WARN__} = sub {};
36             *$symbol = sub {
37 24     24   554 $$symbol = $AUTOLOAD;
38 24         98 my ($class, $method) = $AUTOLOAD =~ m/(.*::)(.*)/;
39 24         31 my ($invocant, @args) = @_;
40 24 50       28 print STDERR "Delegating: $AUTOLOAD...\n" if ::DEBUG;
41 2     2   1024 use Data::Dumper 'Dumper';
  2         13271  
  2         761  
42 24 50       24 print STDERR "...on: ", Dumper $invocant if ::DEBUG;
43 24         35 my @context = ($invocant, $method, @args);
44 24 50       34 $invocant = "${class}${invocant}" unless ref $invocant;
45 24         19 my $wantarray = wantarray;
46 24         28 my @delegators = _delegators_for(@context);
47 24 100       33 goto &{$real_AUTOLOAD} unless @delegators;
  2         6  
48 22         14 my (@results, $delegated);
49 22         19 DELEGATOR: foreach my $delegator ( @delegators ) {
50 22 50 33     38 next if $delegator->{other} && keys %$delegated;
51 22         15 my @to = @{$delegator->{to}};
  22         26  
52 22         16 my @as = @{$delegator->{as}};
  22         41  
53 22 100       32 if (@to==1) {
    100          
54 14 50       16 print STDERR "[$to[0]]\n" if ::DEBUG;
55 14 50       22 next DELEGATOR if exists $delegated->{$to[0]};
56 14         13 foreach my $as (@as) {
57 16         22 push @results, delegate($delegated,$wantarray,$invocant,$to[0],$as,\@args);
58             }
59             }
60             elsif (@as==1) {
61 6 50       7 print STDERR "[$to[0]]\n" if ::DEBUG;
62 6         4 foreach my $to (@to) {
63 15 50       21 next if exists $delegated->{$to};
64 15         19 push @results, delegate($delegated,$wantarray,$invocant,$to,$as[0],\@args);
65             }
66             }
67             else {
68 2         2 while (1) {
69 6 100 66     19 last unless @to && @as;
70 4         5 my $to = shift @to;
71 4         4 my $as = shift @as;
72 4 50       6 next if exists $delegated->{$to};
73 4         7 push @results, delegate($delegated,$wantarray,$invocant,$to,$as,\@args);
74             }
75             }
76             }
77 22 50       39 goto &{$real_AUTOLOAD} unless keys %$delegated;
  0         0  
78             return $wantarray
79 22 0       91 ? ( @results>1 ? @results : @{$results[0]} )
  0 100       0  
    50          
80             : ( @results>1 ? \@results : $results[0] );
81 1         7 };
82              
83 1 50 33     1 unless (*{"${class}::DESTROY"}{CODE} ||
  1         13  
84             _delegators_for($class,'DESTROY')) {
85 0     0   0 *{"${class}::DESTROY"} = sub {};
  0         0  
86             }
87             }
88              
89             sub delegate {
90 35     35 1 36 my ($delegated,$wantarray,$invocant,$to,$as,$args) = @_;
91 2     2   10 no strict 'refs';
  2         3  
  2         342  
92             my $target = ref $to ? $to
93             : $to =~ /^->(\w+)$/ ? $invocant->$+()
94             : $to eq -SELF ? $invocant
95 35 100       96 : $invocant->{$to};
    100          
    100          
96 35 50       34 return unless eval {
97 35 50       109 $target->can($as) || $target->can('AUTOLOAD')
98             };
99 35 50       69 my $result = $wantarray
100             ? [$target->$as(@$args)]
101             : $target->$as(@$args);
102 35 50       134 return if $@;
103 35         40 $_[0]->{$to}++;
104 35         70 return $result
105             }
106              
107             sub _delegators_for {
108 24     24   23 my ($self, $method, @args) = @_;
109              
110 24         15 my @attrs;
111 24   33     37 my $class = ref($self)||$self;
112 24         14 foreach my $candidate ( @{$mappings{$class}} ) {
  24         36  
113             push @attrs, $candidate->{send}->can_send(scalar(@attrs),
114             $candidate->{to},
115             $candidate->{as},
116 432         581 @_);
117             }
118 24 100       47 return @attrs if @attrs;
119 2     2   10 no strict 'refs';
  2         3  
  2         921  
120 2         2 my @ancestors = @{$class.'::ISA'};
  2         6  
121 2         2 my $parent;
122 2         4 while ($parent = shift @ancestors) {
123 2 50       8 next unless exists $mappings{$parent};
124 0         0 foreach my $candidate ( @{$mappings{$parent}} ) {
  0         0  
125             push @attrs, $candidate->{send}->can_send(scalar(@attrs),
126             $candidate->{to},
127             $candidate->{as},
128 0         0 @_);
129             }
130 0 0       0 return @attrs if @attrs;
131 0         0 unshift @ancestors, @{$parent.'::ISA'};
  0         0  
132             }
133 2         3 return @attrs;
134             }
135              
136             sub _new {
137 18     18   15 my ($class, $args) = @_;
138 18         16 my ($send, $send_val) = splice @$args, 0, 2;
139 18 50       28 croak "Expected 'send => ' but found '$send => $send_val'"
140             unless $send eq 'send';
141 18 50       26 croak "The expected 'to => ' is missing at end of list"
142             unless @$args >= 2;
143 18         17 my ($to, $to_val) = splice @$args, 0, 2;
144 18 50       23 croak "Expected 'to => ' but found '$to => $to_val'"
145             unless $to eq 'to';
146              
147 18         18 $send_val = _class_for(Send => $send_val)->_new($send_val);
148 18         17 my $to_obj = _class_for(To => $to_val)->_new($to_val);
149 18         25 my $self = bless { send=>$send_val, to=>$to_obj }, $class;
150 18 100 50     37 if (($args->[0]||"") eq 'as') {
151 14         16 my ($as, $as_val) = splice @$args, 0, 2;
152 14 50 100     29 croak "Arrays specified for 'to' and 'as' must be same length"
      66        
153             unless ref($to_val) ne 'ARRAY'
154             || ref($as_val) ne 'ARRAY'
155             || @$to_val == @$as_val;
156 14         16 $self->{as} = _class_for(As => $as_val)->_new($as_val);
157             }
158             else {
159 4 50       6 croak "'to => -SELF' is meaningless without 'as => '"
160             if $to_val eq -SELF;
161 4         5 $self->{as} = Class::Delegation::As::Sent->_new();
162             }
163 18         1595 return $self;
164             }
165              
166             my %allowed;
167             @{$allowed{Send}}{qw(ARRAY Regexp CODE)} = ();
168             @{$allowed{To}}{qw(ARRAY Regexp CODE)} = ();
169             @{$allowed{As}}{qw(ARRAY CODE)} = ();
170              
171             sub _class_for {
172 55     55   52 my ($subclass, $value) = @_;
173 55         43 my $type = ref($value);
174 55 100       97 return "Class::Delegation::${subclass}::SCALAR" unless $type;
175             croak "'\l$subclass' value cannot be $type reference"
176 21 50       28 unless exists $allowed{$subclass}{$type};
177 21         40 return "Class::Delegation::${subclass}::${type}";
178             }
179              
180             package # Hide from CPAN indexer
181             SELF;
182              
183       0     sub DESTROY {}
184             sub AUTOLOAD {
185 1     1   35 my ($name) = $SELF::AUTOLOAD =~ m/.*::(.+)/;
186 1         20 bless \$name, 'SELF'
187             }
188 2     2   8 use overload 'neg' => sub { "->${$_[0]}" };
  2     1   7  
  2         13  
  1         1  
  1         11  
189              
190              
191             package Class::Delegation::Send::SCALAR;
192              
193             sub _new {
194 17 50   17   22 return bless {}, "Class::Delegation::Send::ALL" if $_[1] eq '-ALL';
195 17 50       18 return bless {}, "Class::Delegation::Send::OTHER" if $_[-1] eq '-OTHER';
196 17         13 my $val = pop;
197 17         29 return bless \$val, $_[0]
198             }
199              
200             sub can_send {
201 408     408   363 my ($self, $sent, $to, $as, @context) = @_;
202 408 100       466 return { to => [$to->attr_for(@context)],
203             as => [$as->name_for(@context)],
204             }
205             if $$self eq $context[1];
206 391         369 return;
207             }
208              
209              
210             package Class::Delegation::Send::ARRAY;
211              
212             sub _new {
213             my @delegators =
214 2     2   1 map { Class::Delegation::_class_for(Send => $_)->_new($_) } @{$_[1]};
  5         5  
  2         4  
215 2         4 bless \@delegators, $_[0];
216             }
217              
218             sub can_send {
219 48     48   54 my ($self, @context) = @_;
220 48         38 return map { $_->can_send(@context) } @$self;
  120         109  
221             }
222              
223              
224             package Class::Delegation::Send::Regexp;
225              
226             sub _new {
227 2     2   1 my ($class, $regex) = @_;
228 2         2 my $self = bless \$regex, $class;
229 2         3 return $self;
230             }
231              
232              
233             sub can_send {
234 48     48   45 my ($self, $sent, $to, $as, @context) = @_;
235 48 100       141 return { to => [$to->attr_for(@context)],
236             as => [$as->name_for(@context)],
237             }
238             if $context[1] =~ $$self;
239 45         45 return;
240             }
241              
242              
243             package Class::Delegation::Send::CODE;
244              
245 2     2   3 sub _new { bless $_[1], $_[0] }
246              
247             sub can_send {
248 48     48   45 my ($self, $sent, $to, $as, @context) = @_;
249 48 100       57 return { to => [$to->attr_for(@context)],
250             as => [$as->name_for(@context)],
251             }
252             if $self->(@context);
253 46         158 return;
254             }
255              
256             package Class::Delegation::Send::ALL;
257              
258             sub can_send {
259 0     0   0 my ($self, $sent, $to, $as, @context) = @_;
260 0 0       0 return { to => [$to->attr_for(@context)],
261             as => [$as->name_for(@context)],
262             }
263             if $context[1] ne 'DESTROY';
264 0         0 return;
265             }
266              
267             package Class::Delegation::Send::OTHER;
268              
269             sub can_send {
270 0     0   0 my ($self, $sent, $to, $as, @context) = @_;
271 0 0       0 return { to => [$to->attr_for(@context)],
272             as => [$as->name_for(@context)],
273             other => 1,
274             }
275             if $context[1] ne 'DESTROY';
276 0         0 return;
277             }
278              
279            
280             package Class::Delegation::To::SCALAR;
281              
282             sub _new {
283 19     19   17 my ($class, $value) = @_;
284 19 50       25 return bless {}, "Class::Delegation::To::ALL" if $value eq '-ALL';
285 19         28 return bless \$value, $class
286             }
287              
288 23     23   22 sub attr_for { return ${$_[0]} }
  23         53  
289              
290              
291             package Class::Delegation::To::ARRAY;
292              
293             sub _new {
294 5     5   5 my ($class, $array) = @_;
295 5   100     4 bless [ map {("Class::Delegation::To::".(ref||"SCALAR"))->_new($_)} @$array ], $class;
  10         32  
296             }
297              
298             sub attr_for {
299 7     7   10 my ($self, @context) = @_;
300 7         10 return map { $_->attr_for(@context) } @$self;
  14         14  
301             }
302              
303             package Class::Delegation::To::Regexp;
304              
305             sub _new {
306 2     2   1 my ($class, $regex) = @_;
307 2         3 my $self = bless \$regex, $class;
308 2         3 return $self;
309             }
310              
311             sub attr_for {
312 4     4   4 my ($self, $invocant, @context) = @_;
313 4 50       5 print STDERR "[[$$self]]\n" if ::DEBUG;
314 4         8 return grep { $_ =~ $$self } keys %$invocant;
  20         56  
315             }
316              
317              
318             package Class::Delegation::To::CODE;
319              
320 2     2   3 sub _new { bless $_[1], $_[0] }
321              
322             sub attr_for {
323 2     2   3 my ($self, @context) = @_;
324 2         7 return $self->(@context)
325             }
326              
327              
328             package Class::Delegation::To::ALL;
329              
330             sub attr_for {
331 0     0   0 my ($self, $invocant, @context) = @_;
332 0         0 return keys %$invocant;
333             }
334              
335              
336              
337             package Class::Delegation::As::SCALAR;
338              
339             sub _new {
340 7     7   7 my ($class, $value) = @_;
341 7         17 bless \$value, $class;
342             }
343              
344 7     7   19 sub name_for { ${$_[0]} }
  7         40  
345              
346             package Class::Delegation::As::ARRAY;
347              
348             sub _new {
349 4     4   4 my ($class, $value) = @_;
350 4         6 bless $value, $class;
351             }
352              
353 4     4   3 sub name_for { @{$_[0]} }
  4         15  
354              
355              
356             package Class::Delegation::As::Sent;
357              
358 4     4   10 sub _new { bless {}, $_[0] }
359              
360             sub name_for {
361 5     5   5 my ($self, $invocant, $method) = @_;
362 5         15 return $method;
363             }
364              
365             package Class::Delegation::As::CODE;
366              
367 3     3   4 sub _new { bless $_[1], $_[0] }
368              
369             sub name_for {
370 6     6   7 my ($self, @context) = @_;
371 6         11 return $self->(@context)
372             }
373              
374             1;
375              
376             __END__