File Coverage

blib/lib/Object/Meta/Plugin/Host.pm
Criterion Covered Total %
statement 112 204 54.9
branch 17 54 31.4
condition 7 30 23.3
subroutine 28 55 50.9
pod 8 10 80.0
total 172 353 48.7


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2             # $Id: Host.pm,v 1.23 2003/12/11 14:27:17 nothingmuch Exp $
3              
4             package Object::Meta::Plugin::Host;
5              
6 1     1   1049 use strict;
  1         2  
  1         38  
7 1     1   5 use warnings;
  1         1  
  1         35  
8              
9 1     1   4 use autouse Carp => qw(croak);
  1         2  
  1         11  
10 1     1   63 use Scalar::Util qw(reftype);
  1         2  
  1         118  
11 1     1   1006 use Tie::RefHash;
  1         33166  
  1         1376  
12              
13             our $VERSION = 0.02;
14             our $AUTOLOAD;
15              
16             sub new {
17 1     1 0 3185 my $pkg = shift;
18 1         6 my $self = {
19             plugins => {}, # plugin ref hash
20             methods => {}, # method look up, with an array of plugin refs per method
21             };
22            
23 1         4 tie %{ $self->{plugins} }, 'Tie::RefHash';
  1         11  
24            
25 1         16 bless $self, $pkg;
26             }
27              
28             sub plugins {
29 3     3 1 26 my $self = shift;
30 3         28 return $self->{plugins};
31             }
32              
33             sub methods {
34 7     7 1 9 my $self = shift;
35              
36 7         44 return $self->{methods};
37             }
38              
39             sub plug {
40 1     1 1 51 my $self = shift;
41 1         3 my $plugin = shift;
42            
43 1 50       2 croak "$plugin doesn't look like a plugin" if (grep { not $plugin->can($_) } qw/init/);
  1         12  
44              
45 1   33     5 my $x = $self->register($plugin->init(@_) or croak "init() did not return an export list");
46            
47 1 50       34 if ($x->info->style() eq 'implicit'){
48            
49 0 0       0 if (reftype($plugin) eq 'ARRAY'){
50 0 0       0 warnings::warnif($plugin,"You probably shouldn't use implicit access context shims if the underlying plugin's structure is already a tied array. Use the 'tied' style if you want to suppress this message") if do { local $@; eval { tied (@{$plugin}) } };
  0         0  
  0         0  
  0         0  
  0         0  
51             } else {
52 0 0       0 warnings::warnif($plugin,"Overloading a plugin's \@{} operator will create unexpected behavior under the implicit style") if (overload::Method($plugin, '@{}'));
53             }
54             } else {
55             STYLE: {
56 1         2 foreach my $style (@Object::Meta::Plugin::Host::Context::styles){
  1         3  
57 3 100       14 last STYLE if ($x->info->style() eq $style)
58             }
59            
60 0         0 croak "Unknown plugin style \"",$x->info->style(),"\" for $plugin";
61             }
62             }
63            
64 1         4 $x;
65             }
66              
67             sub unplug { #
68 0     0 1 0 my $self = shift;
69              
70 0         0 foreach my $plugin (@_){
71 0         0 foreach my $method (keys %{ $self->methods }){
  0         0  
72 0 0       0 next unless $plugin->can($method);
73 0         0 @{ $self->methods->{$method} } = grep { $_ != $plugin } @{ $self->methods->{$method} };
  0         0  
  0         0  
  0         0  
74 0 0       0 delete $self->methods->{$method} unless @{ $self->methods->{$method} };
  0         0  
75             }
76            
77 0         0 delete $self->plugins->{$plugin};
78             }
79             }
80              
81             sub register { # export list
82 1     1 1 3 my $self = shift;
83 1         2 my $x = shift;
84            
85 1 50 33     6 croak "$x doesn't look like a valid export list" if (!$x or grep { not $x->can($_) } qw/list plugin exists merge unmerge info/);
  6         33  
86            
87 1         6 foreach my $method ($x->list()){
88 3 50       72 croak "Method \"$method\" is reserved for use by the context object" if Object::Meta::Plugin::Host::Context->UNIVERSAL::can($method);
89 3 50       107 croak "Can't locate object method \"$method\" via plugin ", $x->plugin(), unless $x->plugin->can($method);
90            
91 3   50     9 my $stack = $self->stack($method) || [];
92            
93 3         5 push @{$stack}, $x->plugin();
  3         11  
94            
95 3         9 $self->stack($method, $stack);
96             }
97 1 50       6 exists $self->plugins->{$x->plugin} ? $self->plugins->{$x->plugin}->merge($x) : $self->plugins->{$x->plugin} = $x; # should return success
98             }
99              
100             sub unregister {
101 0     0 1 0 my $self = shift;
102            
103 0         0 foreach my $x (@_){
104 0 0 0     0 croak "$x doesn't look like a valid export list" if (!$x or grep { not $x->can($_) } qw/list plugin/);
  0         0  
105            
106 0         0 $self->plugins->{$x->plugin}->unmerge($x);
107            
108 0         0 foreach my $method ($x->list()){
109 0 0       0 next unless $self->stack($method);
110            
111 0         0 @{ $self->stack($method) } = grep { $_ != $x->plugin } @{ $self->stack($method) };
  0         0  
  0         0  
  0         0  
112            
113 0 0       0 delete $self->methods->{$method} unless (@{ $self->stack($method) });
  0         0  
114             }
115             }
116             }
117              
118             sub stack { # : lvalue { # grant access to the stack of a certain method.
119 7     7 1 10 my $self = shift;
120 7         9 my $method = shift;
121            
122 7 100       60 @_ ? ($self->methods->{$method} = shift) : $self->methods->{$method};
123             }
124              
125             sub specific {
126 0     0 1 0 my $self = shift;
127 0         0 my $plugin = shift;
128            
129 0 0       0 croak "$plugin is not plugged into $self" unless exists $self->plugins->{$plugin};
130            
131 0         0 Object::Meta::Plugin::Host::Context->new($self, $plugin);
132             }
133              
134             sub can { # provide a subref you can goto
135 0     0 0 0 my $self = shift;
136 0         0 my $method = shift;
137             return $self->UNIVERSAL::can($method)
138 0     0   0 || ($self->stack($method) && sub { $AUTOLOAD = $method; goto &AUTOLOAD })
  0         0  
139 0   0     0 || ($self->UNIVERSAL::can('super') && $self->super->can($method));
140             }
141              
142             sub AUTOLOAD { # where the magic happens
143 1     1   9 my $self = shift;
144            
145 1         17 $AUTOLOAD =~ /([^:]*?)$/;
146 1         2 my $method = $1;
147            
148 1 50       9 croak "Method \"$method\" is reserved for use by the context object" if Object::Meta::Plugin::Host::Context->UNIVERSAL::can($method); # UNIVERSAL can differs
149            
150 1 50       3 return undef if $method eq 'DESTROY';
151 1 50       4 if (my $stack = $self->stack($method)){
    0          
152 1         3 Object::Meta::Plugin::Host::Context->new($self, ${ $stack }[ -1 ])->$method(@_);
  1         6  
153             } elsif ($self->can('super')){
154 0         0 $self->super->$method(@_);
155 0         0 } else { croak "Can't locate object method \"$method\" via any plugin in $self" }
156             }
157              
158             package Object::Meta::Plugin::Host::Context; # the wrapper object which defines the context of a plugin
159              
160 1     1   12 use strict;
  1         3  
  1         43  
