File Coverage

blib/lib/POE/Declare.pm
Criterion Covered Total %
statement 47 47 100.0
branch n/a
condition n/a
subroutine 15 15 100.0
pod n/a
total 62 62 100.0


line stmt bran cond sub pod time code
1             package POE::Declare;
2              
3             =pod
4              
5             =head1 NAME
6              
7             POE::Declare - A POE abstraction layer for conciseness and simplicity
8              
9             =head1 SYNOPSIS
10              
11             package MyComponent;
12            
13             use strict;
14             use POE::Declare;
15            
16             declare foo => 'Attribute';
17             declare bar => 'Internal';
18             declare TimeoutError => 'Message';
19            
20             sub hello : Event {
21             print "Hello World!\n";
22             }
23            
24             sub hello_timeout : Timeout(30) {
25             print "Alas, I die!\n";
26            
27             # Tell our parent as well
28             $_[SELF]->TimeoutError;
29             }
30            
31             1;
32              
33             =head1 DESCRIPTION
34              
35             B
36             WITHOUT NOTICE>
37              
38             L is a very powerful and flexible system for doing asynchronous
39             programming.
40              
41             But personally, I find it confusing and tricky to use at times.
42              
43             In particular, I have found it hard to resolve L's way of
44             programming with the highly abstracted OO that I am used to,
45             with layer stacked upon layer ad-infinitum to create powerful
46             and complex systems that are still easy to maintain.
47              
48             I have found this particularly noticable as the scale of a
49             codebase gets later. At three levels of abstraction the layering
50             become quite difficult, and beyond this it became worse and worse.
51              
52             B is my attempt to resolve this problem by locking
53             down some of the traditional flexibility of POE, and by (hopefully)
54             makeing it easier to split the implementation of each object between
55             an object-oriented half and a POE half.
56              
57             This will hopefully allow me to utilise POE's asynchronous nature,
58             while retaining the traditional codebase scaling capability
59             provided by normal OO.
60              
61             Of course, this entire exercise is something of a grand experiment
62             and it may well turn out that I am wrong. But I think I'm heading
63             in the right general direction (I just don't know if I'm taking
64             quite the right path).
65              
66             =head1 ARCHITECTURE
67              
68             B is composed of three main modules, and a tree of
69             slot/attribute classes.
70              
71             =head2 POE::Declare
72              
73             =head2 POE::Declare::Object
74              
75             L is the abstract base class for all classes created
76             by B.
77              
78             =head2 POE::Declare::Meta
79              
80             L implements the metadata objects that describe each of
81             the B classes.
82              
83             =head2 POE::Declare::Slot
84              
85             POE::Declare::Meta::Slot
86             POE::Declare::Meta::Internal
87             POE::Declare::Meta::Attribute
88             POE::Declare::Meta::Param
89             POE::Declare::Meta::Message
90             POE::Declare::Meta::Event
91             POE::Declare::Meta::Timeout
92              
93             =head2 POE::Declare::Meta::Internal
94              
95             L is a slot class that won't generate any
96             functionality, but allows you to reserve an attribute for internal use
97             so that they won't be used by any sub-classes.
98              
99             =head2 POE::Declare::Meta::Attribute
100              
101             L is a slot class used for readable
102             attributes.
103              
104             =head2 POE::Declare::Meta::Param
105              
106             L is a slot class for attributes that
107             are provided to the constructor as a parameter.
108              
109             =head2 POE::Declare::Meta::Message
110              
111             TO BE COMPLETED
112              
113             =head2 POE::Declare::Meta::Event
114              
115             L is a class for named POE events that can be
116             called or yielded to by other POE messages/events.
117              
118             =head2 POE::Declare::Meta::Timeout
119              
120             L is a L sub-class
121             that is designed to trigger from an alarm and generates additional methods
122             to manage the alarms.
123              
124             =head1 FUNCTIONS
125              
126             For the first few releases, I plan to leave this module undocumented.
127              
128             That I am releasing this distribution at all is more of a way to
129             mark my progress, and to allow other POE/OO people to look at the
130             implementation and comment.
131              
132             =cut
133              
134 7     7   415935 use 5.008007;
  7         19  
