File Coverage

blib/lib/Class/Delegation.pm
Criterion Covered Total %
statement 179 198 90.4
branch 56 92 60.8
condition 16 24 66.6
subroutine 43 50 86.0
pod 1 1 100.0
total 295 365 80.8


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