File Coverage

blib/lib/Class/IntrospectionMethods/Catalog.pm
Criterion Covered Total %
statement 181 199 90.9
branch 40 60 66.6
condition 5 7 71.4
subroutine 37 40 92.5
pod 2 5 40.0
total 265 311 85.2


line stmt bran cond sub pod time code
1             # $Author: domi $
2             # $Date: 2004/12/13 12:20:10 $
3             # $Name: $
4             # $Revision: 1.4 $
5              
6             package Class::IntrospectionMethods::Catalog ;
7 16     16   57279 use strict ;
  16         30  
  16         590  
8 16     16   82 use warnings ;
  16         28  
  16         468  
9 16     16   77 use Carp ;
  16         29  
  16         3922  
10 16     16   20834 use Storable qw/dclone/;
  16         89096  
  16         2009  
11 16     16   20460 use Data::Dumper ;
  16         226006  
  16         1913  
12              
13             require Exporter;
14 16     16   146 use vars qw/$VERSION @ISA @EXPORT_OK @CARP_NOT/ ;
  16         26  
  16         3547  
15             @ISA = qw(Exporter);
16             @EXPORT_OK = qw(set_global_catalog set_method_info set_method_in_catalog);
17             @CARP_NOT=qw/Class::IntrospectionMethods/ ;
18              
19             $VERSION = sprintf "%d.%03d", q$Revision: 1.4 $ =~ /(\d+)\.(\d+)/;
20              
21             my $obsolete_behavior = 'carp' ;
22             my $support_legacy = 0 ;
23              
24             sub set_obsolete_behavior
25             {
26 15     15 0 118 $obsolete_behavior = shift;
27 15         52 $support_legacy = shift ;
28             }
29              
30             sub warn_obsolete
31             {
32 1 50   1 0 5 return if $obsolete_behavior eq 'skip' ;
33 16     16   96 no strict 'refs';
  16         31  
  16         6814  
34 0         0 $obsolete_behavior->(@_) ;
35             }
36              
37             =head1 NAME
38              
39             Class::IntrospectionMethods::Catalog - manage catalogs from IntrospectionMethods
40              
41             =head1 SYNOPSIS
42              
43             No synopsis. Directly used by Class::IntrospectionMethods
44              
45             =head1 DESCRIPTION
46              
47             This class handles slot catalogs for L.
48              
49             =cut
50              
51             # These lexical variables are also used in ClassCatalog and
52             # ObjectCatalog
53             my %construction_info ;
54             my %catalog_info ;
55              
56             =head1 Exported functions
57              
58             =head2 set_method_info( target_class, method_name, info_ref )
59              
60             Store construction info for method C of class
61             C.
62              
63             =cut
64              
65             sub set_method_info
66             {
67 91     91 1 1551 my ($target_class, $maker_slot_name, $info) = @_ ;
68 91         358 $construction_info{$target_class}{$maker_slot_name} = $info ;
69             }
70              
71             =head2 set_global_catalog (target_class, ...)
72              
73             Store catalog informations. The first parameter is the class featuring
74             the methods declared in the global catalog.
75              
76             Following paramaters is a set of named paramaters (e.g. key => value):
77              
78             =over
79              
80             =item name
81              
82             Mandatory name for the global catalog
83              
84             =item list
85              
86             array ref containing the list of slot and catalog. E.g.:
87              
88             list => [
89             [qw/foo bar baz/] => foo_catalog,
90             [qw/a b z/] => alpha_catalog,
91             my_object => my_catalog
92             ],
93              
94             =item isa
95              
96             Optional hash ref declaring a containment for catalog. E.g:
97              
98             list => [ 'foo' => 'USER' ,
99             'admin' => 'ROOT' ],
100             isa => { USER => 'ROOT' }
101              
102             Then the 'ROOT' catalog will return 'foo', and the 'USER' catalog will
103             return 'foo' and 'admin'.
104              
105             =item help
106              
107             Optional hash ref (C<< slot_name => help >>). Store some help
108             information for each slot.
109              
110             =back
111              
112             set_global_catalog will construct:
113              
114             =over
115              
116             =item *
117              
118             A ClassCatalog object containing the global catalog informations.
119              
120             =item *
121              
122             A sub_ref containing the ClassCatalog object in a closure.
123              
124             =back
125              
126             Returns ( C, sub_ref ). The sub_ref is to be installed in
127             the target class.
128              
129             When called as a class method, the subref will return the ClassCatalog
130             object. When called as a target class method, the subref will return
131             an ObjectCatalog object associated to the ClassCatalog object stored
132             in the closure.
133              
134             These 2 object have the same API. ObjectCatalog is used to contain
135             catalog changes that may occur at run-time. ClassCatalog informations
136             will not change.
137              
138             =cut
139              
140             # the closures defined here have a class scope not an object
141             # scope. I.e there's one storage per class
142              
143             sub set_global_catalog
144             {
145 6     6 1 891 my $target_class = shift ;
146 6         30 my %arg = @_ ;
147              
148 6 50       77 my $global_catalog_name = delete $arg{name}
149             or croak "set_global_catalog: no name defined";
150              
151             # get list of slot -> catalog
152 6 50       29 croak "set_global_catalog: no list defined" unless defined $arg{list};
153              
154             # this object is stored in the closure below
155 6         66 my $class_catalog = Class::IntrospectionMethods::ClassCatalog
156             -> new ( target_class => $target_class, %arg ) ;
157              
158             my $sub = sub
159             {
160 45     45   1452 my $self = shift ;
161 45 100 66     252 return $self->{$global_catalog_name} ||=
162             Class::IntrospectionMethods::ObjectCatalog ->
163             new ( class_catalog => $class_catalog ) if ref $self;
164 27         116 return $class_catalog ;
165 6         61 } ;
166              
167 6         105 $catalog_info{$target_class}=$sub ;
168              
169 6         18 my @methods = ($global_catalog_name, $sub ) ;
170              
171 6         55 return @methods ;
172             }
173              
174             sub set_method_in_catalog
175             {
176 17     17 0 28 my ($target_class,$slot,$catalog) = @_ ;
177              
178 17 50       45 croak "set_global_catalog was not called for class $target_class, ",
179             "Did you forgot to 'global_catalog' parameter in make_methods call ?"
180             unless defined $catalog_info{$target_class} ;
181              
182 17         25 my $f = $catalog_info{$target_class} ;
183              
184 17         35 &$f->add($slot,$catalog) ;
185             }
186              
187             1;
188              
189             package Class::IntrospectionMethods::AnyCatalog ;
190 16     16   104 use Carp;
  16         31  
  16         15063  
