File Coverage

blib/lib/Class/Std/Slots.pm
Criterion Covered Total %
statement 142 149 95.3
branch 42 54 77.7
condition 13 23 56.5
subroutine 21 22 95.4
pod 5 5 100.0
total 223 253 88.1


line stmt bran cond sub pod time code
1             package Class::Std::Slots;
2              
3 2     2   107054 use warnings;
  2         5  
  2         71  
4 2     2   10 use strict;
  2         4  
  2         63  
5 2     2   9 use Carp;
  2         7  
  2         177  
6 2     2   11 use Scalar::Util qw(blessed refaddr weaken);
  2         4  
  2         2259  
7              
8             our $VERSION = '0.31';
9              
10             my %signal_map = (); # maps id -> signame -> array of connected slots
11             my %signal_busy = (); # maps id -> signame -> busy flag
12             my %patched = (); # classes whose DESTROY we've patched
13              
14             # Subs we export to caller's namespace
15             my @exported_subs = qw(
16             connect
17             disconnect
18             signals
19             has_slots
20             emit_signal
21             );
22              
23             sub _massage_signal_names {
24 29     29   38 my $sig_names = shift;
25              
26 29 50       69 croak "Missing signal name"
27             unless defined( $sig_names );
28              
29 29 100       75 $sig_names = [$sig_names]
30             unless ref( $sig_names );
31              
32 29 50       80 croak "Signal name must be a scalar or an array reference"
33             unless ref( $sig_names ) eq 'ARRAY';
34              
35 29         32 for my $sig_name ( @{$sig_names} ) {
  29         55  
36 31 100       260 croak "Invalid signal name '$sig_name'"
37             unless $sig_name =~ /^\w(?:[\w\d])*$/;
38             }
39              
40 28         60 return $sig_names;
41             }
42              
43             sub _check_signals_exist {
44 10     10   12 my $class = shift;
45 10         12 my $sig_names = shift;
46              
47 10         12 for my $sig_name ( @{$sig_names} ) {
  10         22  
48              
49             # OK to call UNIVERSAL::can() here because we do actually want to
50             # know whether a method named after this signal exists rather than
51             # whether this class or one of its superclasses can respond to
52             # a particular message - so we're not interested in any overridden
53             # version of can()
54 10 100       23 croak "Signal '$sig_name' undefined"
55             unless UNIVERSAL::can( $class, $sig_name );
56             }
57             }
58              
59             sub emit_signal {
60 1     1 1 6 my $self = shift;
61 1         4 my $sig_names = _massage_signal_names( shift );
62              
63 1         3 for my $sig_name ( @{$sig_names} ) {
  1         4  
64 1         5 _emit_signal( $self, $sig_name, @_ );
65             }
66             }
67              
68             sub _emit_signal {
69 28     28   33 my $self = shift;
70 28         35 my $sig_name = shift;
71 28         53 my $src_id = refaddr( $self );
72              
73 28 50       100 unless ( blessed( $self ) ) {
74 0         0 croak "Signal '$sig_name' must be invoked as a method\n";
75             }
76              
77 28 100       97 if ( exists( $signal_busy{$src_id}->{$sig_name} ) ) {
78 2         311 croak "Attempt to re-enter signal '$sig_name'";
79             }
80              
81             # Flag this signal as busy
82 26         53 $signal_busy{$src_id}->{$sig_name}++;
83              
84             # We still want to remove the busy lock on the signal
85             # even if one of the slots dies - so wrap the whole
86             # thing in an eval.
87 26         39 eval {
88              
89             # Get the slots registered with this signal
90 26         53 my $slots = $signal_map{$src_id}->{$sig_name};
91              
92             # Might have none... It's not an error.
93 26 100       55 if ( defined $slots ) {
94 13         15 for my $slot ( @{$slots} ) {
  13         27  
95 14         14 my ( $dst_obj, $dst_method, $options ) = @{$slot};
  14         29  
96 14 50       29 if ( defined( $dst_obj ) ) {
97              
98 14         20 my @args = @_;
99              
100             # The reveal_source option causes a hashref
101             # describing the source of the signal to
102             # be prepended to the args.
103 14 50       30 if ( $options->{reveal_source} ) {
104 0         0 unshift @args,
105             {
106             source => $self,
107             signal => $sig_name,
108             options => $options
109             };
110             }
111              
112             # Call an anon sub or a method
113 14 100       46 if ( blessed( $dst_obj ) ) {
114 12         75 $dst_obj->$dst_method( @args );
115             }
116             else {
117 2         6 $dst_obj->( @args );
118             }
119             }
120             }
121             }
122             };
123              
124             # Remove busy flag
125 26         330 delete $signal_busy{$src_id}->{$sig_name};
126              
127             # Rethrow any error
128 26 100       92 die if $@;
129             }
130              
131             sub _destroy {
132 4     4   9 my $src_id = shift;
133 4         9 delete $signal_map{$src_id};
134 4         11 delete $signal_busy{$src_id};
135             }
136              
137             sub has_slots {
138 8     8 1 2080 my $src_obj = shift;
139 8         18 my $sig_names = _massage_signal_names( shift );
140              
141 8 50       32 croak 'Usage: $obj->has_slots($sig_name)'
142             unless blessed $src_obj;
143              
144 8         11 for my $sig_name ( @{$sig_names} ) {
  8         15  
145 9         18 my $src_id = refaddr( $src_obj );
146 9 100       62 return 1 if exists $signal_map{$src_id}->{$sig_name};
147             }
148              
149 5         24 return;
150             }
151              
152             sub _connect_usage {
153 2     2   202 croak
154             'Usage: $source->connect($sig_name, $dst_obj, $dst_method [, { options }])';
155             }
156              
157             sub connect {
158 16     16 1 7943 my $src_obj = shift;
159 16         31 my $sig_names = _massage_signal_names( shift );
160 15         24 my $dst_obj = shift;
161 15         14 my $dst_method;
162              
163 15 100 66     98 _connect_usage()
164             unless blessed( $src_obj )
165             && defined( $dst_obj );
166              
167 14 100       45 if ( blessed( $dst_obj ) ) {
168 13   66     33 $dst_method = shift || _connect_usage();
169 12 100       60 croak "Slot '$dst_method' not handled by " . ref( $dst_obj )
170             unless $dst_obj->can( $dst_method );
171             }
172             else {
173 1 50       5 _connect_usage() unless ref( $dst_obj ) eq 'CODE';
174             }
175              
176 11   100     116 my $options = shift || {};
177 11         26 my $src_id = refaddr( $src_obj );
178 11         14 my $caller = ref( $src_obj );
179              
180 11 100       36 _check_signals_exist( $caller, $sig_names )
181             unless $options->{undeclared};
182              
183 10   66     141 my $weaken = !( $options->{strong} || ref( $dst_obj ) eq 'CODE' );
184 10         13 for my $sig_name ( @{$sig_names} ) {
  10         20  
185              
186             # Stash the object and method so we can call it later.
187 10         23 my $dst_data = [ $dst_obj, $dst_method, $options ];
188 10 100       38 weaken( $dst_data->[0] ) if $weaken;
189 10         11 push @{ $signal_map{$src_id}->{$sig_name} }, $dst_data;
  10         49  
190             }
191              
192             # Now badness: we replace the DESTROY that Class::Std dropped into
193             # the caller's namespace with our own. See the note under BUGS AND
194             # LIMITATIONS about this technique for replacing Class::Std's
195             # destructor.
196 10 100       28 unless ( exists $patched{$caller} ) {
197              
198             # If there's nothing in the hash for this object we can't have
199             # installed our destructor yet - so do it now.
200              
201 2     2   12 no strict 'refs';
  2         4  
  2         128  
202              
203 3         7 my $destroy_func = $caller . '::DESTROY';
204 3         4 my $current_func = *{$destroy_func}{CODE};
  3         12  
205              
206 3         13 local $^W = 0; # Disable subroutine redefined warning
207 2     2   10 no warnings; # Need this too.
  2         4  
  2         1027  
208              
209 3         14 *{$destroy_func} = sub {
210              
211             # Destroy our members
212 4     4   677 _destroy( $src_id );
213              
214             # Chain the existing destructor
215 4         15 $current_func->( @_ );
216 3         15 };
217              
218             # Remember we've patched this one...
219 3         11 $patched{$caller}++;
220             }
221              
222 10         30 return;
223             }
224              
225             sub disconnect {
226 10     10 1 2888 my $src_obj = shift;
227 10         23 my $src_id = refaddr( $src_obj );
228              
229 10 50       33 croak 'disconnect must be called as a member'
230             unless blessed $src_obj;
231              
232 10 100       25 if ( @_ ) {
233 1         4 my $sig_names = _massage_signal_names( shift );
234 1         2 my $dst_obj = shift; # optional
235 1         2 my $dst_method = shift; # optional - undef is ok in the grep below
236 1         3 my $dst_id = refaddr( $dst_obj );
237              
238 1         2 for my $sig_name ( @{$sig_names} ) {
  1         2  
239 1         4 my $slots = $signal_map{$src_id}->{$sig_name};
240              
241 1 50       4 if ( defined( $dst_obj ) ) {
242 1 50       3 if ( defined $slots ) {
243              
244             # Nasty block to filter out matching connections.
245 1 50 33     6 @{$slots} = grep {
  2   33     33  
      66        
      33        
246 1         2 defined $_
247             && defined $_->[0]
248             && (
249             $dst_id != refaddr( $_->[0] )
250             || (
251             (
252             defined( $dst_method )
253             && defined( $_->[1] )
254             && ( $dst_method ne $_->[1] )
255             )
256             )
257             )
258 1         2 } @{$slots};
259             }
260             }
261             else {
262              
263             # Delete all connections for given signal
264 0         0 delete $signal_map{$src_id}->{$sig_name};
265             }
266             }
267             }
268             else {
269              
270             # Delete /all/ connections for this object
271 9         44 delete $signal_map{$src_id};
272             }
273             }
274              
275             sub signals {
276 3     3 1 20 my $caller = caller;
277 3         10 my $sig_names = _massage_signal_names( \@_ );
278              
279 3         4 for my $sig_name ( @{$sig_names} ) {
  3         4  
280 4 50       13 croak "Signal '$sig_name' already declared"
281             if UNIVERSAL::can( $caller, $sig_name );
282              
283 4         159 my $sig_func = $caller . '::' . $sig_name;
284              
285             # Create the subroutine stub
286 2     2   12 no strict 'refs';
  2         4  
  2         318  
287 4         19 *{$sig_func} = sub {
288 27     27   1794 my $self = shift;
289 27         89 _emit_signal( $self, $sig_name, @_ );
290              
291             # Make sure we don't ever have a return value
292 20         47 return;
293             }
294 4         15 }
295              
296 3         8 return;
297             }
298              
299             sub import {
300 4     4   852 my $caller = caller;
301              
302             # Install our exported subs
303 2     2   11 no strict 'refs';
  2         4  
  2         271  
304 4         8 for my $sub ( @exported_subs ) {
305 20         21 *{ $caller . '::' . $sub } = \&{$sub};
  20         1724  
  20         36  
306             }
307             }
308              
309             sub DESTROY {
310 0     0     my $self = shift;
311              
312             # Tidy up for us
313 0           my $src_id = refaddr( $self );
314 0           _destroy( $src_id );
315              
316             # and for them.
317 0           $self->SUPER::DESTROY();
318             }
319              
320             1; # Magic true value required at end of module
321             __END__