File Coverage

blib/lib/Class/Component.pm
Criterion Covered Total %
statement 276 290 95.1
branch 90 110 81.8
condition 38 53 71.7
subroutine 48 50 96.0
pod 3 4 75.0
total 455 507 89.7


line stmt bran cond sub pod time code
1             package Class::Component;
2              
3 20     20   28492 use strict;
  20         43  
  20         694  
4 20     20   104 use warnings;
  20         64  
  20         1055  
5             our $VERSION = '0.17';
6              
7             for my $method (qw/ load_components load_plugins new register_method register_hook remove_method remove_hook call run_hook NEXT /) {
8 20     20   102 no strict 'refs';
  20         35  
  20         2426  
9 529     529   55849 *{__PACKAGE__."::$method"} = sub { Class::Component::Implement->$method(@_) };
10             }
11              
12             for my $name (qw/ config components plugins methods hooks /) {
13             my $method = "class_component_$name";
14 20     20   113 no strict 'refs';
  20         43  
  20         3484  
15             *{__PACKAGE__."::$method"} = sub {
16 531 50   531   1341 $_[0]->{"_$method"} = $_[1] if $_[1];
17 531         13262 $_[0]->{"_$method"}
18             };
19             }
20              
21             sub import {
22 38     38   472 my($class, %args) = @_;
23 38 100       33187 return unless $class eq 'Class::Component';
24 20         84 my $pkg = caller(0);
25              
26 20 50       537 unless ($pkg->isa('Class::Component')) {
27 20     20   134 no strict 'refs';
  20         38  
  20         6904  
28 20         44 unshift @{"$pkg\::ISA"}, $class;
  20         340  
29             }
30              
31 20         175 Class::Component::Implement->init($pkg, %args);
32             }
33              
34 25     25 1 128 sub class_component_load_component_resolver {}
35 70     70 1 306 sub class_component_load_plugin_resolver {}
36              
37             sub class_component_reinitialize {
38 2     2 1 44 my($class, %args) = @_;
39 2         24 Class::Component::Implement->init($class, %args);
40             }
41              
42             sub class_component_clear_isa_list {
43 3     3 0 6 my $class = shift;
44              
45 3   66     34 my $klass = $_[0] || ref($class) || $class;
46 3         17 my $isa_list = Class::Component::Implement->component_isa_list;
47 3         5 for my $key (keys %{ $isa_list }) {
  3         13  
48 15 100 100     184 delete $isa_list->{$key} if $key =~ /^$klass-/ || $key eq $klass;
49             }
50              
51 3         15 my $pkg_require_cache = Class::Component::Implement->pkg_require_cache;
52 3         4 for my $key (keys %{ $pkg_require_cache }) {
  3         12  
53 11 50 33     91 delete $pkg_require_cache->{$key} if $key =~ /^$klass\::/ || $key eq $klass;
54             }
55             }
56              
57             package # hide from PAUSE
58             Class::Component::Implement;
59              
60 20     20   134 use strict;
  20         36  
  20         626  
61 20     20   138 use warnings;
  20         59  
  20         730  
62 20     20   125 use base qw( Class::Data::Inheritable );
  20         51  
  20         20666  
63              
64             my $component_isa_list = {};
65             my $default_components = {};
66             my $default_plugins = {};
67             my $default_configs = {};
68             my $reload_plugin_maps = {};
69              
70 20     20   25324 use UNIVERSAL::require;
  20         34282  
  20         222  
71              
72 20     20   28892 use Carp::Clan qw/Class::Component/;
  20         93386  
  20         158  
73 20     20   34845 use Class::Inspector;
  20         101521  
  20         468  
