File Coverage

blib/lib/Math/PartialOrder/Loader.pm
Criterion Covered Total %
statement 135 240 56.2
branch 54 122 44.2
condition 19 59 32.2
subroutine 18 26 69.2
pod 0 10 0.0
total 226 457 49.4


line stmt bran cond sub pod time code
1             # -*- Mode: Perl -*-
2              
3             #
4             # Copyright (c) 2001, Bryan Jurish. All rights reserved.
5             #
6             # This package is free software. You may redistribute it
7             # and/or modify it under the same terms as Perl itself.
8             #
9              
10             ###############################################################
11             #
12             # File: Math::PartialOrder::Loader.pm
13             # Author: Bryan Jurish
14             #
15             # Description: Load QuD Hierarchies from files
16             #
17             ###############################################################
18              
19              
20             package Math::PartialOrder::Loader;
21 7     7   1215 use Math::PartialOrder::Base;
  7         12  
  7         1180  
22             @ISA = qw(Exporter);
23             @EXPORT = qw();
24             @EXPORT_OK = qw($_tr_name $_tr_parents $_tr_attrs);
25             %EXPORT_TAGS = (
26             trvars => [qw($_tr_name $_tr_parents $_tr_attrs)],
27             );
28              
29             our $VERSION = 0.01;
30             our $BIN_COMPAT = 0.01;
31             our ($_tr_name, $_tr_parents, $_tr_attrs) = (0..2);
32              
33             our $PS_VIEWER = 'gv';
34             our @TMPFILES = qw();
35             our $UNLINK_TMPFILES = 1;
36              
37             package Math::PartialOrder::Base;
38 7     7   5354 use FileHandle;
  7         605933  
  7         46  
39 7     7   8318 eval "use Storable qw();";
  7         26060  
  7         95  
40 7     7   2796 eval "use GraphViz;";
  0         0  
  0         0  
41 7     7   8694 eval "use File::Temp;";
  7         80711  
  7         665  
