File Coverage

blib/lib/Class/InsideOut.pm
Criterion Covered Total %
statement 286 302 94.7
branch 96 110 87.2
condition 26 31 83.8
subroutine 48 49 97.9
pod 7 7 100.0
total 463 499 92.7


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