File Coverage

blib/lib/Net/Analysis/Dispatcher.pm
Criterion Covered Total %
statement 61 66 92.4
branch 13 18 72.2
condition 1 2 50.0
subroutine 12 12 100.0
pod 3 4 75.0
total 90 102 88.2


line stmt bran cond sub pod time code
1             package Net::Analysis::Dispatcher;
2             # $Id: Dispatcher.pm 131 2005-10-02 17:24:31Z abworrall $
3              
4 8     8   306456 use 5.008000;
  8         32  
  8         416  
5             our $VERSION = '0.01';
6 8     8   46 use strict;
  8         18  
  8         274  
7 8     8   44 use warnings;
  8         12  
  8         461  
8 8     8   50 use overload q("") => sub { $_[0]->as_string() }; # OO style stringify
  8     3   16  
  8         92  
  3         477  
9 8     8   572 use Carp qw(carp croak);
  8         19  
  8         509  
10              
11 8     8   7705 use Params::Validate qw(:all);
  8         97752  
  8         7410  
12              
13             # {{{ POD
14              
15             =head1 NAME
16              
17             Net::Analysis::Dispatcher - handle the event stuff for the proto analysers
18              
19             =head1 SYNOPSIS
20              
21             use Net::Analysis::Dispatcher;
22              
23             my $d = Net::Analysis::Dispatcher->new();
24             my $listener = Net::Analysis::Listener::TCP->new();
25             $d->add_listener (listener => $listener);
26              
27             =head1 DESCRIPTION
28              
29             This class is used to register listener objects. Whenever any of the objects
30             emit an event, the dispatcher is used to make sure other interested listeners
31             receive the event.
32              
33             =cut
34              
35             # }}}
36              
37             # {{{ new
38              
39             # {{{ POD
40              
41             =head2 new ()
42              
43             Takes no arguments, tells no lies.
44              
45             =cut
46              
47             # }}}
48              
49             sub new {
50 4     4 1 5250 my ($class) = shift;
51              
52 4         10 my %h;
53              
54 4         16 $h{listeners} = []; # List of objects that are listening to events
55              
56 4         15 my ($self) = bless (\%h, $class);
57              
58 4         16 return $self;
59             }
60              
61             # }}}
62              
63             # {{{ add_listener
64              
65             =head2 add_listener (listener => $obj, config => $hash)
66              
67             This method adds a new listener to the list of things to be notified of each
68             event.
69              
70             If the listener object has a field C, then we attempt to put the listener
71             in that position in the event queue. Valid values are C and C, to
72             receive events first and last. Listener::TCP likes to be first, since it adds
73             extra info to the C that other modules might like to see.
74              
75             If a listener has already claimed the first or last spot, then we croak with an
76             error.
77              
78             =cut
79              
80             sub add_listener {
81 9     9 1 1969 my ($self) = shift;
82              
83 9         214 my %h = validate (@_, { listener => 1, #{ can => "emit" }, <-- broken :(
84             config => { default => {} },
85             });
86              
87             # XXXX workaround issue where Params::Validate rejects mocked methods
88 9 50       72 if (!$h{listener}->can('emit')) {
89 0         0 carp "add_listener needs an object that can ->emit() !\n";
90 0         0 return undef;
91             }
92              
93 9 100       245 if (exists $h{listener}{pos}) {
94 2 50       14 if ($h{listener}{pos} !~ /^(first|last)$/) {
95 0         0 croak "$h{listener} has invalid pos; $h{listener}{pos}\n";
96             }
97 2 50       9 if (exists $self->{pos}{$h{listener}{pos}}) {
98 0         0 croak "position '$h{listener}{pos}' taken; bad $h{listener}\n";
99             }
100 2         13 $self->{pos}{$h{listener}{pos}} = $h{listener};
101              
102             } else {
103 7         14 push (@{$self->{listeners}}, $h{listener});
  7         125  
104             }
105             }
106              
107             # }}}
108             # {{{ emit_event
109              
110             =head2 emit_event (name => 'event_name', args => $hash)
111              
112             The name must be a valid Perl function name. By convention, it should start
113             with the name of the module that is emitting the event (e.g.
114             C).
115              
116             Where your code is emitting events, it must must document the args in detail,
117             so that listeners will know what to do with them.
118              
119             This method runs through the listener list, and if appropriate, invokes the
120             listening method for each listener.
121              
122             A listener gets the event if it has a method which has the same name as the
123             C.
124              
125             =cut
126              
127             sub emit_event {
128 4     4 1 828 my $self = shift;
129              
130 4         26 my %h = @_;
131 4   50     20 $h{args} ||= {};
132              
133 4 50       22 if ($self->{_i_am_invoking}) {
134             # warn "Argh, circular mayhem ($h{name})\n"; exit;
135             }
136              
137             ## Adverse performance impacts, so commented out
138             # my %h = validate (@_, { name => { regex => qr/^[a-z][a-z0-9_]+$/ },
139             # args => { default => {} },
140             # });
141              
142             # If we have any listeners that wanted a special place in the queue, then
143             # give it to them. This stuff will only trigger on the very first event.
144 4 100       20 if (exists $self->{pos}{first}) {
145 1         2 unshift (@{$self->{listeners}}, delete ($self->{pos}{first}));
  1         5  
146             }
147 4 100       19 if (exists $self->{pos}{last}) {
148 1         2 push (@{$self->{listeners}}, delete ($self->{pos}{last}));
  1         4  
149             }
150              
151 4         18 $self->_invoke_callbacks (\%h);
152             }
153              
154             # }}}
155              
156             # {{{ as_string
157              
158             sub as_string {
159 3     3 0 7 my ($self) = @_;
160 3         9 my $s = '';
161              
162 3         6 $s .= "Dispatching to [".join(',', map {"$_"} @{$self->{listeners}})."]";
  2         12  
  3         13  
163              
164 3         57 return $s;
165             }
166              
167             # }}}
168              
169             # {{{ _invoke_callbacks
170              
171             sub _invoke_callbacks {
172 4     4   8 my $self = shift;
173 4         8 my ($h) = @_;
174              
175 4         11 $self->{_i_am_invoking} = 1;
176              
177             # Memoise this iteration & 'can' call ? Results won't change !
178 4         7 foreach my $l (@{$self->{listeners}}) {
  4         14  
179 11         24 my $method = $h->{name};
180 11 100       63 if ($l->can($method)) {
181 10         239 eval {
182 10         235 $l->$method($h->{args});
183 10 50       1651 }; if ($@) {
184 0         0 carp ("Listener '$l' die()d on method $h->{name}:\n$@");
185             }
186             }
187             }
188              
189 4         25 delete ($self->{_i_am_invoking});
190             }
191              
192             # }}}
193              
194              
195             1;
196             __END__