File Coverage

blib/lib/Class/Delegation.pm
Criterion Covered Total %
statement 183 205 89.2
branch 56 92 60.8
condition 14 24 58.3
subroutine 44 51 86.2
pod 1 1 100.0
total 298 373 79.8


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