File Coverage

blib/lib/Class/MethodMapper.pm
Criterion Covered Total %
statement 34 221 15.3
branch 0 74 0.0
condition 0 6 0.0
subroutine 12 31 38.7
pod 11 13 84.6
total 57 345 16.5


line stmt bran cond sub pod time code
1             ## Copyright (c) 2000, 2001
2             ## Carnegie Mellon University Sphinx Group, Kevin A. Lenzo, Alan W Black
3             ## This software is available under the same terms as Perl itself.
4             ## Thanks much to Martijn van Beers (LotR)
5              
6             =head1 NAME
7              
8             Class::MethodMapper - Abstract Class wrapper for AutoLoader
9              
10             =head1 SYNOPSIS
11              
12             BEGIN {
13             @MMDerived::ISA = qw(Class::MethodMapper
14             Exporter AutoLoader);
15             }
16              
17             sub new {
18             my $class = shift;
19             my @args = @_;
20              
21             my $self = Class::MethodMapper->new();
22             bless $self, $class;
23              
24             my %map = (
25             'time_style' => {
26             'type' => 'parameter',
27             'doc' => 'How recording duration is decided',
28             'domain' => 'enum',
29             'options' => [qw(track prompt fixed click_stop deadman)],
30             'value' => 'prompt',
31             },
32              
33             'iter_plan' => {
34             'type' => 'volatile',
35             'doc' => 'Currently active plan for iteration: perl code.',
36             'value' => 'play; color("yellow"); hold(0.75); color("red"); '
37             . 'record; color;' , # see FestVox::ScriptLang
38              
39             },
40             );
41              
42             $self->set_map(%map);
43             $self->set(@args) if @args;
44             $self;
45             }
46              
47             =head1 DESCRIPTION
48              
49             Class::MethodMapper takes a hash of hashes and creates
50             get() and set() methods, with (some) validation, for the
51             maps listed. Generally, a C is something that
52             can be saved and restored, whereas a C is not
53             serialized at save-time.
54              
55             =cut
56              
57              
58             package Class::MethodMapper;
59             $Class::MethodMapper::VERSION = "1.0";
60 1     1   841 use strict;
  1         3  
  1         42  
61              
62 1     1   5 use Exporter;
  1         2  
  1         38  
63 1     1   1331 use AutoLoader;
  1         1720  
  1         6  
64 1     1   932 use English;
  1         5498  
  1         6  
65 1     1   625 use Cwd;
  1         2  
  1         80  
66 1     1   1104 use Sys::Hostname;
  1         1506  
  1         79  
67 1     1   1082 use UNIVERSAL qw(isa);
  1         15  
  1         7  
68 1     1   1758 use IO::File;
  1         12358  
  1         157  
69 1     1   1432 use Data::Dumper;
  1         10811  
  1         94  
