File Coverage

blib/lib/POE/Component/Basement.pm
Criterion Covered Total %
statement 156 163 95.7
branch 39 54 72.2
condition 6 13 46.1
subroutine 24 24 100.0
pod 1 1 100.0
total 226 255 88.6


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             POE::Component::Basement
4              
5             =cut
6             package POE::Component::Basement;
7 5     5   462136 use strict;
  5         13  
  5         183  
8              
9 5     5   28 use Scalar::Util qw/ refaddr /;
  5         8  
  5         613  
10 5     5   4253 use UNIVERSAL::require;
  5         7841  
  5         47  
11 5     5   3876 use Sub::Installer;
  5         21651  
  5         43  
12 5     5   147 use Carp;
  5         10  
  5         246  
13 5     5   5640 use NEXT;
  5         24638  
  5         45  
14 5     5   4059 use POE;
  5         148540  
  5         38  
15              
16 5     5   303295 use vars qw/ %STATES @CARP_NOT @EXPORT_SUBS $VERSION /;
  5         10  
  5         362  
17 5     5   26 use base qw/ POE::Component::Basement::Std Class::Data::Inheritable /;
  5         9  
  5         3515  
18 5     5   3170 BEGIN { @CARP_NOT = qw/ attributes / }
19              
20             __PACKAGE__->mk_classdata( $_ ) for qw/ session_class /;
21             __PACKAGE__->session_class( 'POE::Session' );
22             $VERSION = .01;
23              
24             =head1 SYNOPSIS
25              
26             package POE::MyComponent;
27              
28             # use as base
29             use base qw/ POE::Component::Basement /;
30            
31             # where the initializations happen (see Class::Std)
32             sub BUILD { ... }
33            
34             # see also Class::Std and Class::Data::Inheritable also
35             # for accessor creation etc.
36            
37             # define states
38             sub state_one : State( :inline<_start> ) { ... }
39             sub state_two : State( :object ) { ... }
40             sub state_three : State( :package ) { ... }
41            
42             # combined
43             sub state_multi : State( :inline :package ) { ... }
44             ...
45              
46             # chained events
47             sub first : State( :object :chained ) { ... }
48             sub second : State( :object ) { ... }
49             ...
50              
51             # calling in a row
52             sub first : State( :object :next ) { ... }
53             sub second : State( :object ) { ... }
54             ...
55            
56             # usage
57             my $comp = POE::MyComponent->new ({
58            
59             # single alias or array reference for multiple
60             aliases => [qw/ mycomp shub_niggurath /],
61              
62             ... # your specific init_arg's.
63             });
64              
65             =head1 DESCRIPTION
66              
67             Provides L and base L component functionality. This module is
68             still kinda experimental.
69              
70             =head1 CLASS AND INSTANCE DATA
71              
72             =head2 Setting C
73              
74             Determines on which class the session should be built on. To use, for instance,
75             L, set the option like this:
76              
77             MyComponent->session_class( 'POE::Session::MessageBased' );
78              
79             The default is L.
80              
81             =cut
82              
83             my %aliases_of : ATTR( :init_arg :default<[]> );
84              
85             =head2 Option C
86              
87             Can be a single value to be set as alias, or an array reference. If the latter,
88             L's C is called for each of it's elements. This must
89             be supplied as argument to the C method. See L for examples.
90              
91             =head1 ATTRIBUTES
92              
93             This module just uses the attribute C and delegates all other attribute
94             handling to L. Parameters can multiple, separated by spaces. They
95             look like those of L to be coherent. As an example:
96              
97             sub start_event : State( :inline<_start> ) {
98             ...
99             }
100              
101             This would create an C for our session, named C<_start>.
102              
103             =head2 inline, package and object
104              
105             Create C, C or C in your session.
106             Multiple specifications of these parameters cause multiple events to be defined.
107             Have a look at L for more information.
108              
109             =head2 chained
110              
111             sub first : State( :inline<_start> :chained ) {
112             print "Called first.\n";
113             return 23;
114             }
115            
116             sub end : State( :inline ) {
117             my $last_return = $_[ARG0];
118             print "Called second. First returned $last_return\n";
119             }
120              
121             Specifies with which event the current state should be chained. If you use
122             C, the given event will be triggered after the sub has completed. It's
123             return values will be passed to the chained event.
124              
125             =head2 next
126              
127             # the event gets triggered
128             POE::Kernel->yield( foo => 333 );
129              
130             sub first : State( :inline :next ) {
131             my ( $nr ) = $_[ARG0];
132             ...
133             }
134            
135             sub second : State( :inline ) {
136             my ( $nr ) = $_[ARG0];
137             ...
138             }
139            
140             An event that was specified with C is triggered right after completion
141             of the current subroutine. The C event gets the same parameters as the
142             current.
143              
144             =head2 error
145              
146             sub first : State( :inline :error ) {
147             die "in the name of Cthulhu";
148             }
149              
150             sub second : State( :inline ) {
151             my $error = $_[ARG0];
152             print 'An Error has occured: ' . $error;
153             }
154              
155             If an C handling state is defined, C will build an C block
156             around the subroutine call and emit the event specified with C. First
157             argument is the error message.
158              
159             =head1 INHERITANCE
160              
161             Currently, you can overload the called methods in package and object states.
162             Though you have to do this without specifying a new C attribute. The
163             new method has the same attributes as the overriden. The latter can also be
164             called with L. This basic way works like:
165              
166             # the original
167             package Original;
168             sub whatever : State( :package<_start> ) { ... }
169             ...
170            
171             # the new one
172             package NewOne;
173             use base qw/ Original /;
174             sub whatever { ... }
175              
176             But for information, I'm planning the possibility to override specific events.
177              
178             =head1 METHODS
179              
180             Methods starting with an underline (C<_>) is thought as internal to
181             POE::Component::Basement and should therefore not be called directly.
182              
183             =cut
184              
185             sub _parse_attribute {
186 40     40   61 my ( $attr ) = @_;
187 40 50       224 if ( $attr =~ /^(\w+)(?:\((.*)\))?$/ ) { return ( $1, $2 ) }
  40         195  
188 0         0 return;
189             }
190              
191             =head2 _parse_attributes
192              
193             ( $name, $param ) = _parse_attribute( $attribute )
194            
195             Takes an attribute and tries to split it into name and parameters. Returns
196             undef if nothing usable found.
197              
198             =cut
199              
200             sub new {
201 4     4 1 736 my $class = shift;
202            
203             # delegate original call
204 4         60 my $self = $class->NEXT::new( @_ );
205            
206             # collect all states of this class
207 4         1484 my $states = $self->get_states;
208            
209             # session class
210 4         47 my $sc = $class->session_class;
211            
212             # load and create session
213 4         53 UNIVERSAL::require( $sc );
214 4         268 $sc->create ( %$states );
215            
216             # register aliases
217 4         1431 my $aliases = $aliases_of{ ident $self };
218             POE::Kernel->alias_set( $_ )
219 4 50       42 for ( ref $aliases eq 'ARRAY' ? @$aliases : ($aliases) );
220            
221             # they shall receive us
222 4         165 return $self;
223             }
224              
225             =head2 new
226              
227             Constructor. See L for usage. This overrides the C method
228             provided by L.
229              
230             =cut
231              
232             # no warnings about redefinement
233 5     5   52 { no warnings 'redefine';
  5         11  
  5         2495  
234            
235             # called per sub
236             sub MODIFY_CODE_ATTRIBUTES {
237 40     40   10859 my ( $class, $code, @attrs ) = @_;
238 40         57 my @unknown;
239              
240             # walk attributes of sub
241 40         77 for my $attr (@attrs) {
242            
243             # parse the attribute into pieces
244 40 50       78 if ( my ( $name, $param ) = _parse_attribute( $attr ) ) {
245            
246             # recognized as 'State' attribute
247 40 100       103 if ( lc $name eq 'state' ) {
248            
249             # split up states
250 15         39 my ( $states, $params ) = _parse_parameters( $param );
251            
252             # die without states
253 15 50       21 croak 'No states detected' unless %{ $states || {} };
  15 50       49  
254            
255             # register states of component
256 15         33 register_states( $class, $code, $states, $params );
257            
258             # finished attribute, next
259 15         55 next;
260             }
261             }
262            
263             # unable to parse or unknown, ignore
264 25         70 push @unknown, $attr;
265             }
266            
267             # return what we haven't processed
268 40         297 return $class->NEXT::MODIFY_CODE_ATTRIBUTES( $code, @unknown );
269             }
270             }
271              
272             =head2 MODIFY_CODE_ATTRIBUTES
273              
274             This is an internal sub that's responsible for building your state-map, as it
275             is called on specification of an attribute. See perldoc's L for
276             more information about this subject. This is an I, do not
277             call directly.
278              
279             =cut
280              
281             my %code_replacement;
282              
283             sub _create_modified_state : RESTRICTED {
284 15         22 my ( $class, $code, $params ) = @_;
285 15         53 my $orig_code = $code;
286 15         18 my $tag;
287              
288             # we have a chained event. next hop gets return values as ARG0
289 15 100       49 if ( my $next = $params->{chained} ) {
    100          
290 5         8 my $last_code = $code;
291             $code = sub {
292 5     5   1778 my @rets = $last_code->( @_ );
293 4         1850 POE::Kernel->yield( $next, @rets );
294 5         21 };
295             }
296             elsif ( my $next = $params->{next} ) {
297 1         1 my $last_code = $code;
298             $code = sub {
299 1     1   1132 $last_code->( @_ );
300 1         836 POE::Kernel->yield( $next, @_[POE::Session::ARG0 .. $#_] );
301 1         3 };
302             }
303            
304             # check for error handling
305 15 100       40 if ( my $err_handler = $params->{error} ) {
306 2         3 my $last_code = $code;
307             $code = sub {
308 2         1008 eval { $last_code->( @_ ) };
  2         7  
309 2 100       643 POE::Kernel->yield( $err_handler, $@ ) if $@;
310             }
311 2         6 }
312              
313             # install new sub if modified
314 15 100       69 if ( refaddr $code ne refaddr $orig_code ) {
315 6         27 $code_replacement{ refaddr $orig_code } = $code;
316             }
317            
318 15         34 return $orig_code;
319 5     5   29 }
  5         9  
  5         28  
320              
321             =head2 _create_modified_state
322              
323             $code = _create_modified_state( \&coderef, \%params );
324              
325             Does the wrapping for the more enhanced attributes. Internal, do not call.
326              
327             =cut
328              
329             sub register_states : RESTRICTED {
330 15         37 my ( $class, $code, $states, $params ) = @_;
331              
332             # have a look at every state
333 15         57 while ( my ( $state, $type ) = each %$states ) {
334              
335             # see if we need to do something on the code
336 15         48 $code = _create_modified_state( $class, $code, $params );
337            
338             # remember
339 15         95 $STATES{ $class }{ $type }{ $state } = $code;
340             }
341            
342 15         23 return;
343 5     5   1755 }
  5         6  
  5         20  
344              
345             =head2 register_states
346              
347             void register_states( $class, $coderef, { state_name => 'type' } );
348              
349             Registers states corresponding to a specific code reference. Accepted state
350             names are C, C and C. Internal, do not call.
351              
352             =cut
353              
354             sub _flatten_inheritance : RESTRICTED {
355 4         52 my ( $class ) = @_;
356              
357             # bad refs
358 5     5   1558 no strict 'refs';
  5         9  
  5         613  
359              
360             # include father class
361 4         10 my ( %classmap, @isa_queue );
362 4         11 $classmap{ $class } = 1;
363              
364             # we start with the specified class and walk the queue
365 4         17 push @isa_queue, @{ $class . '::ISA' };
  4         19  
366 4         24 while ( my $c = shift @isa_queue ) {
367              
368             # only act on unseen classes
369 14 50       34 unless ( exists $classmap{ $c } ) {
370              
371             # remember class and add it's @ISA to the queue
372 14         22 $classmap{ $c } = 1;
373 14         19 push @isa_queue, @{ $c . '::ISA' };
  14         68  
374             }
375             }
376              
377             # tell our caller what we've found
378 4         22 return keys %classmap;
379 5     5   23 }
  5         7  
  5         19  
380              
381             =head2 _flatten_inheritance
382              
383             @classes_in_family = _flatten_inheritance( $rootclass )
384              
385             Returns an array with names of classes used in the specified C<$rootclass>'
386             inheritance tree. This is internal, do not call.
387              
388             =cut
389              
390             sub get_states : RESTRICTED {
391 4         75 my ( $self ) = @_;
392 4         10 my ( %struct, %seen_states );
393 4   33     21 my $own_class = ref( $self ) || $self;
394              
395             # we iterate through our inheritance and collect our states
396 4   33     31 for my $class ( _flatten_inheritance( ref( $self ) || $self ) ) {
397 18 100       52 next unless exists $STATES{ $class };
398              
399             # walk through our types and set up their states
400 6         10 while ( my ( $type, $states ) = each %{ $STATES{ $class } } ) {
  17         69  
401            
402             # get each state name and code reference
403 11 50       15 while ( my ( $state, $code ) = each %{ $states || {} } ) {
  26         174  
404            
405             # we only allow unique states
406 15 50       46 if ( exists $seen_states{ $state } ) {
407 0         0 die "State $state defined twice "
408             . "($seen_states{$state} and $class)\n";
409             }
410 15         32 else { $seen_states{ $state } = $class }
411              
412             # get the name of the sub
413 15 50       51 my $name = _get_symbol_name( $class, $code )
414             or die "No name in symbol table for $state in $class\n";
415              
416             # code might have to be replaced
417 15 100       82 if ( my $newcode = $code_replacement{ refaddr $code } ) {
418 6         28 Sub::Installer::reinstall_sub
419             ( $class => { $name => $newcode } );
420 6         119 $code = $newcode;
421             }
422            
423             # package states just need a name
424 15 100       78 if ( lc $type eq 'package' ) {
    100          
    50          
425            
426             # the session wants an array reference
427 4   100     41 $struct{ $type . '_states' } ||= [ $own_class, {} ];
428            
429             # add new state to package
430 4         19 $struct{ $type . '_states' }[1]{ $state } = $name;
431             }
432            
433             # inline states get the code reference
434             elsif ( lc $type eq 'inline' ) {
435 8         31 $struct{ $type . '_states' }{ $state } = $code;
436             }
437            
438             # object is like package, just with ourself
439             elsif ( lc $type eq 'object' ) {
440            
441             # object states are surrounded by array ref
442 3   50     32 $struct{ $type . '_states' } ||= [ $self, {} ];
443            
444             # object states obviously need an object
445 3 50 33     32 die 'Didn\'t get an object as first argument'
446             unless ref $self and UNIVERSAL::isa( $self, 'UNIVERSAL' );
447            
448             # save states under object
449 3         13 $struct{ $type . '_states' }[1]{ $state } = $name;
450             }
451             }
452             }
453             }
454            
455             # return ready structure
456 4         17 return \%struct;
457 5     5   2991 }
  5         9  
  5         20  
458              
459             =head2 get_states
460              
461             \%struct = $comp->get_states()
462              
463             Returns a structure containing the defined inline, package and object states
464             ready to use for Ls constructor. Internal and restricted, do not
465             call.
466              
467             =cut
468              
469             # we cache the sub names
470             my %symcache;
471              
472             sub _get_symbol_name : RESTRICTED {
473 15         210 my ( $class, $code ) = @_;
474              
475             # we need symrefs, and reset symbol table hash
476 5     5   1450 no strict 'refs';
  5         10  
  5         672  
477 15         18 keys %{ $class . '::' };
  15         39  
478            
479             # return cached version, if existing
480 15 50       56 return $symcache{ refaddr( $code ) }
481             if $symcache{ refaddr( $code ) };
482            
483             # walk symbol table
484 15         18 while ( my ( $name, $smth ) = each %{ $class . '::' } ) {
  259         907  
485            
486             # cache name and return
487 259         1205 return $symcache{ refaddr( $code ) } = $name
488 259 100       266 if refaddr( *{ $smth }{CODE} ) eq refaddr( $code );
489             }
490            
491             # no name was found
492 0         0 return undef;
493 5     5   24 }
  5         8  
  5         20  
494              
495             =head2 _get_symbol_name
496              
497             $subname = _get_symbol_name( $class, $coderef );
498              
499             Searches for a code reference in the symbol table of a class and returns the
500             sub's name if found. Otherwise undef. Do not call.
501              
502             =cut
503              
504             sub _parse_parameters {
505 15     15   22 my ( $string ) = @_;
506 15         18 my %struct;
507            
508             # extract everything that looks like a parameter
509 15         86 while ( $string =~ s/: ([a-z0-9_.;]+?) <(.*?)> //xi ) {
510 23         55 my ( $name, $value ) = ( $1, $2 );
511            
512             # empty ones have to be ignored
513 23 50       44 next unless $name;
514            
515             # states
516 23 100       29 if ( grep { $name eq $_ } qw/ inline package object / ) {
  69 50       143  
  24         48  
517 15         81 $struct{states}{ $value } = $name;
518             }
519            
520             # enhanced
521             elsif ( grep { $name eq $_ } qw/ error chained next / ) {
522 8         33 $struct{params}{ $name } = $value;
523             }
524            
525             # huh?!
526             else {
527 0         0 croak "Unknown parameter: '$name'";
528             }
529             }
530            
531             # there was some part in that attr line we didn't understand
532 15 50       45 if ( $string =~ /\S/ ) {
533            
534             # we're dying anyways, so let's make it pretty
535 0         0 $string =~ s/^\s+//; $string =~ s/\s+$//;
  0         0  
536 0         0 croak "Unable to understand: '$string'";
537             }
538            
539 15         46 return @struct{qw/ states params /};
540             }
541              
542             =head2 _parse_parameters
543              
544             %parameters = _parse_parameters( $parameter_string )
545              
546             This function looks for parameters formed like :name and returns them in
547             ( name => value, .. ) like pairs. Dies on malformed or unknown parameters.
548             Internal method, do not call.
549              
550             =cut
551              
552             =head1 SEE ALSO
553              
554             L, L, L
555              
556             =head1 REQUIRES
557              
558             L, L, L, L, L, L,
559             L
560              
561             =head1 AUTHOR
562              
563             Robert Sedlacek
564              
565             =head1 LICENSE
566              
567             You can copy and/or modify this module under the same terms as perl itself.
568              
569             =cut
570              
571             1;