File Coverage

blib/lib/OOB.pm
Criterion Covered Total %
statement 148 148 100.0
branch 68 68 100.0
condition 11 11 100.0
subroutine 30 30 100.0
pod n/a
total 257 257 100.0


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 13     13   776944 use strict;
  13         123  
  13         420  
6 13     13   60 use warnings;
  13         23  
  13         600  
7              
8             # version
9             $OOB::VERSION = '1.00';
10              
11             # modules that we need
12 13     13   74 use Scalar::Util qw( blessed refaddr reftype );
  13         18  
  13         881  
13 13     13   5941 use Sub::Identify qw( sub_fullname );
  13         12961  
  13         1240  
14              
15             # the actual out-of-band data
16             my %data;
17              
18             # set DEBUG constant if appropriate
19             BEGIN {
20 13   100 13   109 my $debug = 0 + ( $ENV{OOB_DEBUG} || 0 );
21 13         581 eval "sub DEBUG () { $debug }";
22              
23             # we're debugging
24 13 100       79 if ($debug) {
25 3         97 warn "OOB debugging enabled\n";
26              
27             # create OOB::dump
28 13     13   84 no warnings 'once';
  13         23  
  13         1342  
29             *OOB::dump = sub {
30 4     4   2971 require Data::Dumper;
31 4 100       12142 if ( defined wantarray ) {
32 3 100       23 return wantarray ? %data : \%data;
33             }
34 1         5 warn Data::Dumper::Dumper( \%data );
35             }
36 3         26 }
37              
38             # can't use __PACKAGE__, so we use __OOB__
39 13         965 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 13     13   91 no warnings 'redefine';
  13         21  
  13         4809  
48              
49             # cloak ourselves from "blessed"
50             *Scalar::Util::blessed = sub ($) {
51 30     30   96 my $blessed = blessed $_[0];
52 30 100       69 return if !$blessed;
53 21 100       101 return $blessed->isa(__OOB__) ? undef : $blessed;
54 13     13   206 };
55              
56             # determine whether someone else already stole ref()
57 13         65 my $old_ref = \&CORE::GLOBAL::ref;
58 13         25 eval { $old_ref->() };
  13         189  
59 13 100       104 $old_ref = undef if $@ =~ m#CORE::GLOBAL::ref#;
60 13         35 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 128     128   66743 my $blessed = blessed $_[0];
66 128 100 100     513 return reftype $_[0] if $blessed and $blessed->isa(__OOB__);
67 118 100       344 return $old_ref ? $old_ref->( $_[0] ) : CORE::ref $_[0];
68 13         96 };
69              
70             # determine whether someone else already stole blessed()
71 13         59 my $old_bless = \&CORE::GLOBAL::bless;
72 13         61 eval { $old_bless->() };
  13         136  
