File Coverage

blib/lib/Class/EHierarchy.pm
Criterion Covered Total %
statement 698 797 87.5
branch 220 316 69.6
condition 36 69 52.1
subroutine 88 88 100.0
pod 25 25 100.0
total 1067 1295 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.00 2017/01/09 08:47:12 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   88202 use 5.008003;
  7         19  
21              
22 7     7   27 use strict;
  7         5  
  7         123  
23 7     7   27 use warnings;
  7         8  
  7         186  
24 7     7   22 use vars qw($VERSION @EXPORT @EXPORT_OK %EXPORT_TAGS);
  7         6  
  7         372  
25 7     7   22 use base qw(Exporter);
  7         9  
  7         539  
26 7     7   28 use Carp;
  7         10  
  7         445  
27 7     7   28 use Scalar::Util qw(weaken);
  7         7  
  7         654  
28              
29             ($VERSION) = ( q$Revision: 2.00 $ =~ /(\d+(?:\.(\d+))+)/sm );
30              
31             # Ordinal indexes for the @objects element records
32 7     7   26 use constant CEH_OREF => 0;
  7         9  
  7         503  
33 7     7   51 use constant CEH_PID => 1;
  7         7  
  7         280  
34 7     7   63 use constant CEH_PKG => 2;
  7         10  
  7         256  
35 7     7   19 use constant CEH_CLASSES => 3;
  7         12  
  7         224  
36 7     7   21 use constant CEH_CREF => 4;
  7         6  
  7         233  
37              
38             # Ordinal indexes for the @properties element records
39 7     7   21 use constant CEH_PATTR => 0;
  7         7  
  7         209  
40 7     7   19 use constant CEH_PNAME => 1;
  7         10  
  7         208  
41 7     7   19 use constant CEH_PPKG => 1;
  7         6  
  7         228  
42 7     7   20 use constant CEH_PVAL => 2;
  7         7  
  7         249  
43              
44             # Property attribute masks
45 7     7   24 use constant CEH_PATTR_SCOPE => 7;
  7         7  
  7         228  
46 7     7   21 use constant CEH_PATTR_TYPE => 504;
  7         13  
  7         338  
47              
48             # Property attribute scopes
49 7     7   24 use constant CEH_PUB => 1;
  7         10  
  7         217  
50 7     7   19 use constant CEH_RESTR => 2;
  7         5  
  7         256  
51 7     7   19 use constant CEH_PRIV => 4;
  7         6  
  7         268  
52              
53             # Property attribute types
54 7     7   20 use constant CEH_SCALAR => 8;
  7         6  
  7         235  
55 7     7   19 use constant CEH_ARRAY => 16;
  7         6  
  7         240  
56 7     7   20 use constant CEH_HASH => 32;
  7         16  
  7         205  
57 7     7   19 use constant CEH_CODE => 64;
  7         7  
  7         217  
58 7     7   18 use constant CEH_REF => 128;
  7         13  
  7         225  
59 7     7   51 use constant CEH_GLOB => 256;
  7         6  
  7         234  
60              
61             # Property flags
62 7     7   21 use constant CEH_NO_UNDEF => 512;
  7         6  
  7         1493  
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   1042 return map { $$_[CEH_OREF] } grep {defined} @objects;
  9         16  
  32         76  
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   22 my $obj = CORE::shift;
105 24 100       49 my $id = @recoveredIDs ? CORE::shift @recoveredIDs : $#objects + 1;
106              
107 24         59 $$obj = $id;
108 24         32 $objects[$id] = [];
109 24         31 $objects[$id][CEH_CREF] = [];
110 24         25 $objects[$id][CEH_CLASSES] = [];
111 24         18 $objects[$id][CEH_OREF] = $obj;
112 24         34 $objects[$id][CEH_PKG] = ref $obj;
113 24         90 weaken( $objects[$$obj][CEH_OREF] );
114              
115 24 100       52 $id = '0 but true' if $id == 0;
116              
117             # Build object class list
118             {
119 7     7   25 no strict 'refs';
  7         16  
  7         8405  
  24         15  
120              
121 24         25 my ( $isaref, $tclass, $nclass, @classes, $n, $l );
122 24         21 my $class = ref $obj;
123              
124             # Get the first level of classes we're subclassed from
125 24         23 $isaref = *{"${class}::ISA"}{ARRAY};
  24         64  
126 24 50       43 $isaref = [] unless defined $isaref;
127 24         34 foreach $tclass (@$isaref) {
128 24 100 100     168 CORE::push @classes, $tclass
129             if $tclass ne __PACKAGE__
130             and "$tclass"->isa(__PACKAGE__);
131             }
132              
133             # Now, recurse into parent classes.
134 24         47 $n = 0;
135 24         24 $l = scalar @classes;
136 24         48 while ( $n < $l ) {
137 7         18 foreach $tclass ( @classes[ $n .. ( $l - 1 ) ] ) {
138 7         6 $isaref = *{"${tclass}::ISA"}{ARRAY};
  7         17  
139 7 50       20 $isaref = [] unless defined $isaref;
140 7         10 foreach $nclass (@$isaref) {
141 7 50 33     20 CORE::push @classes, $nclass
142             if $nclass ne __PACKAGE__
143             and "$nclass"->isa(__PACKAGE__);
144             }
145             }
146 7         10 $n = scalar @classes - $l + 1;
147 7         13 $l = scalar @classes;
148             }
149              
150             # Add our current class
151 24         26 CORE::push @classes, $class;
152              
153             # Save the list
154 24         31 foreach (@classes) { _addClass( $obj, $_ ) }
  31         49  
155             }
156              
157 24         30 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   72 my $obj = CORE::shift;
168 24         26 my $pid = $objects[$$obj][CEH_PID];
169 24         21 my @children = @{ $objects[$$obj][CEH_CREF] };
  24         35  