42             Math::PartialOrder::Loader->import(q(:trvars));
43              
44             ###############################################################
45             # Constants
46             ###############################################################
47             # None
48              
49             ###############################################################
50             # perl-load
51             ###############################################################
52              
53             # usage: add_perl_class($class,%opts);
54             # + options:
55             # descend=>$bool
56             sub from_perl_isa {
57 0     0 0 0 my ($h,$class,%opts) = @_;
58 0         0 my %seen = ();
59 0 0       0 $opts{descend} = 1 unless (defined($opts{descend}));
60 0 0       0 if ($opts{descend}) {
61 0         0 my @q = ($class);
62 0         0 while (defined($class = shift(@q))) {
63 0 0       0 next if (exists($seen{$class}));
64 0         0 $seen{$class} = undef;
65 0         0 $h->add($class, @{$class.'::ISA'});
  0         0  
66 0         0 push(@q, grep { !exists($seen{$_}) } @{$class.'::ISA'});
  0         0  
  0         0  
67             }
68             } else {
69             # just add this class
70 0         0 $h->add($class,@{$class.'::ISA'});
  0         0  
71             }
72             }
73              
74              
75             ###############################################################
76             # Text-Load
77             ###############################################################
78              
79             #----------------------------------------------------------------------
80             # Public loading functions:
81             # load($filename,\%opts) => undef
82             # load($FileHandle,\%opts) => undef
83             # + loads in hierarchy from $filename or $FileHandle,
84             # wiping any existant data
85             # Private loading functions:
86             # _load($FileHandle,\%opts) => undef
87             # + top-level do the work of loading
88             #----------------------------------------------------------------------
89             sub load {
90 5     5 0 176 my $self = shift;
91 5         11 my $file = shift;
92 5 50       13 return $self->_load($file,@_) if (ref($file));
93              
94 5         37 my $handle = FileHandle->new("<$file");
95 5 50       524 croak("open failed for `$file': $!") unless (defined($handle));
96 5         50 my $rv = $self->_load($handle, @_);
97 5         24 $handle->close();
98 5         120 return $rv;
99             }
100              
101             sub _load {
102 5     5   10 my ($h,$fh,$args) = @_;
103 5 50       19 $args = {} unless (defined($args));
104              
105             # actual loading
106 5         12 my ($gtline,$subx,$sub,$super,@supers,%subattrs,$attref);
107 5 50       21 $args->{gtsep} = '>' unless (defined($args->{gtsep}));
108             # read in the hierarchy
109 5         111 while ($gtline = <$fh>) {
110 65         104 chomp($gtline);
111 65 100 66     259 if ($gtline =~ /^\s*root\s*=\s*(.*)/i && $1 ne $h->root) {
112 5         16 $h->replace($h->root,$1);
113 5         27 next;
114             }
115 60 100 66     468 next unless ($gtline !~ /^\s*\#/ && $gtline !~ /^\s*$/);
116 40         301 ($subx, $super) = split(/\s+(?:$args->{gtsep})\s+/, $gtline);
117              
118 40         202 ($sub, %subattrs) = split(/\s*[\[\]:,]\s*/, $subx);
119 40         297 $sub =~ s/^\s*(\S+)/$1/;
120 40 100       87 if (defined($super)) {
121 35         112 $super =~ s/^(\S+)\s*/$1/;
122 35         110 @supers = split(/\s*,\s*/, $super);
123             } else {
124 5         12 @supers = qw();
125             }
126              
127 40 100       114 if (!$h->has_type($sub)) { # We found a new subtype...
128 28         78 $h->add($sub, @supers);
129             }
130             else {
131             # subtype already defined -- add/override attributes
132 12         37 $attref = $h->_attributes($sub);
133             }
134              
135             # now, set the attributes
136 40 50       98 if (%subattrs) {
137 0   0     0 $attref = $h->_attributes($sub) || {};
138 0         0 %$attref = (%$attref, %subattrs);
139 0         0 $h->_attributes($sub, $attref);
140             }
141              
142 40 100       157 if (@supers) { # HACK!
143 35         109 $h->move($sub, @supers);
144             }
145             #elsif (! grep { $_ eq $sub } $h->root()) {
146             # warn("Undefined supertype ",
147             # (defined($super) ? "'$super'" : ''),
148             # " for subtype '$sub' during text load.\n");
149             #}
150             }
151 5         24 return $h;
152             }
153              
154              
155             ##############################################################
156             # Text-Save
157             ###############################################################
158              
159             #----------------------------------------------------------------------
160             # Public saving functions:
161             # save($filename) => undef
162             # save($FileHandle) => undef
163             # Private saving functions:
164             # _save($FileHandle) => undef
165             #----------------------------------------------------------------------
166             sub save {
167 5     5 0 195 my $self = shift;
168 5         9 my $file = shift;
169 5 50       12 return $self->_save($file,@_) if (ref($file));
170              
171 5         49 my $handle = FileHandle->new(">$file");
172 5 50       992 croak("open failed for `$file': $!") unless (defined($handle));
173 5         47 $self->_save($handle,@_);
174 5         22 $handle->close();
175             }
176              
177             sub _save {
178 5     5   10 my ($h,$fh,$args) = @_;
179 5 50       34 $args = {} unless (defined($args));
180              
181             # actual save
182 5         9 my ($sub,@supers,$super,$attrs);
183 5   33     226 print $fh ("# Save-file auto-generated by ", __PACKAGE__ , "::save\n",
184             "# Hierarchy Class = ", ref($h) || $h, "\n",
185             "# Hierarchy Version = ", $h->VERSION, "\n",
186             "# Loader Version = ", Math::PartialOrder::Loader->VERSION, "\n",
187             "ROOT=",$h->root,"\n");
188              
189 5         51 foreach $sub ($h->types) {
190 40         120 @supers = $h->parents($sub);
191 40         138 $attrs = $h->_attributes($sub);
192 0         0 print $fh ("$sub [",
193             (defined($attrs)
194 40 50       144 ? join(',', map { "$_:$attrs->{$_}" } keys(%$attrs))
195             : qw()),
196             "]",
197             $sub ne $h->root && @supers ?
198             (
199             "\t >\t ",
200             join(',',
201             grep {
202 40 50 66     154 defined($_) && $h->has_type($_)
    100          
203             } @supers)
204             ) : qw(),
205             "\n");
206             }
207 5         18 return $h;
208             }
209              
210              
211              
212             ##############################################################
213             # Visualization
214             ###############################################################
215             sub graphviz {
216 0     0 0 0 my ($h,%opts) = @_;
217 0 0       0 $opts{nodelabel} = ":NAME:\n:ATTRIBUTES:" unless (exists($opts{nodelabel}));
218 0 0       0 $opts{label_node} = \&_gv_label_node unless (exists($opts{label_node}));
219              
220 0         0 my $g = GraphViz->new(directed => 1,
221             rankdir => 0, # top->bottom linking
222             node => {
223             shape => 'plaintext',
224             },
225             edge => {
226             dir => 'none',
227             },
228             %opts);
229 0         0 my ($type,$parent,$label);
230              
231             # add nodes
232 0         0 foreach $type ($h->types) {
233 0 0 0     0 if (ref($opts{label_node}) && ref($opts{label_node}) eq 'CODE') {
234 0         0 $label = &{$opts{label_node}}($h,$type,\%opts);
  0         0  
235             } else {
236 0         0 $label = "$type";
237             }
238 0         0 $g->add_node($type, label => $label);
239             }
240              
241             # add edges
242 0         0 foreach $type ($h->types) {
243 0         0 foreach $parent ($h->parents($type)) {
244 0         0 $g->add_edge($type, $parent);
245             }
246             }
247 0         0 return $g;
248             }
249              
250             # label_node callback($h,$t,$opts)
251             sub _gv_label_node {
252 0     0   0 my ($h,$type,$opts) = @_;
253 0         0 my ($label);
254 0 0       0 if ($h->can('get_appr_bytype')) {
255             # we have Approp
256 0         0 $label = "$type";
257 0         0 my $appr = $h->get_appr_bytype($type);
258 0         0 my ($f);
259 0         0 foreach $f (keys(%$appr)) {
260 0         0 $label .= "\n$f:$appr->{$f}";
261             }
262             } else {
263             # default labelling (hack)
264 0         0 my $attrs = $h->_attributes($type);
265 0         0 my $attrstr = join("\n", map { "$_:" . $attrs->{$_} } keys(%$attrs));
  0         0  
266 0         0 $label = $opts->{nodelabel};
267 0         0 $label =~ s/:NAME:/$type/;
268 0         0 $label =~ s/:ATTRIBUTES:/$attrstr/;
269             }
270 0         0 return $label;
271             }
272              
273             ###############################################################
274             # Viewing Utility
275             ###############################################################
276             *gv = \&viewps;
277             sub viewps {
278 0     0 0 0 my $h = shift;
279 0         0 my ($fh,$filename) = File::Temp::tempfile('hiXXXXXX', SUFFIX => '.ps');
280 0         0 $fh->print($h->graphviz->as_ps);
281 0         0 close($fh);
282 0         0 system("$Math::PartialOrder::Loader::PS_VIEWER \"$filename\" &");
283 0 0       0 if ($Math::PartialOrder::Loader::UNLINK_TMPFILES) {
284 0         0 push(@Math::PartialOrder::Loader::TMPFILES,$filename);
285 0         0 sleep(1);
286             }
287             }
288              
289             ##############################################################
290             # Binary store/retrieve
291             ###############################################################
292              
293             # $h->store($file)
294             sub store {
295 5     5 0 12 my ($h,$file) = @_;
296              
297 5 50       76 my $handle = ref($file) ? $file : FileHandle->new(">$file");
298 5 50       912 croak("open failed for file `$file': $!") unless (defined($handle));
299              
300 5         51 my $storeme = $h->_store;
301 5         37 Storable::store_fd($storeme->{Head}, $handle); # store headers first
302 5         928 delete($storeme->{Head}); # ... and only once
303 5         16 Storable::store_fd($storeme, $handle);
304              
305 5 50       298 $handle->close() unless (ref($file));
306 5         565 return $h;
307             }
308             # $h->nstore($file)
309             sub nstore {
310 0     0 0 0 my ($h,$file) = @_;
311              
312 0 0       0 my $handle = ref($file) ? $file : FileHandle->new(">$file");
313 0 0       0 croak("open failed for file `$file': $!") unless (defined($handle));
314              
315 0         0 my $storeme = $h->_store;
316 0         0 Storable::nstore_fd($storeme->{Head}, $handle); # store headers first
317 0         0 delete($storeme->{Head}); # ... and only once
318 0         0 Storable::nstore_fd($storeme, $handle);
319              
320 0 0       0 $handle->close() unless (ref($file));
321 0         0 return $h;
322             }
323             # $h->retrieve($file)
324             sub retrieve {
325 5     5 0 205 my $h = shift;
326 5         14 my $file = shift;
327              
328 5 50       46 my $handle = ref($file) ? $file : FileHandle->new("<$file");
329 5 50       382 croak("open failed for file `$file': $!") unless (defined($handle));
330              
331             # get and check headers
332 5         22 my $head = Storable::retrieve_fd($handle);
333 5 50       265 unless (defined($h->_retrieve_head($head))) {
334 0   0     0 carp("Error: retrieve($file) failed for hierarchy of class `", ref($h) || $h, "'");
335 0         0 return $h;
336             }
337              
338             # do the retrieval
339 5         23 my $retr = Storable::retrieve_fd($handle);
340 5         239 $retr->{Head} = $head;
341 5         43 my $rv = $h->_retrieve($retr);
342              
343             # and clean things up
344 5 50       27 $handle->close() unless (ref($file));
345 5         153 return $rv;
346             }
347              
348              
349             ##############################################################
350             # In-Memory Store/Retrieve
351             ###############################################################
352              
353             # $frozen = $h->freeze();
354 0     0 0 0 sub freeze { return Storable::freeze($_[0]->_store); }
355              
356             # $h->thaw($frozen);
357 0     0 0 0 sub thaw { return $_[0]->_retrieve(Storable::thaw($_[1])); }
358              
359              
360              
361             #--------------------------------------------------------------
362             # Storage: $h->_store() => $Ref_To_Store
363             # + hooks:
364             # _store_before(\%storeme),
365             # _store_type(\@typerec,\%storeme)
366             # _store_after(\%storeme)
367              
368             #--------------------------------------------------------------
369             # $headers = $h->_store_head(),
370             # $headers = $class->_store_head()
371             sub _store_head {
372 5     5   10 my $h = shift;
373             return
374             {
375 5   33     181 Class => ref($h) || $h,
376             Cversion => $h->VERSION,
377             Ccompat => $h->_get_bin_compat,
378             Hstring => "$h",
379             Lversion => $Math::PartialOrder::Loader::VERSION,
380             Lcompat => $Math::PartialOrder::Loader::BIN_COMPAT
381             };
382             }
383              
384             #--------------------------------------------------------------
385             sub _store {
386 5     5   9 my $h = shift;
387 5   33     18 my $class = ref($h) || $h;
388 5         57 my $head = $h->_store_head();
389 5         53 my $refs = { "$h" => $h->_hattributes };
390 5         12 my $trs = [];
391 5         21 my $storeme = { Head => $head,
392             Refs => $refs,
393             Types => $trs };
394              
395             # preliminary storage-hook
396 5 100       80 $h->_store_before($storeme) if ($h->can('_store_before'));
397              
398 5         7 my ($tr,$attrs);
399 5         17 foreach ($h->types) {
400             # update nested refs
401 40         101 $refs->{$_} = $_;
402 40         107 $attrs = $h->_attributes($_);
403 40 50       81 $refs->{$attrs} = $attrs if (defined($attrs));
404              
405             # create the type-record
406 40         163 $tr = [
407             "$_", # name
408 40 50       245 [ map { "$_" } $h->parents($_) ], # parents
409             defined($attrs)
410             ? "$attrs" # attrs | undef
411             : undef
412             ];
413              
414             # type-storage hook
415 40 100       220 $h->_store_type($tr,$storeme) if ($h->can('_store_type'));
416              
417 40         50 push(@{$trs}, $tr);
  40         92  
418             }
419              
420             # post-processing hook
421 5 50       56 $h->_store_after($storeme) if ($h->can('_store_after'));
422              
423 5         16 return $storeme;
424             }
425              
426             #--------------------------------------------------------------
427             # + \%tostore|\%retrieved format:
428             # { Head => \%Headers,
429             # Refs => \%RefsByString,
430             # Types => \@TypeRecs, ... }
431             #
432             # + \%Headers format:
433             # { Class => $ClassName,
434             # Cversion => $ClassVersion,
435             # Hstring => "$HierarchyAsString",
436             # Lversion => $LoaderVersion,
437             # Lcompat => $MinLoaderVersion }
438             #
439             # + \%RefsByString format: { "$OldStringVal" => $Reference }
440             # + \@TypeRecs format: [ \@TypeRec1, ..., \@TypeRecN ]
441             # + \@TypeRec format: [ "$TypeName", \@ParentsNames, "$AttrsName" ]
442             #--------------------------------------------------------------
443              
444              
445             #--------------------------------------------------------------
446             # Compatibility
447             #--------------------------------------------------------------
448 0     0   0 sub _is_bin_compat { return undef; } # just a dummy
449 3     3   36 sub _get_bin_compat { return {}; } # just a dummy
450              
451              
452             #--------------------------------------------------------------
453             # Retrieval: $h->_retrieve(\%retrieved) => $h
454             # + hooks:
455             # _retrieve_before(\%storeme),
456             # _retrieve_type_before(\@typerec,\%storeme)
457             # _retrieve_type(\@typerec,\%storeme)
458             # _retrieve_type_after(\@typerec,\%storeme)
459             # _retrieve_after(\%storeme)
460             #
461             sub _retrieve {
462 5     5   9 my ($h,$retr) = @_;
463 5         8 my ($compat);
464              
465             # can we do this?
466 5 50       16 unless (defined($compat = $h->_retrieve_head($retr->{Head}))) {
467 0   0     0 carp("Error: _retrieve() failed for hierarchy of class `", ref($h) || $h, "'");
468 0         0 return $h;
469             }
470              
471             # get the hierarchy attributes...
472 5         35 $h->_hattributes($retr->{Refs}{$retr->{Head}{Hstring}});
473              
474             # preliminary retrieval
475 5 50 33     140 $h->_retrieve_before($retr) if ($compat && $h->can('_retrieve_before'));
476              
477             # get the types
478 5         11 my $typerecs = $retr->{Types};
479 5         5 my ($tr);
480 5         11 foreach $tr (@$typerecs) {
481             # preliminary type-retrieval
482 40 50 33     229 $h->_retrieve_type_before($tr,$retr) if ($compat && $h->can('_retrieve_type_before'));
483              
484             # get the actual type
485 40 50 33     211 if ($compat && $h->can('_retrieve_type')) {
486             # override
487 40         87 $h->_retrieve_type($tr,$retr);
488             } else {
489             # defaults
490 0         0 _retrieve_type($h,$tr,$retr);
491             }
492              
493             # postprocessing
494 40 50 33     258 $h->_retrieve_type_after($tr,$retr) if ($compat && $h->can('_retrieve_type_after'));
495             }
496              
497 5 100 66     63 $h->_retrieve_after($retr) if ($compat && $h->can('_retrieve_after'));
498 5         13 return $h;
499             }
500              
501              
502             # $h->_retrieve_type($rec,$retr)
503             # $retr = { Head => \@Hdrs, Types => \@TypeRecs, Refs => \%Refs }
504             # $tr = [ $Name, \@Parents, $AttrsName ]
505             sub _retrieve_type {
506 24         82 $_[0]->add($_[2]->{Refs}{$_[1]->[$_tr_name]},
507 24     24   58 @{$_[2]->{Refs}}{@{$_[1]->[$_tr_parents]}});
  24         47  
508 24 50       91 $_[0]->_attributes
509             ($_[2]->{Refs}{$_[1]->[$_tr_name]},
510             $_[2]->{Refs}{$_[1]->[$_tr_attrs]}) if (defined($_[1]->[$_tr_attrs]));
511             }
512              
513             # $bool_or_undef = $h->_retrieve_head($head)
514             sub _retrieve_head {
515 10     10   19 my ($h,$head) = @_;
516 10         13 my ($class,$compat);
517              
518             # check reftype & existence
519 10 50 33     62 unless (ref($head) && ref($head) eq 'HASH') {
520 0         0 carp("Warning: non-hashref '$head' cannot be header");
521 0         0 return undef;
522             }
523              
524             # does the stored hierarchy have a version?
525 10 50       28 if (!defined($head->{Lversion})) {
526 0         0 carp("Warning: stored hierarchy has no Lversion");
527 0         0 return undef;
528             }
529              
530             # does the stored hierarchy have a version?
531 10 50       26 if (!defined($head->{Lcompat})) {
532 0         0 carp("Warning: stored hierarchy has no Lcompat");
533 0         0 return undef;
534             }
535              
536             # does the stored hierarchy have a hierarchy-string?
537 10 50       24 if (!defined($head->{Hstring})) {
538 0         0 carp("Warning: stored hierarchy has no Hstring");
539 0         0 return undef;
540             }
541              
542             # is the storage-routine too old?
543 10 50       28 if ($head->{Lversion} < $Math::PartialOrder::Loader::BIN_COMPAT) {
544 0         0 carp("Warning: obsolete Math::PartialOrder::Loader stored hierarchy\n",
545             " > stored version = $head->{Lversion}\n",
546             " > required version >= $Math::PartialOrder::Loader::BIN_COMPAT\n",
547             " >");
548 0         0 return undef;
549             }
550              
551             # is this load-routine too old?
552 10 50       28 if ($Math::PartialOrder::Loader::VERSION < $head->{Lcompat}) {
553 0         0 carp("Warning: obsolete Math::PartialOrder::Loader retrieval routine\n",
554             " > this version = $Math::PartialOrder::Loader::VERSION\n",
555             " > required version >= $head->{Lcompat}\n",
556             " >");
557 0         0 return undef;
558             }
559              
560             # do we have class?
561 10 50       26 if (!defined($head->{Class})) {
562 0         0 carp("Warning: stored hierarchy has no Class!");
563 0         0 return 0;
564             }
565              
566             # do we have class-version?
567 10 50       20 if (!defined($head->{Cversion})) {
568 0         0 carp("Warning: stored hierarchy has no Cversion");
569 0         0 return 0;
570             }
571              
572             # is the stored hierarchy the same class & version as the caller?
573 10 50 33     222 if (defined($class = (ref($h)||$h))
      33        
      33        
574             && $head->{Class} eq $class
575             && $head->{Cversion} == $h->VERSION)
576             {
577 10         41 return 1; # whew.
578             }
579              
580             # does the stored thingy have compatibility hash?
581 0 0         if (!defined($head->{Ccompat})) {
582 0           carp("Warning: stored hierarchy has no Ccompat");
583 0           return 0;
584             }
585              
586             # the stored hierarchy knows something about us, too...
587 0 0         if (defined($head->{Ccompat}{$class})) {
588 0 0         if ($head->{Ccompat}{$class} > $h->VERSION) {
589 0           carp("Warning: binary-incompatible hierarchy package detected\n",
590             " > this class = `$class'\n",
591             " > stored class = `$head->{Class}'\n",
592             " > this version = ", $class->VERSION, "\n",
593             " > required version >= $head->{Ccompat}{$class}\n",
594             " > (maybe it's time to update?)");
595 0           return 0;
596             }
597             }
598              
599             # finally, lookup in the retrieving hierarchy
600 0 0 0       if (defined($compat = $h->_get_bin_compat)
601             && defined($compat->{$head->{Class}})) {
602 0 0         if ($compat->{$head->{Class}} > $head->{Cversion})
603             {
604             # just issue a warning
605 0           carp("Warning: binary-incompatible stored hierarchy detected\n",
606             " > stored class = `$head->{Class}'\n",
607             " > this class = `$class'\n",
608             " > stored version = $head->{Cversion}\n",
609             " > required version >= $compat->{$head->{Class}}\n",
610             " > (maybe it's time to recompile?)");
611 0           return 0;
612             }
613 0           return 1;
614             }
615              
616             # use the defaults
617 0           return 0;
618             }
619              
620              
621             END {
622 7 50   7   2747 unlink(@Math::PartialOrder::Loader::TMPFILES) if
623             ($Math::PartialOrder::Loader::UNLINK_TMPFILES);
624             }
625              
626             1;
627             __END__