File Coverage

blib/lib/Tree/Simple.pm
Criterion Covered Total %
statement 233 236 98.7
branch 121 134 90.3
condition 27 32 84.3
subroutine 50 50 100.0
pod 34 36 94.4
total 465 488 95.2


line stmt bran cond sub pod time code
1             package Tree::Simple;
2              
3 11     11   163621 use strict;
  11         16  
  11         279  
4 11     11   39 use warnings;
  11         14  
  11         431  
5              
6             our $VERSION = '1.31';
7              
8 11     11   41 use Scalar::Util qw(blessed);
  11         17  
  11         1509  
9              
10             ## -----------------------------------------------
11             ## Tree::Simple
12             ## -----------------------------------------------
13              
14             my $USE_WEAK_REFS;
15              
16             sub import {
17 11     11   61 shift;
18 11 50       9883 return unless @_;
19 0 0       0 if (lc($_[0]) eq 'use_weak_refs') {
20 0         0 $USE_WEAK_REFS++;
21 0         0 *Tree::Simple::weaken = \&Scalar::Util::weaken;
22             }
23             }
24              
25             ## class constants
26 11     11   50 use constant ROOT => "root";
  11         17  
  11         25613  
27              
28             ### constructor
29              
30             sub new {
31 136     136 1 9698 my ($_class, $node, $parent) = @_;
32 136   66     362 my $class = ref($_class) || $_class;
33 136         165 my $tree = bless({}, $class);
34 136         210 $tree->_init($node, $parent, []);
35 133         277 return $tree;
36             }
37              
38             ### -----------------------------------------------
39             ### methods
40             ### -----------------------------------------------
41              
42             ## -----------------------------------------------
43             ## private methods
44              
45             sub _init {
46 136     136   135 my ($self, $node, $parent, $children) = @_;
47             # set the value of the unique id
48 136         652 ($self->{_uid}) = ("$self" =~ /\((.*?)\)$/);
49             # set the value of the node
50 136         156 $self->{_node} = $node;
51             # and set the value of _children
52 136         130 $self->{_children} = $children;
53 136         106 $self->{_height} = 1;
54 136         130 $self->{_width} = 1;
55             # Now check our $parent value
56 136 100       179 if (defined($parent)) {
57 9 100 100     87 if (blessed($parent) && $parent->isa("Tree::Simple")) {
    100          
58             # and set it as our parent
59 3         7 $parent->addChild($self);
60             }
61             elsif ($parent eq $self->ROOT) {
62 3         15 $self->_setParent( $self->ROOT );
63             }
64             else {
65 3         23 die "Insufficient Arguments : parent argument must be a Tree::Simple object";
66             }
67             }
68             else {
69 127         354 $self->_setParent( $self->ROOT );
70             }
71             }
72              
73             sub _setParent {
74 263     263   307 my ($self, $parent) = @_;
75 263 100 66     1266 (defined($parent) &&
      66        
76             (($parent eq $self->ROOT) || (blessed($parent) && $parent->isa("Tree::Simple"))))
77             || die "Insufficient Arguments : parent also must be a Tree::Simple object";
78 259         278 $self->{_parent} = $parent;
79 259 100       432 if ($parent eq $self->ROOT) {
80 143         189 $self->{_depth} = -1;
81             }
82             else {
83 116 50       174 weaken($self->{_parent}) if $USE_WEAK_REFS;
84 116         140 $self->{_depth} = $parent->getDepth() + 1;
85             }
86             }
87              
88             sub _detachParent {
89 18 50   18   20 return if $USE_WEAK_REFS;
90 18         13 my ($self) = @_;
91 18         18 $self->{_parent} = undef;
92             }
93              
94             sub _setHeight {
95 142     142   111 my ($self, $child) = @_;
96 142         164 my $child_height = $child->getHeight();
97 142 100       284 return if ($self->{_height} >= $child_height + 1);
98 66         67 $self->{_height} = $child_height + 1;
99              
100             # and now bubble up to the parent (unless we are the root)
101 66 100       96 $self->getParent()->_setHeight($self) unless $self->isRoot();
102             }
103              
104             sub _setWidth {
105 107     107   101 my ($self, $child_width) = @_;
106 107         86 $self->{_width} += $child_width;
107             # and now bubble up to the parent (unless we are the root)
108 107 100       109 $self->getParent()->_setWidth($child_width) unless $self->isRoot();
109             }
110              
111             ## -----------------------------------------------
112             ## mutators
113              
114             sub setNodeValue {
115 23     23 1 41 my ($self, $node_value) = @_;
116 23 100       36 (defined($node_value)) || die "Insufficient Arguments : must supply a value for node";
117 22         23 $self->{_node} = $node_value;
118             }
119              
120             sub setUID {
121 2     2 1 22 my ($self, $uid) = @_;
122 2 100       13 ($uid) || die "Insufficient Arguments : Custom Unique ID's must be a true value";
123 1         3 $self->{_uid} = $uid;
124             }
125              
126             ## -----------------------------------------------
127             ## child methods
128              
129             sub addChild {
130 36     36 1 5186 splice @_, 1, 0, $_[0]->getChildCount;
131 36         89 goto &insertChild;
132             }
133              
134             sub addChildren {
135 21     21 1 294 splice @_, 1, 0, $_[0]->getChildCount;
136 21         48 goto &insertChildren;
137             }
138              
139             sub generateChild
140             {
141 3     3 1 703 return $_[0]->addChild($_[0]->new($_[1]) );
142             }
143              
144             sub _insertChildAt {
145 81     81   4848 my ($self, $index, @trees) = @_;
146              
147 81 100       159 (defined($index))
148             || die "Insufficient Arguments : Cannot insert child without index";
149              
150             # check the bounds of our children
151             # against the index given
152 79         104 my $max = $self->getChildCount();
153 79 100       135 ($index <= $max)
154             || die "Index Out of Bounds : got ($index) expected no more than ("
155             . $self->getChildCount() . ")";
156              
157 77 100       148 (@trees)
158             || die "Insufficient Arguments : no tree(s) to insert";
159              
160 74         68 my($new_width) = 0;
161              
162 74         104 foreach my $tree (@trees) {
163 127 100 100     739 (blessed($tree) && $tree->isa("Tree::Simple"))
164             || die "Insufficient Arguments : Child must be a Tree::Simple object";
165 116         160 $tree->_setParent($self);
166 116         160 $self->_setHeight($tree);
167              
168 116         139 $new_width += getWidth($tree);
169              
170 116 100       148 $tree->fixDepth() unless $tree->isLeaf();
171             }
172              
173 63 100       78 $self -> _setWidth($new_width - ($self -> isLeaf ? 1 : 0) );
174              
175             # if index is zero, use this optimization
176 63 100       142 if ($index == 0) {
    100          
177 44         33 unshift @{$self->{_children}} => @trees;
  44         74  
178             }
179             # if index is equal to the number of children
180             # then use this optimization
181             elsif ($index == $max) {
182 11         12 push @{$self->{_children}} => @trees;
  11         18  
183             }
184             # otherwise do some heavy lifting here
185             else {
186 8         6 splice @{$self->{_children}}, $index, 0, @trees;
  8         16  
187             }
188              
189 63         126 $self;
190             }
191              
192             *insertChildren = \&_insertChildAt;
193              
194             # insertChild is really the same as insertChildren, you are just
195             # inserting an array of one tree
196             *insertChild = \&insertChildren;
197              
198             sub removeChildAt {
199 16     16 0 921 my ($self, $index) = @_;
200 16 100       36 (defined($index))
201             || die "Insufficient Arguments : Cannot remove child without index.";
202 15 100       24 ($self->getChildCount() != 0)
203             || die "Illegal Operation : There are no children to remove";
204             # check the bounds of our children
205             # against the index given
206 14 100       21 ($index < $self->getChildCount())
207             || die "Index Out of Bounds : got ($index) expected no more than ("
208             . $self->getChildCount() . ")";
209 13         13 my $removed_child;
210             # if index is zero, use this optimization
211 13 100       29 if ($index == 0) {
    100          
212 8         8 $removed_child = shift @{$self->{_children}};
  8         13  
213             }
214             # if index is equal to the number of children
215             # then use this optimization
216 5         9 elsif ($index == $#{$self->{_children}}) {
217 2         2 $removed_child = pop @{$self->{_children}};
  2         3  
218             }
219             # otherwise do some heavy lifting here
220             else {
221 3         4 $removed_child = $self->{_children}->[$index];
222 3         1 splice @{$self->{_children}}, $index, 1;
  3         5  
223             }
224             # make sure we fix the height
225 13         25 $self->fixHeight();
226 13         21 $self->fixWidth();
227             # make sure that the removed child
228             # is no longer connected to the parent
229             # so we change its parent to ROOT
230 13         32 $removed_child->_setParent($self->ROOT);
231             # and now we make sure that the depth
232             # of the removed child is aligned correctly
233 13 100       16 $removed_child->fixDepth() unless $removed_child->isLeaf();
234             # return this removed child
235             # it is the responsibility
236             # of the user of this module
237             # to properly dispose of this
238             # child (and all its sub-children)
239 13         53 return $removed_child;
240             }
241              
242             sub removeChild {
243 13     13 1 88 my ($self, $child_to_remove) = @_;
244 13 100       36 (defined($child_to_remove))
245             || die "Insufficient Arguments : you must specify a child to remove";
246             # maintain backwards compatibility
247             # so any non-ref arguments will get
248             # sent to removeChildAt
249 12 100       32 return $self->removeChildAt($child_to_remove) unless ref($child_to_remove);
250             # now that we are confident it's a reference
251             # make sure it is the right kind
252 11 100 100     92 (blessed($child_to_remove) && $child_to_remove->isa("Tree::Simple"))
253             || die "Insufficient Arguments : Only valid child type is a Tree::Simple object";
254 9         12 my $index = 0;
255 9         22 foreach my $child ($self->getAllChildren()) {
256 11 100       45 ("$child" eq "$child_to_remove") && return $self->removeChildAt($index);
257 3         3 $index++;
258             }
259 1         8 die "Child Not Found : cannot find object ($child_to_remove) in self";
260             }
261              
262             sub getIndex {
263 14     14 1 15 my ($self) = @_;
264 14 100       39 return -1 if $self->{_parent} eq $self->ROOT;
265 13         11 my $index = 0;
266 13         20 foreach my $sibling ($self->{_parent}->getAllChildren()) {
267 27 100       74 ("$sibling" eq "$self") && return $index;
268 14         15 $index++;
269             }
270             }
271              
272             ## -----------------------------------------------
273             ## Sibling methods
274              
275             # these addSibling and addSiblings functions
276             # just pass along their arguments to the addChild
277             # and addChildren method respectively, this
278             # eliminates the need to overload these method
279             # in things like the Keyable Tree object
280              
281             sub addSibling {
282 2     2 1 21 my ($self, @args) = @_;
283 2 100       4 (!$self->isRoot())
284             || die "Insufficient Arguments : cannot add a sibling to a ROOT tree";
285 1         2 $self->{_parent}->addChild(@args);
286             }
287              
288             sub addSiblings {
289 2     2 1 249 my ($self, @args) = @_;
290 2 100       5 (!$self->isRoot())
291             || die "Insufficient Arguments : cannot add siblings to a ROOT tree";
292 1         4 $self->{_parent}->addChildren(@args);
293             }
294              
295             sub insertSiblings {
296 4     4 1 275 my ($self, @args) = @_;
297 4 100       6 (!$self->isRoot())
298             || die "Insufficient Arguments : cannot insert sibling(s) to a ROOT tree";
299 2         29 $self->{_parent}->insertChildren(@args);
300             }
301              
302             # insertSibling is really the same as
303             # insertSiblings, you are just inserting
304             # and array of one tree
305             *insertSibling = \&insertSiblings;
306              
307             # I am not permitting the removal of siblings
308             # as I think in general it is a bad idea
309              
310             ## -----------------------------------------------
311             ## accessors
312              
313 5     5 1 17 sub getUID { $_[0]{_uid} }
314 216     216 1 3459 sub getParent { $_[0]{_parent} }
315 217     217 1 1695 sub getDepth { $_[0]{_depth} }
316 282     282 1 1051 sub getNodeValue { $_[0]{_node} }
317 280     280 1 434 sub getWidth { $_[0]{_width} }
318 259     259 1 421 sub getHeight { $_[0]{_height} }
319              
320             # for backwards compatibility
321             *height = \&getHeight;
322              
323 480     480 1 1581 sub getChildCount { $#{$_[0]{_children}} + 1 }
  480         1254  
324              
325             sub getChild {
326 108     108 1 5108 my ($self, $index) = @_;
327 108 100       185 (defined($index))
328             || die "Insufficient Arguments : Cannot get child without index";
329 107         252 return $self->{_children}->[$index];
330             }
331              
332             sub getAllChildren {
333 372     372 1 3012 my ($self) = @_;
334             return wantarray ?
335 362         587 @{$self->{_children}}
336             :
337 372 100       406 $self->{_children};
338             }
339              
340             sub getSibling {
341 11     11 1 25 my ($self, $index) = @_;
342 11 100       17 (!$self->isRoot())
343             || die "Insufficient Arguments : cannot get siblings from a ROOT tree";
344 10         15 $self->getParent()->getChild($index);
345             }
346              
347             sub getAllSiblings {
348 33     33 1 3909 my ($self) = @_;
349 33 100       43 (!$self->isRoot())
350             || die "Insufficient Arguments : cannot get siblings from a ROOT tree";
351 32         44 $self->getParent()->getAllChildren();
352             }
353              
354             sub getSiblingCount
355             {
356 5     5 1 606 my($self) = @_;
357              
358 5 50       9 return $self->isRoot ? 0 : $#{$self->getAllSiblings};
  5         7  
359              
360             } # End of getSiblingCount.
361              
362             ## -----------------------------------------------
363             ## informational
364              
365 288     288 1 374 sub isLeaf { $_[0]->getChildCount == 0 }
366              
367             sub isRoot {
368 374     374 1 18116 my ($self) = @_;
369 374   66     1862 return (!defined($self->{_parent}) || $self->{_parent} eq $self->ROOT);
370             }
371              
372             sub size {
373 21     21 1 476 my ($self) = @_;
374 21         14 my $size = 1;
375 21         14 foreach my $child ($self->getAllChildren()) {
376 20         22 $size += $child->size();
377             }
378 21         18 return $size;
379             }
380              
381             sub isFirstChild
382             {
383 4     4 0 505 my($self) = @_;
384              
385 4 50       6 return $self->isRoot ? 0 : $_[0]->getIndex == 0;
386              
387             } # End of isFirstChild.
388              
389             sub isLastChild
390             {
391 4     4 1 770 my($self) = @_;
392              
393 4 50       6 return $self->isRoot ? 0 : $self->getIndex == ($self->getParent->getChildCount - 1);
394              
395             } # End of isLastChild.
396              
397             ## -----------------------------------------------
398             ## misc
399              
400             # NOTE:
401             # Occasionally one wants to have the
402             # depth available for various reasons
403             # of convenience. Sometimes that depth
404             # field is not always correct.
405             # If you create your tree in a top-down
406             # manner, this is usually not an issue
407             # since each time you either add a child
408             # or create a tree you are doing it with
409             # a single tree and not a hierarchy.
410             # If however you are creating your tree
411             # bottom-up, then you might find that
412             # when adding hierarchies of trees, your
413             # depth fields are all out of whack.
414             # This is where this method comes into play
415             # it will recurse down the tree and fix the
416             # depth fields appropriately.
417             # This method is called automatically when
418             # a subtree is added to a child array
419             sub fixDepth {
420 19     19 1 15 my ($self) = @_;
421             # make sure the tree's depth
422             # is up to date all the way down
423             $self->traverse(sub {
424 41     41   26 my ($tree) = @_;
425 41 50       46 return if $tree->isRoot();
426 41         80 $tree->{_depth} = $tree->getParent()->getDepth() + 1;
427             }
428 19         83 );
429             }
430              
431             # NOTE:
432             # This method is used to fix any height
433             # discrepancies which might arise when
434             # you remove a sub-tree
435             sub fixHeight {
436 21     21 1 18 my ($self) = @_;
437             # we must find the tallest sub-tree
438             # and use that to define the height
439 21         20 my $max_height = 0;
440 21 100       24 unless ($self->isLeaf()) {
441 17         32 foreach my $child ($self->getAllChildren()) {
442 62         58 my $child_height = $child->getHeight();
443 62 100       91 $max_height = $child_height if ($max_height < $child_height);
444             }
445             }
446             # if there is no change, then we
447             # need not bubble up through the
448             # parents
449 21 100       50 return if ($self->{_height} == ($max_height + 1));
450             # otherwise ...
451 13         12 $self->{_height} = $max_height + 1;
452             # now we need to bubble up through the parents
453             # in order to rectify any issues with height
454 13 100       17 $self->getParent()->fixHeight() unless $self->isRoot();
455             }
456              
457             sub fixWidth {
458 29     29 1 29 my ($self) = @_;
459 29         23 my $fixed_width = 0;
460 29         34 $fixed_width += $_->getWidth() foreach $self->getAllChildren();
461 29         28 $self->{_width} = $fixed_width;
462 29 100       32 $self->getParent()->fixWidth() unless $self->isRoot();
463             }
464              
465             sub traverse {
466 216     216 1 1956 my ($self, $func, $post) = @_;
467 216 100       272 (defined($func)) || die "Insufficient Arguments : Cannot traverse without traversal function";
468 215 100       291 (ref($func) eq "CODE") || die "Incorrect Object Type : traversal function is not a function";
469 213 100 100     288 (ref($post) eq "CODE") || die "Incorrect Object Type : post traversal function is not a function"
470             if defined($post);
471 211         184 foreach my $child ($self->getAllChildren()) {
472 182         199 $func->($child);
473 182         215 $child->traverse($func, $post);
474 182 100       283 defined($post) && $post->($child);
475             }
476             }
477              
478             # this is an improved version of the
479             # old accept method, it now it more
480             # accepting of its arguments
481             sub accept {
482 11     11 1 366 my ($self, $visitor) = @_;
483             # it must be a blessed reference and ...
484 11 100 100     123 (blessed($visitor) &&
      66        
485             # either a Tree::Simple::Visitor object, or ...
486             ($visitor->isa("Tree::Simple::Visitor") ||
487             # it must be an object which has a 'visit' method available
488             $visitor->can('visit')))
489             || die "Insufficient Arguments : You must supply a valid Visitor object";
490 7         21 $visitor->visit($self);
491             }
492              
493             ## -----------------------------------------------
494             ## cloning
495              
496             sub clone {
497 33     33 1 30 my ($self) = @_;
498             # first clone the value in the node
499 33         35 my $cloned_node = _cloneNode($self->getNodeValue());
500             # create a new Tree::Simple object
501             # here with the cloned node, however
502             # we do not assign the parent node
503             # since it really does not make a lot
504             # of sense. To properly clone it would
505             # be to clone back up the tree as well,
506             # which IMO is not intuitive. So in essence
507             # when you clone a tree, you detach it from
508             # any parentage it might have
509 33         39 my $clone = $self->new($cloned_node);
510             # however, because it is a recursive thing
511             # when you clone all the children, and then
512             # add them to the clone, you end up setting
513             # the parent of the children to be that of
514             # the clone (which is correct)
515             $clone->addChildren(
516 33 100       41 map { $_->clone() } $self->getAllChildren()
  29         36  
517             ) unless $self->isLeaf();
518             # return the clone
519 33         50 return $clone;
520             }
521              
522             # this allows cloning of single nodes while
523             # retaining connections to a tree, this is sloppy
524             sub cloneShallow {
525 2     2 1 4 my ($self) = @_;
526 2         2 my $cloned_tree = { %{$self} };
  2         13  
527 2         5 bless($cloned_tree, ref($self));
528             # just clone the node (if you can)
529 2         3 $cloned_tree->setNodeValue(_cloneNode($self->getNodeValue()));
530 2         5 return $cloned_tree;
531             }
532              
533             # this is a helper function which
534             # recursively clones the node
535             sub _cloneNode {
536 44     44   36 my ($node, $seen) = @_;
537             # create a cache if we don't already
538             # have one to prevent circular refs
539             # from being copied more than once
540 44 100       65 $seen = {} unless defined $seen;
541             # now here we go...
542 44         23 my $clone;
543             # if it is not a reference, then lets just return it
544 44 100       83 return $node unless ref($node);
545             # if it is in the cache, then return that
546 10 50       7 return $seen->{$node} if exists ${$seen}{$node};
  10         18  
547             # if it is an object, then ...
548 10 100       18 if (blessed($node)) {
549             # see if we can clone it
550 4 100       20 if ($node->can('clone')) {
551 2         4 $clone = $node->clone();
552             }
553             # otherwise respect that it does
554             # not want to be cloned
555             else {
556 2         7 $clone = $node;
557             }
558             }
559             else {
560             # if the current slot is a scalar reference, then
561             # dereference it and copy it into the new object
562 6 100 100     23 if (ref($node) eq "SCALAR" || ref($node) eq "REF") {
    100          
    100          
563 3         3 my $var = "";
564 3         2 $clone = \$var;
565 3         3 ${$clone} = _cloneNode(${$node}, $seen);
  3         3  
  3         6  
566             }
567             # if the current slot is an array reference
568             # then dereference it and copy it
569             elsif (ref($node) eq "ARRAY") {
570 1         1 $clone = [ map { _cloneNode($_, $seen) } @{$node} ];
  4         4  
  1         2  
571             }
572             # if the current reference is a hash reference
573             # then dereference it and copy it
574             elsif (ref($node) eq "HASH") {
575 1         4 $clone = {};
576 1         2 foreach my $key (keys %{$node}) {
  1         3  
577 2         4 $clone->{$key} = _cloneNode($node->{$key}, $seen);
578             }
579             }
580             else {
581             # all other ref types are not copied
582 1         1 $clone = $node;
583             }
584             }
585             # store the clone in the cache and
586 10         18 $seen->{$node} = $clone;
587             # then return the clone
588 10         14 return $clone;
589             }
590              
591              
592             ## -----------------------------------------------
593             ## Desctructor
594              
595             sub DESTROY {
596             # if we are using weak refs
597             # we don't need to worry about
598             # destruction, it will just happen
599 25 50   25   1554 return if $USE_WEAK_REFS;
600 25         45 my ($self) = @_;
601             # we want to detach all our children from
602             # ourselves, this will break most of the
603             # connections and allow for things to get
604             # reaped properly
605 25 50       44 if ($self->{_children}) {
606 25         20 foreach my $child (@{$self->{_children}}) {
  25         208  
607 18 50       27 defined $child && $child->_detachParent();
608             }
609             }
610             # we do not need to remove or undef the _children
611             # of the _parent fields, this will cause some
612             # unwanted releasing of connections.
613             }
614              
615             ## -----------------------------------------------
616             ## end Tree::Simple
617             ## -----------------------------------------------
618              
619             1;
620              
621             __END__