File Coverage

blib/lib/Package/Watchdog/Sub.pm
Criterion Covered Total %
statement 51 52 98.0
branch 15 18 83.3
condition 3 3 100.0
subroutine 11 11 100.0
pod 4 4 100.0
total 84 88 95.4


line stmt bran cond sub pod time code
1             package Package::Watchdog::Sub;
2 6     6   838 use strict;
  6         27  
  6         206  
3 6     6   30 use warnings;
  6         12  
  6         155  
4 6     6   28 use Package::Watchdog::Util;
  6         14  
  6         531  
5 6     6   32 use Carp;
  6         12  
  6         4123  
6              
7             #{{{ POD
8              
9             =pod
10              
11             =head1 NAME
12              
13             Package::Watchdog::Sub - Base object for managing overriden subroutines.
14              
15             =head1 DESCRIPTION
16              
17             Only once instance of a class based on this one can exist per package and sub.
18             Each instance manages exactly one sub. When the instance is created it
19             overrides the subroutine with a new one. The instance will expire when the
20             original sub is restored.
21              
22             =head1 ACCESSORS
23              
24             The following accessors methods are automatically generated using
25             Package::Watchdog::Util::build_accessors().
26              
27             =over 4
28              
29             =item package()
30              
31             Name of the package the sub is in.
32              
33             =item sub()
34              
35             Name of the sub being managed.
36              
37             =back
38              
39             =head1 METHODS
40              
41             =over 4
42              
43             =cut
44              
45             #}}}
46              
47             my @ACCESSORS = qw/package sub original tracker/;
48             build_accessors( @ACCESSORS );
49              
50             =item _instance( $class, $package, $sub )
51              
52             Get/Set the current instance of $class built for $package::$sub. FOR INTERNAL
53             USE ONLY!
54              
55             =cut
56              
57             our %INSTANCES;
58             sub _instance {
59 230     230   451 my ( $class, $package, $sub ) = splice( @_, 0, 3);
60 230 100       620 $INSTANCES{ $class }{ $package }{ $sub } = shift( @_ ) if @_;
61 230         568 return $INSTANCES{ $class }{ $package }{ $sub };
62             }
63              
64             =item $sub_ref = new_sub()
65              
66             Must be overriden by a subclass. Should return a replacement sub for the sub
67             being managed.
68              
69             =cut
70              
71             sub new_sub {
72 1     1 1 1621 my $self = shift;
73 1         54 croak( (ref $self ) . " must override new_sub()" );
74             }
75              
76             =item $sub_ref = _new_sub()
77              
78             FOR INTERNAL USE ONLY
79              
80             Wraps the sub from new_sub() in additional logic to ensure the original sub is
81             restored after an exception.
82              
83             =cut
84              
85             sub _new_sub {
86 54     54   76 my $self = shift;
87 54         160 my $new_sub = $self->new_sub;
88             return sub {
89 47     47   12010 my $want = wantarray();
90 47         65 my @return;
91 47         61 my $live = eval { @return = proper_return( $want, $new_sub, @_ ); 1 };
  47         161  
  24         41  
92 47 100       5187 unless( $live ) {
93 23         102 $self->restore();
94 23         270 croak( $@ );
95             }
96 24 100       84 return @return if $want;
97 13 100       35 return shift( @return ) if defined( $want );
98 10 50       21 return @return if @return > 1;
99 10         38 return shift( @return );
100             }
101 54         353 }
102              
103             =item $obj = $class->new( $package, $sub, $tracker, @params )
104              
105             Constructs a new instance, or returns the existing instance of $class managing
106             $package::$sub. In the case of an existing instance the tracker is appended to
107             the list of trackers. @params is passed to init().
108              
109             init() is called both for new instances and existing.
110              
111             =cut
112              
113             sub new {
114 63     63 1 4598 my $class = shift;
115 63         100 my ( $package, $sub, $tracker ) = @_;
116 63 50       144 croak( 'no sub' ) unless $sub;
117 63         322 my $self = $class->_instance( $package, $sub );
118 63 100       151 unless ($self) {
119 54         357 $self = $class->_instance(
120             $package,
121             $sub,
122             bless(
123             {
124             package => $package,
125             sub => $sub,
126             tracker => $tracker,
127             },
128             $class
129             ),
130             );
131              
132 54 50       228 if ( prototype( $package . '::' . $sub )) {
133 0         0 warn "Cannot override $package\::$sub as it has a prototype";
134             }
135             else {
136 54         162 $self->do_override;
137             }
138             }
139              
140 63         187 return $self;
141             }
142              
143             =item do_override()
144              
145             INTERNAL USE ONLY!
146              
147             Replaces the managed sub with _new_sub(). Will refuse to run if the instance
148             has expired. Automatically called by new(), you should NEVER need to runthis
149             yourself.
150              
151             =cut
152              
153             sub do_override {
154 56     56 1 212 my $self = shift;
155 56         198 my $current = (ref $self)->_instance( $self->package, $self->sub );
156 56 100 100     389 die( "Cannot run do_override on expired instance" )
157             unless $current and $self == $current;
158 54         159 $self->original( copy_sub( $self->package, $self->sub ));
159 54         157 set_sub(
160             $self->package,
161             $self->sub,
162             $self->_new_sub,
163             );
164 54         101 return $self;
165             }
166              
167             =item restore()
168              
169             Restore the original subroutine and expire this instance.
170              
171             =cut
172              
173             sub restore {
174 57     57 1 21382 my $self = shift;
175 57         209 set_sub(
176             $self->package,
177             $self->sub,
178             $self->original,
179             );
180 57         242 (ref $self)->_instance( $self->package, $self->sub, undef );
181 57         195 return $self;
182             }
183              
184             1;
185              
186             __END__