File Coverage

blib/lib/Class/Container.pm
Criterion Covered Total %
statement 203 248 81.8
branch 79 136 58.0
condition 30 50 60.0
subroutine 29 31 93.5
pod 12 17 70.5
total 353 482 73.2


line stmt bran cond sub pod time code
1 2     2   8208 use strict;
  2         12  
  2         234  
2             package Class::Container;
3             {
4             $Class::Container::VERSION = '0.13';
5             }
6              
7             my $HAVE_WEAKEN;
8             BEGIN {
9 2     2   6 eval {
10 2         10 require Scalar::Util;
11 2         125 Scalar::Util->import('weaken');
12 2         4 $HAVE_WEAKEN = 1;
13             };
14            
15 2 50       43 *weaken = sub {} unless defined &weaken;
16             }
17              
18 2     2   8 use Carp;
  2         4  
  2         106  
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   633 use Params::Validate qw(:all);
  2         12987  
  2         2474  
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 2293 my $proto = shift;
52 54   33     152 my $class = ref($proto) || $proto;
53 54         153 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       291 if ($HAVE_WEAKEN) {
60 51         90 my $c = $self->get_contained_object_spec;
61 51         115 foreach my $name (keys %$c) {
62 40 100       75 next if $c->{$name}{delayed};
63 21         56 $self->{$name}{container}{container} = $self;
64 21         56 weaken $self->{$name}{container}{container};
65             }
66             }
67 51         213 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             $type = ($spec->{type} & ARRAYREF ? 'list' :
108             $spec->{type} & SCALAR ? 'string' :
109             $spec->{type} & CODEREF ? 'code' :
110 0 0       0 $spec->{type} & HASHREF ? 'hash' :
    0          
    0          
    0          
111             undef); # Oh well
112             }
113              
114 0   0     0 my $descr = $spec->{descr} || '(No description available)';
115             $out{$class}{valid_params}{$name} = { type => $type,
116             pv_type => $spec->{type},
117             default => $default,
118             descr => $descr,
119             required => defined $default || $spec->{optional} ? 0 : 1,
120 0 0 0     0 public => exists $spec->{public} ? $spec->{public} : 1,
    0          
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             $out{$class}{contained_objects}{$name}
131 0         0 = {map {$_, $contains->{$name}{$_}} qw(class delayed descr)};
  0         0  
132             }
133             }
134              
135 0         0 return %out;
136             }
137              
138             sub dump_parameters {
139 12     12 1 18 my $self = shift;
140 12   66     25 my $class = ref($self) || $self;
141            
142 12         15 my %params;
143 12         14 foreach my $param (keys %{ $class->validation_spec }) {
  12         17  
144 30 100       49 next if $param eq 'container';
145 18         25 my $spec = $class->validation_spec->{$param};
146 18 100 66     57 if (ref($self) and defined $self->{$param}) {
147 16         27 $params{$param} = $self->{$param};
148             } else {
149 2 50       7 $params{$param} = $spec->{default} if exists $spec->{default};
150             }
151             }
152            
153 12         16 foreach my $name (keys %{ $class->get_contained_object_spec }) {
  12         18  
154 6 50       11 next unless ref($self);
155             my $contained = ($self->{container}{contained}{$name}{delayed} ?
156             $self->delayed_object_class($name) :
157 6 100       20 $params{$name});
158            
159 6 50       23 my $subparams = UNIVERSAL::isa($contained, __PACKAGE__) ? $contained->dump_parameters : {};
160            
161 6   100     16 my $more = $self->{container}{contained}{$name}{args} || {};
162 6         13 $subparams->{$_} = $more->{$_} foreach keys %$more;
163            
164 6         14 @params{ keys %$subparams } = values %$subparams;
165 6         14 delete $params{$name};
166             }
167 12         28 return \%params;
168             }
169              
170             sub show_containers {
171 23     23 1 42 my $self = shift;
172 23         31 my $name = shift;
173 23         64 my %args = (indent => '', @_);
174              
175 23 100       58 $name = defined($name) ? "$name -> " : "";
176              
177 23         40 my $out = "$args{indent}$name$self";
178 23 100       50 $out .= " (delayed)" if $args{delayed};
179 23         34 $out .= "\n";
180 23 50       65 return $out unless $self->isa(__PACKAGE__);
181              
182 23 100       71 my $specs = ref($self) ? $self->{container}{contained} : $self->get_contained_object_spec;
183              
184 23         61 while (my ($name, $spec) = each %$specs) {
185 18   66     56 my $class = $args{args}{"${name}_class"} || $spec->{class};
186 18         35 $self->_load_module($class);
187              
188 18 50       49 if ($class->isa(__PACKAGE__)) {
189             $out .= $class->show_containers($name,
190             indent => "$args{indent} ",
191             args => $spec->{args},
192 18         87 delayed => $spec->{delayed});
193             } else {
194 0         0 $out .= "$args{indent} $name -> $class\n";
195             }
196             }
197              
198 23         131 return $out;
199             }
200              
201             sub _expire_caches {
202 58     58   188 %VALID_CACHE = %CONTAINED_CACHE = ();
203             }
204              
205             sub valid_params {
206 38     38 1 2838 my $class = shift;
207 38 100       85 if (@_) {
208 33         106 $class->_expire_caches;
209 33 100 66     150 $VALID_PARAMS{$class} = @_ == 1 && !defined($_[0]) ? {} : {@_};
210             }
211 38   100     101 return $VALID_PARAMS{$class} ||= {};
212             }
213              
214             sub contained_objects
215             {
216 24     24 1 130 my $class = shift;
217 24         48 $class->_expire_caches;
218 24         40 $CONTAINED_OBJECTS{$class} = {};
219 24         51 while (@_) {
220 24         52 my ($name, $spec) = (shift, shift);
221 24 100       95 $CONTAINED_OBJECTS{$class}{$name} = ref($spec) ? $spec : { class => $spec };
222             }
223             }
224              
225             sub _decorator_AUTOLOAD {
226 9     9   323 my $self = shift;
227 2     2   19 use vars qw($AUTOLOAD);
  2         4  
  2         407  
228 9         45 my ($method) = $AUTOLOAD =~ /([^:]+)$/;
229 9 100       97 return if $method eq 'DESTROY';
230 1 50       3 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         6 unshift @_, $self->{_decorates};
234 1         3 goto $subr;
235             }
236              
237             sub _decorator_CAN {
238 11     11   273 my ($self, $method) = @_;
239 11 100       67 return $self->SUPER::can($method) if $self->SUPER::can($method);
240 2 50       9 if (ref $self) {
241 2 100       10 return $self->{_decorates}->can($method) if $self->{_decorates};
242 1         4 return;
243             } else {
244 0         0 return $DECORATEES{$self}->can($method);
245             }
246             }
247              
248             sub decorates {
249 5     5 1 55 my ($class, $super) = @_;
250            
251 2     2   14 no strict 'refs'; ## no critic
  2         3  
  2         1876  
252 5   33     13 $super ||= ${$class . '::ISA'}[0];
  5         18  
253            
254             # Pass through unknown method invocations
255 5         7 *{$class . '::AUTOLOAD'} = \&_decorator_AUTOLOAD;
  5         13  
256 5         10 *{$class . '::can'} = \&_decorator_CAN;
  5         10  
257            
258 5         8 $DECORATEES{$class} = $super;
259 5         17 $VALID_PARAMS{$class}{_decorates} = { isa => $super, optional => 1 };
260             }
261              
262             sub container {
263 1     1 1 8 my $self = shift;
264 1 50       3 die "The ", ref($self), "->container() method requires installation of Scalar::Util" unless $HAVE_WEAKEN;
265 1         4 return $self->{container}{container};
266             }
267              
268             sub call_method {
269 5     5 0 13 my ($self, $name, $method, @args) = @_;
270            
271 5 50       11 my $class = $self->contained_class($name)
272             or die "Unknown contained item '$name'";
273              
274 5         12 $self->_load_module($class);
275 5         10 return $class->$method( %{ $self->{container}{contained}{$name}{args} }, @args );
  5         19  
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 76 my $class = shift;
285              
286 54         93 my $c = $class->get_contained_object_spec;
287 54 100 100     167 return {@_, container => {}} unless %$c or $DECORATEES{$class};
288            
289 35         78 my %args = @_;
290            
291 35 100       63 if ($DECORATEES{$class}) {
292             # Fix format
293             $args{decorate_class} = [$args{decorate_class}]
294 8 100 66     22 if $args{decorate_class} and !ref($args{decorate_class});
295            
296             # Figure out which class to decorate
297 8         13 my $decorate;
298 8 100       13 if (my $c = $args{decorate_class}) {
299 3 50       7 $decorate = @$c ? shift @$c : undef;
300 3 50       7 delete $args{decorate_class} unless @$c;
301             }
302 8 100       19 $c->{_decorates} = { class => $decorate } if $decorate;
303             }
304              
305             # This one is special, don't pass to descendants
306 35   100     93 my $container_stuff = delete($args{container}) || {};
307              
308 35         51 keys %$c; # Reset the iterator - why can't I do this in get_contained_object_spec??
309 35         46 my %contained_args;
310             my %to_create;
311            
312 35         92 while (my ($name, $spec) = each %$c) {
313             # Figure out exactly which class to make an object of
314 47         129 my ($contained_class, $c_args) = $class->_get_contained_args($name, \%args);
315 47         88 @contained_args{ keys %$c_args } = (); # Populate with keys
316 47         168 $to_create{$name} = { class => $contained_class,
317             args => $c_args };
318             }
319            
320 35         76 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 46         79 delete $args{"${name}_class"};
324              
325 46 100       79 if ($spec->{delayed}) {
326 20         37 $container_stuff->{contained}{$name} = $to_create{$name};
327 20         73 $container_stuff->{contained}{$name}{delayed} = 1;
328             } else {
329 26   66     68 $args{$name} ||= $to_create{$name}{class}->new(%{$to_create{$name}{args}});
  25         64  
330 25         101 $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         75 my $my_spec = $class->validation_spec;
337 34         86 delete @args{ grep {!exists $my_spec->{$_}} keys %contained_args };
  18         49  
338 34 100       60 delete $c->{_decorates} if $DECORATEES{$class};
339              
340 34         50 $args{container} = $container_stuff;
341 34         96 return \%args;
342             }
343              
344             sub create_delayed_object
345             {
346 5     5 1 17 my ($self, $name) = (shift, shift);
347 5 50       13 croak "Unknown delayed item '$name'" unless $self->{container}{contained}{$name}{delayed};
348              
349 5 50       10 if ($HAVE_WEAKEN) {
350 5         13 push @_, container => {container => $self};
351 5         15 weaken $_[-1]->{container};
352             }
353 5         18 return $self->call_method($name, 'new', @_);
354             }
355              
356             sub delayed_object_class
357             {
358 4     4 1 203 my $self = shift;
359 4         5 my $name = shift;
360             croak "Unknown delayed item '$name'"
361 4 50       11 unless $self->{container}{contained}{$name}{delayed};
362              
363 4         8 return $self->{container}{contained}{$name}{class};
364             }
365              
366             sub contained_class
367             {
368 9     9 0 123 my ($self, $name) = @_;
369             croak "Unknown contained item '$name'"
370 9 50       23 unless my $spec = $self->{container}{contained}{$name};
371 9         30 return $spec->{class};
372             }
373              
374             sub delayed_object_params
375             {
376 0     0 1 0 my ($self, $name) = (shift, shift);
377             croak "Unknown delayed object '$name'"
378 0 0       0 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   77 my ($class, $name, $args) = @_;
399            
400 47 50       80 my $spec = $class->get_contained_object_spec->{$name}
401             or croak "Unknown contained object '$name'";
402              
403 47   66     148 my $contained_class = $args->{"${name}_class"} || $spec->{class};
404 47 50       174 croak "Invalid class name '$contained_class'"
405             unless $contained_class =~ /^[\w:]+$/;
406              
407 47         119 $class->_load_module($contained_class);
408 47 100       164 return ($contained_class, {}) unless $contained_class->isa(__PACKAGE__);
409              
410 46         92 my $allowed = $contained_class->allowed_params($args);
411              
412 46         57 my %contained_args;
413 46         89 foreach (keys %$allowed) {
414 111 100       187 $contained_args{$_} = $args->{$_} if exists $args->{$_};
415             }
416 46         126 return ($contained_class, \%contained_args);
417             }
418              
419             sub _load_module {
420 95     95   140 my ($self, $module) = @_;
421            
422 95 50       113 unless ( eval { $module->can('new') } )
  95         356  
423             {
424 2     2   16 no strict 'refs'; ## no critic
  2         5  
  2         458  
425 0         0 eval "use $module"; ## no critic
426 0 0       0 croak $@ if $@;
427             }
428             }
429              
430             sub allowed_params
431             {
432 75     75 1 291 my $class = shift;
433 75 100       126 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         152 my $c = $class->get_contained_object_spec;
444 75         95 my %p = %{ $class->validation_spec };
  75         133  
445            
446 75         154 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       45 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       49 if ( exists $args->{"${name}_class"} ) {
456 7         12 $contained_class = $args->{"${name}_class"};
457 7         16 $p{"${name}_class"} = { type => SCALAR }; # Add to spec
458             } else {
459 18         25 $contained_class = $c->{$name}{class};
460             }
461            
462             # We have to make sure it is loaded before we try calling allowed_params()
463 25         59 $class->_load_module($contained_class);
464 25 50       84 next unless $contained_class->can('allowed_params');
465            
466 25         52 my $subparams = $contained_class->allowed_params($args);
467            
468 25         47 foreach (keys %$subparams) {
469 42   66     101 $p{$_} ||= $subparams->{$_};
470             }
471             }
472              
473 75         125 return \%p;
474             }
475              
476             sub _iterate_ISA {
477 528     528   833 my ($class, $look_in, $cache_in, $add) = @_;
478              
479 528 100       2221 return $cache_in->{$class} if $cache_in->{$class};
480              
481 100         107 my %out;
482            
483 2     2   12 no strict 'refs'; ## no critic
  2         4  
  2         433  
484 100         110 foreach my $superclass (@{ "${class}::ISA" }) {
  100         232  
485 79 100       207 next unless $superclass->isa(__PACKAGE__);
486 77         141 my $superparams = $superclass->_iterate_ISA($look_in, $cache_in, $add);
487 77         185 @out{keys %$superparams} = values %$superparams;
488             }
489 100 100       178 if (my $x = $look_in->{$class}) {
490 52         109 @out{keys %$x} = values %$x;
491             }
492            
493 100 100       188 @out{keys %$add} = values %$add if $add;
494            
495 100         306 return $cache_in->{$class} = \%out;
496             }
497              
498             sub get_contained_object_spec {
499 258   66 258 0 728 return (ref($_[0]) || $_[0])->_iterate_ISA(\%CONTAINED_OBJECTS, \%CONTAINED_CACHE);
500             }
501              
502             sub validation_spec {
503 193   33 193 1 706 return (ref($_[0]) || $_[0])->_iterate_ISA(\%VALID_PARAMS, \%VALID_CACHE, { container => {type => HASHREF} });
504             }
505              
506             1;
507              
508             __END__