74              
75 106     106   425 sub component_isa_list { $component_isa_list }
76 15     15   4807 sub default_components { $default_components }
77 3     3   11 sub default_plugins { $default_plugins }
78 6     6   131 sub default_configs { $default_configs }
79              
80             sub init {
81 22     22   159 my($class, $c, %args) = @_;
82 22         89 $c = $class->_class($c);
83              
84 22   100     168 $default_components->{$c} ||= [];
85 22   100     123 $default_plugins->{$c} ||= [];
86 22 50       84 $default_configs->{$c} = delete $args{config} if defined $args{config};
87              
88 22         51 delete $reload_plugin_maps->{$c};
89 22 100       864 $reload_plugin_maps->{$c} = \&_reload_plugin if $args{reload_plugin};
90             }
91              
92             sub shared_configs {
93 0     0   0 my($class, $from, $to) = @_;
94              
95 0         0 $default_components->{$to} = $default_components->{$from};
96 0         0 $default_plugins->{$to} = $default_plugins->{$from};
97 0         0 $reload_plugin_maps->{$to} = $reload_plugin_maps->{$from};
98             }
99              
100             sub load_components {
101 22     22   76 my($class, $c, @components) = @_;
102              
103 22         65 for my $component (@components) {
104 25         118 $class->_load_component($c, $component);
105             }
106             }
107              
108             sub _load_component {
109 27     27   67 my($class, $c, $component, $reload) = @_;
110 27         117 $c = $class->_class($c);
111              
112 27         52 my $pkg;
113 27 100 66     296 if (($pkg = $component) =~ s/^\+// || ($pkg = $c->class_component_load_component_resolver($component))) {
114 2 50       19 $pkg->require or croak $@;
115             } else {
116 25 50       142 unless ($pkg = $class->pkg_require($c => "Component::$component")) {
117 0 0       0 $@ and croak $@;
118 0         0 croak "$component is not installed";
119             }
120             }
121              
122 27 100       146 unless ($reload) {
123 25         41 for my $default (@{ $default_components->{$c} }) {
  25         105  
124 10 100       58 return if $pkg eq $default;
125             }
126             }
127              
128 20     20   9716 no strict 'refs';
  20         56  
  20         46896  
129 25         48 unshift @{"$c\::ISA"}, $pkg;
  25         463  
130 25         118 for my $isa_pkg (@{ $class->isa_list($c) }) {
  25         176  
131 93         147 my $key = $c;
132 93         110 my $from;
133 93 100       407 unless ($c eq $isa_pkg) {
134 68         147 $key .= "-$isa_pkg";
135 68         140 $from = $isa_pkg;
136             }
137 93         241 $class->component_isa_list->{$key} = $class->isa_list($c, $from);
138             }
139 25 100       113 push @{ $default_components->{$c} }, $pkg unless $reload;
  23         66  
140 25 100       572 $pkg->class_component_load_component_init($c) if $pkg->can('class_component_load_component_init');
141             }
142              
143             sub load_plugins {
144 65     65   186 my($class, $c, @plugins) = @_;
145              
146 65 100       259 return $class->load_plugins_default($c, @plugins) unless ref $c;
147              
148 46         111 for my $plugin (@plugins) {
149 67         258 $class->_load_plugin($c, $plugin);
150             }
151             }
152              
153             sub load_plugins_default {
154 25     25   60 my($class, $c, @plugins) = @_;
155              
156             LOOP:
157 25         62 for my $plugin (@plugins) {
158 25         35 for my $default (@{ $default_plugins->{$c} }) {
  25         76  
159 7 100       39 next LOOP if $plugin eq $default;
160             }
161 22         42 push @{ $default_plugins->{$c} }, $plugin;
  22         133  
162             }
163             }
164              
165             sub _load_plugin {
166 79     79   161 my($class, $c, $plugin) = @_;
167              
168             # config option support
169 79         584 my $config;
170 79 100       255 if (ref($plugin) eq 'HASH') {
171 8   100     38 $config = $plugin->{config} || {};
172 8         225 $plugin = $plugin->{module};
173             }
174 79 50       199 return unless $plugin;
175              
176 79         126 my $pkg;
177 79 100 66     942 if (($pkg = $plugin) =~ s/^\+// || ($pkg = $c->class_component_load_plugin_resolver($plugin))) {
178 9 50       119 $pkg->require or croak $@;
179             } else {
180 70 50       334 unless ($pkg = $class->pkg_require($c => "Plugin::$plugin")) {
181 0 0       0 $@ and croak $@;
182 0         0 croak "$plugin is not installed";
183             }
184             }
185              
186 79         1521 my $class_component_plugins = $c->class_component_plugins;
187 79 100       258 unless ($config) {
188 71         109 for my $default (@{ $class_component_plugins }) {
  71         210  
189 39 100       217 return if $pkg eq ref($default);
190             }
191             }
192              
193 71   100     562 my $obj = $pkg->new($config || $c->class_component_config->{$plugin} || {}, $c);
194 71         120 push @{ $class_component_plugins }, $obj;
  71         155  
195 71         373 $obj->register($c);
196             }
197              
198             sub new {
199 40     40   118 my($class, $c, $args) = @_;
200 40   100     224 $args ||= {};
201              
202 40         894 my $self = bless {
203 40   100     78 %{ $args },
204             _class_component_plugins => [],
205             _class_component_components => $default_components->{$c},
206             _class_component_methods => {},
207             _class_component_hooks => {},
208             _class_component_config => $args->{config} || $default_configs->{$c} || {},
209             _class_component_default_plugins => $default_plugins->{$c},
210             }, $c;
211              
212 40 100       98 $self->load_plugins(@{ $default_plugins->{$c} }, @{ $args->{load_plugins} || [] });
  40         107  
  40         345  
213              
214 40         272 $self;
215             }
216              
217             sub register_method {
218 108     108   277 my($class, $c, @methods) = @_;
219 108         497 while (my($method, $plugin) = splice @methods, 0, 2) {
220 108         402 $c->class_component_methods->{$method} = $plugin
221             }
222             }
223              
224             sub register_hook {
225 38     38   102 my($class, $c, @hooks) = @_;
226 38         168 while (my($hook, $obj) = splice @hooks, 0, 3) {
227 38 100       154 $c->class_component_hooks->{$hook} = [] unless $c->class_component_hooks->{$hook};
228 38         61 push @{ $c->class_component_hooks->{$hook} }, $obj;
  38         171  
229             }
230             }
231              
232             sub remove_method {
233 4     4   11 my($class, $c, @methods) = @_;
234 4         22 while (my($method, $plugin) = splice @methods, 0, 2) {
235 5 100       11 next unless ref($c->class_component_methods->{$method}) eq $plugin;
236 2         7 delete $c->class_component_methods->{$method};
237             }
238             }
239              
240             sub remove_hook {
241 2     2   8 my($class, $c, @hooks) = @_;
242 2         13 while (my($hook, $remove_obj) = splice @hooks, 0, 3) {
243 2         14 my $i = -1;
244 2         5 for my $obj (@{ $c->class_component_hooks->{$hook} }) {
  2         110  
245 2         4 $i++;
246 2 50 33     22 next unless ref($obj->{plugin}) eq $remove_obj->{plugin} && $obj->{method} eq $remove_obj->{method};
247 2         4 splice @{ $c->class_component_hooks->{$hook} }, $i, 1;
  2         5  
248             }
249 2 50       7 delete $c->class_component_hooks->{$hook} unless @{ $c->class_component_hooks->{$hook} };
  2         7  
250             }
251             }
252              
253             sub call {
254 125     125   328 my($class, $c, $method, @args) = @_;
255 125 100       364 return unless my $plugin = $c->class_component_methods->{$method};
256 106 100       392 if (ref $plugin eq 'HASH') {
257             # extend method
258 10         57 my $obj = $plugin;
259 10         21 $plugin = $obj->{plugin};
260 10         23 my $real_method = $obj->{method};
261 10 50 33     103 return unless $plugin && $real_method;
262 10         47 $class->reload_plugin($c, $plugin);
263 10 100       151 if (ref $real_method eq 'CODE') {
    50          
264 4         209 $real_method->($plugin, $c, @args);
265             } elsif (!ref($real_method)) {
266 6         23 $plugin->$real_method($c, @args);
267             }
268             } else {
269 96         311 $class->reload_plugin($c, $plugin);
270 96         706 $plugin->$method($c, @args);
271             }
272             }
273              
274             sub run_hook {
275 63     63   156 my($class, $c, $hook, $args) = @_;
276 63 100       212 return unless my $hooks = $c->class_component_hooks->{$hook};
277 54         422 $class->reload_plugin($c, $hooks->[0]->{plugin});
278              
279 54         197 my @ret;
280 54         85 for my $obj (@{ $hooks }) {
  54         133  
281 62         212 my($plugin, $method) = ($obj->{plugin}, $obj->{method});
282 62         256 my $ret = $plugin->$method($c, $args);
283 62         649 push @ret, $ret;
284             }
285 54         370 \@ret;
286             }
287              
288             sub _reload_plugin {
289 14     14   22 my($class, $c, $pkg) = @_;
290 14 100       37 return if Class::Inspector->loaded($class->_class($pkg));
291              
292 4         353 $default_components->{$class->_class($c)} = $c->class_component_components;
293 4         21 $default_plugins->{$class->_class($c)} = $c->class_component_plugins;
294              
295 4         9 for my $component (@{ $default_components->{$class->_class($c)} }) {
  4         12  
296 2         5 $class->_load_component($c, '+' . $class->_class($component), 1);
297             }
298              
299 4         16 for my $plugin (@{ $c->class_component_plugins }) {
  4         18  
300 6         17 $class->_load_plugin($c, '+' . $class->_class($plugin));
301             }
302              
303             }
304              
305             sub reload_plugin {
306 160     160   273 my($class, $c) = @_;
307 160 100       488 return unless my $code = $reload_plugin_maps->{$class->_class($c)};
308 14         47 goto $code;
309             }
310              
311             sub NEXT {
312 62     62   161 my($class, $c, $method, @args) = @_;
313 62   66     199 my $klass = ref $c || $c;
314 62         134 my $caller = caller(1);
315              
316 62   66     284 my $isa_list_cache = $component_isa_list->{"$klass-$caller"} || $class->isa_list_cache($c, $caller);
317 62         89 my @isa = @{ $isa_list_cache };
  62         169  
318              
319 62         133 for my $pkg (@isa) {
320 67 100       615 next unless $pkg->can($method);;
321 42         109 my $next = "$pkg\::$method";
322 42         182 return $c->$next(@args);
323             }
324              
325 20         46 for my $pkg (@isa) {
326 22 50       252 next unless $pkg->can('AUTOLOAD');
327 0         0 my $next = "$pkg\::$method";
328 0         0 return $c->$next(@args);
329             }
330             }
331              
332             sub isa_list_cache {
333 135     135   246 my($class, $c, $from) = @_;
334 135   66     589 my $key = ref $c || $c;
335 135 100       351 $key .= "-$from" if $from;
336 135 100       512 $component_isa_list->{$key} = $class->isa_list($c, $from) unless $component_isa_list->{$key};
337 135         642 $component_isa_list->{$key};
338             }
339              
340             sub isa_list {
341 145     145   273 my($class, $c, $from) = @_;
342 145   66     593 $c = ref $c || $c;
343              
344 145         406 my $isa_list = $class->_fetch_isa_list($c);
345 145         378 my $isa_mark = {};
346 145         461 $class->_mark_isa_list($isa_list, $isa_mark, 0);
347 145         509 my @isa = $class->_sort_isa_list($isa_list, $isa_mark, 0);
348              
349 145         238 my @next_classes;
350 145         210 my $f = 0;
351 145 100       336 $f = 1 unless $from;
352 145         237 for my $pkg (@isa) {
353 574 100       1003 if ($f) {
354 325         674 push @next_classes, $pkg;
355             } else {
356 249 100       786 next unless $pkg eq $from;
357 77         179 $f = 1;
358             }
359             }
360 145         1440 \@next_classes;
361             }
362              
363             sub _fetch_isa_list {
364 622     622   1878 my($class, $base) = @_;
365              
366 622         1803 my $isa_list = { pkg => $base, isa => [] };
367 20     20   170 no strict 'refs';
  20         48  
  20         20205  
368 622         827 for my $pkg (@{"$base\::ISA"}) {
  622         2432  
369 477         559 push @{ $isa_list->{isa} }, $class->_fetch_isa_list($pkg);
  477         1494  
370             }
371 622         1761 $isa_list;
372             }
373              
374             sub _mark_isa_list {
375 622     622   941 my($class, $isa_list, $isa_mark, $nest) = @_;
376              
377 622         5839 for my $list (@{ $isa_list->{isa} }) {
  622         1303  
378 477         1769 $class->_mark_isa_list($list, $isa_mark, $nest + 1);
379             }
380 622         1127 my $pkg = $isa_list->{pkg};
381 622 100 66     3101 $isa_mark->{$pkg} = { nest => $nest, count => 0 } if !$isa_mark->{$pkg} || $isa_mark->{$pkg}->{nest} < $nest;
382 622         14634 $isa_mark->{$pkg}->{count}++;
383             }
384              
385             sub _sort_isa_list {
386 622     622   1194 my($class, $isa_list, $isa_mark, $nest) = @_;
387              
388 622         695 my @isa;
389 622         1389 my $pkg = $isa_list->{pkg};
390 622 100       1731 unless (--$isa_mark->{$pkg}->{count}) {
391 574         851 push @isa, $pkg;
392             }
393              
394 622         683 for my $list (@{ $isa_list->{isa} }) {
  622         1307  
395 477         1515 my @ret = $class->_sort_isa_list($list, $isa_mark, $nest + 1);
396 477         1194 push @isa, @ret;
397             }
398              
399 622         1993 @isa;
400             }
401              
402             sub _class {
403 243     243   418 my($class, $c) = @_;
404 243 100       1415 ref($c) || $c;
405             }
406              
407             my $pkg_require_cache = {};
408 3     3   6 sub pkg_require_cache { $pkg_require_cache }
409 0     0   0 sub pkg_require_cache_clear { $pkg_require_cache = {} }
410             sub pkg_require {
411 267     267   539 my($class, $c, $pkg) = @_;
412 267   66     869 $c = ref $c || $c;
413              
414 267         346 my $isa_list;
415 267 100       876 if ($isa_list = $component_isa_list->{$c}) {
416 249 100       782 if (my $cache = $pkg_require_cache->{$pkg}) {
417 181 100       296 if ($cache->{isa_list} eq join('-', @{ $isa_list })) {
  181         802  
418 141         600 return $cache->{pkg};
419             }
420             }
421             }
422 126   100     433 $isa_list ||= [];
423              
424 126         208 my $obj = { isa_list => join('-', @{ $isa_list }) };
  126         592  
425 126         362 $pkg_require_cache->{$pkg} = $obj;
426 126         237 for my $isa_pkg (@{ $class->isa_list_cache($c) }) {
  126         458  
427 232 50       34624 unless ($isa_list) {
428 0         0 $isa_list = $component_isa_list->{$c};
429 0         0 $obj->{isa_list} = join('-', @{ $isa_list });
  0         0  
430             }
431              
432 232         570 my $new_pkg = "$isa_pkg\::$pkg";
433 232 100       1134 next unless Class::Inspector->installed($new_pkg);
434 126 50       12092 $new_pkg->require or return;
435 126         14494 $obj->{pkg} = $new_pkg;
436 126         1053 return $new_pkg;
437             }
438             }
439              
440             package Class::Component;
441              
442             1;
443             __END__