File Coverage

blib/lib/Pixie/Proxy.pm
Criterion Covered Total %
statement 160 190 84.2
branch 35 52 67.3
condition 5 18 27.7
subroutine 37 44 84.0
pod 0 8 0.0
total 237 312 75.9


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Pixie::Proxy - placeholders for real objects in a Pixie store
4              
5             =head1 SYNOPSIS
6              
7             use Pixie::Proxy;
8              
9             # this only works for blessed Hashes & Arrays currently:
10             my $obj = MyObject->new
11             my $proxy = Pixie::Proxy->px_make_proxy( $obj );
12              
13             # a while later, after the obj has been stored and $obj goes away...
14             $obj = undef;
15              
16             $proxy->a_method; # same as $obj->a_method, with a lot of magic
17              
18             # auto-magically loads $obj from the store, copies it into
19             # $proxy, and re-blesses $proxy into the right class (MyObject)
20             # before calling a_method() on it.
21              
22             =head1 DESCRIPTION
23              
24             C lets you load objects from a Pixie store on demand. So if
25             you have a tree of objects and you only need to access the root node of the
26             tree, you don't have to face the performance hit of loading the entire tree.
27              
28             C and its subclasses magically fetch the object from the store
29             whenever a method is called, (and if your class uses L, whenever an
30             overloaded operator is used).
31              
32             If proxying interferes with your code, or if you simply prefer to load an
33             entire object heirarchy at one go, simply set the C constant
34             to some true value in the classes you don't want proxied.
35              
36             =cut
37              
38             package Pixie::Proxy;
39              
40 22     22   155823 use strict;
  22         37  
  22         830  
41 22     22   117 use warnings::register;
  22         38  
  22         3191  
42             require overload;
43              
44             # For now we're going to assume that we can only handle hashes or
45             # array based objects. This may not remain the case.
46              
47 22     22   117 use Scalar::Util qw( reftype );
  22         50  
  22         1403  
48              
49 22     22   12911 use Pixie::Object;
  22         58  
  22         641  
50 22     22   14771 use Pixie::FinalMethods;
  22         65  
  22         679  
51 22     22   15596 use Pixie::Complicity;
  22         59  
  22         715  
52              
53 22     22   468 use base qw( Pixie::Object );
  22         54  
  22         24561  
