File Coverage

blib/lib/Fukurama/Class/AttributesHandler.pm
Criterion Covered Total %
statement 153 167 91.6
branch 36 46 78.2
condition 9 12 75.0
subroutine 24 25 96.0
pod 5 5 100.0
total 227 255 89.0


line stmt bran cond sub pod time code
1             package Fukurama::Class::AttributesHandler;
2 5     5   34922 use Fukurama::Class::Version(0.01);
  5         10  
  5         33  
3 5     5   38 use Fukurama::Class::Rigid;
  5         9  
  5         30  
4 5     5   30 use Fukurama::Class::Carp;
  5         17  
  5         40  
5            
6             my $ATT_METHODS;
7             my $EXPORTED;
8             my $LAST_ATTRIBUTE_METHOD;
9             my $SUBS;
10             my $HELPER_METHODS;
11             BEGIN {
12 5     5   15 $ATT_METHODS = {};
13 5         27 $EXPORTED = {};
14 5         11 $SUBS = {};
15 5         9 $LAST_ATTRIBUTE_METHOD = undef;
16 5         443 $HELPER_METHODS = {};
17             }
18            
19             =head1 NAME
20            
21             Fukurama::Class::AttributesHandler - Helper class to provide corrrect handling of attributes
22            
23             =head1 VERSION
24            
25             Version 0.01 (beta)
26            
27             =head1 SYNOPSIS
28            
29             {
30             package MyAttributeHandler;
31             sub MyAttribute {
32             my $class = $_[0];
33             my $method_data = $_[1];
34            
35             warn("Method '$method_data->{'sub_name'}' was resolved at compiletime with data: '$method_data->{'data'}'");
36             # says: Method 'my_own_method' was resolved at compiletime with data: 'foo, bar'
37             }
38             }
39             {
40             package MyClass;
41             use Fukurama::Class::AttributesHandler();
42             Fukurama::Class::AttributesHandler->register_attributes('MyAttributeHandler');
43             Fukurama::Class::AttributesHandler->export('MyClass');
44            
45             sub my_own_method : MyAttribute(foo, bar) {}
46             }
47            
48             =head1 DESCRIPTION
49            
50             This module enables the possibility to define your own subroutine-attributes. This is also done with the CPAN L module
51             but here you get extra information for the subroutine, which use the attribute. E.g. the resolved methodname.
52            
53             This helper class is used from Fukurama::Class::Attribute::OOStandard to enable the OO-method-signatures.
54            
55             =head1 EXPORT
56            
57             =over 4
58            
59             =item MODIFY_CODE_ATTRIBUTES
60            
61             would be decorated if it exist or created if it isn't in the current class.
62            
63             =back
64            
65             =head1 METHODS
66            
67             =over 4
68            
69             =item register_attributes( attribute_handler_class:STRING ) return:BOOLEAN
70            
71             Register a handler class which defines attributes. See L below
72            
73             =item export( export_to_class:STRING ) return:BOOLEAN
74            
75             This will export or decorate the MODIFY_CODE_ATTRIBUTES to the export_to_class class. Be sure that you call this method
76             in a BEGIN block. Perl check them all at compiletime and croak, if some is not defined.
77            
78             =item get_registered_subs( ) return:HASHREF
79            
80             Get the method-definitions from all methods in your code, which use attributes over this attribute handler.
81             This is to check th code structure (or to create some documentation...)
82            
83             =item register_helper_method( methodname:STRING ) return:VOID
84            
85             All registered methodnames would be omitted as attributes, when a attribute-handler-class is parsed. But
86             if they are missed in a attribute-handler-class, the registration would fail.
87            
88             =item run_check( ) return:VOID
89            
90             Resolve all method names, which are unresolved at compiletime, and calls the atribute-definition-methods
91             in the handler-class. This is a helper method for static perl (see Fukurama::Class > BUGS)
92            
93             =back
94            
95             =head1 How to define an attribute-handler-class
96            
97             All methods of an attribute-handler-class have to be attribute-definitions, except these, which are registered via register helper methods.
98             This methods have to start with an uppercase letter (it is a perl specification). They will get a hash reference as single parameter.
99             In this hash you will find information of the method which use your attribute. They are:
100            
101             =over 4
102            
103             =item class:STRING
104            
105             The name of the class, which contain the subroutine which use the attribute (*puh*). Can be empty in some cases. Look at L.
106            
107             =item sub_name:STRING
108            
109             The resolved name of the subroutine, which use the attribute. Perls attributes doesn't resolve the name by itself,
110             so you will normally only get the sub-reference and not the name. It can be empty in some cases. Look at L.
111            
112             =item data:STRING
113            
114             The defined attribute-data. if you say 'sub new : MyAtt(this is a $test)' you will get the string 'this is a $test'.
115            
116             =item sub:CODEREF
117            
118             The code-refrence of the subroutine, which use the attribute.
119            
120             =item resolved:BOOLEAN
121            
122             A flag for the status of method name resolving for this method. In some cases, if you force a call, this flag will
123             be FALSE and the B will be empty.
124            
125             =item attribute:STRING
126            
127             The name of the attribute. This is the same like the name of your attribute-method.
128            
129             =item handler:HASHREF
130            
131             A reference to your attribute class and to the actual attribute method.
132            
133             =item executed: BOOLEAN
134            
135             An internal flag to avoid double callings of your attribute-methods.
136            
137             =back
138            
139             =head1 AUTHOR, BUGS, SUPPORT, ACKNOWLEDGEMENTS, COPYRIGHT & LICENSE
140            
141             see perldoc of L
142            
143             =cut
144            
145             # STATIC boolean
146             sub register_attributes {
147 7     7 1 7491 my $class = $_[0];
148 7         12 my $attribute_class = $_[1];
149            
150 7         16 my @subs = ();
151 7         27 my $check_methods_exist = {};
152             {
153            
154 5     5   27 no strict 'refs';
  5         10  
  5         2835  
  7         11  
155            
156 7         103 my %symbols = %{$attribute_class . '::'};
  7         107  
157 7 50 66 1   124 if(!scalar(%symbols) && !eval("use $attribute_class;return 1;")) {
  1         492  
  0            
  0            
158 1         7 _croak("Failed to load attribute-class '$attribute_class' (maybe this class is empty?): $@\n");
159 0         0 return 0;
160             }
161 6         35 foreach my $name (keys(%symbols)) {
162 55 100       53 next if(!*{$attribute_class . '::' . $name}{'CODE'});
  55         238  
163 14 100       40 if($HELPER_METHODS->{$name}) {
164 5         11 $check_methods_exist->{$name} = 1;
165 5         13 next;
166             }
167 9         30 push(@subs, $name);
168             }
169             }
170            
171 6         16 my @missed_helper_methods = ();
172 6         19 foreach my $name (keys(%$HELPER_METHODS)) {
173 6 100       31 next if($check_methods_exist->{$name});
174 1         3 push(@missed_helper_methods, $name);
175             }
176 6 100       20 if(scalar(@missed_helper_methods)) {
177 1         3 my $msg = join("', '", @missed_helper_methods);
178 1         6 _croak("Needed helper method(s) '$msg' is/are not defined in attribute-class '$attribute_class'. (Maybe class is not compiled yet?)");
179             }
180            
181 5         12 foreach my $name (@subs) {
182 8         71 $class->_register_attribute($attribute_class, $name, 0);
183             }
184 4         10 foreach my $name (@subs) {
185 7         22 $class->_register_attribute($attribute_class, $name, 1);
186             }
187 4         31 return 1;
188             }
189             # STATIC hashref
190             sub get_registered_subs {
191 0     0 1 0 my $class = $_[0];
192            
193 0         0 return $SUBS;
194             }
195             # STATIC void
196             sub _register_attribute {
197 15     15   24 my $class = $_[0];
198 15         17 my $attribute_class = $_[1];
199 15         61 my $name = $_[2];
200 15         18 my $execute_register = $_[3];
201            
202 15 50       38 if($ATT_METHODS->{$name}) {
203 0         0 _croak("Attribute '$name' from attribute-class '$attribute_class' always registered for '$ATT_METHODS->{$name}->{'class'}'", 1);
204 0         0 return;
205             }
206 15 100       59 if($name !~ m/^[A-Z]/) {
207 1         4 my $helper_msg = "'" . join("', '", keys(%$HELPER_METHODS)) . "'";
208 1         8 _croak("Every attribute must start with an uppercase letter (except the helper-method(s) $helper_msg " .
209             "which is/are not an attribute).\n" .
210             "Attribute '$name' from attribute-class '$attribute_class' is not allowed.", 1);
211 0         0 return;
212             }
213            
214 14 100       51 return if(!$execute_register);
215             {
216            
217 5     5   30 no strict 'refs';
  5         10  
  5         1134  
  7         12  
218            
219 7         51 $ATT_METHODS->{$name} = {
220             class => $attribute_class,
221 7         11 coderef => *{$attribute_class . '::' . $name}{'CODE'},
222             };
223             }
224 7         24 return;
225             }
226             # STATIC boolean
227             sub export {
228 32     32 1 3905 my $class = $_[0];
229 32         50 my $export_to_class = $_[1];
230            
231 32 100       116 return 0 if($EXPORTED->{$export_to_class});
232 20         48 $EXPORTED->{$export_to_class} = 1;
233 20         276 $class->_decorate_attribute_handler($export_to_class, "$export_to_class\::MODIFY_CODE_ATTRIBUTES");
234 20         62 return 1;
235             }
236             # STATIC void
237             sub register_helper_method {
238 4     4 1 45 my $class = $_[0];
239 4         8 my $methodname = $_[1];
240            
241 4         15 $HELPER_METHODS->{$methodname} = 1;
242 4         513 return;
243             }
244             # STATIC void
245             sub _decorate_attribute_handler {
246 20     20   37 my $class = $_[0];
247 20         44 my $caller_class = $_[1];
248 20         31 my $identifier = $_[2];
249            
250 5     5   31 no warnings 'redefine';
  5         12  
  5         250  
251 5     5   33 no strict 'refs';
  5         13  
  5         2180  
252            
253 20         36 my $old = *{$identifier}{CODE};
  20         115  
254 20         63 *{$identifier} = sub {
255 34     34   26307 my @unknown_attributes = &_attribute_handler(@_);
256 34 100 100     119 if($old && @unknown_attributes) {
257 4         13 my $caller_class = $_[0];
258 4         10 my $sub_ref = $_[1];
259            
260 4         17 @_ = ($caller_class, $sub_ref, @unknown_attributes);
261 4         28 goto &$old;
262             }
263 30         80 return @unknown_attributes;
264 20         108 };
265 20         47 return;
266             }
267             # AUTOMAGIC string()
268             sub _attribute_handler {
269 34     34   55 my $caller_class = shift(@_);
270 34         54 my $sub_ref = shift(@_);
271 34         75 my @attributes = @_;
272            
273 34 100 100     189 if($LAST_ATTRIBUTE_METHOD && !$LAST_ATTRIBUTE_METHOD->{'resolved'}) {
274 29         93 my $succes = __PACKAGE__->_resolve_sub($LAST_ATTRIBUTE_METHOD);
275 29 50       117 if(!$LAST_ATTRIBUTE_METHOD->{'executed'}) {
276 29         126 my $success = __PACKAGE__->_exec_attribute($LAST_ATTRIBUTE_METHOD);
277             }
278             }
279 34         66 my @unknown_attributes = ();
280 34         60 foreach my $attribute_string (@attributes) {
281 38         137 my ($name, $data) = __PACKAGE__->_split_attribute($attribute_string);
282 38         82 my $handler = $ATT_METHODS->{$name};
283 38 100       91 if(!$handler) {
284 6         12 push(@unknown_attributes, $attribute_string);
285 6         18 next;
286             }
287 32 50       111 if($SUBS->{int($sub_ref)}) {
288 0         0 _croak("Internal failure: subroutine '$sub_ref' allways registered");
289             }
290             $LAST_ATTRIBUTE_METHOD = {
291 32         5473 'attribute' => $name,
292             'handler' => $handler,
293             'sub' => $sub_ref,
294             'class' => $caller_class,
295             'data' => $data,
296             'resolved' => 0,
297             'executed' => 0,
298             };
299 32         156 $SUBS->{int($sub_ref)} = $LAST_ATTRIBUTE_METHOD;
300             }
301 34         112 return @unknown_attributes;
302             }
303             # STATIC boolean
304             sub _resolve_sub {
305 32     32   55 my $class = $_[0];
306 32         55 my $sub_data = $_[1];
307            
308 32 50       84 return 1 if($sub_data->{'resolved'});
309            
310 5     5   32 no strict 'refs';
  5         14  
  5         2933  
311            
312 32         49 my $symbols = \%{$sub_data->{'class'} . '::'};
  32         95  
313 32         110 foreach my $key (keys(%$symbols)) {
314 109 100 33     279 next if(!$symbols->{$key} || !*{$symbols->{$key}}{CODE});
  109         465  
315 59 100       121 if(*{$symbols->{$key}}{CODE} == $sub_data->{'sub'}) {
  59         295  
316 32         98 $sub_data->{'sub_name'} = $key;
317 32         58 $sub_data->{'resolved'} = 1;
318 32         109 return 1;
319             }
320             }
321 0         0 return 0;
322             }
323             # STATIC string()
324             sub _split_attribute {
325 38     38   53 my $class = $_[0];
326 38         166 my $string = $_[1];
327            
328 38         230 my ($name, $data) = $string =~ m/^([^\(]*)(?:\((.*)\)|)$/i;
329 38 50       98 if(!$name) {
330 0         0 _croak("Attribute '$string' is malformed", 1);
331             }
332 38         104 return ($name, $data);
333             }
334             # STATIC sub
335             sub _exec_attribute {
336 32     32   45 my $class = $_[0];
337 32         47 my $sub_data = $_[1];
338            
339 32 50       77 return 1 if($sub_data->{'executed'});
340 32         57 my $att_class = $sub_data->{'handler'}->{'class'};
341 32         56 my $att_method = $sub_data->{'handler'}->{'coderef'};
342            
343 32         54 local $Carp::CarpLevel = $Carp::CarpLevel + 2;
344 32 50       110 if($att_class->$att_method($sub_data)) {
345 32         21535 $sub_data->{'executed'} = 1;
346 32         118 return 1;
347             }
348 0         0 return 0;
349             }
350             # STATIC void
351             sub run_check {
352 19     19 1 3461 my $class = $_[0];
353            
354 19         114 foreach my $ref_no (keys %$SUBS) {
355 123         361 my $entry = $SUBS->{$ref_no};
356 123 100       331 if(!$entry->{'executed'}) {
357 3 50       16 if(!__PACKAGE__->_resolve_sub($entry)) {
358 0         0 _croak("Internal error: can't resolve sub '$entry->{'sub'}'");
359             }
360 3 50       19 if(!__PACKAGE__->_exec_attribute($entry)) {
361 0         0 _croak("Internal error: can't execute attribute '$entry->{'attribute'}' for sub '$entry->{'class'}->$entry->{'sub_name'}'");
362             }
363             }
364             }
365 19         1288 return;
366             }
367            
368 5     5   31 no warnings 'void'; # avoid 'Too late to run CHECK/INIT block'
  5         9  
  5         446  
369            
370             # AUTOMAGIC
371             CHECK {
372 5     5   961 __PACKAGE__->run_check();
373             }
374             # AUTOMAGIC
375             END {
376 5     5   2698 __PACKAGE__->run_check();
377             }
378             1;