161 1     1   10 use warnings;
  1         2  
  1         43  
162              
163 1     1   5 use autouse 'Scalar::Util' => qw(reftype);
  1         7  
  1         7  
164 1     1   89 use autouse Carp => qw(croak);
  1         2  
  1         3  
165              
166             our $VERSION = 0.01;
167             our $AUTOLOAD;
168              
169             our @styles = qw/implicit explicit force-implicit/;
170              
171             sub new {
172 1     1   3 my $pkg = shift;
173            
174 1   50     14 my $self = bless [
175             shift, # host
176             shift, # plugin
177             shift || 0, # instance # a plugin can be plugged into several slots, each of which needs it's own context
178             ], $pkg;
179            
180 1         5 my $style = $self->host->plugins->{$self->plugin}->info->style();
181              
182 1 50       5 return $self if $style eq 'explicit';
183              
184 1 50       3 reftype($self->plugin) eq 'ARRAY' and do {
185 1         1 my @array;
186 1         8 tie @array, __PACKAGE__."::TiedSelf", $self;
187 1         2 $self = \@array;
188             };
189            
190 1         17 bless $self, __PACKAGE__."::Overloaded";
191             }
192              
193             ### these methods access internals
194             ### they need the real value of $self
195              
196             sub instance {
197 0     0   0 my $self = shift;
198 0   0     0 $self = tied(@$self) || $self;
199 0         0 $self->[2];
200             }
201              
202             sub super { # the real host
203 1     1   2 my $self = shift;
204 1   33     50 $self = tied(@$self) || $self;
205 1         4 $self->[0];
206             }
207 1     1   6 sub host { goto &super }
208              
209             sub plugin {
210 4     4   9 my $self = shift;
211 4   66     106 $self = tied(@$self) || $self;
212 4         22 $self->[1];
213             }
214 0     0   0 sub self { goto &plugin }
215              
216             ### methods from here on don't access internals
217              
218             sub offset { # get a context with a numerical offset
219 0     0   0 my $self = shift;
220 0         0 my $offset = -1 * shift;
221 0         0 Object::Meta::Plugin::Host::Context::Offset->new($self->host,$self->plugin,$offset,$self->instance);
222             }
223              
224             sub prev { # an overlying method - call a context one above
225 0     0   0 my $self = shift;
226 0         0 $self->offset(-1);
227             }
228              
229             sub next { # an underlying method - call a context one below
230 0     0   0 my $self = shift;
231 0         0 $self->offset(1);
232             }
233              
234             sub can { # try to return the correct method.
235 0     0   0 my $self = shift;
236 0         0 my $method = shift;
237 0 0 0     0 $self->UNIVERSAL::can($method) || $self->plugin->can($method) || $self->host->can($method); # it's one of these, in that order
238             }
239              
240             sub AUTOLOAD {
241 1     1   2 my $self = shift;
242            
243 1         23 $AUTOLOAD =~ /([^:]*?)$/;
244 1         3 my $method = $1;
245 1 50       3 return undef if $method eq 'DESTROY';
246            
247 1 50       12 if (my $code = $self->plugin->can($method)){ # examine the plugin's export list in the host
248 1         3 unshift @_, $self; # return self to the argument list. Should be O(1). lets hope.
249 1         5 goto &$code;
250             } else {
251 0         0 $self->host->$method(@_);
252             }
253             }
254              
255             package Object::Meta::Plugin::Host::Context::Overloaded;
256 1     1   716 use base 'Object::Meta::Plugin::Host::Context';
  1         15  
  1         639  