54              
55             our $AUTOLOAD;
56             our $VERSION = '2.08_02';
57              
58             ## TODO: rename 'new_proxy_for' ?
59             sub px_make_proxy {
60 21     21 0 10074 my $class = shift;
61 21         39 my($oid, $obj) = @_;
62 21         35 my $proxied_class = ref($obj);
63 21         114 my $real_class = 'Pixie::Proxy::' . reftype($obj);
64              
65             ## TODO: check for / auto load existing subclass here?
66 21 50       69 $real_class .= '::Overloaded' if overload::Overloaded($proxied_class);
67 21         15668 $real_class->new->_oid($oid)
68             ->px_class($proxied_class);
69             }
70              
71             sub px_restore {
72 5     5 0 898 my $class = $_[0]->px_class;
73 5         15 my $pixie = $_[0]->px_the_store;
74              
75 5         15 $_[0]->px_clear_the_store;
76 5         19 my $real_obj = $_[0]->px_fetch_from($pixie);
77 5 50       317 return $_[0] = undef unless defined $real_obj;
78              
79 5         16 $_[0]->populate_from($real_obj);
80 5         16 bless $real_obj, 'Class::Whitehole';
81              
82 5         59 my $ret = bless $_[0], $class;
83             }
84              
85             sub px_fetch_from {
86 6     6 0 19 my $self = shift;
87 6         6 my $pixie = shift;
88 6         15 my $oid = $self->_oid;
89 6         15 $pixie->get_with_strategy($oid, $self->px_lock_strategy);
90             }
91              
92             sub isa {
93 13     13 0 1607 my $self = shift;
94 13         23 my($class) = @_;
95 13 100 66     163 $self->UNIVERSAL::isa($class) || ref($self) && $self->px_class->isa($class);
96             }
97              
98             sub can {
99 3     3 0 22 my $self = shift;
100 3         6 my($method) = @_;
101              
102 3 100 66     40 $self->UNIVERSAL::can($method) ||
103             ref($self) && $self->px_restore->can($method);
104             }
105              
106             #-----------------------------------------------------------------------------
107             # Storable compat methods
108              
109             #
110             # We serialize into the form:
111             # $oid => [ $original_class ]
112             #
113              
114             sub STORABLE_freeze {
115 3     3 0 887 my $self = shift;
116 3         7 my $cloning = shift;
117 3 100       14 return if $cloning;
118              
119 2         17 return $self->_oid, [$self->px_class];
120             }
121              
122             sub STORABLE_thaw {
123 2     2 0 11 my($target, $cloning, $oid, $class) = @_;
124 2 100       9 return if $cloning;
125 1         4 $target->_oid($oid);
126 1         3 $target->px_class($class->[0]);
127 1         4 return $target;
128             }
129              
130             #-----------------------------------------------------------------------------
131             # Pixie Complicity methods
132              
133             sub _px_insertion_thaw {
134 2     2   362 my $self = shift;
135 2         12 $self->px_the_store(Pixie->get_the_current_pixie);
136 2         8 return $self;
137             }
138              
139             sub _px_insertion_freeze {
140 0     0   0 my $self = shift;
141 0         0 my $dupe = ref($self)->new->_oid($self->_oid)
142             ->px_class($self->px_class);
143             }
144              
145             sub _px_extraction_thaw {
146 3     3   167 my $self = shift;
147             # TODO: fix cut-n-paste error below (get_the_current_pixie takes no args),
148             # and use $pixie hereafter
149 3         8 my $pixie = Pixie->get_the_current_pixie($self->_oid);
150 3         16 my $obj = Pixie->get_the_current_pixie->cache_get($self->_oid);
151              
152 3 100       197 if ( defined $obj ) {
153 1         4 bless $self, 'Class::Whitehole';
154 1         8 $pixie->forget_about($self);
155 1         59 return $obj;
156             }
157              
158 2   33     16 $self->px_lock_strategy( $pixie->get_the_current_lock_strategy ||
159             $pixie->lock_strategy );
160              
161 2 100       4 if ($self->px_class->px_is_immediate) {
162 1         7 my $oid = $self->_oid;
163 1         5 bless $self, 'Class::Whitehole';
164 1         4 Pixie->get_the_current_pixie->_get($oid);
165             # TODO: add $obj to the cache?
166             }
167             else {
168 1         4 $self->px_the_store($pixie);
169 1         7 $pixie->cache_insert($self);
170 1         55 return $self;
171             }
172             }
173              
174             #-----------------------------------------------------------------------------
175             # Subclass methods
176              
177 0     0 0 0 sub px_the_store { $_[0]->subclass_responsibility(@_) }
178              
179              
180             #-----------------------------------------------------------------------------
181             # other methods
182              
183             sub DESTROY {
184 16     16   5768 my $self = shift;
185 16         49 local $@ = $@;
186 16 50       60 return unless ref $self;
187 16         53 my $store = $self->px_the_store;
188 16 100       437 $store->forget_about( $self ) if (defined $store);
189             }
190              
191             sub AUTOLOAD {
192 2     2   86 my $method = $AUTOLOAD;
193 2         12 $method =~ s/.*:://;
194 2         11 $_[0]->px_restore->$method(@_[ 1 .. $#_ ]);
195             }
196              
197             #-----------------------------------------------------------------------------
198             # Embedded subclasses
199              
200             package Pixie::Proxy::ARRAY;
201              
202 22     22   142 use base 'Pixie::Proxy';
  22         45  
  22         9340  
203              
204             sub new {
205 3     3   18 my $class = shift;
206 3         17 return bless [], $class;
207             }
208              
209             ## TODO: use constants for array indecies?
210             sub _oid {
211 4     4   255 my $self = shift;
212 4 100       15 if (@_) {
213 2         6 my $new_oid = shift;
214 2         20 $self->[0] = "$new_oid";
215 2         14 return $self;
216             } else {
217 2         11 return $self->[0];
218             }
219             }
220              
221             ## TODO: write px_oid()
222              
223             sub px_class {
224 6     6   1222 my $self = shift;
225 6 100       16 if (@_) {
226 2         5 $self->[1] = shift;
227 2         8 return $self;
228             } else {
229 4         35 return $self->[1];
230             }
231             }
232              
233             sub populate_from {
234 1     1   9 $#{$_[0]} = 0;
  1         5  
235 1         3 @{$_[0]} = @{$_[1]};
  1         5  
  1         2  
236 1         11 return $_[0];
237             }
238              
239             ## TODO: rename 'px_the_current_pixie'
240             ## TODO: hide with Scalar::Footnote?
241             sub px_the_store {
242 6     6   10 my $self = shift;
243 6 100       16 if (@_) {
244 1         3 $self->[2] = shift;
245 1         5 return $self;
246             }
247             else {
248 5         18 return $self->[2];
249             }
250             }
251              
252             sub px_clear_the_store {
253 2     2   4 my $self = shift;
254 2         5 $self->[2] = undef;
255 2         15 return $self;
256             }
257              
258             sub px_lock_strategy {
259 2     2   5 my $self = shift;
260 2 100       6 if (@_) {
261 1         3 $self->[3] = shift;
262 1         6 return $self;
263             }
264             else {
265 1         6 return $self->[3];
266             }
267             }
268              
269              
270             #-----------------------------------------------------------------------------
271             package Pixie::Proxy::HASH;
272              
273 22     22   126 use base 'Pixie::Proxy';
  22         45  
  22         15382  
274              
275             sub new {
276 21     21   49 my $class = shift;
277 21         105 return bless {}, $class;
278             }
279              
280             sub _oid {
281 42     42   1199 my $self = shift;
282 42 100       188 if (@_) {
283 21         31 my $new_oid = shift;
284 21         307 $self->{oid} = "$new_oid";
285 21         154 return $self;
286             } else {
287 21         96 return $self->{oid};
288             }
289             }
290              
291 1     1   4 sub px_oid { $_[0]->_oid }
292              
293             sub px_class {
294 35     35   102 my $self = shift;
295              
296 35 100       70 if (@_) {
297 21         58 $self->{class} = shift;
298 21         76 return $self;
299             } else {
300             ## TODO: check reftype eq 'HASH' & improve warning here
301 14 100       46 unless (ref($self)) {
302 1         8 require Carp;
303 1         26 Carp::confess "Invalid thing: $self";
304             }
305 13         181 return $self->{class};
306             }
307             }
308              
309             ## TODO: rename 'px_populate_from'
310             sub populate_from {
311             # TODO: try more efficient %{$_[0]} = ();
312 6     6   20 foreach my $key (keys %{$_[0]}) {
  6         24  
313 11         30 delete $_[0]->{$key};
314             }
315 6         12 %{$_[0]} = %{$_[1]};
  6         16  
  6         12  
316 6         15 return $_[0];
317             }
318              
319             ## TODO: rename 'px_the_current_pixie'
320             sub px_the_store {
321 38     38   4426 my $self = shift;
322 38 100       104 if (@_) {
323 17         42 $self->{_the_store} = shift;
324 17         51 return $self;
325             }
326             else {
327 21         56 return $self->{_the_store};
328             }
329             }
330              
331             ## TODO: should this return $self as ARRAY does?
332             sub px_clear_the_store {
333 6     6   11 my $self = shift;
334 6         19 delete $self->{_the_store};
335             }
336              
337             sub px_lock_strategy {
338 10     10   119 my $self = shift;
339 10 100       25 if (@_) {
340 3         8 $self->{_lock_strategy} = shift;
341 3         10 return $self;
342             }
343             else {
344 7         56 return $self->{_lock_strategy};
345             }
346             }
347              
348             #-----------------------------------------------------------------------------
349             package Pixie::Proxy::Overloaded;
350              
351             ##
352             ## TODO: should we check if the caller is Pixie all the time?
353             ## TODO: do we need to consider the de-referencing operators?
354             ##
355              
356             my %FALLBACK = (
357             '!' => \&bool_not,
358             '.' => \&concat_str,
359             '""' => \&stringify,
360             'bool' => \&bool,
361             );
362              
363             ## TODO: pull this out into a separate sub & break it down
364             use overload
365             fallback => 0,
366             nomethod => sub {
367 22     22   133 no strict 'refs';
  22         37  
  22         6016  
368 0     0   0 my $method = pop;
369 0         0 my $class = $_[0]->px_class;
370              
371             # TODO: this uses private overload.pm methods, so will break if they
372             # change. Would be good to patch overload.pm so they become part of the
373             # public API.
374              
375             # TODO: replace this with overload::Overloaded( $class ) ?
376             # when you "use overload fallback => $x" you can access $x like this:
377 0         0 my $fallback = $ {$class . "::()"};
  0         0  
378              
379             # this finds the overloaded method of the original class if it exists.
380 0 0 0     0 if ( my $sub = overload::ov_method( overload::mycan($class, "\($method"), $class) ) {
    0          
    0          
381 0         0 $_[0]->px_restore;
382 0         0 &$sub;
383             }
384              
385             # does this respect rules for "magic autogeneration" ?
386             # it looks like similar logic (see overload docs)
387 0         0 elsif (!defined($fallback) || $fallback) {
388             # Try falling back
389 0         0 push @_, $fallback;
390 0 0       0 if (exists $FALLBACK{$method}) {
391 0         0 goto &{$FALLBACK{$method}}
  0         0  
392             }
393             else {
394             # TODO: this tries to behave like overload.pm, but fails when
395             # $fallback is true. Then, how to mimick the correct behaviour?
396             # Best let overload internals do this.
397             # TODO: carp ...
398 0         0 die "No Fallback found for $method";
399             }
400             }
401             # TODO: isn't this just "defined($fallback)" ?
402             elsif (defined $ {$class . "::()"}) {
403 0         0 $_[0]->can('nomethod')->(@_, $method);
404             }
405             else {
406 0         0 require Carp;
407 0         0 Carp::confess "Can't find overloaded method for $method";
408             }
409 22     22   24754 };
  22         15257  
  22         342  
410              
411             ## TODO: factor out a sub: is_caller_pixie( caller )
412             sub bool_not {
413 0 0 0 0     if ( caller->isa('Pixie::Proxy') || caller->isa('Pixie') ) {
414 0           return;
415             }
416             else {
417 0           $_[0]->px_restore;
418 0           return ! $_[0];
419             }
420             }
421              
422             sub bool {
423 0 0 0 0     if ( caller->isa('Pixie::Proxy') || caller->isa('Pixie') ) {
424 0           return 1;
425             }
426             else {
427 0           $_[0]->px_restore;
428 0           return $_[0];
429             }
430             }
431              
432             sub concat_str {
433 0     0     my($target, $rev) = @_[1,2];
434 0 0         return $rev ? ($target . "$_[0]") : ("$_[0]" . $target);
435             }
436              
437             sub stringify {
438 0     0     $_[0]->overload::StrVal;
439             }
440              
441             package Pixie::Proxy::HASH::Overloaded;
442             our @ISA = qw/Pixie::Proxy::HASH Pixie::Proxy::Overloaded/;
443              
444             package Pixie::Proxy::ARRAY::Overloaded;
445             our @ISA = qw/Pixie::Proxy::ARRAY Pixie::Proxy::Overloaded/;
446              
447             1;
448              
449             __END__