File Coverage

blib/lib/Object/LocalVars.pm
Criterion Covered Total %
statement 301 319 94.3
branch 80 98 81.6
condition 7 14 50.0
subroutine 44 45 97.7
pod 5 5 100.0
total 437 481 90.8


line stmt bran cond sub pod time code
1 20     20   188529 use 5.008;
  20         83  
  20         801  
2 20     20   109 use strict;
  20         54  
  20         657  
3 20     20   107 use warnings;
  20         36  
  20         1265  
4              
5             package Object::LocalVars;
6             # ABSTRACT: Outside-in objects with local aliasing of $self and object variables
7             our $VERSION = '0.21'; # VERSION
8              
9             #--------------------------------------------------------------------------#
10             # Required modules
11             #--------------------------------------------------------------------------#
12              
13 20     20   111 use Config;
  20         46  
  20         913  
14 20     20   110 use Carp;
  20         39  
  20         2007  
15 20     20   438 use Scalar::Util 1.09 qw( weaken refaddr );
  20         542  
  20         1867  
16              
17             #--------------------------------------------------------------------------#
18             # Exporting -- wrap import so we can check for necessary warnings
19             #--------------------------------------------------------------------------#
20              
21 20     20   103 use Exporter ();
  20         35  
  20         5864  
22              
23             our @EXPORT = qw(
24             caller give_methods new BUILDALL CLONE DESTROY
25             MODIFY_SCALAR_ATTRIBUTES MODIFY_CODE_ATTRIBUTES
26             );
27              
28             sub import {
29              
30             # check if threads are available
31 35 50   35   21918 if( $Config{useithreads} ) {
32 0         0 my $caller = caller(0);
33            
34             # Warn about sharing, but not for Test:: modules which always
35             # share if any threads are enabled
36 0 0 0     0 if ( $INC{'threads/shared.pm'} && ! $INC{'Test/Builder.pm'} ) {
37 0         0 carp "Warning: threads::shared is enabled, but $caller uses"
38             . " Object::LocalVars (which does not allow shared objects)";
39             }
40             }
41            
42             # Hand off the rest of the import
43 35         4667 goto &Exporter::import;
44             }
45              
46             #--------------------------------------------------------------------------#
47             # Declarations
48             #--------------------------------------------------------------------------#
49            
50             my (%public_methods, %protected_methods, %private_methods);
51              
52             my %base_class_of;
53              
54             my %prefixes_for;
55              
56             #--------------------------------------------------------------------------#
57             # accessor_style
58             #--------------------------------------------------------------------------#
59              
60             sub accessor_style {
61 5     5 1 771 my (undef, $prefix) = @_;
62 5 100       50 croak "Method accessor_style() requires a hash reference"
63             if not ref $prefix eq 'HASH';
64 4         9 my $class = caller(0);
65 4         143 $prefixes_for{ $class } = $prefix;
66             }
67              
68             #--------------------------------------------------------------------------#
69             # base_object
70             #--------------------------------------------------------------------------#
71              
72             sub base_object {
73 20     20   124 no strict 'refs';
  20         56  
  20         3568  
74 2     2 1 291 my (undef, $base) = @_;
75 2         5 my $class = caller(0);
76 2         31 $base_class_of{ $class } = $base;
77            
78             # import it if not already in @ISA
79 2 100       4 if ( ! grep { $_ eq $base } @{$class."::ISA"} ) {
  1         5  
  2         13  
80 1         2 push @{$class."::ISA"}, $base;
  1         12  
81 1         6 $base =~ s{::}{/}g;
82 1         2 $base .= ".pm";
83 1         2 eval { require $base };
  1         778  
84 1 50       353 croak $@ if $@ ne '';
85             }
86              
87             # change to other form of new
88             {
89 20     20   119 no warnings 'redefine';
  20         593  
  20         3795  
  2         4  
90 2         6 *{$class."::new"} = \&_new_with_base;
  2         12  
91             }
92            
93             }
94              
95             #--------------------------------------------------------------------------#
96             # caller
97             #--------------------------------------------------------------------------#
98              
99             # custom caller routine ignores this module and keeps looking upwards.
100             # can't use Sub::Uplevel due to an off-by-one issue in the current version
101              
102 20     20   22469 use subs 'caller';
  20         448  
  20         108  