191              
192             # data : { catalog_list => { catalog_a => [slot1 slot2],
193             # catalog_b => [slot2 slot3]},
194             # slot_list => { slot1 => [catalog_a],
195             # slot2 => [catalog_a catalog_b],
196             # slot3 => [catalog_b]} },
197             # ordered_slot_list => [ slot1 slot2 slot3 ]
198              
199 0     0   0 sub all {confess "deprecated"}
200              
201             sub rebuild
202             {
203 0     0   0 my $self = shift ;
204              
205             # reset and rebuild slot list from catalog_list
206 0         0 delete $self->{slot_list} ;
207 0         0 foreach my $catalog (sort keys %{$self->{catalog_list}} )
  0         0  
208             {
209 0         0 map{ push @{$self->{slot_list}{$_}}, $catalog ;}
  0         0  
  0         0  
210 0         0 @{$self->{catalog_list}{$catalog}} ;
211             }
212             } ;
213              
214             =head1 ClassCatalog or ObjectCatalog methods
215              
216             =cut
217              
218             =head2 catalog( slot_name )
219              
220             Returns the catalogs names containing this slot (does not take into
221             accounts the isa stuff)
222              
223             Return either an array or an array ref depending on context.
224              
225             =cut
226              
227             sub catalog
228             {
229 17     17   8085 my ($self, $slot_name) = @_ ;
230              
231 17 50       71 croak "catalog: Missing slot name"
232             unless defined $slot_name;
233              
234             # returns the catalogs names containing this slot (does not take
235             # into accounts the isa stuff)
236 17         37 my $slist = $self->{slot_list} ;
237              
238 17 50       50 croak "catalog: unknown slot $slot_name, expected",
239             join(',',keys %$slist)
240             unless defined $slist->{$slot_name};
241              
242 17         25 my @result = @{$slist->{$slot_name}} ;
  17         56  
243              
244 17 50       104 return wantarray ? @result : \@result ;
245             }
246              
247             =head2 slot ( catalog_name, ... )
248              
249             Returns the slots contained in the catalogs passed as
250             arguments. (takes into accounts the isa parameter)
251              
252             =cut
253              
254             sub slot
255             {
256 31     31   6084 my $self = shift ;
257 31         75 my @all_cats = @_ ;
258              
259 31 50       91 croak "slot: Missing catalog name" unless @_ ;
260              
261 31         56 my $clist = $self->{catalog_list} ;
262              
263 31         64 foreach my $catalog_name (@all_cats)
264             {
265 32 50       131 if (not defined $clist->{$catalog_name})
266             {
267 0 0       0 if ($support_legacy)
268             {
269 0         0 $self->{catalog_list}{$catalog_name} = [] ;
270 0         0 $self->{class_catalog}->add_catalog($catalog_name) ;
271 0         0 Class::IntrospectionMethods::Catalog::warn_obsolete
272             ("Warning: undeclared catalog $catalog_name, Created ...");
273             }
274             else
275             {
276 0         0 croak "slot: unknown catalog $catalog_name, expected",
277             join(',',keys %$clist) ;
278             }
279             }
280             }
281              
282             # add inherited catalogs
283 32         186 push @all_cats,
284 31         70 map {$self->catalog_isa($_)} @all_cats ;
285              
286             #print "slot: @_ is @all_cats\n";
287 31         40 my @result ;
288 31         36 foreach my $slot (@{$self->ordered_slot_list()})
  31         136  
289             {
290 232         252 my @c = @{$self->{slot_list}{$slot}} ;
  232         616  
291 232         280 my %c ;
292 232         291 foreach my $c (@c) {$c{$c} = 1}
  255         618  
293 232         287 my %isect ;
294 232 100       299 foreach my $c (@all_cats) { $isect{$c} = 1 if $c{$c} }
  286         810  
295              
296 232 100       1024 push @result, $slot if scalar keys %isect ;
297             } ;
298              
299             #print "result is @result\n";
300 31 100       219 return wantarray ? @result : \@result ;
301             }
302              
303             =head2 all_slot()
304              
305             Return a list of all slots (respecting the order defined in
306             global_catalog).
307              
308             =cut
309              
310             sub all_slot
311             {
312 1     1   3 my $self = shift;
313 1         2 return @{$self->ordered_slot_list} ;
  1         7  
314             }
315              
316             =head2 all_catalog()
317              
318             Returns a sorted list of all defined catalogs.
319              
320             =cut
321              
322             sub all_catalog
323             {
324 8     8   96 my ($self) = @_ ;
325 8         16 return sort keys %{$self->{catalog_list}} ;
  8         99  
326             }
327              
328             #internal
329             sub update_catalog_list
330             {
331 15     15   34 my $self = shift ;
332              
333             # reset and update catalog lists (which is somewhat different from rebuild)
334 15         89 delete $self->{catalog} ;
335 15         48 foreach my $slot (sort keys %{$self->{slot_list}} )
  15         146  
336             {
337 89         98 map{ push @{$self->{catalog_list}{$_}}, $slot ;}
  89         391  
  78         227  
338 78         97 @{$self->{slot_list}{$slot}} ;
339             }
340             }
341              
342             package Class::IntrospectionMethods::ObjectCatalog ;
343              
344 16     16   121 use Carp;
  16         28  
  16         995  
