File Coverage

blib/lib/Class/Rebless.pm
Criterion Covered Total %
statement 54 54 100.0
branch 32 34 94.1
condition 5 8 62.5
subroutine 9 9 100.0
pod 1 2 50.0
total 101 107 94.3


line stmt bran cond sub pod time code
1             package Class::Rebless;
2              
3             require 5.005;
4 2     2   78129 use strict;
  2         7  
  2         92  
5 2     2   14 use Carp;
  2         5  
  2         162  
6 2     2   24 use Scalar::Util;
  2         5  
  2         142  
7              
8 2     2   13 use vars qw($VERSION $RE_BUILTIN $MAX_RECURSE);
  2         2  
  2         625  
9              
10             $VERSION = '0.10';
11             $MAX_RECURSE = 1_000;
12              
13              
14             # MODULE INITIALIZATION
15              
16             my %subs = (
17             rebless => sub {
18             my($opts) = @_;
19             $opts->{editor} = sub {
20             my ($obj, $class) = @_;
21             bless $obj, $class;
22             };
23             },
24             rebase => sub {
25             my($opts) = @_;
26             $opts->{editor} = sub {
27             my ($obj, $class) = @_;
28             bless $obj, $class . '::' . ref $obj;
29             };
30             },
31             custom => sub {
32             my($opts) = @_;
33             $opts->{editor} or confess "custom reblesser requires an editor";
34             },
35             );
36              
37             while (my($name, $add_editor_to_opts) = each %subs) {
38 2     2   12 no strict 'refs';
  2         5  
  2         1477  
39             *{__PACKAGE__ . "::$name"} = sub {
40 16     16   90322 my ($proto, $obj, $namespace, $opts) = @_;
41              
42 16   33     124 my $class = ref($proto) || $proto;
43              
44 16   100     79 $opts ||= {};
45 16         53 $add_editor_to_opts->($opts);
46              
47 16         162 my $state = {
48             level => 0,
49             stack => { },
50             seen => { },
51             };
52              
53 16         66 $class->_recurse($obj, $namespace, $opts, $state);
54             };
55             }
56              
57             {
58             my $prune;
59             sub prune {
60 3 100   3 1 16574 $prune = $_[1] if defined $_[1];
61 3         12 $prune;
62             }
63             sub need_prune {
64 50 100   50 0 158 return if not defined $prune;
65 11         41 return $_[1] eq $prune;
66             }
67             }
68              
69             sub _recurse {
70 123     123   207 my ($class, $obj, $namespace, $opts, $state) = @_;
71              
72             # If MAX_RECURSE is 10, we should be allowed to recurse ten times before
73             # throwing an exception. That means we only throw an exception at #11.
74 123 100       310 die "maximum recursion level exceeded" if $state->{level} > $MAX_RECURSE;
75              
76 122         253 my $refaddr = Scalar::Util::refaddr($obj);
77 122 100       231 if (defined $refaddr) {
78 72 100 66     316 return $obj if $state->{seen}{$refaddr}++ and ! $opts->{revisit};
79 71 50       211 return $obj if $state->{stack}{$refaddr};
80             }
81              
82 121         274 local $state->{level} = $state->{level} + 1;
83 121 100       350 local $state->{stack}{ defined $refaddr ? $refaddr : '' } = 1;
84              
85             # rebless this node, possibly pruning (skipping recursion
86             # over its children)
87 121 100       413 if (Scalar::Util::blessed $obj) {
88 50         127 my $res = $opts->{editor}->($obj, $namespace); # re{bless,base} ref
89 50 100       316 return $obj if $class->need_prune($res);
90             }
91              
92 120         257 my $type = Scalar::Util::reftype $obj;
93 120 100       352 return $obj unless defined $type;
94              
95 70 100       239 if ($type eq 'SCALAR') {
    100          
    100          
    100          
96 4         107 $class->_recurse($$obj, $namespace, $opts, $state);
97             } elsif ($type eq 'ARRAY') {
98 10         21 for my $elem (@$obj) {
99 26         61 $class->_recurse($elem, $namespace, $opts, $state);
100             }
101             } elsif ($type eq 'HASH') {
102 45         130 for my $val (values %$obj) {
103 71         183 $class->_recurse($val, $namespace, $opts, $state);
104             }
105             } elsif ($type eq 'GLOB') {
106             # Filehandles are GLOBs, but they don't have ARRAY slots!
107             # Be paranoid, then, and recurse only on defined slots.
108              
109 3         5 my $slot;
110              
111 3 50       12 if (defined ($slot = *$obj{SCALAR})) { # a glob has a scalar...
112 3         12 $class->_recurse($$slot, $namespace, $opts, $state);
113             }
114 3 100       12 if (defined ($slot = *$obj{ARRAY})) {
115 1         3 for my $elem (@$slot) { # and an array...
116 2         7 $class->_recurse($elem, $namespace, $opts, $state);
117             }
118             }
119 3 100       12 if (defined ($slot = *$obj{HASH})) {
120 1         4 for my $val (values %$slot) { # ... and a hash.
121 1         4 $class->_recurse($val, $namespace, $opts, $state);
122             }
123             }
124             }
125 67         390 return $obj;
126             }
127              
128             1;
129              
130              
131             __END__