103             sub caller {
104 50     50   95 my ($uplevel) = @_;
105 50   50     252 $uplevel ||= 0;
106 50         401 $uplevel++ while ( (CORE::caller($uplevel+1))[0] eq __PACKAGE__ );
107 50         384 my @caller = CORE::caller($uplevel+1);
108 50 50       805 return wantarray ? ( @_ ? @caller : @caller[0 .. 2] ) : $caller[0];
    100          
109             }
110              
111             #--------------------------------------------------------------------------#
112             # give_methods
113             #--------------------------------------------------------------------------#
114              
115             sub give_methods {
116 26     26 1 3847 my $package = caller;
117 26         52 for ( @{$public_methods{$package}} ) {
  26         81  
118 106         228 _install_wrapper($package, $_, "public");
119             };
120 26         58 for ( @{$protected_methods{$package}} ) {
  26         99  
121 1         3 _install_wrapper($package, $_, "protected");
122             };
123 26         85 for ( @{$private_methods{$package}} ) {
  26         311  
124 2         5 _install_wrapper($package, $_, "private");
125             };
126 26         95 return 1;
127             }
128              
129             #--------------------------------------------------------------------------#
130             # new()
131             #--------------------------------------------------------------------------#
132              
133             sub new {
134 20     20   6769 no strict 'refs';
  20         582  
  20         3165  
135 41     41 1 45976 my ($class, @args) = @_;
136 41 50       221 die "new can't be called on an object" if ref($class);
137              
138             # create blessed object
139 41         70 my $self = \do{ my $scalar };
  41         170  
140 41         128 bless $self, $class;
141              
142             # call initializer
143 41         155 return BUILDALL( $class, $self, @args );
144             }
145              
146             sub _new_with_base {
147 20     20   124 no strict 'refs';
  20         42  
  20         5469  
148 2     2   1607 my ($class, @args) = @_;
149 2 50       9 die "new can't be called on an object" if ref($class);
150              
151             # create blessed object
152 2         6 my $base_class = $base_class_of{ $class };
153 2         3 my $prebuild = *{$class."::PREBUILD"}{CODE};
  2         14  
154             my @filtered_args
155 2 50       9 = defined $prebuild ? $prebuild->($base_class, @args) : @args;
156 2         12 my $self = $base_class->new( @filtered_args );
157 2         12 bless $self, $class;
158 2         7 my $addr = refaddr $self;
159 2         4 ${$class . "::TRACKER"}{$addr} = $self;
  2         13  
160 2         4 weaken ${$class . "::TRACKER"}{$addr}; # don't let this stop destruction
  2         11  
161              
162             # call initializer -- but skip base_class
163             {
164 2         4 local @{$class."::ISA"}
  2         48  
  2         7  
165 2         6 = grep { $_ ne $base_class } @{$class."::ISA"};
  2         9  
166 2         12 return BUILDALL( $class, $self, @_ );
167             }
168             }
169              
170             #--------------------------------------------------------------------------#
171             # BUILDALL
172             #--------------------------------------------------------------------------#
173              
174             sub BUILDALL {
175 20     20   140 no strict 'refs';
  20         36  
  20         4874  
176 54     54 1 134 my ($class, $self, @args) = @_;
177            
178             # return if we've already initialized this class
179 54         181 my $addr = refaddr $self;
180 54 100       78 return $self if ( exists ${$class . "::TRACKER"}{$addr} );
  54         437  
181              
182             # otherwise register $self in the tracker and continue
183 51         134 ${$class . "::TRACKER"}{$addr} = $self;
  51         220  
184 51         76 weaken ${$class . "::TRACKER"}{$addr}; # don't let this stop destruction
  51         312  
185            
186             # initialize superclasses if they can
187 51         77 for my $superclass (@{"${class}::ISA"}) {
  51         289  
188 11 50       136 if ( my $super_buildall = $superclass->can( 'BUILDALL' ) ) {
189 11         21 my $prebuild = *{$class."::PREBUILD"}{CODE};
  11         61  
190 11 100       50 my @filtered_args =
191             defined $prebuild ? $prebuild->($superclass, @args) : @args;
192 11         79 $super_buildall->($superclass, $self, @filtered_args);
193             }
194             }
195            
196             # initialize self if we have an initializer
197 20         1680 *{$class."::BUILD"}{CODE}->($self, @args)
  51         322  
198 51 100       86 if defined *{$class."::BUILD"}{CODE};
199 51         491 return $self;
200             }
201              
202             #--------------------------------------------------------------------------#
203             # CLONE
204             #--------------------------------------------------------------------------#
205              
206             sub CLONE {
207 20     20   104 no strict 'refs';
  20         521  
  20         4081  
208 0     0   0 my $class = shift;
209 0         0 for my $old_obj_id ( keys %{$class . "::TRACKER"} ) {
  0         0  
210 0         0 my $new_obj_id = refaddr(
211 0         0 ${$class . "::TRACKER"}{$old_obj_id}
212             );
213 0         0 for my $prop ( keys %{"${class}::DATA::"} ) {
  0         0  
214 0         0 my $qualified_name = $class . "::DATA::$prop";
215 0         0 $$qualified_name{ $new_obj_id } = $$qualified_name{ $old_obj_id };
216 0         0 delete $$qualified_name{ $old_obj_id };
217             }
218 0         0 ${$class . "::TRACKER"}{$new_obj_id} = $new_obj_id;
  0         0  
219 0         0 delete ${$class . "::TRACKER"}{$old_obj_id};
  0         0  
220             }
221 0         0 return 1;
222             }
223              
224             #--------------------------------------------------------------------------#
225             # DESTROY
226             #--------------------------------------------------------------------------#
227              
228             sub DESTROY {
229 20     20   101 no strict 'refs';
  20         34  
  20         5659  
230 55     55   939651 my ($self, $class) = @_;
231 55   66     557 $class ||= ref $self;
232            
233             # return if we've already destructed this class
234 55         220 my $addr = refaddr $self;
235 55 100       85 return if ( ! exists ${$class . "::TRACKER"}{$addr} );
  55         560  
236            
237             # otherwise mark that we're destroying this class and continue
238 53         100 delete ${$class . "::TRACKER"}{$addr};
  53         223  
239            
240             # demolish and free data for this class
241 18         653 *{$class."::DEMOLISH"}{CODE}->($self)
  53         363  
242 53 100       87 if defined *{$class."::DEMOLISH"}{CODE};
243 53         161 for ( keys %{"${class}::DATA::"} ) {
  53         319  
244 88         140 delete (${"${class}::DATA::$_"}{$addr});
  88         412  
245             }
246              
247             # destroy all superclasses
248 53         168 for my $superclass ( @{"${class}::ISA"} ) {
  53         1710  
249 13 100       234 if ( my $super_destroyer = $superclass->can("DESTROY") ) {
250 11         61 $super_destroyer->($self, $superclass);
251             }
252             }
253              
254             }
255              
256             #--------------------------------------------------------------------------#
257             # MODIFY_CODE_ATTRIBUTES
258             #--------------------------------------------------------------------------#
259              
260             sub MODIFY_CODE_ATTRIBUTES {
261 109     109   31292 my ($package, $referent, @attrs) = @_;
262 109         195 for my $attr (@attrs) {
263 20     20   121 no strict 'refs';
  20         36  
  20         4386  
264 109 100       508 if ( $attr =~ /^(?:Method|Pub)$/ ) {
    100          
    50          
265 106         127 push @{$public_methods{$package}}, $referent;
  106         229  
266 106         285 undef $attr;
267             }
268             elsif ($attr eq "Prot") {
269 1         2 push @{$protected_methods{$package}}, $referent;
  1         4  
270 1         3 undef $attr;
271             }
272             elsif ($attr eq "Priv") {
273 2         3 push @{$private_methods{$package}}, $referent;
  2         4  
274 2         5 undef $attr;
275             }
276             }
277 109         192 return grep {defined} @attrs;
  109         403  
278             }
279              
280             #--------------------------------------------------------------------------#
281             # MODIFY_SCALAR_ATTRIBUTES
282             #--------------------------------------------------------------------------#
283              
284             sub MODIFY_SCALAR_ATTRIBUTES {
285 70     70   49171 my ($OL_PACKAGE, $referent, @attrs) = @_;
286 70         140 for my $attr (@attrs) {
287 20     20   108 no strict 'refs';
  20         35  
  20         7551  
288 70 100       394 if ($attr eq "Pub") {
    100          
    100          
    100          
    100          
    100          
    100          
    50          
289 45         123 _install_accessors( $OL_PACKAGE, $referent, "public", 0 );
290 45         137 undef $attr;
291             }
292             elsif ($attr eq "Prot") {
293 1         3 _install_accessors( $OL_PACKAGE, $referent, "protected", 0 );
294 1         2 undef $attr;
295             }
296             elsif ( $attr =~ /^(?:Prop|Priv)$/ ) {
297 2         4 _install_accessors( $OL_PACKAGE, $referent, "private", 0 );
298 2         6 undef $attr;
299             }
300             elsif ( $attr =~ /^(?:ReadOnly)$/ ) {
301 1         3 _install_accessors( $OL_PACKAGE, $referent, "readonly", 0 );
302 1         2 undef $attr;
303             }
304             elsif ($attr =~ /^(?:Class|ClassPriv)$/ ) {
305 18         47 _install_accessors( $OL_PACKAGE, $referent, "private", 1 );
306 18         48 undef $attr;
307             }
308             elsif ($attr =~ /^(?:ClassProt)$/ ) {
309 1         3 _install_accessors( $OL_PACKAGE, $referent, "protected", 1 );
310 1         3 undef $attr;
311             }
312             elsif ($attr =~ /^(?:ClassPub)$/ ) {
313 1         4 _install_accessors( $OL_PACKAGE, $referent, "public", 1 );
314 1         3 undef $attr;
315             }
316             elsif ($attr =~ /^(?:ClassReadOnly)$/ ) {
317 1         3 _install_accessors( $OL_PACKAGE, $referent, "readonly", 1 );
318 1         3 undef $attr;
319             }
320             else {
321             # we don't really care
322             }
323             }
324 70         140 return grep {defined} @attrs;
  70         337  
325             }
326              
327             #--------------------------------------------------------------------------#
328             # _findsym
329             #--------------------------------------------------------------------------#
330              
331             my %symcache;
332             sub _findsym {
333 20     20   113 no strict 'refs';
  20         41  
  20         4378  
334 179     179   261 my ($pkg, $ref, $type) = @_;
335 179 50       778 return $symcache{$pkg,$ref} if $symcache{$pkg,$ref};
336 179   33     802 $type ||= ref($ref);
337 179         197 my $found;
338 179         205 foreach my $sym ( values %{$pkg."::"} ) {
  179         812  
339 2184         6673 return $symcache{$pkg,$ref} = \$sym
340 2184 100 100     2047 if *{$sym}{$type} && *{$sym}{$type} == $ref;
  1757         17372  
341             }
342             }
343              
344             #--------------------------------------------------------------------------#
345             # _gen_accessor
346             #--------------------------------------------------------------------------#
347              
348             sub _gen_accessor {
349 48     48   83 my ($package, $name, $classwide) = @_;
350 48 100       375 return $classwide
351             ? "return \$${package}::CLASSDATA{${name}}"
352             : "return \$${package}::DATA::${name}" .
353             "{refaddr( \$_[0] )}" ;
354             }
355              
356             #--------------------------------------------------------------------------#
357             # _gen_class_locals
358             #--------------------------------------------------------------------------#
359              
360             sub _gen_class_locals {
361 20     20   108 no strict 'refs';
  20         38  
  20         8022  
362 109     109   149 my $package = shift;
363 109         137 my $evaltext = "";
364 109         126 my @props = keys %{$package."::CLASSDATA"};
  109         457  
365 109 100       325 return "" unless @props;
366 82         127 my @globs = map { "*${package}::$_" } @props;
  114         308  
367 82         168 my @refs = map { "\\\$${package}::CLASSDATA{$_}" } @props;
  114         268  
368 82         300 $evaltext .= " local ( " . join(", ", @globs) . " ) = ( " .
369             join(", ", @refs) . " );\n";
370 82         299 return $evaltext;
371             }
372              
373             #--------------------------------------------------------------------------#
374             # _gen_acc_mut
375             #--------------------------------------------------------------------------#
376              
377             sub _gen_acc_mut {
378 2     2   4 my ($package, $name, $classwide) = @_;
379 2 50       27 return $classwide
380             ? "return (\@_ > 1) ? " .
381             "\$${package}::CLASSDATA{${name}} = \$_[1] : " .
382             "\$${package}::CLASSDATA{${name}} ; " .
383             "\n"
384             : "return (\@_ > 1) ? " .
385             "\$${package}::DATA::${name}" . "{refaddr( \$_[0] )} = \$_[1] : " .
386             "\$${package}::DATA::${name}" . "{refaddr( \$_[0] )} " .
387             "\n";
388             }
389              
390             #--------------------------------------------------------------------------#
391             # _gen_mutator
392             #--------------------------------------------------------------------------#
393              
394             sub _gen_mutator {
395 48     48   82 my ($package, $name, $classwide) = @_;
396 48 100       285 return $classwide
397             ? "\$${package}::CLASSDATA{${name}} = \$_[1];\n" .
398             "return \$_[0] "
399             : "\$${package}::DATA::${name}" .
400             "{refaddr( \$_[0] )} = \$_[1];\n" .
401             "return \$_[0]";
402             }
403              
404             #--------------------------------------------------------------------------#
405             # _gen_object_locals
406             #--------------------------------------------------------------------------#
407              
408             sub _gen_object_locals {
409 20     20   107 no strict 'refs';
  20         462  
  20         7885  
410 109     109   157 my $package = shift;
411 109         119 my @props = keys %{$package."::DATA::"};
  109         410  
412 109 100       333 return "" unless @props;
413 80         106 my $evaltext = " my \$id;\n"; # need to define it
414 80         164 $evaltext .= " \$id = refaddr(\$obj) if ref(\$obj);\n";
415 80         127 my @globs = map { "*${package}::$_" } @props;
  181         415  
416 80         122 my @refs = map { "\\\$${package}::DATA::$_ {\$id}" } @props;
  181         423  
417 80         320 $evaltext .= " local ( " . join(", ", @globs) . " ) = ( " .
418             join(", ", @refs) . " ) if \$id;\n";
419 80         412 return $evaltext;
420             }
421              
422             #--------------------------------------------------------------------------#
423             # _gen_privacy
424             #--------------------------------------------------------------------------#
425              
426             sub _gen_privacy {
427 207     207   307 my ($package, $name, $privacy) = @_;
428 207         346 SWITCH: for ($privacy) {
429 207 100       587 /public/ && do { return "" };
  198         797  
430              
431 9 100       21 /protected/ && do { return
432 7         37 " my (\$caller) = caller();\n" .
433             " croak q/$name is a protected method and can't be called from ${package}/\n".
434             " unless \$caller->isa( '$package' );\n"
435             };
436              
437 2 50       7 /private/ && do { return
438 2         13 " my (\$caller) = caller();\n" .
439             " croak q/$name is a private method and can't be called from ${package}/\n".
440             " unless \$caller eq '$package';\n"
441             };
442             }
443             }
444              
445             #--------------------------------------------------------------------------#
446             # _install_accessors
447             #--------------------------------------------------------------------------#
448              
449             sub _install_accessors {
450 70     70   137 my ($package,$scalarref,$privacy,$classwide) = @_;
451 20     20   114 no strict 'refs';
  20         28  
  20         8864  
452              
453             # find name from reference
454 70 50       166 my $symbol = _findsym($package, $scalarref) or die;
455 70         152 my $name = *{$symbol}{NAME};
  70         138  
456              
457             # make the property exist to be found by give_methods()
458 70 100       154 if ($classwide) {
459 21         51 ${$package."::CLASSDATA"}{$name} = undef;
  21         122  
460             }
461             else {
462 49         69 %{$package."::DATA::".$name} = ();
  49         324  
463             }
464              
465             # determine names for accessor/mutator
466 70         153 my $get = $prefixes_for{ $package }{get};
467 70         111 my $set = $prefixes_for{ $package }{set};
468 70 100       227 my $acc = ( defined $get ? $get : q{} ) . $name;
469 70 100       165 my $mut = ( defined $set ? $set : q{set_} ) . $name;
470              
471             # install accessors
472 70 100       251 return if $privacy eq "private"; # unless private
473 50 100       127 my $accessor_privacy = $privacy eq 'readonly' ? 'public' : $privacy;
474 50 100       111 my $mutator_privacy = $privacy eq 'readonly' ? 'protected' : $privacy;
475 50         62 my $evaltext;
476 50 100       124 if ( $acc ne $mut ) {
477 48         168 $evaltext =
478             "*${package}::${acc} = sub { \n" .
479             _gen_privacy( $package, $name, $accessor_privacy ) .
480             _gen_accessor( $package, $name, $classwide ) .
481             "\n}; \n\n" .
482             "*${package}::${mut} = sub { \n" .
483             _gen_privacy( $package, "set_$name", $mutator_privacy ) .
484             _gen_mutator( $package, $name, $classwide ) .
485             "\n}; "
486             ; # $evaltext
487             }
488             else {
489 2         11 $evaltext =
490             "*${package}::${mut} = sub { \n" .
491             _gen_privacy( $package, "set_$name", $mutator_privacy ) .
492             _gen_acc_mut( $package, $name, $classwide ) .
493             "\n}; "
494             ; # $evaltext
495             }
496            
497 50     30   7851 eval $evaltext; ## no critic
  30         1416  
  28         201  
  28         185  
  27         1798  
  35         4245  
  30         595  
498 50 50       155 die $@ if $@;
499 50         134 return;
500             }
501              
502             #--------------------------------------------------------------------------#
503             # _install_wrapper
504             #--------------------------------------------------------------------------#
505              
506             sub _install_wrapper {
507 109     109   188 my ($package,$coderef,$privacy) = @_;
508 20     20   114 no strict 'refs';
  20         34  
  20         664  
509 20     20   103 no warnings 'redefine';
  20         42  
  20         5383  
510 109 50       223 my $symbol = _findsym($package, $coderef) or die;
511 109         243 my $name = *{$symbol}{NAME};
  109         212  
512 109         148 *{$package."::METHODS::$name"} = $coderef;
  109         654  
513 109         342 my $evaltext = "*${package}::${name} = sub {\n".
514             _gen_privacy( $package, $name, $privacy ) .
515             " my \$obj = shift;\n" .
516             " local \$${package}::self = \$obj;\n" .
517             _gen_class_locals($package) .
518             _gen_object_locals($package) .
519             " local \$Carp::CarpLevel = \$Carp::CarpLevel + 2;\n".
520             " ${package}::METHODS::${name}(\@_);\n".
521             "}\n"
522             ; # my
523             # XXX print "\n\n$evaltext\n\n";
524 109         19012 eval $evaltext; ## no critic
  22         580  
  21         79  
  16         1468  
  17         57  
  17         594  
  19         137  
  14         528  
  13         66  
  14         500  
  15         820  
  14         528  
  14         77  
525 109 50       410 die $@ if $@;
526 109         294 return;
527             }
528              
529             1;
530              
531             __END__