File Coverage

blib/lib/Class/Container.pm
Criterion Covered Total %
statement 203 249 81.5
branch 78 136 57.3
condition 30 50 60.0
subroutine 29 31 93.5
pod 12 17 70.5
total 352 483 72.8


line stmt bran cond sub pod time code
1             package Class::Container;
2              
3             $VERSION = '0.12';
4             $VERSION = eval $VERSION if $VERSION =~ /_/;
5              
6             my $HAVE_WEAKEN;
7             BEGIN {
8 2     2   20115 eval {
9 2         20 require Scalar::Util;
10 2         221 Scalar::Util->import('weaken');
11 2         5 $HAVE_WEAKEN = 1;
12             };
13            
14 2 50       58 *weaken = sub {} unless defined &weaken;
  0         0  
15             }
16              
17 2     2   11 use strict;
  2         8  
  2         78  
18 2     2   11 use Carp;
  2         3  
  2         166  
19              
20             # The create_contained_objects() method lets one object
21             # (e.g. Compiler) transparently create another (e.g. Lexer) by passing
22             # creator parameters through to the created object.
23             #
24             # Any auto-created objects should be declared in a class's
25             # %CONTAINED_OBJECTS hash. The keys of this hash are objects which
26             # can be created and the values are the default classes to use.
27              
28             # For instance, the key 'lexer' indicates that a 'lexer' parameter
29             # should be silently passed through, and a 'lexer_class' parameter
30             # will trigger the creation of an object whose class is specified by
31             # the value. If no value is present there, the value of 'lexer' in
32             # the %CONTAINED_OBJECTS hash is used. If no value is present there,
33             # no contained object is created.
34             #
35             # We return the list of parameters for the creator. If contained
36             # objects were auto-created, their creation parameters aren't included
37             # in the return value. This lets the creator be totally ignorant of
38             # the creation parameters of any objects it creates.
39              
40 2     2   2391 use Params::Validate qw(:all);
  2         39916  
  2         4366  