257 1     1   1784 use overload map { $_, 'plugin' } ('${}', '%{}', '&{}', '*{}', '=', 'nomethod'), fallback => 0; # all ref types except for arrays, aswell as any other value are simply delegated to the plugin's overloading (if at all). No magic autogeneration is to be performed.
  1         12801  
  1         5  
  8         22  
258              
259             package Object::Meta::Plugin::Host::Context::TiedSelf;
260 1     1   850 use base 'Object::Meta::Plugin::Host::Context';
  1         2  
  1         966  
261              
262             #use base 'Tie::Array'; # don't bother wasting the time. tie for arrays is thought to be stable. We'll be overriding anyway for efficiency reasons.
263              
264 1     1   5 sub TIEARRAY { bless $_[1], $_[0] };
265 0     0     sub FETCH { $_[0]->plugin->[$_[1]] };
266 0     0     sub STORE { $_[0]->plugin->[$_[1]] = $_[2] };
267 0     0     sub FETCHSIZE { scalar @{$_[0]->plugin} };
  0            
268 0     0     sub STORESIZE { $#{$_[0]->plugin} = $_[1]-1 };
  0            
269 0     0     sub EXTEND { $#{$_[0]->plugin} += $_[1] };
  0            
270 0     0     sub EXSISTS { exists $_[0]->plugin->[$_[1]] };
271 0     0     sub DELETE { delete $_[0]->plugin->[$_[1]] };
272 0     0     sub CLEAR { @{$_[0]->plugin} = () };
  0            
273 0     0     sub PUSH { push @{$_[0]->plugin}, $_[1] };
  0            
274 0     0     sub POP { pop @{$_[0]->plugin} };
  0            
275 0     0     sub SHIFT { shift @{$_[0]->plugin} };
  0            
276 0     0     sub UNSHIFT { unshift @{$_[0]->plugin}, $_[1] };
  0            
277 0     0     sub SPLICE { @{$_[0]->plugin}, @_}
  0            
278              
279             package Object::Meta::Plugin::Host::Context::Offset; # used to implement next and previous.
280              
281 1     1   7 use strict;
  1         2  
  1         40  
282 1     1   5 use warnings;
  1         2  
  1         36  
283 1     1   4 use autouse Carp => qw(croak);
  1         2  
  1         7  
284              
285             our $AUTOLOAD;
286              
287             sub new {
288 0     0     my $pkg = shift;
289            
290 0   0       my $self = bless {
291             host => shift,
292             plugin => shift,
293             offset => shift,
294             instance => shift || 0,
295             }, $pkg;
296              
297 0           $self;
298             }
299              
300             {
301             my $lookup = sub { # a lexical sub, if you will
302             my $self = shift;
303             my $method = shift;
304            
305             my $stack = $self->{host}->stack($method) || croak "Can't locate object method \"$method\" via any plugin in ${ $self }{host}";
306            
307             my %counts;
308            
309             my $i;
310             my $j = $self->{instance};
311            
312             for ($i = $#$stack; $i >= 0 or croak "${$self}{plugin} which requested an offset is not in the stack for the method \"$method\" which it called"; $i--){
313             ${ $stack }[$i] == $self->{plugin} and (-1 == --$j) and last;
314             $counts{ ${ $stack }[$i] }++ if wantarray;
315             }
316            
317             my $x = $i;
318             $i += $self->{offset};
319            
320             croak "The offset is outside the bounds of the method stack for \"$method\"\n" if ($i > $#$stack or $i < 0);
321            
322             return $stack->[$i] unless wantarray;
323            
324             for (; $x >= $i; $x--){
325             $counts{ ${ $stack }[$x] }++;
326             }
327            
328             return ($stack->[$i], $counts{ $stack->[$i] } - 1);
329            
330             Object::Meta::Plugin::Host::Context->new($self->{host}, ${ $stack }[$i], $counts{${ $stack }[$i]} -1 )->$method(@_);
331             };
332            
333             sub can { # it has to be less ugly than this
334 0     0     my $self = shift;
335 0           my $method = shift;
336            
337 0           return $self->$lookup($method)->can($method);
338             }
339             sub AUTOLOAD { # it has to be less ugly than this
340 0     0     my $self = shift;
341 0           $AUTOLOAD =~ /([^:]*?)$/;
342 0           my $method = $1;
343 0 0         return undef if $method eq 'DESTROY';
344            
345 0           Object::Meta::Plugin::Host::Context->new($self->{host}, $self->$lookup($method) )->$method(@_);
346             }
347             }
348              
349             1; # Keep your mother happy.
350              
351             __END__