File Coverage

blib/lib/OOB.pm
Criterion Covered Total %
statement 121 145 83.4
branch 52 66 78.7
condition 12 14 85.7
subroutine 27 29 93.1
pod n/a
total 212 254 83.4


line stmt bran cond sub pod time code
1             package OOB; # fool various source parsers
2             package OOB::function; # keep OOB namespace as clean as possible
3              
4             # be as strict and verbose as possible
5 6     6   294780 use strict;
  6         47  
  6         144  
6 6     6   25 use warnings;
  6         7  
  6         233  
7              
8             # version
9             $OOB::VERSION = '0.13';
10              
11             # modules that we need
12 6     6   30 use Scalar::Util qw( blessed refaddr reftype );
  6         8  
  6         271  
13 6     6   2441 use Sub::Identify qw( sub_fullname );
  6         5110  
  6         540  
14              
15             # the actual out-of-band data
16             my %data;
17              
18             # set DEBUG constant if appropriate
19             BEGIN {
20 6   100 6   40 my $debug = 0 + ( $ENV{OOB_DEBUG} || 0 );
21 6         216 eval "sub DEBUG () { $debug }";
22              
23             # we're debugging
24 6 100       32 if ($debug) {
25 2         25 warn "OOB debugging enabled\n";
26              
27             # create OOB::dump
28 6     6   35 no warnings 'once';
  6         8  
  6         513  
29             *OOB::dump = sub {
30 1     1   752 require Data::Dumper;
31 1 50       5619 if ( defined wantarray ) {
32 1 50       8 return wantarray ? %data : \%data;
33             }
34 0         0 warn Data::Dumper::Dumper( \%data );
35             }
36 2         17 }
37              
38             # can't use __PACKAGE__, so we use __OOB__
39 6         404 eval "sub __OOB__ () { 'OOB' }";
40             } #BEGIN
41              
42             # coderefs of stolen DESTROY methods by class
43             my %stolen = ( __OOB__ . '' => \&DESTROY );
44              
45             # install cloaking functions
46             BEGIN {
47 6     6   32 no warnings 'redefine';
  6         10  
  6         2045  
48              
49             # cloak ourselves from "blessed"
50             *Scalar::Util::blessed = sub ($) {
51 30     30   78 my $blessed = blessed $_[0];
52 30 100       59 return if !$blessed;
53 21 100       86 return $blessed->isa(__OOB__) ? undef : $blessed;
54 6     6   67 };
55              
56             # determine whether someone else already stole ref()
57 6         25 my $old_ref = \&CORE::GLOBAL::ref;
58 6         10 eval { $old_ref->() };
  6         88  
59 6 50       41 $old_ref = undef if $@ =~ m#CORE::GLOBAL::ref#;
60 6         18 warn "CORE::ref function was already stolen\n"
61             if DEBUG and $old_ref;
62              
63             # cloak ourselves from "ref"
64             *CORE::GLOBAL::ref = sub {
65 80     80   30350 my $blessed = blessed $_[0];
66 80 100 100     280 return reftype $_[0] if $blessed and $blessed->isa(__OOB__);
67 71 50       172 return $old_ref ? $old_ref->( $_[0] ) : CORE::ref $_[0];
68 6         33 };
69              
70             # determine whether someone else already stole blessed()
71 6         27 my $old_bless = \&CORE::GLOBAL::bless;
72 6         11 eval { $old_bless->() };
  6         31  
73 6 100       52 $old_bless = undef if $@ =~ m#CORE::GLOBAL::bless#;
74 6         19 warn "CORE::bless function was already stolen\n"
75             if DEBUG and $old_bless;
76              
77             # make sure reblessing OOB objects does the right thing
78             *CORE::GLOBAL::bless = sub {
79 45     45   24351 my $blessed = blessed $_[0];
80 45   66     140 my $class = $_[1] || caller();
81              
82             # make sure we can DESTROY if a new class
83 45 100 100     126 _register_DESTROY($class) if $blessed and $stolen{$blessed};
84              
85 45 50       314442 return $old_bless
86             ? $old_bless->( $_[0], $class )
87             : CORE::bless $_[0], $class;
88 6         3972 };
89             } #BEGIN
90              
91             # what we may export
92             my %export_ok;
93             @export_ok{ qw(
94             OOB_get
95             OOB_reset
96             OOB_set
97             ) } = ();
98              
99             # enable final debugger if necessary
100             END {
101 6     6   2279 if (DEBUG) {
102             require Data::Dumper;
103             warn "Final state of OOB data:\n";
104             warn Data::Dumper::Dumper( \%data );
105             }
106             }
107              
108             # satisfy -require-
109             1;
110              
111             #-------------------------------------------------------------------------------
112             #
113             # Functional Interface
114             #
115             #-------------------------------------------------------------------------------
116             # OOB_get
117             #
118             # IN: 1 reference to value
119             # 2 key to fetch
120             # 3 package in which key lives (optional)
121             # OUT: 1 value or undef
122              
123             sub OOB_get {
124              
125             # we're debugging
126 126     124   653 if ( DEBUG > 1 ) {
127             my $id = _unique_id( $_[0] );
128             my $key = _generate_key( $_[1], $_[2] );
129             warn "OOB_get with @_: $id -> $key\n";
130             }
131              
132             # return value without autovivifying
133 126 100       6318 if ( my $values = $data{ _generate_key( $_[1], $_[2] ) } ) {
134 126         211 return $values->{ _unique_id( $_[0] ) };
135             }
136              
137 0         0 return;
138             } #OOB_get
139              
140             #-------------------------------------------------------------------------------
141             # OOB_reset
142             #
143             # IN: 1 reference to value
144             # 2 package in which key lives (optional)
145             # OUT: 1 hash ref with all values
146              
147             sub OOB_reset {
148              
149             # we're debugging
150 0     0   0 if ( DEBUG > 1 ) {
151             my $id = _unique_id( $_[0] );
152             warn "OOB_reset with @_: $id\n";
153             }
154              
155             # which values to remove?
156 0         0 my $id = _unique_id( $_[0] );
157              
158             # need to tell the world what we removed
159 0 50       0 if ( defined wantarray ) {
160             my %removed =
161 0         0 map { $_ => delete $data{$_}->{$id} }
162 0         0 grep { exists $data{$_}->{$id} }
  0         0  
163             keys %data;
164 0         0 return \%removed;
165             }
166              
167             # no need to tell what we deleted
168 0         0 delete $_->{$id} foreach values %data;
169              
170 0         0 return;
171             } #OOB_reset
172              
173             #-------------------------------------------------------------------------------
174             # OOB_set
175             #
176             # IN: 1 reference to value
177             # 2 key to set
178             # 3 value to set
179             # 4 package in which key lives (optional)
180             # OUT: 1 any old value
181             # 2 id of value (optional, refaddr derived)
182              
183             sub OOB_set {
184              
185             # scalar specified
186 113 100   113   3638 if ( !reftype $_[0] ) {
    100          
187 8         21 CORE::bless \$_[0], __OOB__;
188             }
189              
190             # already blessed and not seen before
191             elsif ( my $blessed = blessed $_[0] ) {
192 79         126 _register_DESTROY($blessed);
193             }
194              
195             # not blessed yet, so bless it now
196             else {
197 26         41 CORE::bless $_[0], __OOB__;
198             }
199              
200             # we're debugging
201 113         133 if ( DEBUG > 1 ) {
202             my $id = _unique_id( $_[0] );
203             my $key = _generate_key( $_[1], $_[3] );
204             warn "OOB_set with @_: $id -> $key\n";
205             }
206              
207             # want to know old value
208 113 100       190 if ( defined wantarray ) {
209 30         43 my $id = _unique_id( $_[0] );
210 30         55 my $key = _generate_key( $_[1], $_[3] );
211 30         80 my $old = $data{$key}->{$id};
212 30         42 $data{$key}->{$id} = $_[2];
213 30 50       121 return wantarray ? ( $old, $id ) : $old;
214             }
215              
216             # just set it
217 83         254 $data{ _generate_key( $_[1], $_[3] ) }->{ _unique_id( $_[0] ) } = $_[2];
218              
219 83         151 return;
220             } #OOB_set
221              
222             #-------------------------------------------------------------------------------
223             #
224             # Standard Perl features
225             #
226             #-------------------------------------------------------------------------------
227             # import
228             #
229             # Export any constants requested
230             #
231             # IN: 1 class (ignored)
232             # 2..N constants to be exported / attributes to be defined
233              
234             sub OOB::import {
235 6     6   42 my $class = shift;
236              
237             # nothing to export / defined
238 6 100 66     38 if (!@_) {
    50          
    100          
239 1         8 return;
240             }
241              
242             # we want all constants
243             elsif ( @_ == 1 and $_[0] eq ':all' ) {
244 0         0 @_ = keys %export_ok;
245             }
246              
247             # assume none exportables are attributes
248 6         27 elsif ( my @attributes = grep { !exists $export_ok{$_} } @_ ) {
249 4         11 _register_attribute( $class, $_ ) foreach @attributes;
250              
251             # reduce to real exportables
252 4         7 @_ = grep { exists $export_ok{$_} } @_;
  4         11  
253             }
254              
255             # something to export
256 5 100       14 if (@_) {
257              
258             # determine namespace to export to
259 1         2 my $namespace = caller() . '::';
260 1         1 warn "Exporting @_ to $namespace\n" if DEBUG;
261              
262             # export requested constants
263 6     6   38 no strict 'refs';
  6         16  
  6         2261  
264 1         3 *{$namespace.$_} = \&$_ foreach @_;
  2         7  
265             }
266              
267 5         1380 return;
268             } #OOB::import
269              
270             #-------------------------------------------------------------------------------
271             # AUTOLOAD
272             #
273             # Manage auto-creation of missing methods
274             #
275             # IN: 1 class
276             # 2 key
277             # 3 value to set
278              
279             sub OOB::AUTOLOAD {
280              
281             # attempting to call debug when not debugging
282 31 50   31   135 return if $OOB::AUTOLOAD eq 'OOB::dump';
283            
284             # don't know what to do with it
285 31         39 my $class = shift;
286 31 50       91 if ( !$class->isa(__OOB__) ) {
287 0         0 require Carp;
288 0         0 Carp::croak( "Undefined subroutine $OOB::AUTOLOAD" );
289             }
290              
291             # seems to be an attribute we don't know about
292 31 100       69 if ( @_ == 2 ) {
    100          
293 15         64 require Carp;
294 15         68 $OOB::AUTOLOAD =~ m#::(\w+)$#;
295 15         1142 Carp::croak( "Attempt to set unregistered OOB attribute '$1'" );
296             }
297              
298             # registration
299             elsif ( !@_ ) {
300 1         7 _register_attribute( $OOB::AUTOLOAD =~ m#^(.*)::(\w+)$# );
301             }
302              
303 16         21 return;
304             } #OOB::AUTOLOAD
305              
306             #-------------------------------------------------------------------------------
307             # DESTROY
308             #
309             # IN: 1 instantiated object
310              
311             sub OOB::DESTROY {
312              
313             # what is the id?
314 45     45   2800 my $id = _unique_id( $_[0] );
315              
316             # we're debugging
317 45         47 if (DEBUG) {
318             warn "OOB::DESTROY with @_: $id\n";
319             }
320              
321             # perform the deletion
322 45         151 delete $_->{$id} foreach values %data;
323              
324 45         147 return;
325             } #OOB::DESTROY
326            
327             #-------------------------------------------------------------------------------
328             #
329             # Internal methods
330             #
331             #-------------------------------------------------------------------------------
332             # _generate_key
333             #
334             # Return the key of the given parameters
335             #
336             # IN: 1 basic key value
337             # 2 any package specification (default: 2 levels up)
338             # OUT: 1 key to be used in internal hash
339              
340             sub _generate_key {
341              
342             # fetch the namespace
343 245 50   237   813 my $namespace = defined $_[1]
    100          
344             ? ( "$_[1]" ? "$_[1]--" : '' )
345             : ( caller(1) )[0] . '--';
346              
347 237         578 return $namespace . $_[0];
348             } #_generate_key
349              
350             #-------------------------------------------------------------------------------
351             # _register_attribute
352             #
353             # Register a new class method
354             #
355             # IN: 1 namespace
356             # 2 key
357              
358             sub _register_attribute {
359 5     5   11 my ( $namespace, $key ) = @_;
360              
361             # install a method to handle it
362 6     6   44 no strict 'refs';
  6         10  
  6         863  
363 5         26 *{ $namespace . '::' . $key } = sub {
364 132 50   132   21147 return if @_ < 2; # another registration and huh?
365 132 100       316 return @_ == 3
366             ? OOB_set( $_[1], $key => $_[2], $namespace )
367             : OOB_get( $_[1], $key, $namespace );
368 5         16 };
369             } #_register_attribute
370              
371             #-------------------------------------------------------------------------------
372             # _register_DESTROY
373             #
374             # IN: 1 class to register DESTROY method for
375              
376             sub _register_DESTROY {
377 84     84   103 my $blessed = shift;
378              
379             # already has DESTROY method installed
380 84 100       172 return if $stolen{$blessed};
381              
382             # there is a DESTROY method, need to insert ours
383 5 50       43 if ( my $destroy = $blessed->can('DESTROY') ) {
384 0         0 $stolen{$blessed} = $destroy;
385 0         0 my $fullname = sub_fullname($destroy);
386 6     6   37 no strict 'refs';
  6         17  
  6         381  
387 0     0   0 *$fullname = sub { $destroy->( $_[0] ); &OOB::DESTROY( $_[0] ) };
  0         0  
  0         0  
388             }
389              
390             # no DESTROY method yet, to set one
391             else {
392 6     6   32 no strict 'refs';
  6         53  
  6         986  
393 5         15 *{ $blessed . '::DESTROY' } = $stolen{$blessed} = \&OOB::DESTROY;
  5         25  
394             }
395             } #_register_DESTROY
396              
397             #-------------------------------------------------------------------------------
398             # _unique_id
399             #
400             # Return the key of the given parameters
401             #
402             # IN: 1 reference to value to work with
403             # OUT: 1 id to be used in internal hash
404              
405             sub _unique_id {
406              
407             # no ref, make it!
408 282     282   462 my $reftype = reftype $_[0];
409 282 100       543 if ( !$reftype ) {
    50          
410 16         80 return refaddr \$_[0];
411             }
412              
413             # special handling for refs to refs
414             elsif ( $reftype eq 'REF' ) {
415 0         0 my $ref = ${$_[0]};
  0         0  
416 0         0 $ref = ${$ref} while reftype $ref eq 'REF';
  0         0  
417 0         0 return refaddr $ref;
418             }
419              
420             # just use the refaddr
421 266         801 return refaddr $_[0];
422             } #_unique_id
423              
424              
425             #-------------------------------------------------------------------------------
426              
427             __END__