File Coverage

blib/lib/Class/EHierarchy.pm
Criterion Covered Total %
statement 699 798 87.5
branch 221 318 69.5
condition 36 69 52.1
subroutine 88 88 100.0
pod 25 25 100.0
total 1069 1298 82.3


line stmt bran cond sub pod time code
1             # Class::EHierarchy -- Base class for hierarchally ordered objects
2             #
3             # (c) 2017, Arthur Corliss
4             #
5             # $Id: lib/Class/EHierarchy.pm, 2.01 2019/05/23 07:29:49 acorliss Exp $
6             #
7             # This software is licensed under the same terms as Perl, itself.
8             # Please see http://dev.perl.org/licenses/ for more information.
9             #
10             #####################################################################
11              
12             #####################################################################
13             #
14             # Environment definitions
15             #
16             #####################################################################
17              
18             package Class::EHierarchy;
19              
20 7     7   410957 use 5.008003;
  7         81  
21              
22 7     7   40 use strict;
  7         12  
  7         155  
23 7     7   33 use warnings;
  7         13  
  7         271  
24 7     7   53 use vars qw($VERSION @EXPORT @EXPORT_OK %EXPORT_TAGS);
  7         14  
  7         529  
25 7     7   41 use base qw(Exporter);
  7         13  
  7         1135  
26 7     7   49 use Carp;
  7         11  
  7         545  
27 7     7   50 use Scalar::Util qw(weaken);
  7         11  
  7         620  
28              
29             ($VERSION) = ( q$Revision: 2.01 $ =~ /(\d+(?:\.(\d+))+)/sm );
30              
31             # Ordinal indexes for the @objects element records
32 7     7   52 use constant CEH_OREF => 0;
  7         14  
  7         735  
33 7     7   48 use constant CEH_PID => 1;
  7         13  
  7         380  
34 7     7   43 use constant CEH_PKG => 2;
  7         12  
  7         379  
35 7     7   73 use constant CEH_CLASSES => 3;
  7         15  
  7         361  
36 7     7   50 use constant CEH_CREF => 4;
  7         33  
  7         404  
37              
38             # Ordinal indexes for the @properties element records
39 7     7   56 use constant CEH_PATTR => 0;
  7         22  
  7         359  
40 7     7   43 use constant CEH_PNAME => 1;
  7         13  
  7         343  
41 7     7   40 use constant CEH_PPKG => 1;
  7         14  
  7         327  
42 7     7   42 use constant CEH_PVAL => 2;
  7         29  
  7         354  
43              
44             # Property attribute masks
45 7     7   42 use constant CEH_PATTR_SCOPE => 7;
  7         11  
  7         309  
46 7     7   52 use constant CEH_PATTR_TYPE => 504;
  7         12  
  7         396  
47              
48             # Property attribute scopes
49 7     7   59 use constant CEH_PUB => 1;
  7         20  
  7         339  
50 7     7   41 use constant CEH_RESTR => 2;
  7         18  
  7         318  
51 7     7   39 use constant CEH_PRIV => 4;
  7         13  
  7         322  
52              
53             # Property attribute types
54 7     7   39 use constant CEH_SCALAR => 8;
  7         12  
  7         433  
55 7     7   46 use constant CEH_ARRAY => 16;
  7         12  
  7         320  
56 7     7   39 use constant CEH_HASH => 32;
  7         14  
  7         283  
57 7     7   36 use constant CEH_CODE => 64;
  7         11  
  7         308  
58 7     7   39 use constant CEH_REF => 128;
  7         12  
  7         305  
59 7     7   46 use constant CEH_GLOB => 256;
  7         28  
  7         340  
60              
61             # Property flags
62 7     7   41 use constant CEH_NO_UNDEF => 512;
  7         20  
  7         1742  
