File Coverage

blib/lib/POE/Declare/Object.pm
Criterion Covered Total %
statement 106 162 65.4
branch 22 56 39.2
condition 1 15 6.6
subroutine 28 40 70.0
pod 19 19 100.0
total 176 292 60.2


line stmt bran cond sub pod time code
1             package POE::Declare::Object;
2              
3             =pod
4              
5             =head1 NAME
6              
7             POE::Declare::Object - Base class for all POE::Declare classes
8              
9             =head1 DESCRIPTION
10              
11             L provides the base package that delivers core
12             functionality for all instantiated L objects.
13              
14             Functionality and methods defined here are available in all L
15             classes.
16              
17             =head1 METHODS
18              
19             =cut
20              
21 4     4   105 use 5.008007;
  4         32  
  4         181  
22 4     4   24 use strict;
  4         9  
  4         131  
23 4     4   23 use warnings;
  4         8  
  4         118  
24 4     4   2269 use attributes ();
  4         3160  
  4         95  
25 4     4   28 use Carp ();
  4         7  
  4         57  
26 4     4   33 use Scalar::Util ();
  4         7  
  4         60  
27 4     4   22 use Params::Util ();
  4         8  
  4         79  
28 4     4   20 use POE;
  4         6  
  4         34  
29 4     4   1366 use POE::Session ();
  4         12  
  4         60  
30 4     4   21 use POE::Declare ();
  4         8  
  4         80  
31              
32 4     4   19 use vars qw{$VERSION};
  4         8  
  4         244  
