File Coverage

blib/lib/Fukurama/Class/Implements.pm
Criterion Covered Total %
statement 131 133 98.5
branch 22 28 78.5
condition 9 12 75.0
subroutine 22 22 100.0
pod 3 3 100.0
total 187 198 94.4


line stmt bran cond sub pod time code
1             package Fukurama::Class::Implements;
2 5     5   29653 use Fukurama::Class::Version(0.02);
  5         10  
  5         41  
3 5     5   31 use Fukurama::Class::Rigid;
  5         556  
  5         38  
4 5     5   31 use Fukurama::Class::Carp;
  5         9  
  5         42  
5 5     5   654 use Fukurama::Class::Tree();
  5         10  
  5         2523  
6              
7             our $LEVEL_DISABLE = 0;
8             our $LEVEL_CHECK_NONE = 1;
9             our $LEVEL_CHECK_ALL = 2;
10              
11             our $CHECK_LEVEL = $LEVEL_CHECK_ALL;
12              
13             my $ERRORS = {};
14             my $ISA_ALREADY_DECORATED;
15             my $REGISTER = {};
16              
17             =head1 NAME
18              
19             Fukurama::Class::Implements - Pragma to provide interfaces
20              
21             =head1 VERSION
22              
23             Version 0.02 (beta)
24              
25             =head1 SYNOPSIS
26              
27             package MyClass;
28             use Fukurama::Class::Implements('MyParent');
29              
30             =head1 DESCRIPTION
31              
32             This pragma-like module enables te possibility to use interfaces (like in java). The implementation
33             of all subroutines (except perls speacials) will be checked at compiletime. Your package won't inherit
34             from this interface but every isa() will say that it is. Use Fukurama::Class instead, to get all the
35             features for OO.
36              
37             =head1 CONFIG
38              
39             You can define the check-level which describes how the module will check implementations.
40             The following levels are allowed:
41              
42             =over 4
43              
44             =item DISABLE (0)
45              
46             There is no check and no change in UNIVERSAL. If you use this level, it's like you remove this module.
47             There are no side effects. You should only use this, if you never use the isa() method to check for interfaces.
48              
49             =item CHECK_NONE (1)
50              
51             All Registration-Processes are executed and UNIVERSAL::isa would be decorated, but there would be no check.
52             This level is recommended for production.
53              
54             =item CHECK_ALL (2)
55              
56             All Classes would checked for implementation. This is the default behavior when you does'n change the
57             check-level.
58              
59             =back
60              
61             =head1 EXPORT
62              
63             =over 4
64              
65             =item UNIVERSAL::isa
66              
67             would be decorated
68              
69             =back
70              
71             =head1 METHODS
72              
73             =over 4
74              
75             =item implements( child_class:STRING, interface_class:STRING ) return:VOID
76              
77             Helper-method, which would executed by every pragma usage.
78              
79             =item run_check() return:VOID
80              
81             Helper method for static perl (see Fukurama::Class > BUGS)
82              
83             =item register_class_tree() return:VOID
84              
85             Helper method to register needed handler in Fukurama::Class::Tree
86              
87             =back
88              
89             =head1 AUTHOR, BUGS, SUPPORT, ACKNOWLEDGEMENTS, COPYRIGHT & LICENSE
90              
91             see perldoc of L
92              
93             =cut
94              
95             # void
96             my $BUILD_HANDLER = sub {
97             my $classname = $_[0];
98             my $classdef = $_[1];
99            
100             my $interface_def = $REGISTER->{$classname};
101             return if(!$interface_def);
102            
103             my $inheritation_paths = [];
104             foreach my $interface (keys(%$interface_def)) {
105             my $interface_inheritation_paths = Fukurama::Class::Tree->get_inheritation_path($interface);
106             foreach my $path (@$interface_inheritation_paths) {
107             unshift(@$path, $interface);
108             push(@$inheritation_paths, $path);
109             }
110             push(@$inheritation_paths, [$interface]) if(!scalar(@$interface_inheritation_paths));
111             }
112             $classdef->{'implements'} = $inheritation_paths if(scalar(@$inheritation_paths));
113             return;
114             };
115             # void
116             my $CHECK_HANDLER = sub {
117             my $classname = $_[0];
118             my $classdef = $_[1];
119            
120             return if($CHECK_LEVEL <= $LEVEL_CHECK_NONE);
121             my $paths = $classdef->{'implements'};
122             return if(ref($paths) ne 'ARRAY');
123            
124             my $interface_list = {};
125             foreach my $path (@$paths) {
126             my $level = 0;
127             foreach my $class (@$path) {
128             ++$level;
129             $interface_list->{$class} ||= ($level == 1 ? 1 : 0);
130             }
131             }
132             __PACKAGE__->_check_implementations($classname, $interface_list);
133             return;
134             };
135             # AUTOMAGIC void
136             sub import {
137 9     9   2181 my $class = $_[0];
138 9         18 my $interface = $_[1];
139 9         15 my $import_depth = $_[2];
140            
141 9   50     45 $import_depth ||= 0;
142 9         68 my $child = [caller($import_depth)]->[0];
143 9         44 $class->implements($child, $interface);
144 8         496 return undef;
145             }
146             # void
147             sub implements {
148 13     13 1 21 my $class = $_[0];
149 13         21 my $child = $_[1];
150 13         20 my $interface = $_[2];
151            
152 13 50       39 return if($CHECK_LEVEL == $LEVEL_DISABLE);
153              
154 5     5   34 no strict 'refs';
  5         7  
  5         4844  
155              
156 13         43 $class->_decorate_isa();
157 13 50 66 1   16 if(!%{"$interface\::"} && !eval("use $interface();return 1")) {
  13         151  
  1         440  
  0            
  0            
158 1         7 _croak($@);
159             }
160 12   100     60 $REGISTER->{$child} ||= {};
161 12         39 $REGISTER->{$child}->{$interface} = undef;
162 12         43 $class->register_class_tree();
163 12         29 return;
164             }
165             # void
166             sub register_class_tree {
167 15     15 1 27 my $class = $_[0];
168            
169 15         78 Fukurama::Class::Tree->register_build_handler($BUILD_HANDLER);
170 15         55 Fukurama::Class::Tree->register_check_handler($CHECK_HANDLER);
171 15         26 return;
172             }
173             # void
174             sub run_check {
175 15     15 1 7162 my $class = $_[0];
176 15         30 my $type = $_[1];
177            
178 15 50       64 return if($CHECK_LEVEL <= $LEVEL_CHECK_NONE);
179 15 100       51 $type = 'MANUAL' if(!defined($type));
180            
181 15 50       59 if($CHECK_LEVEL == $LEVEL_CHECK_ALL) {
182 15         89 Fukurama::Class::Tree->run_check($type);
183             }
184 11         127 return;
185             }
186             # void
187             sub _check_implementations {
188 21     21   37 my $class = $_[0];
189 21         36 my $checked_class = $_[1];
190 21         35 my $checked_class_interfaces = $_[2];
191            
192 21         37 my $error_list = [];
193 21         40 my $interface_defs = [];
194 21         361 my @interfaces = keys(%$checked_class_interfaces);
195 21         46 foreach my $interface (@interfaces) {
196 47         250 push(@$interface_defs, {
197             class => $interface,
198             subs => [Fukurama::Class::Tree->get_class_subs($interface)],
199             });
200             }
201 21         47 my $class_def = {};
202 21         83 foreach my $sub (Fukurama::Class::Tree->get_class_subs($checked_class)) {
203 32         89 $class_def->{$sub} = undef;
204             }
205 21         93 $class->_check_class_def($checked_class, $class_def, $interface_defs, $error_list);
206            
207 21 100       54 if(@$error_list) {
208 5         10 my $errors = '';
209 5         12 foreach my $e (@$error_list) {
210 11         29 my $key = $e->{'class'} . '-' . $e->{'method'};
211 11 100       33 next if($ERRORS->{$key});
212 4         17 $errors .= "\n > You doesn't implement method '$e->{method}' in class '$e->{class}' which is defined in interface(es): " .
213 4         15 join(', ', @{$e->{interfaces}});
214 4         17 $ERRORS->{$key} = 1;
215             }
216 5 100       34 _croak(scalar(@$error_list) . " Interface-Error(s):$errors\n", 1) if($errors);
217             }
218 19         93 return;
219             }
220             # void
221             sub _check_class_def {
222 21     21   40 my $class = $_[0];
223 21         41 my $obj_class = $_[1];
224 21         37 my $class_def = $_[2];
225 21         30 my $interface_defs = $_[3];
226 21         32 my $errorlist = $_[4];
227            
228 21         69 my $interface_methods = $class->_merge_interface_methods($interface_defs);
229 21         97 foreach my $method (keys %$interface_methods) {
230 36         126 $class->_check_method_implementation($obj_class, $method, exists($class_def->{$method}), $interface_methods->{$method}, $errorlist);
231             }
232 21         71 return;
233             }
234             # void
235             sub _check_method_implementation {
236 36     36   69 my $class = $_[0];
237 36         49 my $obj_class = $_[1];
238 36         42 my $method = $_[2];
239 36         56 my $class_method_exist = $_[3];
240 36         48 my $interface_method_list = $_[4];
241 36         46 my $error_list = $_[5];
242            
243 36 100       83 if(!$class_method_exist) {
244 11         20 my $definitions = [];
245 11         20 foreach my $interface (@$interface_method_list) {
246 17         42 push(@$definitions, $interface);
247             }
248 11         140 push(@$error_list, {
249             class => $obj_class,
250             method => $method,
251             interfaces => $definitions,
252             });
253             }
254 36         101 return;
255             }
256             # hash[]
257             sub _merge_interface_methods {
258 21     21   33 my $class = $_[0];
259 21         30 my $interface_defs = $_[1];
260            
261 21         37 my $methodnames = {};
262 21         151 foreach my $def (@$interface_defs) {
263 47         68 foreach my $method (@{$def->{'subs'}}) {
  47         104  
264 59   100     314 $methodnames->{$method} ||= [];
265 59         68 push(@{$methodnames->{$method}}, $def->{'class'});
  59         203  
266             }
267             }
268 21         55 return $methodnames;
269             }
270             # string{}
271             sub _has_interface {
272 41539     41539   49125 my $class = $_[0];
273 41539         50002 my $obj_class = $_[1];
274 41539         44859 my $interface_class = $_[2];
275            
276 41539 50       93709 return 0 if(!defined($obj_class));
277 41539         58079 my $interfaces = $REGISTER->{$obj_class};
278 41539 100 66     156659 return 0 if(!$interfaces || !exists($interfaces->{$interface_class}));
279 9         59 return 1;
280             }
281             # void
282             sub _decorate_isa {
283 13     13   21 my $class = $_[0];
284            
285 5     5   37 no strict 'refs';
  5         9  
  5         163  
286 5     5   27 no warnings 'redefine';
  5         14  
  5         847  
287            
288 13 100       52 return if($ISA_ALREADY_DECORATED);
289            
290 3         7 my $identifier = 'UNIVERSAL::isa';
291 3         6 my $old = *{$identifier}{'CODE'};
  3         11  
292 3 50       17 die("Unable to decorate non existing sub $identifier") if(!$old);
293            
294 3         12 *{$identifier} = sub {
295 41539     41539   374255 my $obj_class = $_[0];
296 41539         47100 my $type = $_[1];
297            
298 41539 100       91190 return 1 if($class->_has_interface($obj_class, $type));
299            
300 41530         333036 goto &$old;
301 3         17 };
302 3         8 $ISA_ALREADY_DECORATED = 1;
303 3         7 return;
304             }
305              
306 5     5   28 no warnings 'void'; # avoid 'Too late to run CHECK/INIT block'
  5         9  
  5         471  
307              
308             # AUTOMAGIC void
309             sub CHECK {
310 5     5   29 __PACKAGE__->run_check('CHECK');
311             }
312             # AUTOMAGIC void
313             sub END {
314 5     5   2121 __PACKAGE__->run_check('END');
315             }
316             1;