345 16     16   92 use Storable qw(dclone) ;
  16         32  
  16         777  
346 16     16   76 use vars qw($AUTOLOAD @ISA);
  16         26  
  16         11192  
347              
348             @ISA = qw/Class::IntrospectionMethods::AnyCatalog/ ;
349              
350             sub new
351             {
352 4     4   16 my $type =shift ;
353 4         15 my $self = { @_ } ;
354              
355 4 50       22 croak __PACKAGE__,"->new: no class_catalog given" unless defined
356             $self->{class_catalog} ;
357              
358 4         24 $self->{slot_list} =
359             dclone($self->{class_catalog}->slot_list() ) ;
360              
361 4         17 bless $self, $type ;
362 4         47 $self->update_catalog_list ;
363              
364 4         42 return $self ;
365             }
366              
367             =head1 ObjectClass methods
368              
369             Unknown methods will be forwarded to associated ClassCatalog object.
370              
371             =head2 change( slot_name, catalog_name )
372              
373             Move the slot into catalog C.
374              
375             =cut
376              
377             sub change
378             {
379 4     4   2009 my ($self, $slot_name, $catalog_name) = @_ ;
380              
381 4 50       20 croak "set_catalog, change command: Missing slot name"
382             unless defined $slot_name;
383 4 50       19 croak "set_catalog, change command: Missing catalog name"
384             unless defined $catalog_name;
385              
386             # check new catalog
387 4 100       24 my @cat = ref $catalog_name ? sort @$catalog_name : ($catalog_name) ;
388             map
389             {
390 4 100       10 if (not defined $self->{catalog_list}{$_})
  5         35  
391             {
392 1 50       6 if ($support_legacy)
393             {
394 1         7 Class::IntrospectionMethods::Catalog::warn_obsolete("Warning: Undeclared catalog $_. Created...");
395 1         5 $self->{class_catalog}->add_catalog($_);
396 1         6 $self->{catalog_list}{$_} = [ $slot_name ] ;
397             }
398             else
399             {
400 0         0 croak "set_catalog, change command: unknown catalog ",
401             "$catalog_name, expected '",
402 0         0 join("','",keys %{$self->{catalog_list}}),"'\n"
403             }
404             }
405             } @cat ;
406              
407             # move slot from older catalog(s) to other(s)
408 4         14 $self->{slot_list}{$slot_name} = \@cat ;
409              
410 4         18 $self->update_catalog_list ;
411              
412 4         29 return @cat ;
413             }
414              
415             =head2 reset( slot_name )
416              
417             Put back slot in catalog as defined by global_catalog (and as stored
418             in ClassCatalog).
419              
420             =cut
421              
422             sub reset
423             {
424 1     1   6 my ($self, $slot_name) = @_ ;
425              
426 1 50       8 croak "set_catalog, change command: Missing slot name"
427             unless defined $slot_name;
428              
429             # move slot from older catalog(s) to other(s)
430 1         29 my @cat = $self->{class_catalog}->catalog($slot_name);
431 1         6 $self->{slot_list}{$slot_name} = \@cat ; ;
432              
433 1         6 $self->update_catalog_list ;
434              
435 1         6 return @cat ;
436             } ;
437              
438             # Used to provide legacy
439             sub add
440             {
441 1     1   1149 my ($self, $slot,$catalog) = @_ ;
442              
443 1 50       6 my @cat = ref $catalog ? @$catalog : ($catalog) ;
444 1         2 map { push @{$self->{catalog_list}{$_}}, $slot;} @cat ;
  2         4  
  2         9  
445 1         4 $self->{slot_list}{$slot} = \@cat ;
446              
447 1         7 $self->{class_catalog}->add($slot,$catalog) ;
448             }
449              
450             # forward unknown method to associated ClassCatalog
451             sub AUTOLOAD
452             {
453 54     54   5865 my $meth = $AUTOLOAD;
454 54         357 $meth =~ s/.*:://;
455 54 50       141 return if $meth eq 'DESTROY' ;
456 54         197 shift -> {class_catalog} -> $meth(@_) ;
457             }
458              
459             package Class::IntrospectionMethods::ClassCatalog ;
460              
461 16     16   109 use Carp;
  16         29  
  16         1131  