33             BEGIN {
34 4     4   4273 $VERSION = '0.59';
35             }
36              
37             # Inside-out storage of internal values
38             my %ID = ();
39              
40             # Set default attributes
41             POE::Declare::declare( Alias => 'Param' );
42              
43              
44              
45              
46              
47             #####################################################################
48             # Attribute Hooks
49              
50             # Only events are supported for now
51             sub MODIFY_CODE_ATTRIBUTES {
52 12     12   1337 my ($class, $code, $name, @params) = @_;
53              
54             # Can't declare events for classes that are already compiled
55 12 50       46 if ( $POE::Declare::META{$class} ) {
56 0         0 Carp::croak("Can't declare event for finalized class $class");
57             }
58              
59             # Register an event
60 12 100       34 if ( $name eq 'Event' ) {
61             # Add to the coderef event register
62 10         53 $POE::Declare::EVENT{Scalar::Util::refaddr($code)} = [
63             'POE::Declare::Meta::Event',
64             ];
65 10         40 return ();
66             }
67              
68             # Register a timeout
69 2 50       12 if ( $name =~ /^Timeout\b/ ) {
70 2 50       13 unless ( $name =~ /^Timeout\((.+)\)$/ ) {
71 0         0 Carp::croak("Missing or invalid timeout");
72             }
73 2         6 my $delay = $1;
74 2         4 my $variance = 0;
75 2 50       12 if ( defined Params::Util::_STRING($delay) ) {
76 2 50       7 if ( $delay =~ /^(.+?)\+\-(.+?)\z/ ) {
77 0         0 $delay = $1;
78 0         0 $variance = $2;
79             }
80             }
81 2 50       61 unless ( Params::Util::_POSINT($delay) ) {
82 0         0 Carp::croak("Missing or invalid timeout");
83             }
84 2         35 $POE::Declare::EVENT{Scalar::Util::refaddr($code)} = [
85             'POE::Declare::Meta::Timeout',
86             delay => $delay,
87             ];
88 2         7 return ();
89             }
90              
91             # Unknown method type
92 0         0 Carp::croak("Unknown or unsupported attribute $name");
93             }
94              
95             =pod
96              
97             =head2 meta
98              
99             The C method can be run on either a class or instances of that class,
100             and returns the L metadata object for that class.
101              
102             =cut
103              
104             # Moved to code generation
105             # sub meta ($) {
106             # POE::Declare::meta( ref $_[0] || $_[0] );
107             # }
108              
109              
110              
111              
112              
113             #####################################################################
114             # Constructor
115              
116             =pod
117              
118             =head2 new
119              
120             # Create an object, but do not spawn it
121             my $object = My::Class->new(
122             Param1 => 'value',
123             Param2 => 'value',
124             );
125              
126             The C constructor is used to create a L component
127             B immediately starting it up.
128              
129             This is typically assemble to build heirachies of interlinked
130             components and services, without the need to start all of them
131             simultaneously.
132              
133             Instead, a startup routine in the top object of the heirachy can
134             undertake a controlled startup process, bootstrapping each piece of
135             the overall application.
136              
137             All constructors take a series of named params and return a new instance,
138             or throw an exception on error.
139              
140             =cut
141              
142             sub new {
143 5     5 1 16991 my $class = shift;
144 5         129 my $meta = $class->meta;
145 4         13 my $self = bless { }, $class;
146 4         22 my %param = @_;
147              
148             # Check the Alias
149 4 50       16 if ( exists $param{Alias} ) {
150 0 0       0 unless ( Params::Util::_STRING($param{Alias}) ) {
151 0         0 Carp::croak("Did not provide a valid Alias param, must be a string");
152             }
153 0         0 $self->{Alias} = delete $param{Alias};
154             } else {
155 4         18 $self->{Alias} = $meta->next_alias;
156             }
157              
158             # Check and default params
159 4         100 foreach ( $meta->_params ) {
160 12 100       34 next unless exists $param{$_};
161 2         6 $self->{$_} = delete $param{$_};
162             }
163              
164             # Check for unsupported params
165 4 100       16 if ( %param ) {
166 2         8 my $names = join ', ', sort keys %param;
167 2         20 die("Unknown or unsupported $class param(s) $names");
168             }
169              
170             # Check and normalize message registration
171 2         10 foreach ( $meta->_messages ) {
172 0 0       0 next unless exists $self->{$_};
173 0         0 $self->{$_} = _CALLBACK($self->{$_});
174             }
175              
176             # Clear out any accidentally set internal values
177 2         12 delete $ID{Scalar::Util::refaddr($self)};
178              
179 2         7 $self;
180             }
181              
182             # Check the validity of a provided message handler.
183             sub _CALLBACK {
184 0     0   0 my $it = $_[0];
185              
186             # The callback is an anonymous subroutine
187 0 0       0 return $it if Params::Util::_CODE($it);
188              
189             # Otherwise, we also allow a reference to an array,
190             # which contains two identifiers (like foo_bar).
191             # This will be converted to a call to the relevant
192             # POE session.event
193 0 0 0     0 if (
      0        
      0        
194             Params::Util::_ARRAY0($it)
195             and
196             scalar(@$it) == 2
197             and
198             _ALIAS($it->[0])
199             and
200             Params::Util::_IDENTIFIER($it->[1])
201             ) {
202             # Create a closure for the call
203 0         0 my $session = $it->[0];
204 0         0 my $event = $it->[1];
205             my $closure = sub {
206 0     0   0 $poe_kernel->call( $session, $event, @_ );
207 0         0 };
208 0         0 return $closure;
209             }
210              
211             # Otherwise, not valid
212 0         0 Carp::croak('Invalid message event handler');
213             }
214              
215             # Check the format of an alias
216             sub _ALIAS {
217 0 0 0 0   0 Params::Util::_IDENTIFIER($_[0])
    0          
218             or (
219             defined(Params::Util::_STRING($_[0]))
220             and
221             $_[0] =~ /\.\d+$/
222             ) ? $_[0] : undef;
223             }
224              
225             =pod
226              
227             =head2 Alias
228              
229             The C method returns the L alias that will be used with
230             this object instance.
231              
232             These will typically be of the form C<'My::Class.123'> but may be a different
233             value if a custom C param has been explicitly passed to the constructor.
234              
235             =cut
236              
237             # This is auto-generated
238             # sub Alias {
239             # $_[0]->{Alias};
240             # }
241              
242             =pod
243              
244             =head2 spawn
245              
246             # Spawn (i.e. startup) an existing object
247             $object->spawn;
248            
249             # Create the start the object in one call
250             my $alias = My::Class->spawn(
251             Param1 => 'value',
252             Param2 => 'value',
253             );
254              
255             The C method is used to create the L for this object.
256              
257             It returns the session alias as a convenience, or throws an exception on error.
258              
259             When called on the class instead of an object, it provides a shortcut method
260             for a one-shot construction and spawning of an object, returning the object
261             instead of the session alias.
262              
263             Throws an exception on error.
264              
265             =cut
266              
267             sub spawn {
268             # Handle the class context
269 2 50   2 1 10 unless ( ref $_[0] ) {
270 0         0 my $class = shift;
271 0         0 my $self = $class->new( @_ );
272 0         0 $self->spawn;
273 0         0 return $self;
274             }
275              
276             # Create the session
277 2         6 my $self = shift;
278 2         58 my $meta = $self->meta;
279 2         14 POE::Session->create(
280             heap => $self,
281             package_states => [
282             $meta->name => [ $meta->_package_states ],
283             ],
284             )->ID;
285              
286             # Return the alias
287 2         268 $self->Alias;
288             }
289              
290             =pod
291              
292             =head2 spawned
293              
294             The C method returns true if the L for a B
295             object has been created, or false if not.
296              
297             =cut
298              
299             sub spawned {
300 5     5 1 1297 !! $ID{Scalar::Util::refaddr($_[0])};
301              
302             }
303              
304             =pod
305              
306             =head2 session_id
307              
308             The C accessor finds and returns the internal L
309             id for this instance, or C if the object has not been spawned.
310              
311             =cut
312              
313             sub session_id {
314 4     4 1 26 $ID{Scalar::Util::refaddr($_[0])};
315             }
316              
317             =pod
318              
319             =head2 session
320              
321             The C accessor finds and returns the internal L
322             object for this instance, or C if the object has not been spawned.
323              
324             =cut
325              
326             sub session {
327 8 100   8 1 55 my $id = $ID{Scalar::Util::refaddr($_[0])} or return undef;
328 6 50       27 $poe_kernel->ID_id_to_session($id) or return undef;
329             }
330              
331             =pod
332              
333             =head2 kernel
334              
335             The C method is provided as a convenience. It returns the
336             L object that objects of this class will run in.
337              
338             =cut
339              
340 4     4   24 use constant kernel => $poe_kernel;
  4         13  
  4         2959  