63              
64             @EXPORT = qw();
65             @EXPORT_OK = qw(CEH_PUB CEH_RESTR CEH_PRIV CEH_SCALAR CEH_ARRAY
66             CEH_HASH CEH_CODE CEH_REF CEH_GLOB CEH_NO_UNDEF _declProperty
67             _declMethod );
68             %EXPORT_TAGS = ( all => [@EXPORT_OK] );
69              
70             #####################################################################
71             #
72             # Module code follows
73             #
74             #####################################################################
75              
76             ##########################################################
77             # Hierarchal code support
78             ##########################################################
79              
80             {
81              
82             # Array of object references and metadata
83             my @objects;
84              
85             # Array of recycled IDs availabe for use
86             my @recoveredIDs;
87              
88             sub _dumpObjects {
89              
90             # Purpose: Provides a list of objects
91             # Returns: List of refs
92             # Usage: @objects = _dumpObjects();
93              
94 13     13   2972 return map { $$_[CEH_OREF] } grep {defined} @objects;
  9         27  
  32         248  
95             }
96              
97             sub _getID {
98              
99             # Purpose: Generates and assigns a unique ID to the passed
100             # object, and initializes the internal records
101             # Returns: Integer
102             # Usage: $id = _genID();
103              
104 24     24   48 my $obj = CORE::shift;
105 24 100       72 my $id = @recoveredIDs ? CORE::shift @recoveredIDs : $#objects + 1;
106              
107 24         100 $$obj = $id;
108 24         58 $objects[$id] = [];
109 24         55 $objects[$id][CEH_CREF] = [];
110 24         48 $objects[$id][CEH_CLASSES] = [];
111 24         44 $objects[$id][CEH_OREF] = $obj;
112 24         57 $objects[$id][CEH_PKG] = ref $obj;
113 24         97 weaken( $objects[$$obj][CEH_OREF] );
114              
115 24 100       70 $id = '0 but true' if $id == 0;
116              
117             # Build object class list
118             {
119 7     7   52 no strict 'refs';
  7         14  
  7         12406  
  24         38  
120              
121 24         44 my ( $isaref, $tclass, $nclass, @classes, $n, $l );
122 24         45 my $class = ref $obj;
123              
124             # Get the first level of classes we're subclassed from
125 24         36 $isaref = *{"${class}::ISA"}{ARRAY};
  24         90  
126 24 50       68 $isaref = [] unless defined $isaref;
127 24         61 foreach $tclass (@$isaref) {
128 24 100 100     210 CORE::push @classes, $tclass
129             if $tclass ne __PACKAGE__
130             and "$tclass"->isa(__PACKAGE__);
131             }
132              
133             # Now, recurse into parent classes.
134 24         46 $n = 0;
135 24         42 $l = scalar @classes;
136 24         63 while ( $n < $l ) {
137 7         30 foreach $tclass ( @classes[ $n .. ( $l - 1 ) ] ) {
138 7         14 $isaref = *{"${tclass}::ISA"}{ARRAY};
  7         22  
139 7 50       26 $isaref = [] unless defined $isaref;
140 7         20 foreach $nclass (@$isaref) {
141 7 50 33     38 CORE::push @classes, $nclass
142             if $nclass ne __PACKAGE__
143             and "$nclass"->isa(__PACKAGE__);
144             }
145             }
146 7         20 $n = scalar @classes - $l + 1;
147 7         18 $l = scalar @classes;
148             }
149              
150             # Add our current class
151 24         50 CORE::push @classes, $class;
152              
153             # Save the list
154 24         48 foreach (@classes) { _addClass( $obj, $_ ) }
  31         107  
155             }
156              
157 24         54 return $id;
158             }
159              
160             sub _delID {
161              
162             # Purpose: Recovers the ID for re-use while deleting the
163             # old data structures
164             # Returns: Boolean
165             # Usage: _recoverID($id);
166              
167 24     24   37 my $obj = CORE::shift;
168 24         57 my $pid = $objects[$$obj][CEH_PID];
169 24         36 my @children = @{ $objects[$$obj][CEH_CREF] };
  24         51  
170              
171             # Have the parent disown this child
172 24 100       74 _disown( $objects[$pid][CEH_OREF], $obj ) if defined $pid;
173 24 50       54 _disown( $obj, $objects[$_][CEH_OREF] ) if @children;
174              
175             # Clean up internal data structures
176 24         64 $objects[$$obj] = undef;
177 24         52 CORE::push @recoveredIDs, $$obj;
178              
179 24         39 return 1;
180             }
181              
182             sub isStale {
183              
184             # Purpose: Checks to see if the object reference is
185             # stale
186             # Returns: Boolean
187             # Usage: $rv = $obj->isStale;
188              
189 397     397 1 589 my $obj = CORE::shift;
190              
191 397   100     2846 return not( defined $obj
192             and defined $objects[$$obj]
193             and defined $objects[$$obj][CEH_OREF]
194             and $obj eq $objects[$$obj][CEH_OREF] );
195             }
196              
197             sub _addClass {
198              
199             # Purpose: Records a super class for the object
200             # Returns: Boolean
201             # Usage: $rv = _addClass($obj, $class);
202              
203 31     31   51 my $obj = CORE::shift;
204 31         47 my $class = CORE::shift;
205              
206 31         81 CORE::push @{ $objects[$$obj][CEH_CLASSES] }, $class
207             if defined $class
208 31 50 33     79 and not grep /^$class$/s, @{ $objects[$$obj][CEH_CLASSES] };
  31         230  
209              
210 31         82 return 1;
211             }
212              
213             sub _getClasses {
214              
215             # Purpose: Returns a list of classes
216             # Returns: Array
217             # Usage: @classes = _getClasses($obj);
218              
219 96     96   138 my $obj = CORE::shift;
220              
221 96         121 return @{ $objects[$$obj][CEH_CLASSES] };
  96         285  
222             }
223              
224             sub _adopt {
225              
226             # Purpose: Updates the object records to establish the relationship
227             # Returns: Boolean
228             # Usage: $rv = _adopt($parent, @children);
229              
230 8     8   14 my $obj = CORE::shift;
231 8         21 my @orphans = @_;
232 8         13 my $rv = 1;
233 8         12 my $child;
234              
235 8         28 foreach $child (@orphans) {
236 9 50       33 next if $child->isStale;
237 9 50       34 if ( !defined $objects[$$child][CEH_PID] ) {
238              
239             # Eligible for adoption, record the relationship
240 9         20 $objects[$$child][CEH_PID] = $$obj;
241 9         17 CORE::push @{ $objects[$$obj][CEH_CREF] }, $child;
  9         29  
242              
243             } else {
244              
245             # Already adopted
246 0 0       0 if ( $objects[$$child][CEH_PID] != $$obj ) {
247 0         0 $@ = "object $$child already adopted by another parent";
248 0         0 carp $@;
249 0         0 $rv = 0;
250             }
251             }
252             }
253              
254             # Merge aliases
255 8         33 $obj->_mergeAliases;
256              
257 8         18 return $rv;
258             }
259              
260             sub _disown {
261              
262             # Purpose: Severs the relationship between the parent and children
263             # Returns: Boolean
264             # Usage: $rv = _disown($parent, @children);
265              
266 9     9   17 my $obj = CORE::shift;
267 9         18 my @orphans = @_;
268 9         31 my $rv = 1;
269 9         17 my ($child);
270              
271 9         20 foreach $child (@orphans) {
272 9 50 33     48 if ( defined $objects[$$child][CEH_PID]
273             and $objects[$$child][CEH_PID] == $$obj ) {
274              
275             # A little alias glue code
276 9         34 $child->_pruneAliases();
277              
278             # Emancipate the child
279 9         17 $objects[$$child][CEH_PID] = undef;
280             $objects[$$obj][CEH_CREF] =
281 9         16 [ grep { $_ != $child } @{ $objects[$$obj][CEH_CREF] } ];
  10         38  
  9         42  
282              
283             # More alias glue code
284 9         27 $child->_mergeAliases();
285             }
286             }
287              
288 9         19 return $rv;
289             }
290              
291             sub parent {
292              
293             # Purpose: Returns a reference to the parent object
294             # Returns: Object reference/undef
295             # Usage: $ref = $obj->parent;
296              
297 4     4 1 9 my $obj = CORE::shift;
298 4         6 my $parent;
299              
300 4 100       10 if ( $obj->isStale ) {
301 1         2 $@ = 'parent method called on stale object';
302 1         98 carp $@;
303             } else {
304 3         7 $parent = $objects[$$obj][CEH_PID];
305 3 100       8 $parent =
306             defined $parent
307             ? $objects[$parent][CEH_OREF]
308             : undef;
309             }
310              
311 4         52 return $parent;
312             }
313              
314             sub children {
315              
316             # Purpose: Returns a list of child objects
317             # Returns: List of object references
318             # Usage: @children = $obj->children;
319              
320 81     81 1 665 my $obj = CORE::shift;
321 81         114 my @children;
322              
323 81 100       139 if ( $obj->isStale ) {
324 1         2 $@ = 'children method called on stale object';
325 1         82 carp $@;
326             } else {
327 80         114 @children = @{ $objects[$$obj][CEH_CREF] };
  80         169  
328             }
329              
330 81         259 return @children;
331             }
332              
333             sub siblings {
334              
335             # Purpose: Returns a list of siblings
336             # Returns: List of object references
337             # Usage: @sibling = $obj->siblings;
338              
339 1     1 1 3 my $obj = CORE::shift;
340 1         3 my $parent;
341              
342 1 50       3 if ( $obj->isStale ) {
343 1         3 $@ = 'siblings method called on stale object';
344 1         94 carp $@;
345             } else {
346 0         0 $parent = $objects[$$obj][CEH_PID];
347 0 0       0 $parent = $objects[$parent][CEH_OREF] if defined $parent;
348             }
349              
350 1 50       38 return defined $parent ? $parent->children : ();
351             }
352              
353             sub root {
354              
355             # Purpose: Returns the root object of the tree
356             # Returns: Object reference
357             # Usage: $root = $obj->root;
358              
359 99     99 1 148 my $obj = CORE::shift;
360 99         170 my $pid = $objects[$$obj][CEH_PID];
361 99         134 my $parent;
362              
363 99 100       216 if ( $obj->isStale ) {
364 1         3 $@ = 'root method called on stale object';
365 1         524 carp $@;
366             } else {
367              
368             # Walk up the tree until we find an undefined PID
369 98         181 $pid = $objects[$$obj][CEH_PID];
370 98         216 while ( defined $pid ) {
371 52         80 $parent = $objects[$pid][CEH_OREF];
372 52         107 $pid = $objects[$$parent][CEH_PID];
373             }
374              
375             # The object is the root if no parent was ever found
376 98 100       204 $parent = $obj unless defined $parent;
377             }
378              
379 99         251 return $parent;
380             }
381              
382             sub _getRefById {
383              
384             # Purpose: Returns an object reference by id from the objects array
385             # Returns: Reference
386             # Usage: $obj = _getRefById($index);
387              
388 25     25   33 my $id = CORE::shift;
389              
390 25 50       53 return defined $id ? $objects[$id][CEH_OREF] : undef;
391             }
392              
393             }
394              
395             sub adopt {
396              
397             # Purpose: Formally adopts the children
398             # Returns: Boolean
399             # Usage: $rv = $obj->adopt(@children);
400              
401 10     10 1 2522 my $obj = CORE::shift;
402 10         25 my @children = @_;
403 10         28 my $root = $obj->root;
404 10         17 my $rv;
405              
406 10 50       22 if ( $obj->isStale ) {
407 0         0 $rv = 0;
408 0         0 $@ = 'adopt method called on stale object';
409 0         0 carp $@;
410             } else {
411 10 100       25 if ( grep { $$obj == $$_ } @children ) {
  11 100       45  
    50          
412 1         2 $rv = 0;
413 1         2 $@ = 'object attempted to adopt itself';
414 1         82 carp $@;
415             } elsif (
416             grep {
417 10         50 $root eq $_
418             } @children
419             ) {
420 2         3 $rv = 0;
421 2         4 $@ = 'object attempted to adopt the root';
422 2         168 carp $@;
423             } elsif (
424             grep {
425 8   33     65 !defined or !$_->isa(__PACKAGE__)
426             } @children
427             ) {
428 0         0 $rv = 0;
429 0         0 $@ = 'non-eligible values passed as children for adoption';
430 0         0 carp $@;
431             } else {
432 7         23 $rv = _adopt( $obj, @children );
433             }
434             }
435              
436 10         251 return $rv;
437             }
438              
439             sub disown {
440              
441             # Purpose: Formally adopts the children
442             # Returns: Boolean
443             # Usage: $rv = $obj->adopt(@children);
444              
445 4     4 1 11 my $obj = CORE::shift;
446 4         12 my @children = @_;
447 4         9 my $rv;
448              
449 4 50       13 if ( $obj->isStale ) {
450 0         0 $rv = 0;
451 0         0 $@ = 'disown method called on stale object';
452 0         0 carp $@;
453             } else {
454 4 50 33     12 if ( grep { !defined or !$_->isa(__PACKAGE__) } @children ) {
  4         39  
455 0         0 $rv = 0;
456 0         0 $@ = 'non-eligible values passed as children for disowning';
457 0         0 carp $@;
458             } else {
459 4         18 $rv = _disown( $obj, @children );
460             }
461             }
462              
463 4         17 return $rv;
464             }
465              
466             sub descendents {
467              
468             # Purpose: Returns all descendents of the object
469             # Returns: List of object references
470             # Usage: @descendents = $obj->descendents;
471              
472 29     29 1 44 my $obj = CORE::shift;
473 29         56 my ( @children, @descendents, $child );
474              
475 29 100       48 if ( $obj->isStale ) {
476 1         2 $@ = 'descendents method called on stale object';
477 1         81 carp $@;
478             } else {
479 28         66 @children = $obj->children;
480 28         64 while (@children) {
481 22         33 $child = CORE::shift @children;
482 22         38 CORE::push @descendents, $child;
483 22         44 CORE::push @children, $child->children;
484             }
485             }
486              
487 29         120 return @descendents;
488             }
489              
490             sub _initHierarchy {
491              
492             # Purpose: Initializes the object & class hierarchal data for an object
493             # Returns: Boolean
494             # Usage: $rv = _initHierarchy($obj, $class, @args);
495              
496 24     24   41 my $obj = CORE::shift;
497 24         43 my $class = CORE::shift;
498 24         44 my @args = @_;
499 24         76 my @classes = _getClasses($obj);
500 24         47 my ( $rv, $tclass, %classes );
501              
502             # uniq the class list and save it
503 24         57 %classes = map { $_ => 0 } @classes;
  31         115  
504              
505             # Begin initialization from the top down
506 24         59 foreach $tclass ( reverse @classes ) {
507 31 50       77 unless ( $classes{$tclass} ) {
508              
509             {
510 7     7   67 no strict 'refs';
  7         14  
  7         1112  
  31         50  
511              
512             # call class _initialize()
513             $rv =
514 31         160 defined *{"${tclass}::_initialize"}
515 31 100       48 ? &{"${tclass}::_initialize"}( $obj, @args )
  14         63  
516             : 1;
517              
518             }
519              
520             # Track each class initialization so we only do
521             # it once
522 31         96 $classes{$tclass}++;
523             }
524              
525 31 50       101 last unless $rv;
526             }
527              
528 24         65 return $rv;
529             }
530              
531             sub _destrHierarchy {
532              
533             # Purpose: Destroys hierarchal data for an object
534             # Returns: Boolean
535             # Usage: $rv = _destrHierarchy($obj);
536              
537 24     24   38 my $obj = CORE::shift;
538 24         59 my @classes = _getClasses($obj);
539 24         39 my $tclass;
540              
541             # Attempt to run all the _deconstruct methods
542             {
543 7     7   54 no strict 'refs';
  7         22  
  7         26512  
  24         44  
544              
545 24         45 foreach $tclass ( reverse @classes ) {
546 8         30 &{"${tclass}::_deconstruct"}($obj)
547 31 100       59 if defined *{"${tclass}::_deconstruct"};
  31         170  
548             }
549             }
550              
551 24         113 return 1;
552             }
553              
554             ##########################################################
555             # Alias support
556             ##########################################################
557              
558             {
559              
560             # Array of object aliases
561             my @aliases;
562              
563             # Array of alias maps
564             my @amaps;
565              
566             sub _initAlias {
567              
568             # Purpose: Initializes alias data for an object
569             # Returns: Boolean
570             # Usage: $rv = _initAlias($obj, $alias);
571              
572 23     23   43 my $obj = CORE::shift;
573 23         34 my $alias = CORE::shift;
574              
575             # Store the object aliases and initialize a private map
576 23         54 $aliases[$$obj] = $alias;
577 23 50       88 $amaps[$$obj] = defined $alias ? { $alias => $$obj } : {};
578              
579 23         47 return 1;
580             }
581              
582             sub _destrAlias {
583              
584             # Purpose: Destroys alias data for an object
585             # Returns: Boolean
586             # Usage: $rv = _destrAlias($obj);
587              
588 24     24   46 my $obj = CORE::shift;
589 24         44 my $alias = $aliases[$$obj];
590 24         71 my $root = $obj->root;
591              
592             # Remove aliases from root alias map
593             delete $amaps[$$root]{$alias}
594 24 100 66     89 if defined $alias and $amaps[$$root]{$alias} == $$obj;
595              
596             # Clean up object data
597 24         53 $aliases[$$obj] = undef;
598 24         60 $amaps[$$obj] = undef;
599              
600 24         42 return 1;
601             }
602              
603             sub _mergeAliases {
604              
605             # Purpose: Merges an alias with the family tree alias index
606             # Returns: Boolean
607             # Usage: $rv = _mergeAliases($obj);
608              
609 17     17   35 my $obj = CORE::shift;
610 17         29 my $rv = 1;
611 17         28 my ( $child, $alias, $root );
612              
613             # The alias index is associated with the root of the tree
614 17         30 $root = $obj->root;
615 17         45 foreach $child ( $root->descendents ) {
616              
617             # Skip objects without an alias
618 16 100       47 next unless defined $aliases[$$child];
619              
620             # Get the child's private alias index
621 4         8 $alias = $aliases[$$child];
622              
623             # Update the index if the alias is unclaimed
624 4 50 33     13 if ( CORE::exists $amaps[$$root]{$alias}
625             and $amaps[$$root]{$alias} != $$child ) {
626 0         0 $@ = "alias name collision: $alias";
627 0         0 carp $@;
628 0         0 $rv = 0;
629             } else {
630 4         9 $amaps[$$root]{$alias} = $$child;
631             }
632              
633             # Store the child's prefered alias in its private index,
634             # regardless
635 4         14 $amaps[$$child] = { $alias => $$child };
636             }
637              
638 17         46 return $rv;
639             }
640              
641             sub _pruneAliases {
642              
643             # Purpose: Removes all aliases from this object and its descendents
644             # Returns: Boolean
645             # Usage: $rv = _prunAliases($obj);
646              
647 9     9   17 my $obj = CORE::shift;
648 9         16 my $rv = 1;
649 9         18 my ( $root, $child, $alias );
650              
651 9         22 $root = $obj->root;
652 9         29 foreach $child ( $obj, $obj->descendents ) {
653              
654             # We never prune aliases from an object's own index for itself
655 11 50       44 next if $$child == $$root;
656              
657             # Get the alias and remove it from the root's index if the
658             # alias if valid and pointing to the child in question
659 11         23 $alias = $aliases[$$child];
660 11 100       27 if ( defined $alias ) {
661             delete $amaps[$$root]{$alias}
662             if defined $alias
663 3 50 33     18 and $amaps[$$root]{$alias} == $$child;
664             }
665             }
666              
667 9         21 return $rv;
668             }
669              
670             sub alias {
671              
672             # Purpose: Assigns an alias to an object
673             # Returns: Boolean
674             # Usage: $rv = $obj->alias($name);
675              
676 6     6 1 1380 my $obj = CORE::shift;
677 6         11 my $alias = CORE::shift;
678 6         9 my $rv = 1;
679 6         10 my $root;
680              
681 6 50       14 if ( $obj->isStale ) {
682 0         0 $rv = 0;
683 0         0 $@ = 'alias method called on stale object';
684 0         0 carp $@;
685             } else {
686 6 100 66     34 if ( defined $aliases[$$obj] and length $aliases[$$obj] ) {
    50 33        
687 2         5 $rv = 0;
688 2         6 $@ = "object already has an alias: $aliases[$$obj]";
689 2         289 carp $@;
690             } elsif ( !defined $alias or !length $alias ) {
691 0         0 $rv = 0;
692 0         0 $@ = 'attempt to assign an invalid alias';
693 0         0 carp $@;
694             } else {
695              
696             # Get the root and record the alias in the object's private
697             # map
698 4         11 $root = $obj->root;
699 4         8 $aliases[$$obj] = $alias;
700 4         12 $amaps[$$obj]{$alias} = $$obj;
701              
702 4 100       12 if ( $$root != $$obj ) {
703              
704             # Update the root index
705             #
706             # Make sure no name collisions
707 1 50 33     7 if ( CORE::exists $amaps[$$root]{$alias}
708             and $amaps[$$root]{$alias} != $$obj ) {
709 0         0 $@ = "alias name collision: $alias";
710 0         0 carp $@;
711 0         0 $rv = 0;
712             } else {
713 1         5 $root = $obj->root;
714 1         5 $amaps[$$root]{$alias} = $$obj;
715             }
716             }
717             }
718             }
719              
720 6         184 return $rv;
721             }
722              
723             sub getByAlias {
724              
725             # Purpose: Returns an object reference associated with a given name
726             # Returns: Reference
727             # Usage: $oref = $obj->getByAlias($alias);
728              
729 32     32 1 62 my $obj = CORE::shift;
730 32         49 my $alias = CORE::shift;
731 32         48 my ( $root, $rv );
732              
733 32 50       64 if ( $obj->isStale ) {
    100          
734 0         0 $rv = 0;
735 0         0 $@ = 'getByAlias method called on stale object';
736 0         0 carp $@;
737             } elsif ( defined $alias ) {
738 31         67 $root = $obj->root;
739             $rv = $amaps[$$root]{$alias}
740 31 100       84 if CORE::exists $amaps[$$root]{$alias};
741 31 100       69 $rv = _getRefById($rv) if defined $rv;
742             }
743              
744 32         139 return $rv;
745             }
746              
747             }
748              
749             ##########################################################
750             # Property/Method support
751             ##########################################################
752              
753             {
754              
755             # Property storage
756             my @properties;
757              
758             sub __declProperty {
759              
760             # Purpose: Creates a named property record with associated meta data
761             # Returns: Boolean
762             # Usage: $rv = __declProperty($caller, $obj, $name, $attr);
763              
764 20     20   31 my $caller = CORE::shift;
765 20         26 my $obj = CORE::shift;
766 20         29 my $name = CORE::shift;
767 20         26 my $attr = CORE::shift;
768              
769             # Prepend package scoping in front of private properties
770 20 100       43 $name = "$caller*$name" if $attr & CEH_PRIV;
771              
772             # Apply default attributes
773 20 50       41 $attr |= CEH_SCALAR
774             unless ( $attr ^ CEH_PATTR_TYPE ) > 0;
775 20 50       35 $attr |= CEH_PUB
776             unless ( $attr ^ CEH_PATTR_SCOPE ) > 0;
777              
778             # Save the properties
779 20         29 ${ $properties[$$obj] }{$name} = [];
  20         58  
780 20         29 ${ $properties[$$obj] }{$name}[CEH_PATTR] = $attr;
  20         71  
781 20         29 ${ $properties[$$obj] }{$name}[CEH_PPKG] = $caller;
  20         31  
782 20 100       50 ${ $properties[$$obj] }{$name}[CEH_PVAL] =
  20 100       28  
783             $attr & CEH_ARRAY ? []
784             : $attr & CEH_HASH ? {}
785             : undef;
786              
787 20         45 return 1;
788             }
789              
790             sub _declProperty {
791              
792             # Purpose: Creates a named property record with associated meta data.
793             # This is the public function available for use by
794             # subclasses
795             # Returns: Boolean
796             # Usage: $rv = _declProperty($obj, $name, $attr);
797              
798 1     1   2 my $obj = CORE::shift;
799 1         3 my $name = CORE::shift;
800 1         2 my $attr = CORE::shift;
801 1         3 my $caller = caller;
802 1         6 my $rv = !$obj->isStale;
803              
804 1 50       4 if ($rv) {
805 1 50 33     6 if ( defined $name and length $name ) {
806 1         5 $rv = __declProperty( $caller, $obj, $name, $attr );
807             } else {
808 0         0 $@ = '_declProperty function called with an invalid property';
809 0         0 carp $@;
810 0         0 $rv = 0;
811             }
812             } else {
813 0         0 $@ = '_declProperty function called with a stale object';
814 0         0 carp $@;
815             }
816              
817 1         2 return $rv;
818             }
819              
820             sub _gatekeeper {
821              
822             # Purpose: Checks for a valid property name, and checks ACLs for the
823             # caller
824             # Returns: Property name if allowed, undef otherwise
825             # Usage: $name = $obj->gatekeeper($caller, $name);
826              
827 90     90   124 my $obj = CORE::shift;
828 90         123 my $caller = CORE::shift;
829 90         129 my $name = CORE::shift;
830 90         138 my ( $rv, $class, $cscope, $pscope );
831              
832 90 50 33     279 if ( defined $name and length $name ) {
833              
834             # Check scope and adjust for privately scoped properties
835             $name = "$caller*$name"
836 90 100       283 if CORE::exists $properties[$$obj]{"$caller*$name"};
837              
838 90 100       227 if ( CORE::exists $properties[$$obj]{$name} ) {
839              
840             # Get the property's class
841 85         152 $class = $properties[$$obj]{$name}[CEH_PPKG];
842              
843             # Get the property's scope
844             $pscope =
845 85         166 $properties[$$obj]{$name}[CEH_PATTR] & CEH_PATTR_SCOPE;
846              
847             # Get the caller's scope
848 85 100       377 $cscope =
    100          
849             $caller eq $class ? CEH_PRIV
850             : "$caller"->isa($class) ? CEH_RESTR
851             : CEH_PUB;
852              
853             # Set the values if allowed
854 85 100       176 if ( $cscope >= $pscope ) {
855 81         136 $rv = $name;
856             } else {
857 4         7 $@ = 'property access violation';
858 4         558 carp $@;
859             }
860              
861             } else {
862 5         7 $@ = 'method called with an nonexistent property';
863 5         459 carp $@;
864             }
865             } else {
866 0         0 $@ = 'method called with an invalid property name';
867 0         0 carp $@;
868             }
869              
870 90         825 return $rv;
871             }
872              
873             sub _setProperty {
874              
875             # Purpose: Sets the named property to the passed values
876             # Returns: Boolean
877             # Usage: $rv = $obj->_setProperty($name, @values);
878              
879 31     31   44 my $obj = CORE::shift;
880 31         48 my $name = CORE::shift;
881 31         56 my @val = @_;
882 31         49 my ( $rv, $ptype, $pundef, $pref );
883              
884             # Get some meta data
885 31         37 $ptype = ${ $properties[$$obj] }{$name}[CEH_PATTR] & CEH_PATTR_TYPE;
  31         59  
886 31         47 $pundef = ${ $properties[$$obj] }{$name}[CEH_PATTR] & CEH_NO_UNDEF;
  31         48  
887              
888 31 100 100     108 if ( $ptype != CEH_ARRAY and $ptype != CEH_HASH ) {
889 25         40 $pref = ref $val[0];
890              
891             # Check for undef restrictions
892 25 100 100     60 $rv = 1 if !$pundef or defined $val[0];
893              
894 25 100       48 if ($rv) {
895              
896             # Check types for correctness
897 24 50       68 $rv =
    100          
    100          
    100          
    100          
898             ( !defined $val[0] ) ? 1
899             : $ptype == CEH_SCALAR ? ( $pref eq '' )
900             : $ptype == CEH_CODE ? ( $pref eq 'CODE' )
901             : $ptype == CEH_GLOB ? ( $pref eq 'GLOB' )
902             : $ptype == CEH_REF ? ( length $pref )
903             : 0;
904              
905 24         51 $@ = "data type mismatch for $name";
906 24 100       407 carp $@ unless $rv;
907             }
908              
909             } else {
910              
911             # No validation for array/hash types
912 6         13 $rv = 1;
913             }
914              
915             # Assign the value(s)
916 31 100       366 if ($rv) {
917 26 100       58 if ( $ptype == CEH_ARRAY ) {
    100          
918 3         8 ${ $properties[$$obj] }{$name}[CEH_PVAL] = [@val];
  3         9  
919             } elsif ( $ptype == CEH_HASH ) {
920 3         9 ${ $properties[$$obj] }{$name}[CEH_PVAL] = {@val};
  3         8  
921             } else {
922 20         28 ${ $properties[$$obj] }{$name}[CEH_PVAL] = $val[0];
  20         37  
923             }
924             }
925              
926 31         82 return $rv;
927             }
928              
929             sub set {
930              
931             # Purpose: Sets the named properties to the passed value(s)
932             # Returns: Boolean
933             # Usage: $rv = $obj->set($name, @values);
934              
935 24     24 1 2463 my $obj = CORE::shift;
936 24         51 my $name = CORE::shift;
937 24         46 my @val = @_;
938 24         44 my $caller = caller;
939 24         49 my $rv = !$obj->isStale;
940              
941 24 50       53 if ($rv) {
942 24         52 $name = $obj->_gatekeeper( $caller, $name );
943 24 50       43 if ( defined $name ) {
944 24         52 $rv = $obj->_setProperty( $name, @val );
945             } else {
946 0         0 $rv = 0;
947             }
948             } else {
949 0         0 $@ = 'set method called on a stale object';
950 0         0 carp $@;
951             }
952              
953 24         88 return $rv;
954             }
955              
956             sub _getProperty {
957              
958             # Purpose: Gets the named property's value(s)
959             # Returns: Scalar, Array, Hash, etc.
960             # Usage: @rv = $obj->getProperty($name);
961              
962 38     38   60 my $obj = CORE::shift;
963 38         53 my $name = CORE::shift;
964 38         50 my ( @rv, $ptype );
965              
966             # Get some meta data
967 38         79 $ptype = $properties[$$obj]{$name}[CEH_PATTR] & CEH_PATTR_TYPE;
968              
969             # Retrieve the content
970             @rv =
971 6         21 $ptype == CEH_HASH ? %{ $properties[$$obj]{$name}[CEH_PVAL] }
972 6         18 : $ptype == CEH_ARRAY ? @{ $properties[$$obj]{$name}[CEH_PVAL] }
973 38 100       95 : ( $properties[$$obj]{$name}[CEH_PVAL] );
    100          
974              
975             return
976 38 100       121 $ptype == CEH_HASH ? @rv
    100          
977             : $ptype == CEH_ARRAY ? @rv
978             : $rv[0];
979             }
980              
981             sub get {
982              
983             # Purpose: Gets the named property's value(s)
984             # Returns: Scalar, Array, Hash, etc.
985             # Usage: @rv = $obj->get($name);
986              
987 47     47 1 1629 my $obj = CORE::shift;
988 47         72 my $name = CORE::shift;
989 47         133 my $caller = caller;
990 47         76 my @rv;
991              
992 47 50       100 if ( !$obj->isStale ) {
993 47         130 $name = $obj->_gatekeeper( $caller, $name );
994 47 100       116 if ( defined $name ) {
995 38         95 @rv = $obj->_getProperty($name);
996             }
997             } else {
998 0         0 $@ = 'set method called on a stale object';
999 0         0 carp $@;
1000             }
1001              
1002 47 100       241 return wantarray ? @rv : $rv[0];
1003             }
1004              
1005             sub push {
1006              
1007             # Purpose: Performs a push operation on an array property
1008             # Returns: RV of CORE::push or undef
1009             # Usage: $rv = $obj->push($name, @values);
1010              
1011 3     3 1 2245 my $obj = CORE::shift;
1012 3         7 my $name = CORE::shift;
1013 3         8 my @val = @_;
1014 3         7 my $caller = caller;
1015 3         7 my $rv = !$obj->isStale;
1016              
1017 3 50       8 if ($rv) {
1018 3         6 $rv = undef;
1019 3         7 $name = $obj->_gatekeeper( $caller, $name );
1020 3 50       8 if ( defined $name ) {
1021 3 50       11 if ( ref $properties[$$obj]{$name}[CEH_PVAL] eq 'ARRAY' ) {
1022 3         4 $rv = CORE::push @{ $properties[$$obj]{$name}[CEH_PVAL] },
  3         11  
1023             @val;
1024             } else {
1025 0         0 $@ = 'push attempted on a non-array property';
1026 0         0 carp $@;
1027             }
1028             }
1029             } else {
1030 0         0 $@ = 'push method called on a stale object';
1031 0         0 carp $@;
1032             }
1033              
1034 3         14 return $rv;
1035             }
1036              
1037             sub pop {
1038              
1039             # Purpose: Performs a pop operation on an array property
1040             # Returns: RV of CORE::pop or undef
1041             # Usage: $rv = $obj->pop($name);
1042              
1043 1     1 1 3 my $obj = CORE::shift;
1044 1         2 my $name = CORE::shift;
1045 1         3 my $caller = caller;
1046 1         4 my $rv = !$obj->isStale;
1047              
1048 1 50       4 if ($rv) {
1049 1         2 $rv = undef;
1050 1         3 $name = $obj->_gatekeeper( $caller, $name );
1051 1 50       4 if ( defined $name ) {
1052 1 50       5 if ( ref $properties[$$obj]{$name}[CEH_PVAL] eq 'ARRAY' ) {
1053 1         2 $rv = CORE::pop @{ $properties[$$obj]{$name}[CEH_PVAL] };
  1         4  
1054             } else {
1055 0         0 $@ = 'pop attempted on a non-array property';
1056 0         0 carp $@;
1057             }
1058             }
1059             } else {
1060 0         0 $@ = 'pop method called on a stale object';
1061 0         0 carp $@;
1062             }
1063              
1064 1         5 return $rv;
1065             }
1066              
1067             sub unshift {
1068              
1069             # Purpose: Performs an unshift operation on an array property
1070             # Returns: RV of CORE::unshift or undef
1071             # Usage: $rv = $obj->unshift($name, @values);
1072              
1073 3     3 1 7 my $obj = CORE::shift;
1074 3         5 my $name = CORE::shift;
1075 3         9 my @val = @_;
1076 3         7 my $caller = caller;
1077 3         6 my $rv = !$obj->isStale;
1078              
1079 3 50       8 if ($rv) {
1080 3         5 $rv = undef;
1081 3         7 $name = $obj->_gatekeeper( $caller, $name );
1082 3 50       8 if ( defined $name ) {
1083 3 50       9 if ( ref $properties[$$obj]{$name}[CEH_PVAL] eq 'ARRAY' ) {
1084             $rv =
1085 3         5 CORE::unshift @{ $properties[$$obj]{$name}[CEH_PVAL]
  3         9  
1086             },
1087             @val;
1088             } else {
1089 0         0 $@ = 'unshift attempted on a non-array property';
1090 0         0 carp $@;
1091             }
1092             }
1093             } else {
1094 0         0 $@ = 'unshift method called on a stale object';
1095 0         0 carp $@;
1096             }
1097              
1098 3         15 return $rv;
1099             }
1100              
1101             sub shift {
1102              
1103             # Purpose: Performs a shift operation on an array property
1104             # Returns: RV of CORE::shift or undef
1105             # Usage: $rv = $obj->shift($name);
1106              
1107 1     1 1 3 my $obj = CORE::shift;
1108 1         2 my $name = CORE::shift;
1109 1         3 my $caller = caller;
1110 1         2 my $rv = !$obj->isStale;
1111              
1112 1 50       4 if ($rv) {
1113 1         3 $rv = undef;
1114 1         3 $name = $obj->_gatekeeper( $caller, $name );
1115 1 50       4 if ( defined $name ) {
1116 1 50       7 if ( ref $properties[$$obj]{$name}[CEH_PVAL] eq 'ARRAY' ) {
1117             $rv =
1118 1         3 CORE::shift @{ $properties[$$obj]{$name}[CEH_PVAL] };
  1         4  
1119             } else {
1120 0         0 $@ = 'shift attempted on a non-array property';
1121 0         0 carp $@;
1122             }
1123             }
1124             } else {
1125 0         0 $@ = 'shift method called on a stale object';
1126 0         0 carp $@;
1127             }
1128              
1129 1         5 return $rv;
1130             }
1131              
1132             sub exists {
1133              
1134             # Purpose: Performs an exists operation on a hash property
1135             # Returns: RV of CORE::exists or undef
1136             # Usage: $rv = $obj->exists($name, $key);
1137              
1138 2     2 1 5 my $obj = CORE::shift;
1139 2         3 my $name = CORE::shift;
1140 2         4 my $key = CORE::shift;
1141 2         4 my $caller = caller;
1142 2         5 my $rv = !$obj->isStale;
1143              
1144 2 50       6 if ($rv) {
1145 2         4 $rv = undef;
1146 2         5 $name = $obj->_gatekeeper( $caller, $name );
1147 2 50       6 if ( defined $name ) {
1148 2 50       7 if ( ref $properties[$$obj]{$name}[CEH_PVAL] eq 'HASH' ) {
1149             $rv =
1150             CORE::exists $properties[$$obj]{$name}[CEH_PVAL]
1151 2         5 {$key};
1152             } else {
1153 0         0 $@ = 'exists attempted on a non-hash property';
1154 0         0 carp $@;
1155             }
1156             }
1157             } else {
1158 0         0 $@ = 'exists method called on a stale object';
1159 0         0 carp $@;
1160             }
1161              
1162 2         9 return $rv;
1163             }
1164              
1165             sub keys {
1166              
1167             # Purpose: Performs a keys operation on a hash property
1168             # Returns: RV of CORE::keys or empty array
1169             # Usage: $rv = $obj->keys($name);
1170              
1171 1     1 1 3 my $obj = CORE::shift;
1172 1         2 my $name = CORE::shift;
1173 1         2 my $caller = caller;
1174 1         2 my @rv;
1175              
1176 1 50       3 if ( !$obj->isStale ) {
1177 1         4 $name = $obj->_gatekeeper( $caller, $name );
1178 1 50       5 if ( defined $name ) {
1179 1 50       5 if ( ref $properties[$$obj]{$name}[CEH_PVAL] eq 'HASH' ) {
1180 1         3 @rv = CORE::keys %{ $properties[$$obj]{$name}[CEH_PVAL] };
  1         5  
1181             } else {
1182 0         0 $@ = 'keys attempted on a non-hash property';
1183 0         0 carp $@;
1184             }
1185             }
1186             } else {
1187 0         0 $@ = 'keys method called on a stale object';
1188 0         0 carp $@;
1189             }
1190              
1191 1         4 return @rv;
1192             }
1193              
1194             sub merge {
1195              
1196             # Purpose: Merges the specified ordinal or associated records into
1197             # the named property
1198             # Returns: Boolean
1199             # Usage: $rv = $obj->merge($name, 'foo' => 'bar');
1200             # Usage: $rv = $obj->merge($name, 1 => 'bar');
1201              
1202 2     2 1 3202 my $obj = CORE::shift;
1203 2         5 my $name = CORE::shift;
1204 2         8 my %updates = @_;
1205 2         6 my $rv = !$obj->isStale;
1206 2         6 my $caller = caller;
1207 2         4 my ( $k, $v );
1208              
1209 2 50       5 if ($rv) {
1210 2         7 $name = $obj->_gatekeeper( $caller, $name );
1211 2 50       6 if ( defined $name ) {
1212 2 100       12 if ( ref $properties[$$obj]{$name}[CEH_PVAL] eq 'ARRAY' ) {
    50          
1213 1         6 while ( ( $k, $v ) = each %updates ) {
1214 3         11 $properties[$$obj]{$name}[CEH_PVAL][$k] = $v;
1215             }
1216             } elsif ( ref $properties[$$obj]{$name}[CEH_PVAL] eq 'HASH' )
1217             {
1218 1         5 while ( ( $k, $v ) = each %updates ) {
1219 2         9 $properties[$$obj]{$name}[CEH_PVAL]{$k} = $v;
1220             }
1221             } else {
1222 0         0 $@ = 'merge attempted on a non-hash/array property';
1223 0         0 carp $@;
1224             }
1225             }
1226             } else {
1227 0         0 $@ = 'merge method called on a stale object';
1228 0         0 carp $@;
1229             }
1230              
1231 2         10 return $rv;
1232             }
1233              
1234             sub subset {
1235              
1236             # Purpose: Returns the associated or ordinal values from the named
1237             # property
1238             # Returns: Array of values
1239             # Usage: @values = $obj->subset($name, qw(foo bar));
1240             # Usage: @values = $obj->subset($name, 1, 7);
1241              
1242 2     2 1 3188 my $obj = CORE::shift;
1243 2         5 my $name = CORE::shift;
1244 2         5 my @keys = @_;
1245 2         5 my $caller = caller;
1246 2         5 my ( @rv, $k, $l );
1247              
1248 2 50       5 if ( !$obj->isStale ) {
1249 2         7 $name = $obj->_gatekeeper( $caller, $name );
1250 2 50       6 if ( defined $name ) {
1251 2 100       11 if ( ref $properties[$$obj]{$name}[CEH_PVAL] eq 'ARRAY' ) {
    50          
1252 1         2 $l = $#{ $properties[$$obj]{$name}[CEH_PVAL] };
  1         4  
1253 1         3 foreach $k (@keys) {
1254             CORE::push @rv, (
1255             $k <= $l
1256 4 50       13 ? $properties[$$obj]{$name}[CEH_PVAL][$k]
1257             : undef
1258             );
1259             }
1260             } elsif ( ref $properties[$$obj]{$name}[CEH_PVAL] eq 'HASH' )
1261             {
1262 1         3 foreach $k (@keys) {
1263             CORE::push @rv, (
1264             CORE::exists $properties[$$obj]{$name}[CEH_PVAL]
1265             {$k}
1266 2 50       8 ? $properties[$$obj]{$name}[CEH_PVAL]{$k}
1267             : undef
1268             );
1269             }
1270             } else {
1271 0         0 $@ = 'subset attempted on a non-hash/array property';
1272 0         0 carp $@;
1273             }
1274             }
1275             } else {
1276 0         0 $@ = 'subset method called on a stale object';
1277 0         0 carp $@;
1278             }
1279              
1280 2         10 return @rv;
1281             }
1282              
1283             sub remove {
1284              
1285             # Purpose: Removes the ordinal or associated values from the named
1286             # property
1287             # Returns: Boolean
1288             # Usage: $rv = $obj->remove($name, qw(foo bar));
1289             # Usage: $rv = $obj->remove($name, 5, 8);
1290              
1291 2     2 1 2750 my $obj = CORE::shift;
1292 2         4 my $name = CORE::shift;
1293 2         7 my @keys = @_;
1294 2         6 my $caller = caller;
1295 2         5 my $rv = !$obj->isStale;
1296 2         5 my ( $k, $l );
1297              
1298 2 50       6 if ($rv) {
1299 2         6 $name = $obj->_gatekeeper( $caller, $name );
1300 2 50       6 if ( defined $name ) {
1301 2 100       12 if ( ref $properties[$$obj]{$name}[CEH_PVAL] eq 'ARRAY' ) {
    50          
1302 1         2 $l = $#{ $properties[$$obj]{$name}[CEH_PVAL] };
  1         3  
1303 1         5 foreach $k ( sort { $b <=> $a } @keys ) {
  2         7  
1304 3 50       8 splice @{ $properties[$$obj]{$name}[CEH_PVAL] }, $k, 1
  3         8  
1305             unless $k > $l;
1306             }
1307             } elsif ( ref $properties[$$obj]{$name}[CEH_PVAL] eq 'HASH' )
1308             {
1309 1         38 foreach $k (@keys) {
1310 2         37 delete $properties[$$obj]{$name}[CEH_PVAL]{$k};
1311             }
1312             } else {
1313 0         0 $@ = 'remove attempted on a non-hash/array property';
1314 0         0 carp $@;
1315             }
1316             }
1317             } else {
1318 0         0 $@ = 'remove method called on a stale object';
1319 0         0 carp $@;
1320             }
1321              
1322 2         11 return $rv;
1323             }
1324              
1325             sub empty {
1326              
1327             # Purpose: Empties the named array or hash property
1328             # Returns: Boolean
1329             # Usage: $rv = $obj->empty($name);
1330              
1331 2     2 1 1356 my $obj = CORE::shift;
1332 2         4 my $name = CORE::shift;
1333 2         5 my $caller = caller;
1334 2         5 my $rv = !$obj->isStale;
1335              
1336 2 50       6 if ($rv) {
1337 2         6 $name = $obj->_gatekeeper( $caller, $name );
1338 2 50       6 if ( defined $name ) {
1339 2 100       12 if ( ref $properties[$$obj]{$name}[CEH_PVAL] eq 'ARRAY' ) {
    50          
1340 1         2 @{ $properties[$$obj]{$name}[CEH_PVAL] } = ();
  1         4  
1341             } elsif ( ref $properties[$$obj]{$name}[CEH_PVAL] eq 'HASH' )
1342             {
1343 1         3 %{ $properties[$$obj]{$name}[CEH_PVAL] } = ();
  1         25  
1344             } else {
1345 0         0 $@ = 'empty attempted on a non-hash/array property';
1346 0         0 carp $@;
1347             }
1348             }
1349             } else {
1350 0         0 $@ = 'empty method called on a stale object';
1351 0         0 carp $@;
1352             }
1353              
1354 2         10 return $rv;
1355             }
1356              
1357             sub properties {
1358              
1359             # Purpose: Returns a list of property names visible to the caller
1360             # Returns: Array of scalars
1361             # Usage: @names = $obj->properties;
1362              
1363 3     3 1 2494 my $obj = CORE::shift;
1364 3         9 my $caller = caller;
1365 3         6 my @pnames = CORE::keys %{ $properties[$$obj] };
  3         14  
1366 3         7 my @rv;
1367              
1368             # Populate with all the public properties
1369             @rv =
1370 3         8 grep { $properties[$$obj]{$_}[CEH_PATTR] & CEH_PUB } @pnames;
  25         44  
1371              
1372             # Add restricted properties if the caller is a subclass
1373 3 100 66     21 if ( $caller eq ref $obj
1374             or "$caller"->isa($obj) ) {
1375             CORE::push @rv,
1376 2         4 grep { $properties[$$obj]{$_}[CEH_PATTR] & CEH_RESTR }
  14         24  
1377             @pnames;
1378             }
1379              
1380             # Add private properties if the caller is the same class
1381 3 100       11 if ( $caller eq ref $obj ) {
1382 2         59 foreach ( grep /^\Q$caller*\E/s, @pnames ) {
1383 2         7 CORE::push @rv, $_;
1384 2         22 $rv[$#rv] =~ s/^\Q$caller*\E//s;
1385             }
1386             }
1387              
1388 3         20 return @rv;
1389             }
1390              
1391             sub _initProperties {
1392              
1393             # Purpose: Initializes the property data for the object
1394             # Returns: Boolean
1395             # Usage: $rv = _initProperties($obj);
1396              
1397 24     24   279 my $obj = CORE::shift;
1398 24         81 my @classes = _getClasses($obj);
1399 24         49 my $rv = 1;
1400 24         43 my ( $class, @_properties, $prop, $pattr, $pscope, $pname );
1401              
1402             # Initialize storage
1403 24         45 $properties[$$obj] = {};
1404              
1405             # Load properties from top of class hierarchy down
1406 24         91 foreach $class (@classes) {
1407              
1408             # Get the contents of the class array
1409             {
1410 7     7   201 no strict 'refs';
  7         16  
  7         2835  
  31         54  
1411              
1412             @_properties =
1413 31         182 defined *{"${class}::_properties"}
1414 31 100       49 ? @{ *{"${class}::_properties"}{ARRAY} }
  5         8  
  5         19  
1415             : ();
1416             }
1417              
1418             # Process the list
1419 31         75 foreach $prop (@_properties) {
1420 19 50       36 next unless defined $prop;
1421              
1422 19 50       44 unless (
1423             __declProperty(
1424             $class, $obj, @$prop[ CEH_PNAME, CEH_PATTR ] )
1425             ) {
1426 0         0 $rv = 0;
1427 0         0 last;
1428             }
1429              
1430             # Set the default values
1431 19 100 66     68 if ( $rv and defined $$prop[CEH_PVAL] ) {
1432              
1433             # Get the attribute type, scope, and internal prop name
1434 7         14 $pattr = $$prop[CEH_PATTR] & CEH_PATTR_TYPE;
1435 7         11 $pscope = $$prop[CEH_PATTR] & CEH_PATTR_SCOPE;
1436 7 50       27 $pname =
1437             $pscope == CEH_PRIV
1438             ? "${class}::$$prop[CEH_PNAME]"
1439             : $$prop[CEH_PNAME];
1440              
1441             # Store the default values
1442             $rv = $obj->_setProperty( $pname,
1443 1         9 $pattr == CEH_ARRAY ? @{ $$prop[CEH_PVAL] }
1444 7 100       57 : $pattr == CEH_HASH ? %{ $$prop[CEH_PVAL] }
  1 100       4  
1445             : $$prop[CEH_PVAL] );
1446             }
1447              
1448 19 50       43 last unless $rv;
1449             }
1450              
1451             }
1452              
1453 24         57 return $rv;
1454             }
1455              
1456             sub _destrProperties {
1457              
1458             # Purpose: Destroys the object's property data
1459             # Returns: Boolean
1460             # Usage: $rv = _destrProperties($obj);
1461              
1462 24     24   39 my $obj = CORE::shift;
1463              
1464 24         66 $properties[$$obj] = undef;
1465              
1466 24         36 return 1;
1467             }
1468              
1469             }
1470              
1471             {
1472             my %classes; # Class => 1
1473             my %methods; # Class::Method => 1
1474              
1475             sub __declMethod {
1476              
1477             # Purpose: Registers a list of methods as scoped
1478             # Returns: Boolean
1479             # Usage: $rv = __declMethod($class, $attr, $methods);
1480              
1481 9     9   15 my $pkg = CORE::shift;
1482 9         11 my $attr = CORE::shift;
1483 9         13 my $method = CORE::shift;
1484 9         12 my $rv = 1;
1485 9         15 my ( $code, $mfqn );
1486              
1487 9 50 33     38 if ( defined $attr and defined $method and length $method ) {
      33        
1488              
1489             # Quiet some warnings
1490 7     7   70 no warnings qw(redefine prototype);
  7         16  
  7         385  
1491 7     7   100 no strict 'refs';
  7         15  
  7         3275  
1492              
1493             # Get the fully qualified method name and associated code
1494             # block
1495 9         16 $mfqn = "${pkg}::${method}";
1496 9         12 $code = *{$mfqn}{CODE};
  9         25  
1497              
1498             # Quick check to see if we've done this already -- if so
1499             # we skip to the next
1500 9 100       23 return 1 if CORE::exists $methods{$mfqn};
1501              
1502 6 50       22 if ( defined $code ) {
1503              
1504             # Repackage
1505 6 100       19 if ( $attr == CEH_PRIV ) {
    100          
    50          
1506              
1507             # Private methods
1508 2         13 *{$mfqn} = sub {
1509 5     5   3847 my $caller = caller;
1510 5 100       15 goto &{$code} if $caller eq $pkg;
  2         9  
1511 3         12 $@ = 'Attempted to call private method '
1512             . "$method from $caller";
1513 3         414 carp $@;
1514 3         248 return 0;
1515 2         18 };
1516              
1517             } elsif ( $attr == CEH_RESTR ) {
1518              
1519             # Restricted methods
1520 2         9 *{$mfqn} = sub {
1521 5     5   1633 my $caller = caller;
1522 5 100       35 goto &{$code} if "$caller"->isa($pkg);
  3         13  
1523 2         10 $@ = 'Attempted to call restricted method '
1524             . "$method from $caller";
1525 2         176 carp $@;
1526 2         134 return 0;
1527 2         17 };
1528             } elsif ( $attr == CEH_PUB ) {
1529              
1530             # Do nothing
1531              
1532             } else {
1533 0         0 $@ = 'invalid method declaration';
1534 0         0 carp $@;
1535 0         0 $rv = 0;
1536             }
1537              
1538             # Record our handling of this method
1539 6 50       19 $methods{$mfqn} = 1 if $rv;
1540              
1541             }
1542              
1543             } else {
1544 0         0 $@ = 'invalid method declaration';
1545 0         0 carp $@;
1546 0         0 $rv = 0;
1547             }
1548              
1549 6         15 return $rv;
1550             }
1551              
1552             sub _declMethod {
1553              
1554             # Purpose: Wrapper for __declMethod, this is the public interface
1555             # Returns: RV of __declMethod
1556             # Usage: $rv = _declMethod($attr, @propNames);
1557              
1558 6     6   29 my $attr = CORE::shift;
1559 6         9 my $method = CORE::shift;
1560 6         17 my $caller = caller;
1561 6         8 my $rv = 1;
1562              
1563 6 50 33     20 if ( defined $method and length $method ) {
1564 6         12 $rv = __declMethod( $caller, $attr, $method );
1565             } else {
1566 0         0 $@ = '_declMethod function called with an invalid method';
1567 0         0 carp $@;
1568 0         0 $rv = 0;
1569             }
1570              
1571 6         11 return $rv;
1572             }
1573              
1574             sub _initMethods {
1575              
1576             # Purpose: Loads methods from @_methods
1577             # Returns: Boolean
1578             # Usage: $rv = _loadMethods();
1579              
1580 24     24   42 my $obj = CORE::shift;
1581 24         49 my @classes = _getClasses($obj);
1582 24         46 my $rv = 1;
1583 24         39 my ( $class, @_methods, $method );
1584              
1585             # Load methods from the top of the class hierarchy down
1586 24         56 foreach $class (@classes) {
1587              
1588             # Skip if the class has already been processed
1589 31 100       87 next if CORE::exists $classes{$class};
1590              
1591             # Get the contents of the class array
1592             {
1593 7     7   56 no strict 'refs';
  7         13  
  7         3743  
  11         27  
1594              
1595 1         2 @_methods = @{ *{"${class}::_methods"}{ARRAY} }
  1         4  
1596 11 100       20 if defined *{"${class}::_methods"};
  11         83  
1597             }
1598              
1599             # Process the list
1600 11         72 foreach $method (@_methods) {
1601 3 50       8 next unless defined $method;
1602 3 50       9 unless (
1603             __declMethod( $class, @$method[ CEH_PATTR, CEH_PPKG ] ) )
1604             {
1605 0         0 $rv = 0;
1606 0         0 last;
1607             }
1608             }
1609              
1610             # Mark the class as processed
1611 11         38 $classes{$class} = 1;
1612             }
1613              
1614 24         101 return $rv;
1615             }
1616              
1617             }
1618              
1619             ##########################################################
1620             # Class Constructors/Destructors
1621             ##########################################################
1622              
1623             sub new {
1624              
1625             # Purpose: Class constructor for all (sub)classes
1626             # Returns: Reference
1627             # Usage: $obj = new SUBCLASS;
1628 23     23 1 3853 my $class = CORE::shift;
1629 23         55 my @args = @_;
1630 23         44 my $obj = bless \do { my $anon_scalar }, $class;
  23         66  
1631 23         37 my $rv;
1632              
1633             # Get the next available ID
1634 23         60 $rv = _getID($obj);
1635              
1636             # Initialize alias support
1637 23 50       97 $rv = _initAlias($obj) if $rv;
1638              
1639             # Initialize property scope support
1640 23 50       94 $rv = _initProperties($obj) if $rv;
1641              
1642             # Initialize method scope support
1643 23 50       99 $rv = _initMethods($obj) if $rv;
1644              
1645             # Initialize the hierarchal code support
1646 23 50       87 $rv = _initHierarchy( $obj, $class, @args ) if $rv;
1647              
1648 23 50       104 return $rv ? $obj : undef;
1649             }
1650              
1651             sub conceive {
1652              
1653             # Purpose: Same as new() but with hierarchal relationships pre-installed
1654             # Returns: Reference
1655             # Usage: SubClass->conceive($parent, @args);
1656              
1657 1     1 1 589 my $class = CORE::shift;
1658 1         2 my $pobj = CORE::shift;
1659 1         3 my @args = @_;
1660 1         2 my $obj = bless \do { my $anon_scalar }, $class;
  1         4  
1661 1         3 my $rv = 1;
1662              
1663             # Get the next available ID
1664 1 50       6 $rv = _getID($obj) if $rv;
1665              
1666             # Adopt the object before we do anything else
1667 1 50       9 $rv = $pobj->_adopt($obj) if $rv;
1668              
1669             # Initialize property scope support
1670 1 50       7 $rv = _initProperties($obj) if $rv;
1671              
1672             # Initialize method scope support
1673 1 50       5 $rv = _initMethods($obj) if $rv;
1674              
1675             # Initialize the hierarchal code support
1676 1 50       5 $rv = _initHierarchy( $obj, $class, @args ) if $rv;
1677              
1678             # Disown the object if we've failed initialization
1679 1 50       5 $pobj->_disown($obj) unless $rv;
1680              
1681 1 50       9 return $rv ? $obj : undef;
1682             }
1683              
1684             sub DESTROY {
1685              
1686             # Purpose: Garbage collection
1687             # Returns: Boolean
1688             # Usage: $obj->DESTROY();
1689              
1690 30     30   4823 my $obj = CORE::shift;
1691 30         56 my ( $class, @classes );
1692              
1693             # Test to see if this is a stale reference
1694 30 100 66     123 unless ( !defined $$obj or $obj->isStale ) {
1695              
1696             # Destroy from the top of the tree down
1697 24 50       93 foreach ( $obj->children ) { $_->DESTROY if defined }
  5         32  
1698              
1699             # Execute hierarchal destructors
1700 24         77 _destrHierarchy($obj);
1701              
1702             # Destroy aliases
1703 24         121 _destrAlias($obj);
1704              
1705             # Destroy properties
1706 24         69 _destrProperties($obj);
1707              
1708             # Recover the ID
1709 24         58 _delID($obj);
1710             }
1711              
1712 30         950 return 1;
1713             }
1714              
1715             END {
1716 6 0   6   155 foreach ( _dumpObjects() ) { $_->DESTROY if defined }
  0            
1717             }
1718              
1719             1;
1720              
1721             __END__