File Coverage

blib/lib/Class/InsideOut.pm
Criterion Covered Total %
statement 286 302 94.7
branch 96 110 87.2
condition 27 31 87.1
subroutine 48 49 97.9
pod 7 7 100.0
total 464 499 92.9


line stmt bran cond sub pod time code
1             package Class::InsideOut;
2 23     23   582043 use strict;
  23         130  
  23         1744  
3             # ABSTRACT: a safe, simple inside-out object construction kit
4             our $VERSION = '1.13'; # VERSION
5              
6 23     23   136 use vars qw/@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS/;
  23         50  
  23         3867  
7              
8             @ISA = qw ( Exporter );
9             @EXPORT = qw ( ); # nothing by default
10             @EXPORT_OK = qw ( new id options private property public readonly register );
11             %EXPORT_TAGS = (
12             "std" => [ qw( id private public readonly register ) ],
13             "new" => [ qw( new ) ],
14             "all" => [ @EXPORT_OK ],
15             "singleton" => [], # just a flag for import()
16             );
17              
18 23     23   144 use Carp;
  23         53  
  23         2184  
19 23     23   144 use Exporter;
  23         44  
  23         809  
20 23     23   36466 use Class::ISA;
  23         90285  
  23         1020  
21 23     23   215 use Scalar::Util 1.09 qw( refaddr reftype blessed );
  23         1399  
  23         4680  
22              
23             # Check for XS Scalar::Util with weaken() or warn and fallback
24             # syntax of error changed in Scalar::Util so we check both versions
25             BEGIN {
26 23     23   55 eval { Scalar::Util->import( "weaken" ) };
  23         599  
27 23 50       1191 if ( $@ =~ /\AWeak references|weaken is only available/ ) {
28 0         0 warn "Scalar::Util::weaken unavailable: "
29             . "Class::InsideOut will not be thread-safe and will leak memory\n";
30 0         0 *weaken = sub { return @_ };
  0         0  
31             }
32             }
33              
34             #--------------------------------------------------------------------------#
35             # Class data
36             #--------------------------------------------------------------------------#
37              
38             my %PROP_DATA_FOR; # class => { prop_name => property hashrefs }
39             my %PUBLIC_PROPS_FOR; # class => { prop_name => 1 }
40             my %CLASS_ISA; # class => [ list of self and @ISA tree ]
41             my %OPTIONS; # class => { default accessor options }
42             my %OBJECT_REGISTRY; # refaddr => weak object reference
43              
44             #--------------------------------------------------------------------------#
45             # option validation parameters
46             #--------------------------------------------------------------------------#
47              
48             # Private but global so related classes can define their own valid options
49             # if they need them. Modify at your own risk. Done this way so as to
50             # avoid creating class functions to do the same basic thing
51              
52 23     23   1241 use vars qw( %_OPTION_VALIDATION );
  23         45  
  23         8778  