462 16     16   100 use vars qw($AUTOLOAD @ISA);
  16         31  
  16         33363  
463              
464             @ISA = qw/Class::IntrospectionMethods::AnyCatalog/ ;
465              
466             sub new
467             {
468 6     6   25 my $type = shift ;
469              
470 6         36 my $self = { @_ } ;
471              
472 6         35 my @user_list = @{$self -> {list}} ;
  6         29  
473 6         34 while (@user_list)
474             {
475 9         24 my ($slot,$cat) = splice @user_list,0,2 ;
476 9 100       36 my @slot = ref $slot ? @$slot : ($slot) ;
477 9 100       26 my @cat = ref $cat ? @$cat : ($cat) ;
478 22         41 map
479             {
480 9         23 push @{$self->{ordered_slot_list}}, $_ ;
  22         24  
481 22         87 $self->{slot_list}{$_} = \@cat ;
482             } @slot
483             }
484              
485 6         23 bless $self, $type ;
486 6         86 $self->update_catalog_list ;
487              
488 6         24 return $self ;
489             }
490              
491             sub slot_list
492             {
493 4     4   621 return $_[0]->{slot_list} ;
494             }
495              
496             sub ordered_slot_list
497             {
498 32     32   122 return $_[0]->{ordered_slot_list} ;
499             }
500              
501             sub catalog_list
502             {
503 0     0   0 return $_[0]->{catalog_list} ;
504             }
505              
506              
507             # To support legacy, catalogs can be added at run_time not sure it's a
508             # good idea for new application (too many way to mess things up)
509             sub add_catalog
510             {
511 1     1   4 my ($self, $catalog) = @_ ;
512 1   50     18 $self->{catalog_list}{$catalog} ||= [] ;
513             }
514              
515             sub add
516             {
517 18     18   34 my ($self, $slot,$catalog) = @_ ;
518 18         28 push @{$self->{ordered_slot_list}}, $slot ;
  18         80  
519              
520 18 100       58 my @cat = ref $catalog ? @$catalog : ($catalog) ;
521 18         33 map { push @{$self->{catalog_list}{$_}}, $slot;} @cat ;
  19         22  
  19         68  
522 18         102 $self->{slot_list}{$slot} = \@cat ;
523             }
524              
525             =head1 ClassCatalog methods
526              
527             =head2 help ( slot_name )
528              
529             Return the help info for slot_name that was given to
530             set_global_catalog. Return an empty string if no help was
531             provided. This help method is just a place holder, no fancy treatment
532             is done.
533              
534             =cut
535              
536             sub help
537             {
538 2     2   4 my $self = shift;
539 2   100     19 return $self->{help}{$_[0]} || '';
540             }
541              
542             sub catalog_isa
543             {
544 33     33   53 my ($self,$catalog_name)= @_ ;
545              
546 33 50       74 croak "set_catalog, isa command: Missing catalog name"
547             unless defined $catalog_name;
548              
549 33         52 my @result ;
550 33         44 my $next = $catalog_name ;
551 33         61 my $isa = $self->{isa} ;
552 33         131 while (defined $isa->{$next})
553             {
554 7         36 push @result, $next = $isa->{$next} ;
555             }
556 33         126 return @result ;
557             }
558              
559             =head2 info ( slot_name )
560              
561             Returns construction informations of slot_name. This is handy for
562             introspection of actual properties of slot C.
563              
564             The details are returned in an array that contains:
565              
566             =over 8
567              
568             =item *
569              
570             The slot type: i.e. either C scalar>,
571             C array> or C hash>.
572              
573             =item *
574              
575             If the index is tied (for C or C slot type), the array
576             will contain: C $tie_class>. If some constructor
577             arguments are used, the array will also contain C
578             =E \@args>.
579              
580             =item *
581              
582             If the target value (i.e. the scalar) is tied (for all slot types),
583             the array will contain: C $tie_class>. If some constructor
584             arguments are used, the array will also contain
585             C \@args>.
586              
587             =item *
588              
589             If the target value (i.e. the scalar) is a plain object (for all slot
590             types), the array will contain: C $class>. If some
591             constructor arguments are used, the array will also contain
592             C \@args>.
593              
594             =back
595              
596             =cut
597              
598             sub info
599             {
600 7     7   1196 my ($self, $slot_name) = @_ ;
601              
602 7         16 my $tgt = $self->{target_class} ;
603              
604 7         21 my $result = $construction_info{$tgt}{$slot_name};
605              
606 7 50       23 croak "no info on slot $slot_name (class $tgt)" unless
607             defined $result ;
608 7 100       64 return wantarray ? (ref $result eq 'HASH' ? %$result : @$result ) : $result ;
    100          
609             }
610              
611             1;
612              
613             __END__