73 13 100       107 $old_bless = undef if $@ =~ m#CORE::GLOBAL::bless#;
74 13         32 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 78     78   53862 my $blessed = blessed $_[0];
80 78 100       233 my $class = $_[1] ? $_[1] : caller();
81              
82             # make sure we can DESTROY if a new class
83 78 100 100     277 _register_DESTROY($class) if $blessed and $stolen{$blessed};
84              
85 78 100       786662 return $old_bless
86             ? $old_bless->( $_[0], $class )
87             : CORE::bless $_[0], $class;
88 13         9865 };
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 13     13   2870 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 138     135   2113 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 138 100       6681 if ( my $values = $data{ _generate_key( $_[1], $_[2] ) } ) {
134 136         256 return $values->{ _unique_id( $_[0] ) };
135             }
136              
137 2         8 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 2     2   4 if ( DEBUG > 1 ) {
151             my $id = _unique_id( $_[0] );
152             warn "OOB_reset with @_: $id\n";
153             }
154              
155             # which values to remove?
156 2         7 my $id = _unique_id( $_[0] );
157              
158             # need to tell the world what we removed
159 2 100       10 if ( defined wantarray ) {
160             my %removed =
161 2         8 map { $_ => delete $data{$_}->{$id} }
162 1         4 grep { exists $data{$_}->{$id} }
  2         7  
163             keys %data;
164 1         4 return \%removed;
165             }
166              
167             # no need to tell what we deleted
168 1         6 delete $_->{$id} foreach values %data;
169              
170 1         2 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 124 100   124   4051 if ( !reftype $_[0] ) {
    100          
187 8         22 CORE::bless \$_[0], __OOB__;
188             }
189              
190             # already blessed and not seen before
191             elsif ( my $blessed = blessed $_[0] ) {
192 85         141 _register_DESTROY($blessed);
193             }
194              
195             # not blessed yet, so bless it now
196             else {
197 31         57 CORE::bless $_[0], __OOB__;
198             }
199              
200             # we're debugging
201 124         158 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 124 100       237 if ( defined wantarray ) {
209 34         53 my $id = _unique_id( $_[0] );
210 34         69 my $key = _generate_key( $_[1], $_[3] );
211 34         60 my $old = $data{$key}->{$id};
212 34         50 $data{$key}->{$id} = $_[2];
213 34 100       128 return wantarray ? ( $old, $id ) : $old;
214             }
215              
216             # just set it
217 90         208 $data{ _generate_key( $_[1], $_[3] ) }->{ _unique_id( $_[0] ) } = $_[2];
218              
219 90         181 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 13     13   114 my $class = shift;
236              
237             # nothing to export / defined
238 13 100 100     87 if (!@_) {
    100          
    100          
239 6         7108 return;
240             }
241              
242             # we want all constants
243             elsif ( @_ == 1 and $_[0] eq ':all' ) {
244 1         4 @_ = keys %export_ok;
245             }
246              
247             # assume none exportables are attributes
248 9         36 elsif ( my @attributes = grep { !exists $export_ok{$_} } @_ ) {
249 4         12 _register_attribute( $class, $_ ) foreach @attributes;
250              
251             # reduce to real exportables
252 4         28 @_ = grep { exists $export_ok{$_} } @_;
  4         10  
253             }
254              
255             # something to export
256 7 100       43 if (@_) {
257              
258             # determine namespace to export to
259 3         7 my $namespace = caller() . '::';
260 3         4 warn "Exporting @_ to $namespace\n" if DEBUG;
261              
262             # export requested constants
263 13     13   123 no strict 'refs';
  13         37  
  13         5669  
264 3         7 *{$namespace.$_} = \&$_ foreach @_;
  8         34  
265             }
266              
267 7         3987 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 40 100   40   2524 return if $OOB::AUTOLOAD eq 'OOB::dump';
283            
284             # don't know what to do with it
285 38         58 my $class = shift;
286 38 100       130 if ( !$class->isa(__OOB__) ) {
287 1         5 require Carp;
288 1         166 Carp::croak( "Undefined subroutine $OOB::AUTOLOAD" );
289             }
290              
291             # seems to be an attribute we don't know about
292 37 100       103 if ( @_ == 2 ) {
    100          
293 16         72 require Carp;
294 16         73 $OOB::AUTOLOAD =~ m#::(\w+)$#;
295 16         1264 Carp::croak( "Attempt to set unregistered OOB attribute '$1'" );
296             }
297              
298             # registration
299             elsif ( !@_ ) {
300 6         43 _register_attribute( $OOB::AUTOLOAD =~ m#^(.*)::(\w+)$# );
301             }
302              
303 21         40 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 52     52   8542 my $id = _unique_id( $_[0] );
315              
316             # we're debugging
317 52         61 if (DEBUG) {
318             warn "OOB::DESTROY with @_: $id\n";
319             }
320              
321             # perform the deletion
322 52         219 delete $_->{$id} foreach values %data;
323              
324 52         984 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 268 100   259   1010 my $namespace = defined $_[1]
    100          
344             ? ( "$_[1]" ? "$_[1]--" : '' )
345             : ( caller(1) )[0] . '--';
346              
347 259         695 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 10     10   34 my ( $namespace, $key ) = @_;
360              
361             # install a method to handle it
362 13     13   102 no strict 'refs';
  13         24  
  13         2064  
363 10         52 *{ $namespace . '::' . $key } = sub {
364 142 100   142   24497 return if @_ < 2; # another registration and huh?
365 141 100       382 return @_ == 3
366             ? OOB_set( $_[1], $key => $_[2], $namespace )
367             : OOB_get( $_[1], $key, $namespace );
368 10         45 };
369             } #_register_attribute
370              
371             #-------------------------------------------------------------------------------
372             # _register_DESTROY
373             #
374             # IN: 1 class to register DESTROY method for
375              
376             sub _register_DESTROY {
377 90     90   118 my $blessed = shift;
378              
379             # already has DESTROY method installed
380 90 100       190 return if $stolen{$blessed};
381              
382             # there is a DESTROY method, need to insert ours
383 7 100       69 if ( my $destroy = $blessed->can('DESTROY') ) {
384 1         2 $stolen{$blessed} = $destroy;
385 1         4 my $fullname = sub_fullname($destroy);
386 13     13   85 no strict 'refs';
  13         24  
  13         460  
387 13     13   72 no warnings 'redefine';
  13         18  
  13         1046  
388 1     1   16 *$fullname = sub { $destroy->( $_[0] ); &OOB::DESTROY( $_[0] ) };
  1         674  
  1         8  
389             }
390              
391             # no DESTROY method yet, to set one
392             else {
393 13     13   81 no strict 'refs';
  13         38  
  13         2470  
394 6         16 *{ $blessed . '::DESTROY' } = $stolen{$blessed} = \&OOB::DESTROY;
  6         43  
395             }
396             } #_register_DESTROY
397              
398             #-------------------------------------------------------------------------------
399             # _unique_id
400             #
401             # Return the key of the given parameters
402             #
403             # IN: 1 reference to value to work with
404             # OUT: 1 id to be used in internal hash
405              
406             sub _unique_id {
407             # no ref, make it!
408 311     311   534 my $reftype = reftype $_[0];
409 311 100       611 if ( !$reftype ) {
    100          
410 16         76 return refaddr \$_[0];
411             }
412              
413             # special handling for refs to refs
414             elsif ( $reftype eq 'REF' ) {
415 3         4 my $ref = ${$_[0]};
  3         6  
416 3         9 $ref = ${$ref} while reftype $ref eq 'REF';
  3         9  
417 3         12 return refaddr $ref;
418             }
419              
420             # just use the refaddr
421 292         993 return refaddr $_[0];
422             } #_unique_id
423              
424              
425             #-------------------------------------------------------------------------------
426              
427             __END__