70              
71             BEGIN {
72 1     1   1102 @MethodMapper::ISA = qw(Exporter AutoLoader);
73             }
74              
75             =head1 CONSTRUCTORS
76              
77             =over 4
78              
79             =item new(@args)
80              
81             Creates and initializes an empty Class::MethodMapper.
82             Calls C with its arguments.
83              
84             =back
85              
86             =head1 BUILT-IN METHODS
87              
88             =over 4
89              
90             =cut
91              
92             sub new {
93 0     0 1   my $class = shift;
94 0           my $self = {};
95 0           bless $self, $class;
96            
97 0 0         $self->set(@_) if @_;
98              
99 0           return $self;
100             }
101              
102             sub clone {
103 0     0 0   my $self = shift;
104              
105 0           my %map = ($self->get_map('parameter'), $self->get_map('volatile'));
106 0           foreach my $key (keys %map) {
107 0           my $foo = {value => $map{$key}};
108 0           my $type = $self->get_meta ('type', $key);
109 0 0         $type && ($foo->{type} = $type);
110 0           my $doc = $self->get_meta ('doc', $key);
111 0 0         $doc && ($foo->{doc} = $doc);
112 0           my $domain = $self->get_meta ('domain', $key);
113 0 0         $domain && ($foo->{domain} = $domain);
114 0           my $options = $self->get_meta ('options', $key);
115 0 0         $options && ($foo->{options} = $options);
116 0           $map{$key} = $foo;
117             }
118 0           my $new = new Class::MethodMapper;
119 0           bless $new, ref ($self);
120 0           $new->set_map (%map);
121 0 0         $new->set (@_) if @_;
122 0           return $new;
123             }
124              
125             =item set_map(%map)
126              
127             Sets the complete map for this object. See FestVox::InitMap
128             for a good example of a method map; it is the big one that
129             FestVox::PointyClicky itself uses. This should be generalized
130             to let you set B map, as C below.
131              
132             =cut
133              
134             sub set_map {
135 0     0 1   my $self = shift;
136 0           my %map = @_;
137              
138 0           for my $k (keys %map) {
139 0           $self->{$k} = $map{$k};
140             }
141 0           $self;
142             }
143              
144             =item get_map($type)
145              
146             Get the map of a particular type, e.g. C. Note
147             that the object itself is the top-level (complete) map,
148             since Class::MethodMapper writes into variables in the object
149             of the same name; the 'map' itself is just the variables
150             of that C.
151              
152             =cut
153              
154             sub get_map {
155 0     0 1   my $self = shift;
156 0           my $type = shift;
157 0           my %map;
158              
159 0           for my $var (grep $self->{$_}->{type} eq $type, keys %$self) {
160             # bare metal here since it'll be called all the time.
161 0           $map{$var} = $self->{$var}->{value};
162             }
163 0           %map;
164             }
165              
166             =item delete_map(@mapnames)
167              
168             Delete the mapping for each variable in C<@mapnames>.
169              
170             =cut
171              
172             sub delete_map {
173 0     0 1   my $self = shift;
174 0           while (my $k = shift) {
175 0           delete $self->{$k};
176             }
177 0           $self;
178             }
179              
180             =item get_meta('type', 'var')
181              
182             Get the C data of a given type for a named variable
183             in th method map.
184              
185             type e.g. 'volatile', 'parameter'
186             doc some human-readable string do describe this
187             value current value; useful for initialization
188             domain e.g. 'enum' or 'ref'
189             options if domain is 'enum', an array reference of allowed values
190             if domain is 'ref', 'ARRAY', 'HASH' or the name of a class.
191              
192             =cut
193              
194             sub get_meta {
195 0     0 1   my $self = shift;
196 0           my $what = shift;
197 0           my $method = shift;
198 0 0 0       if (defined $self->{$method}
199             and defined $self->{$method}->{$what}) {
200 0           my $it = $self->{$method}->{$what};
201             # do something with ARRAY and HASH refs?
202 0           return($it);
203             } else {
204 0           undef;
205             # warn "$method does't have a meta type $what";
206             }
207             }
208              
209             =item set_meta('type', 'var', value)
210              
211             Just what you would think. Sets the C variable C
212             of C to C.
213              
214             =cut
215              
216             sub set_meta {
217 0     0 1   my $self = shift;
218 0           my $what = shift;
219 0           my $method = shift;
220 0           my $value = shift;
221 0 0         if (defined $self->{$method}) {
222 0           $self->{$method}->{$what} = $value;
223             } else {
224             # warn "$method does't have a meta type $what";
225             }
226 0           $self;
227             }
228              
229              
230             sub _enum_set {
231 0     0     my ($self, $key, $val) = @_;
232 0           my ($class) = $self =~ /^(.*?)=/g;
233              
234 0 0         if (defined (my $options = $self->{$key}->{options})) {
235 0 0         if (grep { $_ eq $val } @$options) {
  0            
236 0           $self->{$key}->{value} = $val;
237             } else {
238 0 0         if ($self =~ /^(.*?)=/) {
239 0           my $sane = $options->[0];
240 0           my $o = join ', ', @$options;
241 0           warn "${class}->$key: '$val' is not one of ($o). "
242             . "Using '$sane' instead.\n";
243 0           $self->{$key}->{value} = $sane;
244             }
245             }
246             } else {
247 0           $self->{$key}->{value} = $val;
248             }
249             }
250              
251             sub _ref_set {
252 0     0     my ($self, $key, $val) = @_;
253 0           my ($class) = $self =~ /^(.*?)=/g;
254              
255 0           my $ref = $self->{$key}->{options};
256 0 0         if (isa ($val, $ref)) {
257 0           $self->{$key}->{value} = $val;
258             } else {
259 0           warn "${class}->$key: '$val' is not a $ref\-ref. "
260             . "Using 'undef' instead.\n";
261 0           $self->{$key}->{value} = undef;
262             }
263             }
264              
265             =item set('var' => 'value')
266              
267             Set the variable C to
268             the value C<'value'>. Checks if C is in the method
269             map, and complains if it is not. Does basic type checking
270             if the C variable C is defined.
271              
272             This means it checks if the value is an element in the array
273             reference in C if C is 'enum' and checks if
274             the value is indeed a reference of the specified type
275             if C is 'ref'
276              
277             =cut
278              
279             sub set {
280 0     0 1   my $self = shift;
281              
282 0 0         if (@_) {
283 0           my $class;
284 0 0         if ($self =~ /^(.*?)=/) {
285 0           $class = $1;
286             }
287            
288 0           while (my $key = shift @_) {
289 0           my $val = shift @_;
290 0 0         if (not defined $self->{$key}) {
291 0           my ($p,$f,$l) = caller;
292 0 0         warn "$class doesn't have a(n) '$key' method [$f line $l]\n"
293             if $class;
294             } else {
295 1     1   10 no strict 'refs';
  1         2  
  1         278  
296 0           my $domain = $self->{$key}->{domain};
297 0 0         if ($domain) {
298 0           my $func = "_$domain\_set";
299 0           $self->$func ($key, $val);
300             } else {
301 0           $self->{$key}->{value} = $val;
302             }
303             }
304             }
305             }
306             }
307              
308             =item get('var')
309              
310             Return the value of 'var' if it is defined and in the
311             method map.
312              
313             =cut
314              
315             sub get {
316 0     0 1   my $self = shift;
317 0           my $method = shift;
318 0           my $caller_file = shift;
319 0           my $caller_line = shift;
320              
321 0 0         if ($self =~ m/^(.*?)=/) {
322 0           my $class = $1;
323            
324 0 0         if (not defined $self->{$method}) {
325 0           warn "MethodMapper: $self Can't AutoLoad instance method $method at $caller_file line $caller_line\n";
326 0           return undef;
327             } else {
328 0 0         if (not defined $self->{$method}->{type}) {
329             # warn "Unknown method call $method of type $type at $caller_file line $caller_line\n";
330 0           return undef;
331             }
332 0           return $self->{$method}->{value};
333             }
334             } else {
335 0           warn "MethodMapper: Can't invoke $method on $self at $caller_file line $caller_line\n";
336 0           return undef;
337             }
338             }
339              
340             sub AUTOLOAD {
341 0     0     my $self = shift ;
342              
343             # for $AUTOLOAD
344 1     1   6 no strict 'vars';
  1         2  
  1         1143  
345              
346 0           my $method = $AUTOLOAD;
347 0           $method =~ s/^.*:://;
348              
349 0 0         if (@_) {
350 0           $self->set($method => $_[0]);
351             } else {
352 0           my ($p, $file, $line) = caller;
353 0           $self->get($method, $file, $line);
354             }
355             }
356              
357              
358             sub DESTROY {
359 0     0     my $self = shift;
360              
361 0           for my $type (keys %$self) {
362 0           for my $param (keys %{$self->{$type}}) {
  0            
363 0           undef $self->{$type}->{$param};
364             }
365             }
366             #FIXME: find out what this was for, and how to change it to
367             #make it not give warnings on subclasses
368             #$self->SUPER::DESTROY;
369             }
370              
371             =item save('type', \&callback, @args)
372              
373             loops over all the keys that have type 'type' and calls
374              
375             &$callback ($self, $key, $value, @args);
376              
377             for each of them, where $key is the value of each key and $value
378             is the hashref for its value.
379              
380             =cut
381              
382             sub save {
383 0     0 1   my ($self, $type, $callback, @args) = @_;
384              
385 0           my %copy = $self->get_map($type);
386 0           foreach my $key (keys %copy) {
387 0           &$callback ($self, $key, $self->{$key}, @args);
388             }
389             }
390              
391             =item save_config ('filename')
392              
393             saves all 'parameter' type key/value pairs to 'filename'
394              
395             =cut
396              
397             sub save_config {
398 0     0 1   my $self = shift;
399 0           my $file = shift;
400              
401 0           my $fh = new IO::File (">$file");
402 0 0         unless (defined $fh) {
403 0           warn "MethodMapper: couldn't save state to $file: $!";
404 0           return 0;
405             }
406              
407 0           my $host = Sys::Hostname::hostname;
408 0           my $username = getpwuid($REAL_USER_ID);
409              
410 0           $self =~ /^(.*?)=/;
411 0           my $class = $1;
412              
413 0           print $fh "#\n";
414 0           print $fh "# $class Configuration\n";
415 0           print $fh "# Last modified: $username\@$host ".localtime()."\n";
416 0           print $fh "#\n\n";
417              
418             my $cb = sub {
419 0     0     my ($self, $key, $value) = @_;
420 0           my $v = '';
421              
422 0 0         if (not defined $value->{value}) {
423 0           $v = '';
424             } else {
425 0           $v = $value->{value};
426             }
427              
428 0           my $t = sprintf "%-20s", $key;
429 0           print $fh "\n";
430              
431 0           print $fh "# $value->{doc}\n";
432 0 0         if ($value->{domain} eq 'ref') {
433 0           local $Data::Dumper::Indent = 1;
434 0           local $Data::Dumper::Terse = 1;
435 0           print $fh "$t => ", Data::Dumper->Dump ([$v]);
436             } else {
437 0           print $fh "$t => $v\n";
438             }
439 0           };
440              
441 0           $self->save ('parameter', $cb);
442 0           print $fh "\n";
443 0           $fh->close;
444              
445 0           return 1;
446             }
447              
448             =item (\&callback, @args)
449              
450             loads earlier saved values of the object keys back by calling
451              
452             &$callback ($self, @args);
453              
454             it expects the callback to return a ($key, $value) list. keeps
455             looping till the callback function returns an undefined key.
456              
457             =cut
458              
459             sub restore {
460 0     0 0   my ($self, $callback, @args) = @_;
461              
462 0           while (1) {
463 0           my ($key, $value) = &$callback ($self, @args);
464 0 0         return unless defined $key;
465 0 0         if (defined $value) {
466 0           $self->set ($key, $value);
467             }
468             }
469             }
470              
471             =item restore_config ('filename')
472              
473             loads values from the file 'filename', which is in the format that
474             save_config writes out.
475              
476             =cut
477              
478             sub restore_config {
479 0     0 1   my ($self, $file) = @_;
480 0           my $fh = new IO::File ($file);
481              
482 0 0         unless (defined $fh) {
483 0           warn "MethodMapper: couldn't restore state from $file: $!\n";
484 0           return 0;
485             }
486             my $cb = sub {
487 0     0     my ($self) = @_;
488              
489             # we only do one var, but we need the while for multiline stuff
490 0 0         return undef if $fh->eof;
491 0           my ($reffirst, $key, $value);
492 0           while (<$fh>) {
493             #my $line = <$fh>;
494              
495 0 0         unless (/\S/) {
496             # try to catch runaway multilines by not allowing them to
497             # contain empty lines.
498 0           $reffirst = '';
499 0           next;
500             }
501 0 0         next if /^\#/; # comment: FIRST char is a #
502              
503 0           chomp;
504 0 0         if ($reffirst ne '') {
505 0 0         my $last = ']' if $reffirst eq '[';
506 0 0         $last = '}' if $reffirst eq '{';
507 0           my $line = $_;
508 0           $line =~ s/^\s+/ /;
509 0           $value .= $line;
510 0 0         next unless /^$last$/;
511 0           return ($key, eval ($value));
512 0           $reffirst = '';
513             }
514 0           ($key, $value) = split /\s+=>\s+/, $_, 2;
515 0 0         if (defined $key) {
516 0 0         if ($self->{$key}->{domain} eq 'ref') {
517 0 0 0       if ($value eq '[' or $value eq '{') {
518 0           $reffirst = $value;
519             }
520             } else {
521 0           return ($key, $value);
522             }
523             }
524             }
525 0           };
526              
527 0           $self->restore ($cb);
528 0           close $fh;
529              
530 0           return 1;
531             }
532              
533              
534             1;
535             __END__