File Coverage

blib/lib/Class/Publisher.pm
Criterion Covered Total %
statement 121 147 82.3
branch 51 70 72.8
condition 32 56 57.1
subroutine 16 21 76.1
pod 7 9 77.7
total 227 303 74.9


line stmt bran cond sub pod time code
1             # $Id: Publisher.pm,v 1.3 2005/03/25 13:20:21 simonflack Exp $
2             package Class::Publisher;
3 2     2   1939 use strict;
  2         6  
  2         83  
4 2     2   12 use Carp;
  2         5  
  2         156  
5 2     2   2210 use Class::ISA;
  2         6721  
  2         65  
6 2     2   15 use Scalar::Util qw/blessed reftype weaken/;
  2         4  
  2         205  
7 2     2   12 use vars '$VERSION';
  2         4  
  2         3766  
8              
9             $VERSION = '0.2';
10             my (%S, %P) = ();
11              
12              
13             # Add one or more subscribers (class name, object or subroutine) to a
14             # subscribed item (class or object). Return new number of subscribers.
15             sub add_subscriber {
16 16     16 1 6808 my ($item, $event, $subscriber, $use_method) = @_;
17 16 100 66     83 $event = '*' unless defined $event && length $event;
18 16 100       31 croak "Invalid subscriber - $subscriber, expected a coderef, object or class name"
19             unless _valid_subscriber($subscriber);
20              
21 13   100     88 my $subscriber_list = $S {$item} {$event} ||= {};
22 13 100       52 weaken($subscriber) if blessed($subscriber);
23 13         15 my $new_subscriber;
24 13 100 33     60 if ($use_method && (!ref $subscriber || blessed($subscriber))) {
      66        
25 5         12 $new_subscriber = [ $subscriber, $use_method ];
26             } else {
27 8         10 $new_subscriber = $subscriber;
28             }
29              
30 13         26 TRACEF("Adding subscriber [%s] of '%s' on [%s]",
31             $subscriber, _event_name($event), _item_name($item));
32 13         43 $subscriber_list->{$subscriber} = $new_subscriber;
33              
34 13         46 return scalar keys %$subscriber_list;
35             }
36              
37              
38             # Remove one or more subscribers from a subscribed item. Return new
39             # number of subscribers.
40             # TODO: Will this work with subroutines?
41             sub delete_subscriber {
42 3     3 1 991 my ($item, $event, $subscriber) = @_;
43 3 50       14 return 0 unless ref $S { $item };
44 3 50 33     21 $event = '*' unless defined $event && length $event;
45              
46 3 50       8 if ($subscriber) {
47 3         5 my @events;
48 3 50 33     19 if (defined $event && length $event) {
49 3         7 @events = ($event);
50             } else {
51 0         0 @events = _get_registered_events($item);
52             }
53              
54 3         6 foreach my $subscribed_event (@events) {
55 3         7 TRACEF("Removing subscriber [%s] of '%s' on [%s]",
56             $subscriber,
57             _event_name($subscribed_event),
58             _item_name($item));
59              
60 3         12 my $removed = delete $S {$item} {$subscribed_event} {$subscriber};
61 3         7 TRACEF("Found subscriber [%s]; removing", $subscriber);
62             }
63             }
64 3 100       13 return defined wantarray ? 0 + $item -> get_subscribers($event) : undef;
65             }
66              
67              
68             # Remove all subscribers from a subscribed item. Return number of
69             # subscribers removed.
70             sub delete_all_subscribers {
71 0     0 1 0 my ($item) = @_;
72 0         0 TRACEF("Removing all subscribers from [%s]", _item_name($item));
73 0 0       0 my $rv = defined wantarray ? 0 + $item -> get_subscribers : undef;
74 0 0       0 return 0 unless ref $S {$item};
75 0         0 $S {$item} = {};
76 0         0 return $rv;
77             }
78              
79              
80             # Tell all subscribers that a event-change has occurred. No return
81             # value.
82             sub notify_subscribers {
83 17     17 1 9993 my ($item, $event, @params) = @_;
84 17 50 33     85 croak "Invalid event name '$event'" if $event && ref $event;
85 17 50 33     83 $event = '*' unless defined $event && length $event;
86 17         32 TRACEF("Notification from [%s] with event [%s]",
87             _item_name($item), _event_name($event));
88              
89 17         76 my @subscribers = $item -> get_subscribers($event);
90 17 50       40 unless ($event eq '*') {
91 17         36 push @subscribers, $item -> get_subscribers('*');
92             }
93              
94 17         21 my %called;
95 17         27 foreach my $s (@subscribers) {
96 20         49 TRACEF("Notifying subscriber [%s]", $s);
97 20 50       61 if ($called {$s}++) {
98 0         0 TRACEF("Already called subscriber [%s]", $s);
99 0         0 next;
100             }
101 20 100 100     187 if (reftype $s && reftype $s eq 'CODE') {
102 4         10 $s -> ($item, $event, @params);
103             }
104             else {
105 16         24 my ($callable, $method) = ($s, 'update');
106 16 100 100     117 if (ref $s && reftype $s eq 'ARRAY' && ! blessed($s)) {
      66        
107 10         21 ($callable, $method) = @$s;
108             }
109 16 50 33     69 next unless $callable && $method;
110 16         58 $callable -> $method($item, $event, @params);
111             }
112             }
113             }
114              
115              
116             # Retrieve *all* subscribers for a particular item. (See docs for what
117             # *all* means.) Returns a list of subscribers
118             sub get_subscribers {
119 45     45 1 4043 my ($item, $event) = @_;
120 45         75 TRACEF("Retrieving subscribers of [%s] on [%s]",
121             _event_name($event), _item_name($item));
122              
123 45         64 my @subscribers = ();
124 45         63 my $class = ref $item;
125 45 100       72 if ($class) {
126 31         63 TRACEF("Retrieving object-specific subscribers from [%s]",
127             _item_name($item));
128 31         50 push @subscribers, _obs_get_subscribers_scoped($item, $event);
129             }
130             else {
131 14         17 $class = $item;
132             }
133 45         102 TRACEF("Retrieving class-specific subscribers from [%s] and its "
134             . "parents", $class);
135 45         84 push @subscribers, _obs_get_subscribers_scoped($class, $event),
136             _obs_get_parent_subscribers($class, $event);
137              
138 45         48 my (@filtered, %seen);
139 45         654 foreach (@subscribers) {
140 32 50       134 push @filtered, $_, unless $seen {$_}++;
141             }
142 45         123 TRACEF("Found subscribers [%s]", join '][', @filtered);
143 45         149 return @filtered;
144             }
145              
146              
147             # Copy all subscribers from one item to another. This DOESN'T copy
148             # subscribers from parents.
149             sub copy_subscribers {
150 0     0 1 0 my ($item_from, $item_to) = @_;
151              
152 0         0 my $rv;
153 0         0 foreach my $event ('', _get_registered_events($item_from)) {
154 0         0 my @obs = _obs_get_explicit_subscribers_scoped($item_from, $event);
155 0         0 foreach my $subscriber (@obs) {
156 0         0 $item_to -> add_subscriber($event, $subscriber);
157 0         0 $rv++;
158             }
159             }
160 0         0 return $rv;
161             }
162              
163              
164             sub count_subscribers {
165 0     0 1 0 my ($item, $event) = @_;
166 0         0 TRACEF("Counting subscribers of [%s] on [%s]",
167             _event_name($event), _item_name($item));
168 0         0 return scalar $item -> get_subscribers($event);
169             }
170              
171              
172             # Log::Trace stubs
173 0     0 0 0 sub TRACE {}
174 228     228 0 289 sub TRACEF {}
175              
176             ############################################################################
177             # Private functions
178              
179             sub _get_registered_events {
180 7     7   9 my ($item) = @_;
181 7 50       18 return () unless ref $S {$item};
182 7   33     6 return grep defined ($_) && length ($_), keys % {$S {$item}};
  7         61  
183             }
184              
185             # Find subscribers from parents
186             sub _obs_get_parent_subscribers {
187 45     45   67 my ($item, $event) = @_;
188 45   33     152 my $class = ref $item || $item;
189              
190             # We only find the parents the first time, so if you muck with
191             # @ISA you'll get unexpected behavior...
192              
193 45 100       101 unless (ref $P {$class}) {
194 3         13 my @parent_path = Class::ISA::super_path($class);
195 3         148 TRACEF("Finding subscribers from parent classes [%s]",
196             join '] [', @parent_path );
197 3         4 my @subscribed_parents = ();
198 3         6 foreach my $parent (@parent_path) {
199 4 100       14 next if ($parent eq 'Class::Publisher');
200 1 50       14 if ($parent -> isa('Class::Publisher')) {
201 1         3 push @subscribed_parents, $parent;
202             }
203             }
204 3         7 push @subscribed_parents, __PACKAGE__;
205 3         6 $P {$class} = \@subscribed_parents;
206 3         10 TRACEF("Found subscribed parents for [%s]: [%s]",
207             $class, join '] [', @subscribed_parents);
208             }
209              
210 45         66 my @parent_subscribers = ();
211 45         42 foreach my $parent (@{$P {$class}}) {
  45         86  
212 46         86 push @parent_subscribers, _obs_get_subscribers_scoped($parent, $event);
213             }
214 45         97 return @parent_subscribers;
215             }
216              
217              
218             # Return subscribers ONLY for the specified item
219             sub _obs_get_subscribers_scoped {
220 122     122   169 my ($item, $event) = @_;
221 122 100       398 return () unless (ref $S {$item});
222              
223 60         61 my @events;
224 60 100 66     752 if (defined $event && length $event) {
225 53         102 @events = ('', $event);
226             } else {
227 7         17 @events = ('', _get_registered_events($item));
228             }
229              
230 60         71 my @subscribers;
231 60         90 foreach (@events) {
232 121 100       361 next unless (ref $S {$item} {$_});
233 32         34 push @subscribers, values %{$S {$item} {$_}};
  32         120  
234             }
235 60         174 return @subscribers;
236             }
237              
238             # Return subscribers EXPLICITLY registered for the specified item AND event
239             sub _obs_get_explicit_subscribers_scoped {
240 0     0   0 my ($item, $event) = @_;
241 0 0 0     0 return () unless ref $S {$item} && ref $S {$item} {$event};
242 0         0 return values %{$S {$item} {$event}};
  0         0  
243             }
244              
245             # Return subscriber validation errors
246             sub _valid_subscriber {
247 16     16   18 my $s = shift;
248              
249 16 100       352 return unless defined $s;
250 15 100 100     52 return 1 if !ref $s && length $s; # Class
251 14 100 100     86 return 1 if ref $s && reftype $s eq 'CODE'; # Subroutine
252 10 100       47 return 1 if blessed ($s); # Object
253 2         229 return 0;
254             }
255              
256             # Used in debugging
257             sub _item_name {
258 109     109   126 my ($item) = @_;
259 109 100       253 return "Class $item" unless (ref $item);
260 81         110 my $item_class = ref $item;
261 81 50       272 if ($item -> can('id')) {
262 0         0 return "Object of class $item_class with ID " . $item -> id;
263             }
264 81         226 return "Instance of class $item_class";
265             }
266              
267             sub _event_name {
268 78     78   94 my ($event) = @_;
269 78 100 66     395 return $event if defined $event && length $event;
270 6         15 return 'all events';
271             }
272              
273             1;
274              
275             __END__