170              
171             # Have the parent disown this child
172 24 100       40 _disown( $objects[$pid][CEH_OREF], $obj ) if defined $pid;
173 24 50       51 _disown( $obj, $objects[$_][CEH_OREF] ) if @children;
174              
175             # Clean up internal data structures
176 24         19 $objects[$$obj] = undef;
177 24         44 CORE::push @recoveredIDs, $$obj;
178              
179 24         23 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 259 my $obj = CORE::shift;
190              
191 397   100     2851 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   24 my $obj = CORE::shift;
204 31         28 my $class = CORE::shift;
205              
206 31         47 CORE::push @{ $objects[$$obj][CEH_CLASSES] }, $class
207             if defined $class
208 31 50 33     54 and not grep /^$class$/s, @{ $objects[$$obj][CEH_CLASSES] };
  31         150  
209              
210 31         53 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   66 my $obj = CORE::shift;
220              
221 96         73 return @{ $objects[$$obj][CEH_CLASSES] };
  96         166  
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   9 my $obj = CORE::shift;
231 8         10 my @orphans = @_;
232 8         12 my $rv = 1;
233 8         5 my $child;
234              
235 8         13 foreach $child (@orphans) {
236 9 50       15 next if $child->isStale;
237 9 50       19 if ( !defined $objects[$$child][CEH_PID] ) {
238              
239             # Eligible for adoption, record the relationship
240 9         12 $objects[$$child][CEH_PID] = $$obj;
241 9         9 CORE::push @{ $objects[$$obj][CEH_CREF] }, $child;
  9         15  
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         20 $obj->_mergeAliases;
256              
257 8         10 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   14 my $obj = CORE::shift;
267 9         11 my @orphans = @_;
268 9         8 my $rv = 1;
269 9         7 my ($child);
270              
271 9         12 foreach $child (@orphans) {
272 9 50 33     37 if ( defined $objects[$$child][CEH_PID]
273             and $objects[$$child][CEH_PID] == $$obj ) {
274              
275             # A little alias glue code
276 9         18 $child->_pruneAliases();
277              
278             # Emancipate the child
279 9         12 $objects[$$child][CEH_PID] = undef;
280             $objects[$$obj][CEH_CREF] =
281 9         6 [ grep { $_ != $child } @{ $objects[$$obj][CEH_CREF] } ];
  10         27  
  9         12  
282              
283             # More alias glue code
284 9         20 $child->_mergeAliases();
285             }
286             }
287              
288 9         10 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 5 my $obj = CORE::shift;
298 4         4 my $parent;
299              
300 4 100       5 if ( $obj->isStale ) {
301 1         2 $@ = 'parent method called on stale object';
302 1         66 carp $@;
303             } else {
304 3         4 $parent = $objects[$$obj][CEH_PID];
305 3 100       6 $parent =
306             defined $parent
307             ? $objects[$parent][CEH_OREF]
308             : undef;
309             }
310              
311 4         30 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 465 my $obj = CORE::shift;
321 81         54 my @children;
322              
323 81 100       82 if ( $obj->isStale ) {
324 1         1 $@ = 'children method called on stale object';
325 1         66 carp $@;
326             } else {
327 80         63 @children = @{ $objects[$$obj][CEH_CREF] };
  80         95  
328             }
329              
330 81         175 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 2 my $obj = CORE::shift;
340 1         1 my $parent;
341              
342 1 50       2 if ( $obj->isStale ) {
343 1         1 $@ = 'siblings method called on stale object';
344 1         66 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       23 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 80 my $obj = CORE::shift;
360 99         100 my $pid = $objects[$$obj][CEH_PID];
361 99         54 my $parent;
362              
363 99 100       125 if ( $obj->isStale ) {
364 1         2 $@ = 'root method called on stale object';
365 1         151 carp $@;
366             } else {
367              
368             # Walk up the tree until we find an undefined PID
369 98         82 $pid = $objects[$$obj][CEH_PID];
370 98         152 while ( defined $pid ) {
371 52         39 $parent = $objects[$pid][CEH_OREF];
372 52         75 $pid = $objects[$$parent][CEH_PID];
373             }
374              
375             # The object is the root if no parent was ever found
376 98 100       154 $parent = $obj unless defined $parent;
377             }
378              
379 99         151 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   21 my $id = CORE::shift;
389              
390 25 50       31 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 1308 my $obj = CORE::shift;
402 10         17 my @children = @_;
403 10         17 my $root = $obj->root;
404 10         9 my $rv;
405              
406 10 50       14 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       13 if ( grep { $$obj == $$_ } @children ) {
  11 100       31  
    50          
412 1         1 $rv = 0;
413 1         2 $@ = 'object attempted to adopt itself';
414 1         68 carp $@;
415             } elsif (
416             grep {
417 10         25 $root eq $_
418             } @children
419             ) {
420 2         3 $rv = 0;
421 2         2 $@ = 'object attempted to adopt the root';
422 2         137 carp $@;
423             } elsif (
424             grep {
425 8   33     52 !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         16 $rv = _adopt( $obj, @children );
433             }
434             }
435              
436 10         128 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 6 my $obj = CORE::shift;
446 4         8 my @children = @_;
447 4         5 my $rv;
448              
449 4 50       7 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     8 if ( grep { !defined or !$_->isa(__PACKAGE__) } @children ) {
  4         33  
455 0         0 $rv = 0;
456 0         0 $@ = 'non-eligible values passed as children for disowning';
457 0         0 carp $@;
458             } else {
459 4         11 $rv = _disown( $obj, @children );
460             }
461             }
462              
463 4         14 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 23 my $obj = CORE::shift;
473 29         19 my ( @children, @descendents, $child );
474              
475 29 100       37 if ( $obj->isStale ) {
476 1         1 $@ = 'descendents method called on stale object';
477 1         64 carp $@;
478             } else {
479 28         37 @children = $obj->children;
480 28         43 while (@children) {
481 22         20 $child = CORE::shift @children;
482 22         20 CORE::push @descendents, $child;
483 22         24 CORE::push @children, $child->children;
484             }
485             }
486              
487 29         64 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   19 my $obj = CORE::shift;
497 24         22 my $class = CORE::shift;
498 24         27 my @args = @_;
499 24         35 my @classes = _getClasses($obj);
500 24         41 my ( $rv, $tclass, %classes );
501              
502             # uniq the class list and save it
503 24         26 %classes = map { $_ => 0 } @classes;
  31         67  
504              
505             # Begin initialization from the top down
506 24         30 foreach $tclass ( reverse @classes ) {
507 31 50       57 unless ( $classes{$tclass} ) {
508              
509             {
510 7     7   30 no strict 'refs';
  7         8  
  7         735  
  31         19  
511              
512             # call class _initialize()
513             $rv =
514 31         92 defined *{"${tclass}::_initialize"}
515 31 100       23 ? &{"${tclass}::_initialize"}( $obj, @args )
  14         32  
516             : 1;
517              
518             }
519              
520             # Track each class initialization so we only do
521             # it once
522 31         47 $classes{$tclass}++;
523             }
524              
525 31 50       55 last unless $rv;
526             }
527              
528 24         35 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   24 my $obj = CORE::shift;
538 24         32 my @classes = _getClasses($obj);
539 24         27 my $tclass;
540              
541             # Attempt to run all the _deconstruct methods
542             {
543 7     7   25 no strict 'refs';
  7         66  
  7         17533  
  24         22  
544              
545 24         26 foreach $tclass ( reverse @classes ) {
546 8         17 &{"${tclass}::_deconstruct"}($obj)
547 31 100       25 if defined *{"${tclass}::_deconstruct"};
  31         132  
548             }
549             }
550              
551 24         33 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   22 my $obj = CORE::shift;
573 23         30 my $alias = CORE::shift;
574              
575             # Store the object aliases and initialize a private map
576 23         22 $aliases[$$obj] = $alias;
577 23 50       43 $amaps[$$obj] = defined $alias ? { $alias => $$obj } : {};
578              
579 23         29 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   26 my $obj = CORE::shift;
589 24         24 my $alias = $aliases[$$obj];
590 24         57 my $root = $obj->root;
591              
592             # Remove aliases from root alias map
593             delete $amaps[$$root]{$alias}
594 24 100 66     61 if defined $alias and $amaps[$$root]{$alias} == $$obj;
595              
596             # Clean up object data
597 24         26 $aliases[$$obj] = undef;
598 24         26 $amaps[$$obj] = undef;
599              
600 24         25 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   14 my $obj = CORE::shift;
610 17         15 my $rv = 1;
611 17         12 my ( $child, $alias, $root );
612              
613             # The alias index is associated with the root of the tree
614 17         20 $root = $obj->root;
615 17         27 foreach $child ( $root->descendents ) {
616              
617             # Skip objects without an alias
618 16 100       27 next unless defined $aliases[$$child];
619              
620             # Get the child's private alias index
621 4         4 $alias = $aliases[$$child];
622              
623             # Update the index if the alias is unclaimed
624 4 50 33     11 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         4 $amaps[$$root]{$alias} = $$child;
631             }
632              
633             # Store the child's prefered alias in its private index,
634             # regardless
635 4         17 $amaps[$$child] = { $alias => $$child };
636             }
637              
638 17         20 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   10 my $obj = CORE::shift;
648 9         7 my $rv = 1;
649 9         9 my ( $root, $child, $alias );
650              
651 9         14 $root = $obj->root;
652 9         16 foreach $child ( $obj, $obj->descendents ) {
653              
654             # We never prune aliases from an object's own index for itself
655 11 50       21 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         12 $alias = $aliases[$$child];
660 11 100       19 if ( defined $alias ) {
661             delete $amaps[$$root]{$alias}
662             if defined $alias
663 3 50 33     20 and $amaps[$$root]{$alias} == $$child;
664             }
665             }
666              
667 9         9 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 1446 my $obj = CORE::shift;
677 6         6 my $alias = CORE::shift;
678 6         5 my $rv = 1;
679 6         4 my $root;
680              
681 6 50       10 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     32 if ( defined $aliases[$$obj] and length $aliases[$$obj] ) {
    50 33        
687 2         2 $rv = 0;
688 2         5 $@ = "object already has an alias: $aliases[$$obj]";
689 2         248 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         8 $root = $obj->root;
699 4         6 $aliases[$$obj] = $alias;
700 4         8 $amaps[$$obj]{$alias} = $$obj;
701              
702 4 100       9 if ( $$root != $$obj ) {
703              
704             # Update the root index
705             #
706             # Make sure no name collisions
707 1 50 33     5 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         2 $root = $obj->root;
714 1         2 $amaps[$$root]{$alias} = $$obj;
715             }
716             }
717             }
718             }
719              
720 6         211 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 35 my $obj = CORE::shift;
730 32         30 my $alias = CORE::shift;
731 32         24 my ( $root, $rv );
732              
733 32 50       45 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         35 $root = $obj->root;
739             $rv = $amaps[$$root]{$alias}
740 31 100       56 if CORE::exists $amaps[$$root]{$alias};
741 31 100       54 $rv = _getRefById($rv) if defined $rv;
742             }
743              
744 32         103 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   18 my $caller = CORE::shift;
765 20         15 my $obj = CORE::shift;
766 20         14 my $name = CORE::shift;
767 20         33 my $attr = CORE::shift;
768              
769             # Prepend package scoping in front of private properties
770 20 100       79 $name = "$caller*$name" if $attr & CEH_PRIV;
771              
772             # Apply default attributes
773 20 50       36 $attr |= CEH_SCALAR
774             unless ( $attr ^ CEH_PATTR_TYPE ) > 0;
775 20 50       25 $attr |= CEH_PUB
776             unless ( $attr ^ CEH_PATTR_SCOPE ) > 0;
777              
778             # Save the properties
779 20         15 ${ $properties[$$obj] }{$name} = [];
  20         29  