135 7     7   25 use strict;
  7         9  
  7         115  
136 7     7   20 use warnings;
  7         8  
  7         145  
137 7     7   23 use Carp ();
  7         7  
  7         66  
138 7     7   18 use Exporter ();
  7         5  
  7         68  
139 7     7   22 use List::Util ();
  7         7  
  7         74  
140 7     7   2924 use Params::Util ();
  7         21596  
  7         122  
141 7     7   3014 use Class::Inspector ();
  7         17734  
  7         127  
142 7     7   1320 use POE;
  7         88872  
  7         44  
143 7     7   129398 use POE::Session ();
  7         10  
  7         91  
144 7     7   2369 use POE::Declare::Meta ();
  7         109  
  7         221  
145              
146             # The base class requires POE::Declare to be fully compiled,
147             # so load it in post-BEGIN with a require rather than at
148             # BEGIN-time with a use.
149             require POE::Declare::Object;
150              
151             # Provide the SELF constant
152 7     7   34 use constant SELF => HEAP;
  7         6  
  7         394  
153              
154 7     7   25 use vars qw{$VERSION @ISA @EXPORT %ATTR %EVENT %META};
  7         8  
  7         537  
155             BEGIN {
156 7     7   9 $VERSION = '0.23_01';
157 7         62 @ISA = qw{ Exporter };
158 7         13 @EXPORT = qw{ SELF declare compile };
159              
160             # Metadata Storage
161 7         7 %ATTR = ();
162 7         8 %EVENT = ();
163 7         240 %META = ();
164             }
165              
166              
167              
168              
169              
170             #####################################################################
171             # Declaration Functions
172              
173             sub import {
174             my $pkg = shift;
175             my $callpkg = caller($Exporter::ExportLevel);
176              
177             # POE::Declare should only be loaded on empty classes.
178             # We only use the simple case here of checking for $VERSION or @ISA
179 7     7   24 no strict 'refs';
  7         7  
  7         4619  
180             if ( defined ${"$callpkg\::VERSION"} ) {
181             Carp::croak("$callpkg already exists, cannot use POE::Declare");
182             }
183             if ( defined @{"$callpkg\::ISA"} ) {
184             # Are we a subclass of an existing POE::Declare class
185             if ( $callpkg->isa('POE::Declare::Object') ) {
186             # Yes, don't set up anything, just do the exports
187             local $Exporter::ExportLevel += 1;
188             return $pkg->SUPER::import(@_);
189             }
190              
191             # This isn't a POE::Declare class
192             Carp::croak("$callpkg already exists, cannot use POE::Declare");
193             }
194              
195             # Set @ISA for the package, which does most of the work
196             @{"$callpkg\::ISA"} = qw{ POE::Declare::Object };
197              
198             # Export the symbols
199             local $Exporter::ExportLevel += 1;
200             $pkg->SUPER::import(@_);
201              
202             # Make "use POE::Declare;" an implicit "use POE;" as well
203             eval "package $callpkg; use POE;";
204             die $@ if $@;
205              
206             return 1;
207             }
208              
209             =pod
210              
211             =head2 declare
212              
213             declare one => 'Internal';
214             declare two => 'Attribute';
215             declare three => 'Param';
216             declare four => 'Message';
217              
218             The C function is exported by default. It takes two parameters,
219             a slot name and a slot type.
220              
221             The slot name can be any legal Perl identifier.
222              
223             The slot type should be one of C, C, C or
224             C.
225              
226             Creates the new slot, throws an exception on error.
227              
228             =cut
229              
230             sub declare (@) {
231             my $pkg = caller();
232             local $Carp::CarpLevel += 1;
233             _declare( $pkg, @_ );
234             }
235              
236             sub _declare {
237             my $pkg = shift;
238             if ( $META{$pkg} ) {
239             Carp::croak("Too late to declare additions to $pkg");
240             }
241              
242             # What is the name of the attribute
243             my $name = shift;
244             unless ( Params::Util::_IDENTIFIER($name) ) {
245             Carp::croak("Did not provide a valid attribute name");
246             }
247              
248             # Has the attribute already been defined
249             if ( $ATTR{$pkg}->{$name} ) {
250             Carp::croak("Attribute $name already defined in class $pkg");
251             }
252              
253             # Resolve the attribute class
254             my $type = do {
255             local $Carp::CarpLevel += 1;
256             _attribute_class(shift);
257             };
258              
259             # Is the class an attribute class?
260             unless ( $type->isa('POE::Declare::Meta::Slot') ) {
261             Carp::croak("The class $type is not a POE::Declare::Slot");
262             }
263              
264             # Create and save the attribute
265             $ATTR{$pkg}->{$name} = $type->new(
266             name => $name,
267             @_,
268             );
269              
270             return 1;
271             }
272              
273             # Resolve an attribute type
274             sub _attribute_class {
275             my $type = shift;
276             if ( Params::Util::_IDENTIFIER($type) ) {
277             $type = "POE::Declare::Meta::$type";
278             } elsif ( Params::Util::_CLASS($type) ) {
279             $type = $type;
280             } else {
281             Carp::croak("Invalid attribute type");
282             }
283              
284             # Try to load the attribute class
285             my $file = $type . '.pm';
286             $file =~ s{::}{/}g;
287             eval { require $file };
288             if ( $@ ) {
289             local $Carp::CarpLevel += 1;
290             my $quotefile = quotemeta $file;
291             if ( $@ =~ /^Can\'t locate $quotefile/ ) {
292             Carp::croak("The attribute class $type does not exist");
293             } else {
294             Carp::croak($@);
295             }
296             }
297              
298             return $type;
299             }
300              
301             =pod
302              
303             =head2 compile
304              
305             The C function indicates that all attributes and events have
306             been defined and the structure should be finalised and compiled.
307              
308             Returns true or throws an exception.
309              
310             =cut
311              
312             sub compile () {
313             my $pkg = caller();
314              
315             # Shortcut if already compiled
316             return 1 if $META{$pkg};
317              
318             # Create the meta object
319             my $meta = $META{$pkg} = POE::Declare::Meta->new($pkg);
320             my @super = reverse $meta->super_path;
321              
322             # Make sure any parent POE::Declare classes are compiled
323             foreach my $parent ( @super ) {
324             next if $META{$parent};
325             Carp::croak("Cannot compile $pkg, parent class $parent not compiled");
326             }
327              
328             # Are any attributes already defined in our parents?
329             foreach my $name ( sort keys %{$ATTR{$pkg}} ) {
330             my $found = List::Util::first {
331             $ATTR{$_}->{attr}->{$name}
332             } @super;
333             Carp::croak(
334             "Duplicate attribute '$name' already defined in "
335             . $found->name
336             ) if $found;
337             $meta->{attr}->{$name} = $ATTR{$pkg}->{$name};
338             }
339              
340             # Compile the individual parts
341             $meta->_compile;
342             }
343              
344             # Get the meta-object for a class.
345             # Primarily used for testing purposes.
346             sub meta {
347             $META{$_[0]};
348             }
349              
350             sub next_alias {
351             my $meta = $META{$_[0]};
352             unless ( $meta ) {
353             Carp::croak("Cannot instantiate $_[0], class not defined");
354             }
355             $meta->next_alias;
356             }
357              
358             1;
359              
360             =pod
361              
362             =head1 SUPPORT
363              
364             Bugs should be always be reported via the CPAN bug tracker at
365              
366             L
367              
368             For other issues, or commercial enhancement or support, contact the author.
369              
370             =head1 AUTHORS
371              
372             Adam Kennedy Eadamk@cpan.orgE
373              
374             =head1 SEE ALSO
375              
376             L, L
377              
378             =head1 COPYRIGHT
379              
380             Copyright 2006 - 2009 Adam Kennedy.
381              
382             This program is free software; you can redistribute
383             it and/or modify it under the same terms as Perl itself.
384              
385             The full text of the license can be found in the
386             LICENSE file included with this module.
387              
388             =cut