41             Params::Validate::validation_options( on_fail => sub { die @_ } );
42              
43             my %VALID_PARAMS = ();
44             my %CONTAINED_OBJECTS = ();
45             my %VALID_CACHE = ();
46             my %CONTAINED_CACHE = ();
47             my %DECORATEES = ();
48              
49             sub new
50             {
51 54     54 1 3693 my $proto = shift;
52 54   33     245 my $class = ref($proto) || $proto;
53 54         382 my $self = bless scalar validate_with
54             (
55             params => $class->create_contained_objects(@_),
56             spec => $class->validation_spec,
57             called => "$class->new()",
58             ), $class;
59 51 50       714 if ($HAVE_WEAKEN) {
60 51         122 my $c = $self->get_contained_object_spec;
61 51         170 foreach my $name (keys %$c) {
62 40 100       128 next if $c->{$name}{delayed};
63 21         91 $self->{$name}{container}{container} = $self;
64 21         87 weaken $self->{$name}{container}{container};
65             }
66             }
67 51         284 return $self;
68             }
69              
70             sub all_specs
71             {
72 0     0 0 0 require B::Deparse;
73 0         0 my %out;
74              
75 0         0 foreach my $class (sort keys %VALID_PARAMS)
76             {
77 0         0 my $params = $VALID_PARAMS{$class};
78              
79 0         0 foreach my $name (sort keys %$params)
80             {
81 0         0 my $spec = $params->{$name};
82 0         0 my ($type, $default);
83 0 0       0 if ($spec->{isa}) {
84 0         0 my $obj_class;
85              
86 0         0 $type = 'object';
87              
88 0 0       0 if (exists $CONTAINED_OBJECTS{$class}{$name}) {
89 0         0 $default = "$CONTAINED_OBJECTS{$class}{$name}{class}->new";
90             }
91             } else {
92 0         0 ($type, $default) = ($spec->{parse}, $spec->{default});
93             }
94              
95 0 0       0 if (ref($default) eq 'CODE') {
    0          
    0          
96 0         0 $default = 'sub ' . B::Deparse->new()->coderef2text($default);
97 0         0 $default =~ s/\s+/ /g;
98             } elsif (ref($default) eq 'ARRAY') {
99 0         0 $default = '[' . join(', ', map "'$_'", @$default) . ']';
100             } elsif (ref($default) eq 'Regexp') {
101 0         0 $type = 'regex';
102 0         0 $default =~ s,^\(\?(\w*)-\w*:(.*)\),/$2/$1,;
103 0         0 $default = "qr$default";
104             }
105 0 0       0 unless ($type) {
106             # Guess from the validation spec
107 0 0       0 $type = ($spec->{type} & ARRAYREF ? 'list' :
    0          
    0          
    0          
108             $spec->{type} & SCALAR ? 'string' :
109             $spec->{type} & CODEREF ? 'code' :
110             $spec->{type} & HASHREF ? 'hash' :
111             undef); # Oh well
112             }
113              
114 0   0     0 my $descr = $spec->{descr} || '(No description available)';
115 0 0 0     0 $out{$class}{valid_params}{$name} = { type => $type,
    0          
116             pv_type => $spec->{type},
117             default => $default,
118             descr => $descr,
119             required => defined $default || $spec->{optional} ? 0 : 1,
120             public => exists $spec->{public} ? $spec->{public} : 1,
121             };
122             }
123              
124 0         0 $out{$class}{contained_objects} = {};
125 0 0       0 next unless exists $CONTAINED_OBJECTS{$class};
126 0         0 my $contains = $CONTAINED_OBJECTS{$class};
127              
128 0         0 foreach my $name (sort keys %$contains)
129             {
130 0         0 $out{$class}{contained_objects}{$name}
131 0         0 = {map {$_, $contains->{$name}{$_}} qw(class delayed descr)};
132             }
133             }
134              
135 0         0 return %out;
136             }
137              
138             sub dump_parameters {
139 12     12 1 16 my $self = shift;
140 12   66     33 my $class = ref($self) || $self;
141            
142 12         15 my %params;
143 12         16 foreach my $param (keys %{ $class->validation_spec }) {
  12         25  
144 30 100       143 next if $param eq 'container';
145 18         39 my $spec = $class->validation_spec->{$param};
146 18 100 66     146 if (ref($self) and defined $self->{$param}) {
147 16         47 $params{$param} = $self->{$param};
148             } else {
149 2 50       11 $params{$param} = $spec->{default} if exists $spec->{default};
150             }
151             }
152            
153 12         30 foreach my $name (keys %{ $class->get_contained_object_spec }) {
  12         27  
154 6 50       29 next unless ref($self);
155 6 100       25 my $contained = ($self->{container}{contained}{$name}{delayed} ?
156             $self->delayed_object_class($name) :
157             $params{$name});
158            
159 6 50       38 my $subparams = UNIVERSAL::isa($contained, __PACKAGE__) ? $contained->dump_parameters : {};
160            
161 6   100     29 my $more = $self->{container}{contained}{$name}{args} || {};
162 6         17 $subparams->{$_} = $more->{$_} foreach keys %$more;
163            
164 6         22 @params{ keys %$subparams } = values %$subparams;
165 6         25 delete $params{$name};
166             }
167 12         36 return \%params;
168             }
169              
170             sub show_containers {
171 23     23 1 45 my $self = shift;
172 23         28 my $name = shift;
173 23         91 my %args = (indent => '', @_);
174              
175 23 100       58 $name = defined($name) ? "$name -> " : "";
176              
177 23         62 my $out = "$args{indent}$name$self";
178 23 100       48 $out .= " (delayed)" if $args{delayed};
179 23         32 $out .= "\n";
180 23 50       94 return $out unless $self->isa(__PACKAGE__);
181              
182 23 100       73 my $specs = ref($self) ? $self->{container}{contained} : $self->get_contained_object_spec;
183              
184 23         75 while (my ($name, $spec) = each %$specs) {
185 18   66     82 my $class = $args{args}{"${name}_class"} || $spec->{class};
186 18         40 $self->_load_module($class);
187              
188 18 50       64 if ($class->isa(__PACKAGE__)) {
189 18         148 $out .= $class->show_containers($name,
190             indent => "$args{indent} ",
191             args => $spec->{args},
192             delayed => $spec->{delayed});
193             } else {
194 0         0 $out .= "$args{indent} $name -> $class\n";
195             }
196             }
197              
198 23         143 return $out;
199             }
200              
201             sub _expire_caches {
202 58     58   359 %VALID_CACHE = %CONTAINED_CACHE = ();
203             }
204              
205             sub valid_params {
206 38     38 1 5920 my $class = shift;
207 38 100       99 if (@_) {
208 33         151 $class->_expire_caches;
209 33 100 66     182 $VALID_PARAMS{$class} = @_ == 1 && !defined($_[0]) ? {} : {@_};
210             }
211 38   100     151 return $VALID_PARAMS{$class} ||= {};
212             }
213              
214             sub contained_objects
215             {
216 24     24 1 182 my $class = shift;
217 24         55 $class->_expire_caches;
218 24         55 $CONTAINED_OBJECTS{$class} = {};
219 24         82 while (@_) {
220 24         39 my ($name, $spec) = (shift, shift);
221 24 100       199 $CONTAINED_OBJECTS{$class}{$name} = ref($spec) ? $spec : { class => $spec };
222             }
223             }
224              
225             sub _decorator_AUTOLOAD {
226 1     1   1 my $self = shift;
227 2     2   25 no strict 'vars';
  2         6  
  2         779  
228 1         9 my ($method) = $AUTOLOAD =~ /([^:]+)$/;
229 1 50       4 return if $method eq 'DESTROY';
230 1 50       5 die qq{Can't locate object method "$method" via package $self} unless ref($self);
231 1 50       3 my $subr = $self->{_decorates}->can($method)
232             or die qq{Can't locate object method "$method" via package } . ref($self);
233 1         3 unshift @_, $self->{_decorates};
234 1         5 goto $subr;
235             }
236              
237             sub _decorator_CAN {
238 11     11   281 my ($self, $method) = @_;
239 11 100       98 return $self->SUPER::can($method) if $self->SUPER::can($method);
240 2 50       6 if (ref $self) {
241 2 100       11 return $self->{_decorates}->can($method) if $self->{_decorates};
242 1         5 return undef;
243             } else {
244 0         0 return $DECORATEES{$self}->can($method);
245             }
246             }
247              
248             sub decorates {
249 5     5 1 105 my ($class, $super) = @_;
250            
251 2     2   15 no strict 'refs';
  2         3  
  2         6273  
252 5   33     13 $super ||= ${$class . '::ISA'}[0];
  5         22  
253            
254             # Pass through unknown method invocations
255 5         8 *{$class . '::AUTOLOAD'} = \&_decorator_AUTOLOAD;
  5         22  
256 5         8 *{$class . '::can'} = \&_decorator_CAN;
  5         18  
257            
258 5         9 $DECORATEES{$class} = $super;
259 5         28 $VALID_PARAMS{$class}{_decorates} = { isa => $super, optional => 1 };
260             }
261              
262             sub container {
263 1     1 1 17 my $self = shift;
264 1 50       5 die "The ", ref($self), "->container() method requires installation of Scalar::Util" unless $HAVE_WEAKEN;
265 1         8 return $self->{container}{container};
266             }
267              
268             sub call_method {
269 5     5 0 14 my ($self, $name, $method, @args) = @_;
270            
271 5 50       20 my $class = $self->contained_class($name)
272             or die "Unknown contained item '$name'";
273              
274 5         132 $self->_load_module($class);
275 5         8 return $class->$method( %{ $self->{container}{contained}{$name}{args} }, @args );
  5         26  
276             }
277              
278             # Accepts a list of key-value pairs as parameters, representing all
279             # parameters taken by this object and its descendants. Returns a list
280             # of key-value pairs representing *only* this object's parameters.
281             sub create_contained_objects
282             {
283             # Typically $self doesn't exist yet, $_[0] is a string classname
284 54     54 0 71 my $class = shift;
285              
286 54         147 my $c = $class->get_contained_object_spec;
287 54 100 100     337 return {@_, container => {}} unless %$c or $DECORATEES{$class};
288            
289 35         102 my %args = @_;
290            
291 35 100       173 if ($DECORATEES{$class}) {
292             # Fix format
293 8 100 66     36 $args{decorate_class} = [$args{decorate_class}]
294             if $args{decorate_class} and !ref($args{decorate_class});
295            
296             # Figure out which class to decorate
297 8         10 my $decorate;
298 8 100       21 if (my $c = $args{decorate_class}) {
299 3 50       10 $decorate = @$c ? shift @$c : undef;
300 3 50       13 delete $args{decorate_class} unless @$c;
301             }
302 8 100       74 $c->{_decorates} = { class => $decorate } if $decorate;
303             }
304              
305             # This one is special, don't pass to descendants
306 35   100     20172 my $container_stuff = delete($args{container}) || {};
307              
308 35         58 keys %$c; # Reset the iterator - why can't I do this in get_contained_object_spec??
309 35         39 my %contained_args;
310             my %to_create;
311            
312 35         115 while (my ($name, $spec) = each %$c) {
313             # Figure out exactly which class to make an object of
314 47         179 my ($contained_class, $c_args) = $class->_get_contained_args($name, \%args);
315 47         112 @contained_args{ keys %$c_args } = (); # Populate with keys
316 47         265 $to_create{$name} = { class => $contained_class,
317             args => $c_args };
318             }
319            
320 35         236 while (my ($name, $spec) = each %$c) {
321             # This delete() needs to be outside the previous loop, because
322             # multiple contained objects might need to see it
323 47         82 delete $args{"${name}_class"};
324              
325 47 100       101 if ($spec->{delayed}) {
326 21         53 $container_stuff->{contained}{$name} = $to_create{$name};
327 21         83 $container_stuff->{contained}{$name}{delayed} = 1;
328             } else {
329 26   66     90 $args{$name} ||= $to_create{$name}{class}->new(%{$to_create{$name}{args}});
  25         106  
330 25         171 $container_stuff->{contained}{$name}{class} = ref $args{$name};
331             }
332             }
333              
334             # Delete things that we're not going to use - things that are in
335             # our contained object specs but not in ours.
336 34         116 my $my_spec = $class->validation_spec;
337 34         108 delete @args{ grep {!exists $my_spec->{$_}} keys %contained_args };
  18         59  
338 34 100       94 delete $c->{_decorates} if $DECORATEES{$class};
339              
340 34         71 $args{container} = $container_stuff;
341 34         280 return \%args;
342             }
343              
344             sub create_delayed_object
345             {
346 5     5 1 19 my ($self, $name) = (shift, shift);
347 5 50       105 croak "Unknown delayed item '$name'" unless $self->{container}{contained}{$name}{delayed};
348              
349 5 50       13 if ($HAVE_WEAKEN) {
350 5         18 push @_, container => {container => $self};
351 5         18 weaken $_[-1]->{container};
352             }
353 5         25 return $self->call_method($name, 'new', @_);
354             }
355              
356             sub delayed_object_class
357             {
358 4     4 1 999 my $self = shift;
359 4         7 my $name = shift;
360 4 50       17 croak "Unknown delayed item '$name'"
361             unless $self->{container}{contained}{$name}{delayed};
362              
363 4         18 return $self->{container}{contained}{$name}{class};
364             }
365              
366             sub contained_class
367             {
368 9     9 0 122 my ($self, $name) = @_;
369 9 50       34 croak "Unknown contained item '$name'"
370             unless my $spec = $self->{container}{contained}{$name};
371 9         51 return $spec->{class};
372             }
373              
374             sub delayed_object_params
375             {
376 0     0 1 0 my ($self, $name) = (shift, shift);
377 0 0       0 croak "Unknown delayed object '$name'"
378             unless $self->{container}{contained}{$name}{delayed};
379              
380 0 0       0 if (@_ == 1) {
381 0         0 return $self->{container}{contained}{$name}{args}{$_[0]};
382             }
383              
384 0         0 my %args = @_;
385              
386 0 0       0 if (keys %args)
387             {
388 0         0 @{ $self->{container}{contained}{$name}{args} }{ keys %args } = values %args;
  0         0  
389             }
390              
391 0         0 return %{ $self->{container}{contained}{$name}{args} };
  0         0  
392             }
393              
394             # Everything the specified contained object will accept, including
395             # parameters it will pass on to its own contained objects.
396             sub _get_contained_args
397             {
398 47     47   65 my ($class, $name, $args) = @_;
399            
400 47 50       1223 my $spec = $class->get_contained_object_spec->{$name}
401             or croak "Unknown contained object '$name'";
402              
403 47   66     203 my $contained_class = $args->{"${name}_class"} || $spec->{class};
404 47 50       210 croak "Invalid class name '$contained_class'"
405             unless $contained_class =~ /^[\w:]+$/;
406              
407 47         133 $class->_load_module($contained_class);
408 47 100       293 return ($contained_class, {}) unless $contained_class->isa(__PACKAGE__);
409              
410 46         154 my $allowed = $contained_class->allowed_params($args);
411              
412 46         58 my %contained_args;
413 46         202 foreach (keys %$allowed) {
414 111 100       304 $contained_args{$_} = $args->{$_} if exists $args->{$_};
415             }
416 46         185 return ($contained_class, \%contained_args);
417             }
418              
419             sub _load_module {
420 95     95   149 my ($self, $module) = @_;
421            
422 95 50       119 unless ( eval { $module->can('new') } )
  95         621  
423             {
424 2     2   55 no strict 'refs';
  2         4  
  2         688  
425 0         0 eval "use $module";
426 0 0       0 croak $@ if $@;
427             }
428             }
429              
430             sub allowed_params
431             {
432 75     75 1 350 my $class = shift;
433 75 100       354 my $args = ref($_[0]) ? shift : {@_};
434            
435             # Strategy: the allowed_params of this class consists of the
436             # validation_spec of this class, merged with the allowed_params of
437             # all contained classes. The specific contained classes may be
438             # affected by arguments passed in, like 'interp' or
439             # 'interp_class'. A parameter like 'interp' doesn't add anything
440             # to our allowed_params (because it's already created) but
441             # 'interp_class' does.
442              
443 75         230 my $c = $class->get_contained_object_spec;
444 75         220 my %p = %{ $class->validation_spec };
  75         215  
445            
446 75         2061 foreach my $name (keys %$c)
447             {
448             # Can accept a 'foo' parameter - should already be in the validation_spec.
449             # Also, its creation parameters should already have been extracted from $args,
450             # so don't extract any parameters.
451 25 50       69 next if exists $args->{$name};
452            
453             # Figure out what class to use for this contained item
454 25         27 my $contained_class;
455 25 100       59 if ( exists $args->{"${name}_class"} ) {
456 7         13 $contained_class = $args->{"${name}_class"};
457 7         23 $p{"${name}_class"} = { type => SCALAR }; # Add to spec
458             } else {
459 18         39 $contained_class = $c->{$name}{class};
460             }
461            
462             # We have to make sure it is loaded before we try calling allowed_params()
463 25         93 $class->_load_module($contained_class);
464 25 50       123 next unless $contained_class->can('allowed_params');
465            
466 25         61 my $subparams = $contained_class->allowed_params($args);
467            
468 25         357 foreach (keys %$subparams) {
469 42   66     198 $p{$_} ||= $subparams->{$_};
470             }
471             }
472              
473 75         268 return \%p;
474             }
475              
476             sub _iterate_ISA {
477 528     528   1283 my ($class, $look_in, $cache_in, $add) = @_;
478              
479 528 100       4587 return $cache_in->{$class} if $cache_in->{$class};
480              
481 100         113 my %out;
482            
483 2     2   13 no strict 'refs';
  2         4  
  2         756  
484 100         237 foreach my $superclass (@{ "${class}::ISA" }) {
  100         437  
485 79 100       1127 next unless $superclass->isa(__PACKAGE__);
486 77         218 my $superparams = $superclass->_iterate_ISA($look_in, $cache_in, $add);
487 77         296 @out{keys %$superparams} = values %$superparams;
488             }
489 100 100       1322 if (my $x = $look_in->{$class}) {
490 52         157 @out{keys %$x} = values %$x;
491             }
492            
493 100 100       293 @out{keys %$add} = values %$add if $add;
494            
495 100         508 return $cache_in->{$class} = \%out;
496             }
497              
498             sub get_contained_object_spec {
499 258   66 258 0 1200 return (ref($_[0]) || $_[0])->_iterate_ISA(\%CONTAINED_OBJECTS, \%CONTAINED_CACHE);
500             }
501              
502             sub validation_spec {
503 193   33 193 1 2256 return (ref($_[0]) || $_[0])->_iterate_ISA(\%VALID_PARAMS, \%VALID_CACHE, { container => {type => HASHREF} });
504             }
505              
506             1;
507              
508             __END__