780 20         15 ${ $properties[$$obj] }{$name}[CEH_PATTR] = $attr;
  20         38  
781 20         10 ${ $properties[$$obj] }{$name}[CEH_PPKG] = $caller;
  20         20  
782 20 100       34 ${ $properties[$$obj] }{$name}[CEH_PVAL] =
  20 100       19  
783             $attr & CEH_ARRAY ? []
784             : $attr & CEH_HASH ? {}
785             : undef;
786              
787 20         30 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         2 my $name = CORE::shift;
800 1         1 my $attr = CORE::shift;
801 1         2 my $caller = caller;
802 1         2 my $rv = !$obj->isStale;
803              
804 1 50       2 if ($rv) {
805 1 50 33     7 if ( defined $name and length $name ) {
806 1         3 $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   68 my $obj = CORE::shift;
828 90         61 my $caller = CORE::shift;
829 90         62 my $name = CORE::shift;
830 90         64 my ( $rv, $class, $cscope, $pscope );
831              
832 90 50 33     242 if ( defined $name and length $name ) {
833              
834             # Check scope and adjust for privately scoped properties
835             $name = "$caller*$name"
836 90 100       189 if CORE::exists $properties[$$obj]{"$caller*$name"};
837              
838 90 100       123 if ( CORE::exists $properties[$$obj]{$name} ) {
839              
840             # Get the property's class
841 85         76 $class = $properties[$$obj]{$name}[CEH_PPKG];
842              
843             # Get the property's scope
844             $pscope =
845 85         87 $properties[$$obj]{$name}[CEH_PATTR] & CEH_PATTR_SCOPE;
846              
847             # Get the caller's scope
848 85 100       286 $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       95 if ( $cscope >= $pscope ) {
855 81         66 $rv = $name;
856             } else {
857 4         5 $@ = 'property access violation';
858 4         383 carp $@;
859             }
860              
861             } else {
862 5         5 $@ = 'method called with an nonexistent property';
863 5         370 carp $@;
864             }
865             } else {
866 0         0 $@ = 'method called with an invalid property name';
867 0         0 carp $@;
868             }
869              
870 90         473 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   25 my $obj = CORE::shift;
880 31         25 my $name = CORE::shift;
881 31         32 my @val = @_;
882 31         26 my ( $rv, $ptype, $pundef, $pref );
883              
884             # Get some meta data
885 31         22 $ptype = ${ $properties[$$obj] }{$name}[CEH_PATTR] & CEH_PATTR_TYPE;
  31         32  
886 31         20 $pundef = ${ $properties[$$obj] }{$name}[CEH_PATTR] & CEH_NO_UNDEF;
  31         42  
887              
888 31 100 100     89 if ( $ptype != CEH_ARRAY and $ptype != CEH_HASH ) {
889 25         21 $pref = ref $val[0];
890              
891             # Check for undef restrictions
892 25 100 100     53 $rv = 1 if !$pundef or defined $val[0];
893              
894 25 100       30 if ($rv) {
895              
896             # Check types for correctness
897 24 50       48 $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         27 $@ = "data type mismatch for $name";
906 24 100       320 carp $@ unless $rv;
907             }
908              
909             } else {
910              
911             # No validation for array/hash types
912 6         7 $rv = 1;
913             }
914              
915             # Assign the value(s)
916 31 100       217 if ($rv) {
917 26 100       36 if ( $ptype == CEH_ARRAY ) {
    100          
918 3         5 ${ $properties[$$obj] }{$name}[CEH_PVAL] = [@val];
  3         3  
919             } elsif ( $ptype == CEH_HASH ) {
920 3         7 ${ $properties[$$obj] }{$name}[CEH_PVAL] = {@val};
  3         4  
921             } else {
922 20         15 ${ $properties[$$obj] }{$name}[CEH_PVAL] = $val[0];
  20         23  
923             }
924             }
925              
926 31         51 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 1107 my $obj = CORE::shift;
936 24         18 my $name = CORE::shift;
937 24         28 my @val = @_;
938 24         21 my $caller = caller;
939 24         32 my $rv = !$obj->isStale;
940              
941 24 50       30 if ($rv) {
942 24         31 $name = $obj->_gatekeeper( $caller, $name );
943 24 50       28 if ( defined $name ) {
944 24         30 $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         60 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   22 my $obj = CORE::shift;
963 38         30 my $name = CORE::shift;
964 38         39 my ( @rv, $ptype );
965              
966             # Get some meta data
967 38         34 $ptype = $properties[$$obj]{$name}[CEH_PATTR] & CEH_PATTR_TYPE;
968              
969             # Retrieve the content
970             @rv =
971 6         15 $ptype == CEH_HASH ? %{ $properties[$$obj]{$name}[CEH_PVAL] }
972 6         16 : $ptype == CEH_ARRAY ? @{ $properties[$$obj]{$name}[CEH_PVAL] }
973 38 100       78 : ( $properties[$$obj]{$name}[CEH_PVAL] );
    100          
974              
975             return
976 38 100       88 $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 938 my $obj = CORE::shift;
988 47         39 my $name = CORE::shift;
989 47         45 my $caller = caller;
990 47         40 my @rv;
991              
992 47 50       65 if ( !$obj->isStale ) {
993 47         77 $name = $obj->_gatekeeper( $caller, $name );
994 47 100       75 if ( defined $name ) {
995 38         61 @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       171 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 997 my $obj = CORE::shift;
1012 3         4 my $name = CORE::shift;
1013 3         5 my @val = @_;
1014 3         3 my $caller = caller;
1015 3         10 my $rv = !$obj->isStale;
1016              
1017 3 50       5 if ($rv) {
1018 3         3 $rv = undef;
1019 3         5 $name = $obj->_gatekeeper( $caller, $name );
1020 3 50       5 if ( defined $name ) {
1021 3 50       6 if ( ref $properties[$$obj]{$name}[CEH_PVAL] eq 'ARRAY' ) {
1022 3         3 $rv = CORE::push @{ $properties[$$obj]{$name}[CEH_PVAL] },
  3         6  
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         11 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 2 my $obj = CORE::shift;
1044 1         1 my $name = CORE::shift;
1045 1         2 my $caller = caller;
1046 1         2 my $rv = !$obj->isStale;
1047              
1048 1 50       3 if ($rv) {
1049 1         2 $rv = undef;
1050 1         2 $name = $obj->_gatekeeper( $caller, $name );
1051 1 50       7 if ( defined $name ) {
1052 1 50       5 if ( ref $properties[$$obj]{$name}[CEH_PVAL] eq 'ARRAY' ) {
1053 1         1 $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         4 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 3 my $obj = CORE::shift;
1074 3         4 my $name = CORE::shift;
1075 3         3 my @val = @_;
1076 3         4 my $caller = caller;
1077 3         7 my $rv = !$obj->isStale;
1078              
1079 3 50       5 if ($rv) {
1080 3         3 $rv = undef;
1081 3         4 $name = $obj->_gatekeeper( $caller, $name );
1082 3 50       6 if ( defined $name ) {
1083 3 50       6 if ( ref $properties[$$obj]{$name}[CEH_PVAL] eq 'ARRAY' ) {
1084             $rv =
1085 3         2 CORE::unshift @{ $properties[$$obj]{$name}[CEH_PVAL]
  3         7  
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         11 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 1 my $obj = CORE::shift;
1108 1         1 my $name = CORE::shift;
1109 1         2 my $caller = caller;
1110 1         2 my $rv = !$obj->isStale;
1111              
1112 1 50       5 if ($rv) {
1113 1         1 $rv = undef;
1114 1         3 $name = $obj->_gatekeeper( $caller, $name );
1115 1 50       3 if ( defined $name ) {
1116 1 50       3 if ( ref $properties[$$obj]{$name}[CEH_PVAL] eq 'ARRAY' ) {
1117             $rv =
1118 1         2 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         3 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 2 my $obj = CORE::shift;
1139 2         3 my $name = CORE::shift;
1140 2         2 my $key = CORE::shift;
1141 2         3 my $caller = caller;
1142 2         3 my $rv = !$obj->isStale;
1143              
1144 2 50       4 if ($rv) {
1145 2         2 $rv = undef;
1146 2         5 $name = $obj->_gatekeeper( $caller, $name );
1147 2 50       5 if ( defined $name ) {
1148 2 50       4 if ( ref $properties[$$obj]{$name}[CEH_PVAL] eq 'HASH' ) {
1149             $rv =
1150             CORE::exists $properties[$$obj]{$name}[CEH_PVAL]
1151 2         3 {$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         6 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 2 my $obj = CORE::shift;
1172 1         1 my $name = CORE::shift;
1173 1         2 my $caller = caller;
1174 1         1 my @rv;
1175              
1176 1 50       2 if ( !$obj->isStale ) {
1177 1         2 $name = $obj->_gatekeeper( $caller, $name );
1178 1 50       3 if ( defined $name ) {
1179 1 50       3 if ( ref $properties[$$obj]{$name}[CEH_PVAL] eq 'HASH' ) {
1180 1         1 @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         2 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 1379 my $obj = CORE::shift;
1203 2         4 my $name = CORE::shift;
1204 2         7 my %updates = @_;
1205 2         4 my $rv = !$obj->isStale;
1206 2         3 my $caller = caller;
1207 2         3 my ( $k, $v );
1208              
1209 2 50       5 if ($rv) {
1210 2         4 $name = $obj->_gatekeeper( $caller, $name );
1211 2 50       5 if ( defined $name ) {
1212 2 100       8 if ( ref $properties[$$obj]{$name}[CEH_PVAL] eq 'ARRAY' ) {
    50          
1213 1         5 while ( ( $k, $v ) = each %updates ) {
1214 3         8 $properties[$$obj]{$name}[CEH_PVAL][$k] = $v;
1215             }
1216             } elsif ( ref $properties[$$obj]{$name}[CEH_PVAL] eq 'HASH' )
1217             {
1218 1         4 while ( ( $k, $v ) = each %updates ) {
1219 2         6 $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         8 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 1381 my $obj = CORE::shift;
1243 2         3 my $name = CORE::shift;
1244 2         3 my @keys = @_;
1245 2         3 my $caller = caller;
1246 2         2 my ( @rv, $k, $l );
1247              
1248 2 50       3 if ( !$obj->isStale ) {
1249 2         4 $name = $obj->_gatekeeper( $caller, $name );
1250 2 50       5 if ( defined $name ) {
1251 2 100       9 if ( ref $properties[$$obj]{$name}[CEH_PVAL] eq 'ARRAY' ) {
    50          
1252 1         1 $l = $#{ $properties[$$obj]{$name}[CEH_PVAL] };
  1         3  
1253 1         2 foreach $k (@keys) {
1254             CORE::push @rv, (
1255             $k <= $l
1256 4 50       9 ? $properties[$$obj]{$name}[CEH_PVAL][$k]
1257             : undef
1258             );
1259             }
1260             } elsif ( ref $properties[$$obj]{$name}[CEH_PVAL] eq 'HASH' )
1261             {
1262 1         2 foreach $k (@keys) {
1263             CORE::push @rv, (
1264             CORE::exists $properties[$$obj]{$name}[CEH_PVAL]
1265             {$k}
1266 2 50       6 ? $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         7 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 1136 my $obj = CORE::shift;
1292 2         2 my $name = CORE::shift;
1293 2         4 my @keys = @_;
1294 2         2 my $caller = caller;
1295 2         4 my $rv = !$obj->isStale;
1296 2         4 my ( $k, $l );
1297              
1298 2 50       3 if ($rv) {
1299 2         3 $name = $obj->_gatekeeper( $caller, $name );
1300 2 50       5 if ( defined $name ) {
1301 2 100       8 if ( ref $properties[$$obj]{$name}[CEH_PVAL] eq 'ARRAY' ) {
    50          
1302 1         1 $l = $#{ $properties[$$obj]{$name}[CEH_PVAL] };
  1         3  
1303 1         5 foreach $k ( sort { $b <=> $a } @keys ) {
  2         5  
1304 3 50       6 splice @{ $properties[$$obj]{$name}[CEH_PVAL] }, $k, 1
  3         6  
1305             unless $k > $l;
1306             }
1307             } elsif ( ref $properties[$$obj]{$name}[CEH_PVAL] eq 'HASH' )
1308             {
1309 1         3 foreach $k (@keys) {
1310 2         4 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         6 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 619 my $obj = CORE::shift;
1332 2         1 my $name = CORE::shift;
1333 2         3 my $caller = caller;
1334 2         4 my $rv = !$obj->isStale;
1335              
1336 2 50       4 if ($rv) {
1337 2         5 $name = $obj->_gatekeeper( $caller, $name );
1338 2 50       4 if ( defined $name ) {
1339 2 100       8 if ( ref $properties[$$obj]{$name}[CEH_PVAL] eq 'ARRAY' ) {
    50          
1340 1         1 @{ $properties[$$obj]{$name}[CEH_PVAL] } = ();
  1         4  
1341             } elsif ( ref $properties[$$obj]{$name}[CEH_PVAL] eq 'HASH' )
1342             {
1343 1         1 %{ $properties[$$obj]{$name}[CEH_PVAL] } = ();
  1         3  
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         6 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 1251 my $obj = CORE::shift;
1364 3         5 my $caller = caller;
1365 3         3 my @pnames = CORE::keys %{ $properties[$$obj] };
  3         12  
1366 3         5 my @rv;
1367              
1368             # Populate with all the public properties
1369             @rv =
1370 3         4 grep { $properties[$$obj]{$_}[CEH_PATTR] & CEH_PUB } @pnames;
  25         25  
1371              
1372             # Add restricted properties if the caller is a subclass
1373 3 100 66     17 if ( $caller eq ref $obj
1374             or "$caller"->isa($obj) ) {
1375             CORE::push @rv,
1376 2         3 grep { $properties[$$obj]{$_}[CEH_PATTR] & CEH_RESTR }
  14         13  
1377             @pnames;
1378             }
1379              
1380             # Add private properties if the caller is the same class
1381 3 100       6 if ( $caller eq ref $obj ) {
1382 2         43 foreach ( grep /^\Q$caller*\E/s, @pnames ) {
1383 2         3 CORE::push @rv, $_;
1384 2         16 $rv[$#rv] =~ s/^\Q$caller*\E//s;
1385             }
1386             }
1387              
1388 3         16 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   21 my $obj = CORE::shift;
1398 24         38 my @classes = _getClasses($obj);
1399 24         24 my $rv = 1;
1400 24         18 my ( $class, @_properties, $prop, $pattr, $pscope, $pname );
1401              
1402             # Initialize storage
1403 24         27 $properties[$$obj] = {};
1404              
1405             # Load properties from top of class hierarchy down
1406 24         34 foreach $class (@classes) {
1407              
1408             # Get the contents of the class array
1409             {
1410 7     7   46 no strict 'refs';
  7         8  
  7         1829  
  31         17  
1411              
1412             @_properties =
1413 31         124 defined *{"${class}::_properties"}
1414 31 100       24 ? @{ *{"${class}::_properties"}{ARRAY} }
  5         6  
  5         17  
1415             : ();
1416             }
1417              
1418             # Process the list
1419 31         38 foreach $prop (@_properties) {
1420 19 50       22 next unless defined $prop;
1421              
1422 19 50       28 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     57 if ( $rv and defined $$prop[CEH_PVAL] ) {
1432              
1433             # Get the attribute type, scope, and internal prop name
1434 7         5 $pattr = $$prop[CEH_PATTR] & CEH_PATTR_TYPE;
1435 7         6 $pscope = $$prop[CEH_PATTR] & CEH_PATTR_SCOPE;
1436 7 50       11 $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         6 $pattr == CEH_ARRAY ? @{ $$prop[CEH_PVAL] }
1444 7 100       27 : $pattr == CEH_HASH ? %{ $$prop[CEH_PVAL] }
  1 100       5  
1445             : $$prop[CEH_PVAL] );
1446             }
1447              
1448 19 50       32 last unless $rv;
1449             }
1450              
1451             }
1452              
1453 24         36 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   21 my $obj = CORE::shift;
1463              
1464 24         17 $properties[$$obj] = undef;
1465              
1466 24         29 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   5 my $pkg = CORE::shift;
1482 9         6 my $attr = CORE::shift;
1483 9         7 my $method = CORE::shift;
1484 9         6 my $rv = 1;
1485 9         6 my ( $code, $mfqn );
1486              
1487 9 50 33     34 if ( defined $attr and defined $method and length $method ) {
      33        
1488              
1489             # Quiet some warnings
1490 7     7   29 no warnings qw(redefine prototype);
  7         8  
  7         236  
1491 7     7   24 no strict 'refs';
  7         7  
  7         2383  
1492              
1493             # Get the fully qualified method name and associated code
1494             # block
1495 9         8 $mfqn = "${pkg}::${method}";
1496 9         4 $code = *{$mfqn}{CODE};
  9         17  
1497              
1498             # Quick check to see if we've done this already -- if so
1499             # we skip to the next
1500 9 100       15 return 1 if CORE::exists $methods{$mfqn};
1501              
1502 6 50       7 if ( defined $code ) {
1503              
1504             # Repackage
1505 6 100       12 if ( $attr == CEH_PRIV ) {
    100          
    50          
1506              
1507             # Private methods
1508 2         9 *{$mfqn} = sub {
1509 5     5   2068 my $caller = caller;
1510 5 100       11 goto &{$code} if $caller eq $pkg;
  2         6  
1511 3         8 $@ = 'Attempted to call private method '
1512             . "$method from $caller";
1513 3         317 carp $@;
1514 3         135 return 0;
1515 2         10 };
1516              
1517             } elsif ( $attr == CEH_RESTR ) {
1518              
1519             # Restricted methods
1520 2         5 *{$mfqn} = sub {
1521 5     5   697 my $caller = caller;
1522 5 100       25 goto &{$code} if "$caller"->isa($pkg);
  3         9  
1523 2         6 $@ = 'Attempted to call restricted method '
1524             . "$method from $caller";
1525 2         141 carp $@;
1526 2         70 return 0;
1527 2         7 };
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       12 $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         12 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   16 my $attr = CORE::shift;
1559 6         4 my $method = CORE::shift;
1560 6         6 my $caller = caller;
1561 6         3 my $rv = 1;
1562              
1563 6 50 33     18 if ( defined $method and length $method ) {
1564 6         7 $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         7 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   22 my $obj = CORE::shift;
1581 24         32 my @classes = _getClasses($obj);
1582 24         31 my $rv = 1;
1583 24         18 my ( $class, @_methods, $method );
1584              
1585             # Load methods from the top of the class hierarchy down
1586 24         24 foreach $class (@classes) {
1587              
1588             # Skip if the class has already been processed
1589 31 100       60 next if CORE::exists $classes{$class};
1590              
1591             # Get the contents of the class array
1592             {
1593 7     7   30 no strict 'refs';
  7         8  
  7         2534  
  11         102  
1594              
1595 1         1 @_methods = @{ *{"${class}::_methods"}{ARRAY} }
  1         3  
1596 11 100       7 if defined *{"${class}::_methods"};
  11         58  
1597             }
1598              
1599             # Process the list
1600 11         18 foreach $method (@_methods) {
1601 3 50       4 next unless defined $method;
1602 3 50       7 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         20 $classes{$class} = 1;
1612             }
1613              
1614 24         27 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 2394 my $class = CORE::shift;
1629 23         30 my @args = @_;
1630 23         21 my $obj = bless \do { my $anon_scalar }, $class;
  23         39  
1631 23         20 my $rv;
1632              
1633             # Get the next available ID
1634 23         44 $rv = _getID($obj);
1635              
1636             # Initialize alias support
1637 23 50       57 $rv = _initAlias($obj) if $rv;
1638              
1639             # Initialize property scope support
1640 23 50       61 $rv = _initProperties($obj) if $rv;
1641              
1642             # Initialize method scope support
1643 23 50       54 $rv = _initMethods($obj) if $rv;
1644              
1645             # Initialize the hierarchal code support
1646 23 50       59 $rv = _initHierarchy( $obj, $class, @args ) if $rv;
1647              
1648 23 50       53 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 371 my $class = CORE::shift;
1658 1         1 my $pobj = CORE::shift;
1659 1         2 my @args = @_;
1660 1         1 my $obj = bless \do { my $anon_scalar }, $class;
  1         2  
1661 1         2 my $rv = 1;
1662              
1663             # Get the next available ID
1664 1 50       5 $rv = _getID($obj) if $rv;
1665              
1666             # Adopt the object before we do anything else
1667 1 50       5 $rv = $pobj->_adopt($obj) if $rv;
1668              
1669             # Initialize property scope support
1670 1 50       3 $rv = _initProperties($obj) if $rv;
1671              
1672             # Initialize method scope support
1673 1 50       4 $rv = _initMethods($obj) if $rv;
1674              
1675             # Initialize the hierarchal code support
1676 1 50       15 $rv = _initHierarchy( $obj, $class, @args ) if $rv;
1677              
1678 1 50       5 return $rv ? $obj : undef;
1679             }
1680              
1681             sub DESTROY {
1682              
1683             # Purpose: Garbage collection
1684             # Returns: Boolean
1685             # Usage: $obj->DESTROY();
1686              
1687 30     30   2627 my $obj = CORE::shift;
1688 30         26 my ( $class, @classes );
1689              
1690             # Test to see if this is a stale reference
1691 30 100 66     84 unless ( !defined $$obj or $obj->isStale ) {
1692              
1693             # Destroy from the top of the tree down
1694 24 50       56 foreach ( $obj->children ) { $_->DESTROY if defined }
  5         22  
1695              
1696             # Execute hierarchal destructors
1697 24         41 _destrHierarchy($obj);
1698              
1699             # Destroy aliases
1700 24         45 _destrAlias($obj);
1701              
1702             # Destroy properties
1703 24         36 _destrProperties($obj);
1704              
1705             # Recover the ID
1706 24         33 _delID($obj);
1707             }
1708              
1709 30         223 return 1;
1710             }
1711              
1712             END {
1713 6 0   6   19 foreach ( _dumpObjects() ) { $_->DESTROY if defined }
  0            
1714             }
1715              
1716             1;
1717              
1718             __END__