53              
54             sub __coderef {
55 28 100 100 28   393 return 1 if reftype($_[0])||"" eq 'CODE';
56            
57             # Avoid loading overload.pm unless we'd have to die otherwise
58 2         12 require overload;
59 2 50 33     8 return 1 if overload::Overloaded($_[0]) && overload::Method($_[0], q[&{}]);
60            
61 2         1699 die "must be a code reference";
62             }
63              
64             %_OPTION_VALIDATION = (
65             privacy => sub {
66             my $v = shift;
67             $v =~ /public|private/ or die "'$v' is not a valid privacy setting"
68             },
69             set_hook => \&__coderef,
70             get_hook => \&__coderef,
71             );
72              
73             #--------------------------------------------------------------------------#
74             # public functions
75             #--------------------------------------------------------------------------#
76              
77             sub import {
78 23     23   669 no strict 'refs';
  23         215  
  23         11212  
79 41     41   1580158 my $caller = caller;
80 41         168 *{ "$caller\::DESTROY" } = _gen_DESTROY( $caller );
  41         384  
81             # check for ":singleton" and do export attach instead of thaw
82 41 100       117 if ( grep { $_ eq ":singleton" } @_ ) {
  115         365  
83 3         55 *{ "$caller\::STORABLE_freeze" } = _gen_STORABLE_freeze( $caller, 1 );
  3         20  
84 3         11 *{ "$caller\::STORABLE_attach" } = _gen_STORABLE_attach( $caller );
  3         19  
85 3         8 @_ = grep { $_ ne ':singleton' } @_; # strip it back out
  13         32  
86             }
87             else {
88 38         442 *{ "$caller\::STORABLE_freeze" } = _gen_STORABLE_freeze( $caller, 0 );
  38         228  
89 38         121 *{ "$caller\::STORABLE_thaw" } = _gen_STORABLE_thaw( $caller );
  38         230  
90             }
91 41         123 local $Exporter::ExportLevel = $Exporter::ExportLevel + 1;
92 41         7118 &Exporter::import;
93             }
94              
95 23     23   53806 BEGIN { *id = \&Scalar::Util::refaddr; }
96              
97             sub options {
98 24     24 1 11792 my $opt = shift;
99 24         48 my $caller = caller;
100 24 100       105 _check_options( $opt ) if defined $opt;
101 18         22 return %{ $OPTIONS{ $caller } = _merge_options( $caller, $opt ) };
  18         47  
102             }
103            
104             sub new {
105 7     7 1 2302 my $class = shift;
106 7 50       22 croak "new() must be called as a class method"
107             if ref $class;
108 7         15 my $self = register( $class );
109 7 100       34 return $self unless @_;
110            
111             # initialization
112 6 100 100     532 croak "Arguments to new must be a hash or hash reference"
      100        
      100        
      66        
113             if ( @_ == 1 && ! ( ref $_[0] && reftype($_[0]) eq 'HASH' ) )
114             || ( @_ > 1 && @_ % 2 );
115            
116 3 100       13 my %args = (@_ == 1) ? %{$_[0]} : @_;
  1         5  
117              
118 3         9 for my $prop ( keys %args ) {
119 5         10 for my $c ( _class_tree( $class ) ) {
120 9         12 my $properties = $PROP_DATA_FOR{ $c };
121 9 50       22 next unless $properties;
122 9 100       18 if ( exists $properties->{$prop} ) {
123 7         31 $properties->{$prop}{ refaddr $self } = $args{$prop};
124             }
125             }
126             }
127              
128 3         12 return $self;
129             }
130              
131             sub private($\%;$) { ## no critic -- prototype
132 15     15 1 4569 &_check_property;
133 10   100     51 $_[2] ||= {};
134 10         14 $_[2] = { %{$_[2]}, privacy => 'private' };
  10         37  
135 10         26 goto &_install_property;
136             }
137              
138             sub property($\%;$) { ## no critic -- prototype
139 49     49 1 6268 &_check_property;
140 43         121 goto &_install_property;
141             }
142              
143             sub public($\%;$) { ## no critic -- prototype
144 44     44 1 5541 &_check_property;
145 39   100     348 $_[2] ||= {};
146 39         54 $_[2] = { %{$_[2]}, privacy => 'public' };
  39         169  
147 39         278 goto &_install_property;
148             }
149              
150             sub readonly($\%;$) { ## no critic -- prototype
151 2     2 1 8 &_check_property;
152 2   50     11 $_[2] ||= {};
153 2         10 $_[2] = {
154             %{$_[2]},
155             privacy => 'public',
156 1     1   4 set_hook => sub { die "is read-only\n" }
157 2         11 };
158 2         7 goto &_install_property;
159             }
160              
161             sub register {
162 58     58 1 14876 my ($obj);
163 58 100       256 if ( @_ == 0 ) {
    100          
164             # register()
165 1         198 croak "Invalid call to register(): empty argument list"
166             }
167             elsif ( @_ == 1 ) {
168             # register( OBJECT | CLASSNAME )
169 55 100       378 if ( blessed $_[0] ) {
    100          
170 45         98 $obj = shift;
171             }
172             elsif ( ref \$_[0] eq 'SCALAR' ) {
173 9         12 $obj = \(my $scalar);
174 9         29 bless $obj, shift;
175             }
176             else {
177 1         189 croak "Invalid argument '$_[0]' to register(): " .
178             "must be an object or class name"
179             }
180             }
181             else {
182             # register( REFERENCE/OBJECT, CLASSNAME )
183 2         3 $obj = shift;
184 2         6 bless $obj, shift; # ok to rebless
185             }
186            
187 56         359 weaken( $OBJECT_REGISTRY{ refaddr $obj } = $obj );
188 56         148 return $obj;
189             }
190              
191             #--------------------------------------------------------------------------#
192             # private functions for implementation
193             #--------------------------------------------------------------------------#
194              
195             # Registering is global to avoid having to register objects for each class.
196             # CLONE is not exported but CLONE in Class::InsideOut updates all registered
197             # objects for all properties across all classes
198              
199             sub CLONE {
200 0     0   0 my $class = shift;
201              
202             # assemble references to all properties for all classes
203 0         0 my @properties = map { values %$_ } values %PROP_DATA_FOR;
  0         0  
204              
205 0         0 for my $old_id ( keys %OBJECT_REGISTRY ) {
206              
207             # retrieve the new object and id
208 0         0 my $object = $OBJECT_REGISTRY{ $old_id };
209 0         0 my $new_id = refaddr $object;
210              
211             # for all properties, relocate data to the new id if
212             # the property has data under the old id
213 0         0 for my $prop ( @properties ) {
214 0 0       0 next unless exists $prop->{ $old_id };
215 0         0 $prop->{ $new_id } = $prop->{ $old_id };
216 0         0 delete $prop->{ $old_id };
217             }
218              
219             # update the registry to the new, cloned object
220 0         0 weaken ( $OBJECT_REGISTRY{ $new_id } = $object );
221 0         0 _deregister( $old_id );
222             }
223             }
224              
225             sub _check_options{
226 60     60   108 my ($opt) = @_;
227 60         106 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
228              
229 60 100       2470 croak "Invalid options argument '$opt': must be a hash reference"
230             if ref $opt ne 'HASH';
231              
232 52         370 my @valid_keys = keys %_OPTION_VALIDATION;
233 52         169 for my $key ( keys %$opt ) {
234 174         552 croak "Invalid option '$key': unknown option"
235 58 100       91 if ! grep { $_ eq $key } @valid_keys;
236 57         218 eval { $_OPTION_VALIDATION{$key}->( $opt->{$key} ) };
  57         160  
237 57 100       729 croak "Invalid option '$key': $@" if $@;
238             }
239            
240 48         126 return;
241             }
242              
243             sub _check_property {
244 110     110   192 my ($label, $hash, $opt) = @_;
245 110         197 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
246 110 100       2768 croak "Invalid property name '$label': must be a perl identifier"
247             if $label !~ /\A[a-z_]\w*\z/i;
248 121         542 croak "Duplicate property name '$label'"
249 101 100       135 if grep { $_ eq $label } keys %{ $PROP_DATA_FOR{ caller(1) } };
  101         746  
250 100 100       304 _check_options( $opt ) if defined $opt;
251 94         165 return;
252             }
253              
254             sub _class_tree {
255 128     128   201 my $class = shift;
256 128   100     643 $CLASS_ISA{ $class } ||= [ Class::ISA::self_and_super_path( $class ) ];
257 128         1452 return @{ $CLASS_ISA{ $class } };
  128         410  
258             }
259              
260             # take either object or object id
261             sub _deregister {
262 47     47   80 my ($arg) = @_;
263 47 50       198 my $obj_id = ref $arg ? refaddr $arg : $arg;
264 47         115 delete $OBJECT_REGISTRY{ $obj_id };
265 47         91 return;
266             }
267              
268             # turn object into hash -- see _revert()
269             sub _evert {
270 19     19   32 my ( $obj ) = @_;
271            
272             # Extract properties to save
273 19         26 my %property_vals;
274 19         51 for my $c ( _class_tree( ref $obj) ) {
275 25 50       77 next unless exists $PROP_DATA_FOR{ $c };
276 25         40 my $properties = $PROP_DATA_FOR{ $c };
277 25         190 for my $prop ( keys %$properties ) {
278 53 100       350 my $value = exists $properties->{$prop}{ refaddr $obj }
279             ? $properties->{$prop}{ refaddr $obj }
280             : undef ;
281 53         193 $property_vals{$c}{$prop} = $value;
282             }
283             }
284              
285             # extract object reference contents (by type)
286 19         67 my $type = reftype $obj;
287 19 50       95 my $contents = $type eq 'SCALAR' ? \do{ my $s = $$obj }
  6 100       17  
    100          
288             : $type eq 'ARRAY' ? [ @$obj ]
289             : $type eq 'HASH' ? { %$obj }
290             : undef # other types not supported
291             ;
292              
293             # assemble reference to hand back
294             return {
295 19         125 class => ref $obj,
296             type => $type,
297             contents => $contents,
298             properties => \%property_vals
299             };
300             }
301              
302             sub _gen_accessor {
303 55     55   299 my ($ref) = @_;
304             return sub {
305 82     82   22844 my $obj = shift;
306 82         229 my $obj_id = refaddr $obj;
307 82 100       1609 $ref->{ $obj_id } = shift if (@_);
308 82         485 return $ref->{ $obj_id };
309 55         285 };
310             }
311            
312             sub _gen_hook_accessor {
313 22     22   57 my ($ref, $name, $get_hook, $set_hook) = @_;
314             return sub {
315 54     54   26861 my ($obj,@args) = @_;
316 54         6473 my $obj_id = refaddr $obj;
317 54 100       330 if (@args) {
    100          
318 28         56 local *_ = \($args[0]);
319 28 100       71 if ($set_hook) {
320 25         34 eval { $set_hook->(@args) };
  25         140  
321 25 100       316 if ( $@ ) { chomp $@; croak "$name() $@" }
  7         27  
  7         1374  
322 18         89 $ref->{ $obj_id } = shift @args;
323             }
324             else {
325 3         19 $ref->{ $obj_id } = shift @args;
326             }
327             }
328             elsif ($get_hook) {
329 15         39 local $_ = $ref->{ $obj_id };
330 15         20 my ( $value, @value );
331 15 100       34 if ( wantarray ) {
332 9         17 @value = eval { $get_hook->() };
  9         30  
333             }
334             else {
335 6         10 $value = eval { $get_hook->() };
  6         20  
336             }
337 15 100       134 if ( $@ ) { chomp $@; croak "$name() $@" }
  3         10  
  3         694  
338 12 100       86 return wantarray ? @value : $value;
339             }
340             else {
341 11         60 return $ref->{ $obj_id };
342             }
343 22         138 };
344             }
345            
346             sub _gen_DESTROY {
347 41     41   85 my $class = shift;
348             return sub {
349 47     47   23221 my $obj = shift;
350 47         157 my $obj_id = refaddr $obj; # cache for later property deletes
351              
352             # Call a custom DEMOLISH hook if one exists.
353 47         75 my $demolish;
354             {
355 23     23   214 no strict 'refs';
  23         43  
  23         7534  
  47         76  
356 47         203 $demolish = *{ "$class\::DEMOLISH" }{CODE};
  47         793  
357             }
358 47 100       175 $demolish->($obj) if defined $demolish;
359              
360             # Clean up properties in all Class::InsideOut parents
361 47         251 for my $c ( _class_tree( $class ) ) {
362 78 100       258 next unless exists $PROP_DATA_FOR{ $c };
363 68         80 delete $_->{ $obj_id } for values %{ $PROP_DATA_FOR{ $c } };
  68         484  
364             }
365              
366             # XXX this global registry could be deleted repeatedly
367             # in superclasses -- SUPER::DESTROY shouldn't be called by DEMOLISH
368             # it should only call SUPER::DEMOLISH if need be; still,
369             # rest of the destructor doesn't need the registry, so early deletion
370             # by a subclass should be safe
371 47         157 _deregister( $obj );
372              
373 47         1466 return;
374 41         292 };
375             }
376              
377             sub _gen_STORABLE_attach {
378 3     3   6 my $class = shift;
379             return sub {
380 5     5   28049 my ( $class, $cloning, $serialized ) = @_;
381 5         37 require Storable;
382 5         20 my $data = Storable::thaw( $serialized );
383            
384             # find a user attach hook
385 5         247 my $hook;
386             {
387 23     23   768 no strict 'refs';
  23         47  
  23         5285  
  5         9  
388 5         7 $hook = *{ "$class\::ATTACH" }{CODE};
  5         31  
389             }
390              
391             # try user hook to recreate, otherwise new(), otherwise give up
392 5 100       40 if ( defined $hook ) {
    100          
393 2         7 return $hook->($class, $cloning, $data);
394             }
395             elsif ( $class->can( "new" ) ) {
396 2         10 return $class->new();
397             }
398             else {
399 1         9 warn "Error attaching to $class:\n" .
400             "Couldn't find STORABLE_attach_hook() or new() in $class\n";
401 1         21 return;
402             }
403 3         27 };
404             }
405            
406             sub _gen_STORABLE_freeze {
407 41     41   95 my ($class, $singleton) = @_;
408             return sub {
409 19     19   15187 my ( $obj, $cloning ) = @_;
410              
411             # Call STORABLE_freeze_hooks in each class if they exists
412 19         81 for my $c ( _class_tree( ref $obj ) ) {
413 25         50 my $hook;
414             {
415 23     23   135 no strict 'refs';
  23         36  
  23         5305  
  25         32  
416 25         31 $hook = *{ "$c\::FREEZE" }{CODE};
  25         128  
417             }
418 25 100       1382 $hook->($obj) if defined $hook;
419             }
420              
421             # Extract properties to save
422 19         65 my $data = _evert( $obj );
423              
424 19 100       49 if ( $singleton ) {
425             # can't return refs, so freeze data as string and return
426 3         25 require Storable;
427 3         14 return Storable::freeze( $data );
428             }
429             else {
430             # return $serialized, @refs
431             # serialized string doesn't matter -- all data has been moved into
432             # the additional ref
433 16         1242 return 'BOGUS', $data;
434             }
435 41         231 };
436             }
437              
438             sub _gen_STORABLE_thaw {
439 38     38   113 my $class = shift;
440             return sub {
441 16     16   7274 my ( $obj, $cloning, $serialized, $data ) = @_;
442              
443 16         49 _revert( $data, $obj );
444              
445             # Call STORABLE_thaw_hooks in each class if they exists
446 16         41 for my $c ( _class_tree( ref $obj ) ) {
447 22         42 my $hook;
448             {
449 23     23   147 no strict 'refs';
  23         50  
  23         3968  
  22         24  
450 22         37 $hook = *{ "$c\::THAW" }{CODE};
  22         100  
451             }
452 22 100       77 $hook->($obj) if defined $hook;
453             }
454              
455 16         162 return;
456 38         177 };
457             }
458              
459             sub _install_property{
460 94     94   153 my ($label, $hash, $opt) = @_;
461              
462 94         317 my $caller = caller(0); # we get here via "goto", so caller(0) is right
463 94         220 $PROP_DATA_FOR{ $caller }{$label} = $hash;
464 94         227 my $options = _merge_options( $caller, $opt );
465 94 100 100     562 if ( exists $options->{privacy} && $options->{privacy} eq 'public' ) {
466 23     23   139 no strict 'refs';
  23         44  
  23         18757  
467 77 100 100     538 *{ "$caller\::$label" } =
  77         490  
468             ($options->{set_hook} || $options->{get_hook})
469             ? _gen_hook_accessor( $hash, $label, $options->{get_hook},
470             $options->{set_hook} )
471             : _gen_accessor( $hash ) ;
472 77         246 $PUBLIC_PROPS_FOR{ $caller }{ $label } = 1;
473             }
474 94         437 return;
475             }
476              
477             sub _merge_options {
478 112     112   153 my ($class, $new_options) = @_;
479 112         134 my @merged;
480 112 100       306 push @merged, %{ $OPTIONS{ $class } } if defined $OPTIONS{ $class };
  53         135  
481 112 100       774 push @merged, %$new_options if defined $new_options;
482 112         448 return { @merged };
483             }
484            
485             sub _revert {
486 16     16   25 my ( $data, $obj ) = @_;
487              
488 16         32 my $contents = $data->{contents};
489 16 50       38 if ( defined $obj ) {
490             # restore contents to the pregenerated object
491 16         68 for ( reftype $obj ) {
492 3         11 /SCALAR/ ? do { $$obj = $$contents } :
493 12         40 /ARRAY/ ? do { @$obj = @$contents } :
494 1         10 /HASH/ ? do { %$obj = %$contents } :
495 16 50       74 do {} ;
    100          
    100          
496             }
497             }
498             else {
499             # just use the contents as the reference
500             # and bless it back into an object
501 0         0 $obj = $contents;
502             }
503              
504 16         47 bless $obj, $data->{class};
505              
506             # restore properties
507 16         48 for my $c ( _class_tree( ref $obj ) ) {
508 22         43 my $properties = $PROP_DATA_FOR{ $c };
509 22 50       60 next unless $properties;
510 22         49 for my $prop ( keys %$properties ) {
511 50         93 my $value = $data->{properties}{ $c }{ $prop };
512 50         210 $properties->{$prop}{ refaddr $obj } = $value;
513             }
514             }
515              
516             # register object
517 16         52 register( $obj );
518 16         24 return $obj;
519             }
520              
521             #--------------------------------------------------------------------------#
522             # private functions for use in testing
523             #--------------------------------------------------------------------------#
524              
525             sub _object_count {
526 12     12   1932 return scalar( keys %OBJECT_REGISTRY );
527             }
528              
529             sub _properties {
530 6     6   4914 my $class = shift;
531 6         14 my %properties;
532 6         25 for my $c ( _class_tree( $class ) ) {
533 9 50       34 next if not exists $PROP_DATA_FOR{ $c };
534 9         12 for my $p ( keys %{ $PROP_DATA_FOR{ $c } } ) {
  9         42  
535 35 100       139 $properties{$c}{$p} = exists $PUBLIC_PROPS_FOR{$c}{$p}
536             ? "public" : "private";
537             }
538             }
539 6         50 return \%properties;
540             }
541              
542             sub _leaking_memory {
543 8     8   11063 my %leaks;
544              
545 8         39 for my $class ( keys %PROP_DATA_FOR ) {
546 21         29 for my $prop ( values %{ $PROP_DATA_FOR{ $class } } ) {
  21         67  
547 47         115 for my $obj_id ( keys %$prop ) {
548 29 50       87 $leaks{ $class }++
549             if not exists $OBJECT_REGISTRY{ $obj_id };
550             }
551             }
552             }
553              
554 8         40 return keys %leaks;
555             }
556              
557             1; # modules must return true
558              
559             __END__