341              
342              
343              
344              
345              
346             #####################################################################
347             # POE::Session Wrappers
348              
349             =pod
350              
351             =head2 ID
352              
353             The C is a wrapper for the equivalent L method, and
354             returns the id number for the L.
355              
356             Returns an integer, or C if the heap object has not spawned.
357              
358             =cut
359              
360             sub ID {
361 4     4 1 31 $ID{Scalar::Util::refaddr($_[0])};
362             }
363              
364             =pod
365              
366             =head2 postback
367              
368             my $handler = $object->postback(
369             'event_name',
370             $first_param,
371             'second_param',
372             );
373             $handler->( $third_param, $first_param );
374              
375             The C method is a wrapper for the equivalent L
376             method, and creates an anonymous subroutine that triggers a C for
377             a named event of the heap object.
378              
379             Returns a C reference, or dies if the heap object has not been
380             spawned.
381              
382             =cut
383              
384             sub postback {
385 2     2 1 1290 shift->session->postback(@_);
386             }
387              
388             =pod
389              
390             =head2 callback
391              
392             my $handler = $object->callback(
393             'event_name',
394             $first_param,
395             'second_param',
396             );
397             $handler->( $third_param, $first_param );
398              
399             The C method is a wrapper for the equivalent L
400             method, and creates an anonymous subroutine that triggers a C for
401             a named event of the heap object.
402              
403             Please don't confuse this for a method relating to "callback events"
404             mentioned earlier, it is not related to them.
405              
406             Returns a C reference, or dies if the heap object has not been
407             spawned.
408              
409             =cut
410              
411             sub callback {
412 2     2 1 1148 shift->session->callback(@_);
413             }
414              
415             =pod
416              
417             =head2 lookback
418              
419             sub create_foo {
420             my $self = shift;
421             my $thing = Other::Class->new(
422             ConnectEvent => $self->lookback('it_connected'),
423             ConnectError => $self->lookback('it_failed'),
424             );
425            
426             ...
427             }
428              
429             The C method is a safe alias for C< [ $self-EAlias, 'event_name' ] >.
430              
431             When creating the lookback, the name will be double checked to verify that
432             the handler actually exists and is registered.
433              
434             Returns a reference to an C containing the heap object's alias and
435             the event name.
436              
437             =cut
438              
439             sub lookback {
440 2     2 1 1226 my $self = shift;
441 2         6 my $class = ref($self);
442 2         72 my $name = Params::Util::_IDENTIFIER($_[0]);
443 2 50       29 unless ( $name ) {
444 0         0 Carp::croak("Invalid identifier name '$_[0]'");
445             }
446              
447             # Does the event exist?
448 2         58 my $attr = $self->meta->attr($name);
449 2 50 33     34 unless ( $attr and $attr->isa('POE::Declare::Meta::Event') ) {
450 0         0 Carp::croak("$class does not have the event '$name'");
451             }
452              
453 2         16 return [ $self->Alias, $name ];
454             }
455              
456              
457              
458              
459              
460             #####################################################################
461             # POE::Kernel Wrappers
462              
463             =pod
464              
465             =head2 post
466              
467             The C method runs a POE kernel C for a named event for the
468             heap object's session.
469              
470             Returns void.
471              
472             =cut
473              
474             sub post {
475 0     0 1 0 $poe_kernel->post( shift->Alias, @_ );
476             }
477              
478             =pod
479              
480             =head2 call
481              
482             The C method runs a POE kernel C for a named event for the
483             heap object's session.
484              
485             Returns as for the particular event handler, but generally returns void.
486              
487             =cut
488              
489             sub call {
490 4     4 1 1803 $poe_kernel->call( shift->Alias, @_ );
491             }
492              
493             ### Wrapper for the (new) POE timer API
494              
495             =pod
496              
497             =head2 alarm_set
498              
499             The C method is equivalent to the L method
500             of the same name, setting an alarm for a named event of the heap object's
501             session.
502              
503             =cut
504              
505             sub alarm_set {
506 0     0 1 0 shift;
507 0         0 $poe_kernel->alarm_set( @_ );
508             }
509              
510             =pod
511              
512             =head2 alarm_adjust
513              
514             The C method is equivalent to the L method
515             of the same name, adjusting an alarm for a named event of the heap
516             object's session.
517              
518             =cut
519              
520             sub alarm_adjust {
521 0     0 1 0 shift;
522 0         0 $poe_kernel->alarm_adjust( @_ );
523             }
524              
525             =pod
526              
527             =head2 alarm_clear
528              
529             The C method is a convenience method. It takes the name of
530             a hash key for the object, containing a timer id. If the ID is set, it
531             is cleared. If not, the method shortcuts.
532              
533             =cut
534              
535             sub alarm_clear {
536 0 0   0 1 0 $_[0]->{$_[1]} or return 1;
537 0         0 $_[0]->alarm_remove(delete $_[0]->{$_[1]});
538             }
539              
540             =pod
541              
542             =head2 alarm_remove
543              
544             The C method is equivalent to the L method
545             of the same name, removing an alarm for a named event of the heap
546             object's session.
547              
548             =cut
549              
550             sub alarm_remove {
551 0     0 1 0 shift;
552 0         0 $poe_kernel->alarm_remove( @_ );
553             }
554              
555             =pod
556              
557             =head2 alarm_remove_all
558              
559             The C method is equivalent to the L method
560             of the same name, removing all alarms for the heap object's session.
561              
562             =cut
563              
564             sub alarm_remove_all {
565 0     0 1 0 shift;
566 0         0 $poe_kernel->alarm_remove_all( @_ );
567             }
568              
569              
570             =pod
571              
572             =head2 delay_set
573              
574             The C method is equivalent to the L method
575             of the same name, setting a delayed alarm for a named event of the
576             heap object's session.
577              
578             =cut
579              
580             sub delay_set {
581 0     0 1 0 shift;
582 0         0 $poe_kernel->delay_set( @_ );
583             }
584              
585             =pod
586              
587             =head2 delay_adjust
588              
589             The C method is equivalent to the L method
590             of the same name, adjusting a delayed alarm for a named event of the
591             heap object's session.
592              
593             =cut
594              
595             sub delay_adjust {
596 0     0 1 0 shift;
597 0         0 $poe_kernel->delay_adjust( @_ );
598             }
599              
600              
601              
602              
603              
604             #####################################################################
605             # Events
606              
607             =pod
608              
609             =head1 EVENTS
610              
611             The following POE events are provided for all classes
612              
613             =head2 _start
614              
615             The default C<_start> implementation is used to register the alias for
616             the heap object with the kernel. As such, if you need to do your own
617             tasks in C<_start> you MUST call it first.
618              
619             sub _start {
620             my $self = $_[HEAP];
621             $_[0]->SUPER::_start(@_[1..$#_]);
622            
623             # Additional tasks here
624             ...
625             }
626              
627             Please note though that the super call will break @_ in the current
628             subroutine, and so you should not use C<$_[KERNEL]> style expressions
629             after the SUPER call.
630              
631             =cut
632              
633             sub _start : Event {
634             # Set the session alias in the POE kernel.
635             # Check to see if there is an accidental clash between
636             # this session's desired alias and any existing alias.
637 2     2   1646 my $alias = $_[HEAP]->Alias;
638 2 50       12 if ( defined $poe_kernel->alias_resolve($alias) ) {
639 0         0 die("Fatal alias name clash, '$alias' already in use");
640             }
641 2 50       85 if ( $poe_kernel->alias_set($alias) ) {
642             # Failed to set alias
643 0         0 die("Failed to set alias '$alias'");
644             }
645              
646             # Register our session id with the session index
647 2         80 $ID{Scalar::Util::refaddr($_[HEAP])} = $_[SESSION]->ID;
648              
649             # Because POE::Declare maintains its own session start/stop
650             # management, the default POE parent/child feature will just
651             # get in the way.
652             # For each created session, ensure it will never have a parent
653             # in the eyes of the POE::Kernel.
654 2         11 SCOPE: {
655 2         13 local $!;
656 2         9 $poe_kernel->detach_myself;
657             }
658              
659 2         40 return;
660 4     4   32 }
  4         10  
  4         32  
661              
662             =pod
663              
664             =head2 _stop
665              
666             The default C<_stop> implementation is used to clean up our resources
667             and aliases in the kernel. As such, if you need to do your own
668             tasks in C<_stop> you should always do them first and then call the
669             SUPER last.
670              
671             sub _stop {
672             my $self = $_[HEAP];
673            
674             # Additional tasks here
675             ...
676            
677             $_[0]->SUPER::_stop(@_[1..$#_]);
678             }
679              
680             =cut
681              
682             sub _stop : Event {
683 0     0   0 delete $ID{Scalar::Util::refaddr($_[HEAP])};
684 4     4   883 }
  4         14  
  4         19  
685              
686             =pod
687              
688             =head2 finish
689              
690             The C method is a convenience provided to simplify the process of
691             shutting down the current object/session.
692              
693             It will automatically clean up as many things as possible from your
694             session, leaving it in a state where the session will shut down as
695             soon as the final outstanding event is processed.
696              
697             Currently, this consists of removing any pending alarms and removing
698             the session alias.
699              
700             =cut
701              
702             sub finish {
703 1     1 1 35 my $self = shift;
704              
705             # Are we running
706 1         14 my $alias = $self->Alias;
707 1         4 my $self_id = $ID{Scalar::Util::refaddr($self)};
708 1         6 my $session = $poe_kernel->alias_resolve($alias);
709 1 50       45 unless ( $self_id ) {
710             # Trying to finish a session when we aren't even spawned in
711             # POE::Declare terms should be treated strictly.
712 1         19 Carp::croak("Called 'finish' for $alias on unspawned session");
713             }
714 0 0         unless ( $session ) {
715             # Show some lenience for now and allow double-finishing of an
716             # active POE session (to allow a class to be sure it has
717             # finished everything if there is any doubt).
718 0           return;
719             }
720              
721             # Check we are in the correct session
722 0           my $current = $poe_kernel->get_active_session;
723 0           my $session_id = $session->ID;
724 0           my $current_id = $current->ID;
725 0 0         unless ( $session_id == $current_id ) {
726 0           Carp::croak("Called 'finish' for $alias from a different session");
727             }
728              
729             # Remove all timers
730 0           $poe_kernel->alarm_remove_all;
731              
732             # Remove the session alias.
733 0 0         unless ( $session_id == $self_id ) {
734 0           die("Session id mismatch error");
735             }
736              
737 0           $poe_kernel->alias_remove($alias);
738             }
739              
740              
741              
742              
743              
744             #####################################################################
745             # Compile the POE::Declare form of POE::Declare::Object itself
746              
747             POE::Declare::compile;
748              
749             =pod
750              
751             =head1 SUPPORT
752              
753             Bugs should be always be reported via the CPAN bug tracker at
754              
755             L
756              
757             For other issues, or commercial enhancement or support, contact the author.
758              
759             =head1 AUTHORS
760              
761             Adam Kennedy Eadamk@cpan.orgE
762              
763             =head1 SEE ALSO
764              
765             L, L, L
766              
767             =head1 COPYRIGHT
768              
769             Copyright 2006 - 2012 Adam Kennedy.
770              
771             This program is free software; you can redistribute
772             it and/or modify it under the same terms as Perl itself.
773              
774             The full text of the license can be found in the
775             LICENSE file included with this module.
776              
777             =cut