File Coverage

blib/lib/Fukurama/Class/Extends.pm
Criterion Covered Total %
statement 110 111 99.1
branch 17 26 65.3
condition 6 11 54.5
subroutine 26 26 100.0
pod 3 3 100.0
total 162 177 91.5


line stmt bran cond sub pod time code
1             package Fukurama::Class::Extends;
2 5     5   25243 use Fukurama::Class::Version(0.01);
  5         11  
  5         35  
3 5     5   30 use Fukurama::Class::Rigid;
  5         9  
  5         41  
4 5     5   30 use Fukurama::Class::Carp;
  5         18  
  5         77  
5 5     5   2385 use Fukurama::Class::Tree();
  5         14  
  5         3513  
6              
7             our $LEVEL_DISABLE = 0;
8             our $LEVEL_CHECK_NONE = 1;
9             our $LEVEL_CHECK_REGISTERED = 2;
10             our $LEVEL_CHECK_CHILDS = 3;
11             our $LEVEL_CHECK_ALL = 4;
12              
13             our $CHECK_LEVEL = $LEVEL_CHECK_CHILDS;
14              
15             my $REGISTER = {};
16             my $ERRORS = {};
17              
18             =head1 NAME
19              
20             Fukurama::Class::Extends - Pragma to extend class inheritation
21              
22             =head1 VERSION
23              
24             Version 0.01 (beta)
25              
26             =head1 SYNOPSIS
27              
28             package MyClass;
29             use Fukurama::Class::Extends('MyParent');
30              
31             =head1 DESCRIPTION
32              
33             This pragma-like module provides some extra check features for inheritation at compiletime.
34             It would check that your parent Module is loaded and that in multi-inheritation there is no
35             subroutine-conflict. Use Fukurama::Class instead, to get all the features for OO.
36              
37             =head1 CONFIG
38              
39             You can define the check-level which describes how the module will check inheritations.
40             The following levels are allowed:
41              
42             =over 4
43              
44             =item $Fukurama::Class::Extends::CHECK_LEVEL = $Fukurama::Class::Extends::LEVEL_DISABLE
45              
46             There is no check. If you use this level, it's like you use B. There are no side effects.
47             This level is recommended for production.
48              
49             =item $Fukurama::Class::Extends::CHECK_LEVEL = $Fukurama::Class::Extends::LEVEL_CHECK_NONE
50              
51             All registration processes are executed, but there would be no check.
52              
53             =item $Fukurama::Class::Extends::CHECK_LEVEL = $Fukurama::Class::Extends::LEVEL_CHECK_REGISTERED
54              
55             All classes, which use this module would checked for Multi-inheritation-conflicts.
56              
57             =item $Fukurama::Class::Extends::CHECK_LEVEL = $Fukurama::Class::Extends::LEVEL_CHECK_CHILDS
58              
59             All classes, which use this module AND all childs of these classes would checked for Multi-inheritation-conflicts.
60             This is the default behavior when you does'n change the check-level.
61              
62             =item $Fukurama::Class::Extends::CHECK_LEVEL = $Fukurama::Class::Extends::LEVEL_CHECK_ALL
63              
64             All classes would checked for Multi-inheritation-conflicts. This means really ALL classes. Even all perl-internals.
65             This level is only for the sake of completeness.
66              
67             =back
68              
69             =head1 EXPORT
70              
71             -
72              
73             =head1 METHODS
74              
75             =over 4
76              
77             =item extends( child_class:STRING, childs_parent_class:STRING ) return:VOID
78              
79             Helper-method, which would executed by every pragma usage.
80              
81             =item run_check() return:VOID
82              
83             Helper method for static perl (see Fukurama::Class > BUGS)
84              
85             =item register_class_tree() return:VOID
86              
87             Helper method to register needed handler in Fukurama::Class::Tree
88              
89             =back
90              
91             =head1 AUTHOR, BUGS, SUPPORT, ACKNOWLEDGEMENTS, COPYRIGHT & LICENSE
92              
93             see perldoc of L
94              
95             =cut
96              
97             # void
98             my $BUILD_HANDLER = sub {
99             my $classname = $_[0];
100             my $classdef = $_[1];
101            
102             my $inheritation_paths = Fukurama::Class::Tree->get_inheritation_path($classname);
103             $classdef->{'extends'} = $inheritation_paths if(scalar(@$inheritation_paths));
104             return;
105             };
106             # void
107             my $CHECK_HANDLER = sub {
108             my $classname = $_[0];
109             my $classdef = $_[1];
110            
111             my $paths = $classdef->{'extends'};
112             return if(ref($paths) ne 'ARRAY' || !__PACKAGE__->_check_this_class($classname, $paths));
113            
114             my $parent_path_subs = [];
115             foreach my $path (@$paths) {
116             my $path_subs = __PACKAGE__->_get_all_subs_for_classpath($path);
117             push(@$parent_path_subs, {
118             subs => $path_subs,
119             path => $path,
120             });
121             }
122             my $all_subs = {};
123             foreach my $entry (@$parent_path_subs) {
124             foreach my $subname (keys(%{$entry->{'subs'}})) {
125             if($all_subs->{$subname} && !__PACKAGE__->_is_same_sub($subname, $entry->{'subs'}->{$subname}, $all_subs->{$subname}->{'subs'}->{$subname})) {
126             my $other_entry = $all_subs->{$subname};
127             my $ident = "$classname\::$entry->{'subs'}->{$subname}";
128             next if($ERRORS->{$ident});
129             _carp("Multi-inheritation-warning for class '$classname':\n" .
130             " > sub '$subname' is defined twice in parent-classes\n" .
131             " > '$entry->{'subs'}->{$subname}' and '$other_entry->{'subs'}->{$subname}'\n" .
132             " > inheritation-path for '$entry->{'subs'}->{$subname}':\n" .
133             " $classname > " . join(' > ', @{$entry->{'path'}}) . "\n" .
134             " > inheritation-path for '$other_entry->{'subs'}->{$subname}':\n" .
135             " $classname > " . join(' > ', @{$other_entry->{'path'}}) . "\n", 1);
136             $ERRORS->{$ident} = 1;
137             }
138             $all_subs->{$subname} = $entry;
139             }
140             }
141             return;
142             };
143             # AUTOMAGIC void
144             sub import {
145 6     6   7730 my $class = $_[0];
146 6         10 my $parent = $_[1];
147            
148 6         39 my ($child) = caller(0);
149 6         16 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
150 6         20 $class->extends($child, $parent);
151 5         914 return undef;
152             }
153             # void
154             sub extends {
155 16     16 1 34 my $class = $_[0];
156 16         24 my $child = $_[1];
157 16         18 my $parent = $_[2];
158 16   50     79 my $import_depth = $_[3] || 0;
159            
160 16     2   122 local $SIG{'__DIE__'} = sub {};
  2         30  
161            
162 5     5   33 no strict 'refs';
  5         9  
  5         2160  
163            
164 16 50       51 if($CHECK_LEVEL > $LEVEL_DISABLE) {
165 16 50 33     28 if(!%{"$child\::"} && eval("use $parent();return 1")) {
  16         77  
166 0         0 _croak($@, $import_depth);
167             }
168             }
169 16 100 66 2   1343 if(!eval("package $child;use base qw($parent);return 1") || $@) {
  2     2   15  
  2     2   4  
  2     2   494  
  2     2   13  
  2     2   6  
  2     1   444  
  2     1   13  
  2     1   4  
  2         392  
  2         13  
  2         4  
  2         503  
  2         15  
  2         3  
  2         396  
  2         16  
  2         5  
  2         829  
  1         9  
  1         2  
  1         16  
  1         7  
  1         3  
  1         19  
  1         9  
  1         2  
  1         20  
  1         9  
  1         2  
  1         49  
170 1         12 _croak("Can't extend class '$parent' in child class '$child':\n$@", $import_depth);
171             }
172            
173 15 50       41 return if($CHECK_LEVEL == $LEVEL_DISABLE);
174 15         36 $REGISTER->{$child} = 1;
175 15         50 $class->register_class_tree();
176 15         91 return;
177             }
178             # STATIC void
179             sub register_class_tree {
180 18     18 1 36 my $class = $_[0];
181            
182 18         110 Fukurama::Class::Tree->register_build_handler($BUILD_HANDLER);
183 18         64 Fukurama::Class::Tree->register_check_handler($CHECK_HANDLER);
184 18         31 return;
185             }
186             # STATIC boolean
187             sub _check_this_class {
188 677     677   779 my $class = $_[0];
189 677         685 my $classname = $_[1];
190 677         668 my $paths = $_[2];
191            
192 677 50       1280 return 1 if($CHECK_LEVEL == $LEVEL_CHECK_ALL);
193 677 50       1259 return 0 if($CHECK_LEVEL == $LEVEL_CHECK_NONE);
194            
195 677 100       1394 return 1 if($REGISTER->{$classname});
196 659 50       1206 return 0 if($CHECK_LEVEL == $LEVEL_CHECK_REGISTERED);
197            
198 659 50       1151 if($CHECK_LEVEL == $LEVEL_CHECK_CHILDS) {
199 659         1039 foreach my $path (@$paths) {
200 905         1285 foreach my $path_class (@$path) {
201 2849 100       11469 return 1 if($REGISTER->{$path_class});
202             }
203             }
204             }
205 656         4082 return 0;
206             }
207             # boolean
208             sub _is_same_sub {
209 2     2   4 my $class = $_[0];
210 2         4 my $subname = $_[1];
211 2         3 my $first_class = $_[2];
212 2         5 my $second_class = $_[3];
213            
214 5     5   30 no strict 'refs';
  5         8  
  5         1460  
215            
216 2 50       3 return 1 if(*{$first_class . '::' . $subname}{'CODE'} == *{$second_class . '::' . $subname}{'CODE'});
  2         8  
  2         11  
217 2         10 return 0;
218             }
219             # hashref
220             sub _get_all_subs_for_classpath {
221 23     23   33 my $class = $_[0];
222 23         36 my $path = $_[1];
223            
224 23         32 my $path_subs = {};
225 23         37 foreach my $parent (@$path) {
226 26         96 foreach my $subname (Fukurama::Class::Tree->get_class_subs($parent)) {
227 147   66     560 $path_subs->{$subname} ||= $parent;
228             }
229             }
230 23         54 return $path_subs;
231             }
232             # void
233             sub run_check {
234 12     12 1 32 my $class = $_[0];
235 12         25 my $type = $_[1];
236            
237 12 100       42 $type = 'MANUAL' if(!defined($type));
238 12 50       91 Fukurama::Class::Tree->run_check('CHECK') if($CHECK_LEVEL > $LEVEL_DISABLE);
239 12         68 return;
240             }
241              
242 5     5   32 no warnings 'void'; # avoid 'Too late to run CHECK/INIT block'
  5         9  
  5         488  
243              
244             # AUTOMAGIC void
245             CHECK {
246 5     5   34 __PACKAGE__->run_check('CHECK');
247             }
248             # AUTOMAGIC void
249             END {
250 5     5   9507 __PACKAGE__->run_check('END');
251             }
252             1;