File Coverage

blib/lib/Tree/Simple.pm
Criterion Covered Total %
statement 235 238 98.7
branch 125 138 90.5
condition 34 36 94.4
subroutine 50 50 100.0
pod 34 36 94.4
total 478 498 95.9


line stmt bran cond sub pod time code
1             package Tree::Simple;
2              
3 12     12   676563 use strict;
  12         116  
  12         337  
4 12     12   60 use warnings;
  12         24  
  12         532  
5              
6             our $VERSION = '1.33';
7              
8 12     12   73 use Scalar::Util qw(blessed);
  12         24  
  12         1585  
9              
10             ## -----------------------------------------------
11             ## Tree::Simple
12             ## -----------------------------------------------
13              
14             my $USE_WEAK_REFS;
15              
16             sub import {
17 12     12   105 shift;
18 12 50       13401 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 12     12   81 use constant ROOT => "root";
  12         38  
  12         44950  
27              
28             ### constructor
29              
30             sub new {
31 136     136 1 15193 my ($_class, $node, $parent) = @_;
32 136   66     453 my $class = ref($_class) || $_class;
33 136         275 my $tree = bless({}, $class);
34 136         371 $tree->_init($node, $parent, []);
35 133         388 return $tree;
36             }
37              
38             ### -----------------------------------------------
39             ### methods
40             ### -----------------------------------------------
41              
42             ## -----------------------------------------------
43             ## private methods
44              
45             sub _init {
46 136     136   247 my ($self, $node, $parent, $children) = @_;
47             # set the value of the unique id
48 136         897 ($self->{_uid}) = ("$self" =~ /\((.*?)\)$/);
49             # set the value of the node
50 136         313 $self->{_node} = $node;
51             # and set the value of _children
52 136         217 $self->{_children} = $children;
53 136         208 $self->{_height} = 1;
54 136         200 $self->{_width} = 1;
55             # Now check our $parent value
56 136 100       283 if (defined($parent)) {
57 9 100 100     90 if (blessed($parent) && $parent->isa("Tree::Simple")) {
    100          
58             # and set it as our parent
59 3         11 $parent->addChild($self);
60             }
61             elsif ($parent eq $self->ROOT) {
62 3         24 $self->_setParent( $self->ROOT );
63             }
64             else {
65 3         26 die "Insufficient Arguments : parent argument must be a Tree::Simple object";
66             }
67             }
68             else {
69 127         324 $self->_setParent( $self->ROOT );
70             }
71             }
72              
73             sub _setParent {
74 263     263   580 my ($self, $parent) = @_;
75 263 100 100     1421 (defined($parent) &&
      100        
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         503 $self->{_parent} = $parent;
79 259 100       602 if ($parent eq $self->ROOT) {
80 143         381 $self->{_depth} = -1;
81             }
82             else {
83 116 50       274 weaken($self->{_parent}) if $USE_WEAK_REFS;
84 116         210 $self->{_depth} = $parent->getDepth() + 1;
85             }
86             }
87              
88             sub _detachParent {
89 18 50   18   36 return if $USE_WEAK_REFS;
90 18         30 my ($self) = @_;
91 18         38 $self->{_parent} = undef;
92             }
93              
94             sub _setHeight {
95 142     142   233 my ($self, $child) = @_;
96 142         275 my $child_height = $child->getHeight();
97 142 100       356 return if ($self->{_height} >= $child_height + 1);
98 66         128 $self->{_height} = $child_height + 1;
99              
100             # and now bubble up to the parent (unless we are the root)
101 66 100       135 $self->getParent()->_setHeight($self) unless $self->isRoot();
102             }
103              
104             sub _setWidth {
105 107     107   174 my ($self, $child_width) = @_;
106 107         160 $self->{_width} += $child_width;
107             # and now bubble up to the parent (unless we are the root)
108 107 100       173 $self->getParent()->_setWidth($child_width) unless $self->isRoot();
109             }
110              
111             ## -----------------------------------------------
112             ## mutators
113              
114             sub setNodeValue {
115 23     23 1 73 my ($self, $node_value) = @_;
116 23 100       56 (defined($node_value)) || die "Insufficient Arguments : must supply a value for node";
117 22         47 $self->{_node} = $node_value;
118             }
119              
120             sub setUID {
121 2     2 1 58 my ($self, $uid) = @_;
122 2 100       20 ($uid) || die "Insufficient Arguments : Custom Unique ID's must be a true value";
123 1         5 $self->{_uid} = $uid;
124             }
125              
126             ## -----------------------------------------------
127             ## child methods
128              
129             sub addChild {
130 36     36 1 6962 splice @_, 1, 0, $_[0]->getChildCount;
131 36         143 goto &insertChild;
132             }
133              
134             sub addChildren {
135 21     21 1 694 splice @_, 1, 0, $_[0]->getChildCount;
136 21         77 goto &insertChildren;
137             }
138              
139             sub generateChild
140             {
141 3     3 1 1074 return $_[0]->addChild($_[0]->new($_[1]) );
142             }
143              
144             sub _insertChildAt {
145 81     81   6868 my ($self, $index, @trees) = @_;
146              
147 81 100       214 (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         156 my $max = $self->getChildCount();
153 79 100       191 ($index <= $max)
154             || die "Index Out of Bounds : got ($index) expected no more than ("
155             . $self->getChildCount() . ")";
156              
157 77 100       196 (@trees)
158             || die "Insufficient Arguments : no tree(s) to insert";
159              
160 74         124 my($new_width) = 0;
161              
162 74         179 foreach my $tree (@trees) {
163 127 100 100     853 (blessed($tree) && $tree->isa("Tree::Simple"))
164             || die "Insufficient Arguments : Child must be a Tree::Simple object";
165 116         331 $tree->_setParent($self);
166 116         288 $self->_setHeight($tree);
167              
168 116         229 $new_width += getWidth($tree);
169              
170 116 100       236 $tree->fixDepth() unless $tree->isLeaf();
171             }
172              
173 63 100       135 $self -> _setWidth($new_width - ($self -> isLeaf ? 1 : 0) );
174              
175             # if index is zero, use this optimization
176 63 100       188 if ($index == 0) {
    100          
177 44         65 unshift @{$self->{_children}} => @trees;
  44         109  
178             }
179             # if index is equal to the number of children
180             # then use this optimization
181             elsif ($index == $max) {
182 11         18 push @{$self->{_children}} => @trees;
  11         28  
183             }
184             # otherwise do some heavy lifting here
185             else {
186 8         18 splice @{$self->{_children}}, $index, 0, @trees;
  8         28  
187             }
188              
189 63         184 $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 1463 my ($self, $index) = @_;
200 16 100       57 (defined($index))
201             || die "Insufficient Arguments : Cannot remove child without index.";
202 15 100       36 ($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       30 ($index < $self->getChildCount())
207             || die "Index Out of Bounds : got ($index) expected no more than ("
208             . $self->getChildCount() . ")";
209 13         25 my $removed_child;
210             # if index is zero, use this optimization
211 13 100       35 if ($index == 0) {
    100          
212 8         15 $removed_child = shift @{$self->{_children}};
  8         23  
213             }
214             # if index is equal to the number of children
215             # then use this optimization
216 5         12 elsif ($index == $#{$self->{_children}}) {
217 2         3 $removed_child = pop @{$self->{_children}};
  2         4  
218             }
219             # otherwise do some heavy lifting here
220             else {
221 3         7 $removed_child = $self->{_children}->[$index];
222 3         6 splice @{$self->{_children}}, $index, 1;
  3         6  
223             }
224             # make sure we fix the height
225 13         49 $self->fixHeight();
226 13         50 $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         52 $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       25 $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         79 return $removed_child;
240             }
241              
242             sub removeChild {
243 13     13 1 161 my ($self, $child_to_remove) = @_;
244 13 100       51 (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       34 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     119 (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         22 my $index = 0;
255 9         26 foreach my $child ($self->getAllChildren()) {
256 11 100       53 ("$child" eq "$child_to_remove") && return $self->removeChildAt($index);
257 3         5 $index++;
258             }
259 1         11 die "Child Not Found : cannot find object ($child_to_remove) in self";
260             }
261              
262             sub getIndex {
263 14     14 1 32 my ($self) = @_;
264 14 100       65 return -1 if $self->{_parent} eq $self->ROOT;
265 13         24 my $index = 0;
266 13         31 foreach my $sibling ($self->{_parent}->getAllChildren()) {
267 27 100       128 ("$sibling" eq "$self") && return $index;
268 14         29 $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 41 my ($self, @args) = @_;
283 2 100       8 (!$self->isRoot())
284             || die "Insufficient Arguments : cannot add a sibling to a ROOT tree";
285 1         7 $self->{_parent}->addChild(@args);
286             }
287              
288             sub addSiblings {
289 2     2 1 404 my ($self, @args) = @_;
290 2 100       9 (!$self->isRoot())
291             || die "Insufficient Arguments : cannot add siblings to a ROOT tree";
292 1         5 $self->{_parent}->addChildren(@args);
293             }
294              
295             sub insertSiblings {
296 4     4 1 442 my ($self, @args) = @_;
297 4 100       14 (!$self->isRoot())
298             || die "Insufficient Arguments : cannot insert sibling(s) to a ROOT tree";
299 2         8 $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 33 sub getUID { $_[0]{_uid} }
314 216     216 1 5819 sub getParent { $_[0]{_parent} }
315 217     217 1 2109 sub getDepth { $_[0]{_depth} }
316 298     298 1 2206 sub getNodeValue { $_[0]{_node} }
317 280     280 1 653 sub getWidth { $_[0]{_width} }
318 259     259 1 623 sub getHeight { $_[0]{_height} }
319              
320             # for backwards compatibility
321             *height = \&getHeight;
322              
323 480     480 1 2285 sub getChildCount { $#{$_[0]{_children}} + 1 }
  480         1865  
324              
325             sub getChild {
326 108     108 1 8246 my ($self, $index) = @_;
327 108 100       252 (defined($index))
328             || die "Insufficient Arguments : Cannot get child without index";
329 107         381 return $self->{_children}->[$index];
330             }
331              
332             sub getAllChildren {
333 381     381 1 3303 my ($self) = @_;
334             return wantarray ?
335 371         1170 @{$self->{_children}}
336             :
337 381 100       619 $self->{_children};
338             }
339              
340             sub getSibling {
341 11     11 1 50 my ($self, $index) = @_;
342 11 100       20 (!$self->isRoot())
343             || die "Insufficient Arguments : cannot get siblings from a ROOT tree";
344 10         21 $self->getParent()->getChild($index);
345             }
346              
347             sub getAllSiblings {
348 33     33 1 9133 my ($self) = @_;
349 33 100       65 (!$self->isRoot())
350             || die "Insufficient Arguments : cannot get siblings from a ROOT tree";
351 32         81 $self->getParent()->getAllChildren();
352             }
353              
354             sub getSiblingCount
355             {
356 5     5 1 580 my($self) = @_;
357              
358 5 50       10 return $self->isRoot ? 0 : $#{$self->getAllSiblings};
  5         11  
359              
360             } # End of getSiblingCount.
361              
362             ## -----------------------------------------------
363             ## informational
364              
365 288     288 1 493 sub isLeaf { $_[0]->getChildCount == 0 }
366              
367             sub isRoot {
368 374     374 1 24276 my ($self) = @_;
369 374   66     2165 return (!defined($self->{_parent}) || $self->{_parent} eq $self->ROOT);
370             }
371              
372             sub size {
373 21     21 1 623 my ($self) = @_;
374 21         23 my $size = 1;
375 21         29 foreach my $child ($self->getAllChildren()) {
376 20         33 $size += $child->size();
377             }
378 21         30 return $size;
379             }
380              
381             sub isFirstChild
382             {
383 4     4 0 717 my($self) = @_;
384              
385 4 50       9 return $self->isRoot ? 0 : $_[0]->getIndex == 0;
386              
387             } # End of isFirstChild.
388              
389             sub isLastChild
390             {
391 4     4 1 1034 my($self) = @_;
392              
393 4 50       9 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 43 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   76 my ($tree) = @_;
425 41 50       78 return if $tree->isRoot();
426 41         107 $tree->{_depth} = $tree->getParent()->getDepth() + 1;
427             }
428 19         123 );
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 40 my ($self) = @_;
437             # we must find the tallest sub-tree
438             # and use that to define the height
439 21         32 my $max_height = 0;
440 21 100       37 unless ($self->isLeaf()) {
441 17         36 foreach my $child ($self->getAllChildren()) {
442 62         104 my $child_height = $child->getHeight();
443 62 100       117 $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       73 return if ($self->{_height} == ($max_height + 1));
450             # otherwise ...
451 13         29 $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       27 $self->getParent()->fixHeight() unless $self->isRoot();
455             }
456              
457             sub fixWidth {
458 29     29 1 57 my ($self) = @_;
459 29         42 my $fixed_width = 0;
460 29         51 $fixed_width += $_->getWidth() foreach $self->getAllChildren();
461 29         44 $self->{_width} = $fixed_width;
462 29 100       65 $self->getParent()->fixWidth() unless $self->isRoot();
463             }
464              
465             sub traverse {
466 225     225 1 3892 my ($self, $func, $post) = @_;
467 225 100       378 (defined($func)) || die "Insufficient Arguments : Cannot traverse without traversal function";
468 224 100       442 (ref($func) eq "CODE") || die "Incorrect Object Type : traversal function is not a function";
469 222 100 100     400 (ref($post) eq "CODE") || die "Incorrect Object Type : post traversal function is not a function"
470             if defined($post);
471 220         317 foreach my $child ($self->getAllChildren()) {
472 191   100     287 my $ret = $func->($child) || '';
473              
474             # Propagate up the stack.
475 191 100       368 return $ret if 'ABORT' eq $ret;
476              
477 190   100     314 $ret = $child->traverse($func, $post) || '';
478 190 100       365 return $ret if 'ABORT' eq $ret;
479              
480 189 100       395 defined($post) && $post->($child);
481             }
482             }
483              
484             # this is an improved version of the
485             # old accept method, it now it more
486             # accepting of its arguments
487             sub accept {
488 11     11 1 686 my ($self, $visitor) = @_;
489             # it must be a blessed reference and ...
490 11 100 100     153 (blessed($visitor) &&
      100        
491             # either a Tree::Simple::Visitor object, or ...
492             ($visitor->isa("Tree::Simple::Visitor") ||
493             # it must be an object which has a 'visit' method available
494             $visitor->can('visit')))
495             || die "Insufficient Arguments : You must supply a valid Visitor object";
496 7         35 $visitor->visit($self);
497             }
498              
499             ## -----------------------------------------------
500             ## cloning
501              
502             sub clone {
503 33     33 1 63 my ($self) = @_;
504             # first clone the value in the node
505 33         54 my $cloned_node = _cloneNode($self->getNodeValue());
506             # create a new Tree::Simple object
507             # here with the cloned node, however
508             # we do not assign the parent node
509             # since it really does not make a lot
510             # of sense. To properly clone it would
511             # be to clone back up the tree as well,
512             # which IMO is not intuitive. So in essence
513             # when you clone a tree, you detach it from
514             # any parentage it might have
515 33         76 my $clone = $self->new($cloned_node);
516             # however, because it is a recursive thing
517             # when you clone all the children, and then
518             # add them to the clone, you end up setting
519             # the parent of the children to be that of
520             # the clone (which is correct)
521             $clone->addChildren(
522 33 100       53 map { $_->clone() } $self->getAllChildren()
  29         49  
523             ) unless $self->isLeaf();
524             # return the clone
525 33         71 return $clone;
526             }
527              
528             # this allows cloning of single nodes while
529             # retaining connections to a tree, this is sloppy
530             sub cloneShallow {
531 2     2 1 10 my ($self) = @_;
532 2         4 my $cloned_tree = { %{$self} };
  2         16  
533 2         7 bless($cloned_tree, ref($self));
534             # just clone the node (if you can)
535 2         8 $cloned_tree->setNodeValue(_cloneNode($self->getNodeValue()));
536 2         6 return $cloned_tree;
537             }
538              
539             # this is a helper function which
540             # recursively clones the node
541             sub _cloneNode {
542 44     44   85 my ($node, $seen) = @_;
543             # create a cache if we don't already
544             # have one to prevent circular refs
545             # from being copied more than once
546 44 100       104 $seen = {} unless defined $seen;
547             # now here we go...
548 44         59 my $clone;
549             # if it is not a reference, then lets just return it
550 44 100       108 return $node unless ref($node);
551             # if it is in the cache, then return that
552 10 50       24 return $seen->{$node} if exists ${$seen}{$node};
  10         33  
553             # if it is an object, then ...
554 10 100       34 if (blessed($node)) {
555             # see if we can clone it
556 4 100       26 if ($node->can('clone')) {
557 2         7 $clone = $node->clone();
558             }
559             # otherwise respect that it does
560             # not want to be cloned
561             else {
562 2         4 $clone = $node;
563             }
564             }
565             else {
566             # if the current slot is a scalar reference, then
567             # dereference it and copy it into the new object
568 6 100 100     42 if (ref($node) eq "SCALAR" || ref($node) eq "REF") {
    100          
    100          
569 3         7 my $var = "";
570 3         6 $clone = \$var;
571 3         6 ${$clone} = _cloneNode(${$node}, $seen);
  3         7  
  3         21  
572             }
573             # if the current slot is an array reference
574             # then dereference it and copy it
575             elsif (ref($node) eq "ARRAY") {
576 1         3 $clone = [ map { _cloneNode($_, $seen) } @{$node} ];
  4         6  
  1         3  
577             }
578             # if the current reference is a hash reference
579             # then dereference it and copy it
580             elsif (ref($node) eq "HASH") {
581 1         2 $clone = {};
582 1         2 foreach my $key (keys %{$node}) {
  1         4  
583 2         4 $clone->{$key} = _cloneNode($node->{$key}, $seen);
584             }
585             }
586             else {
587             # all other ref types are not copied
588 1         9 $clone = $node;
589             }
590             }
591             # store the clone in the cache and
592 10         29 $seen->{$node} = $clone;
593             # then return the clone
594 10         29 return $clone;
595             }
596              
597              
598             ## -----------------------------------------------
599             ## Desctructor
600              
601             sub DESTROY {
602             # if we are using weak refs
603             # we don't need to worry about
604             # destruction, it will just happen
605 25 50   25   2577 return if $USE_WEAK_REFS;
606 25         53 my ($self) = @_;
607             # we want to detach all our children from
608             # ourselves, this will break most of the
609             # connections and allow for things to get
610             # reaped properly
611 25 50       107 if ($self->{_children}) {
612 25         59 foreach my $child (@{$self->{_children}}) {
  25         913  
613 18 50       43 defined $child && $child->_detachParent();
614             }
615             }
616             # we do not need to remove or undef the _children
617             # of the _parent fields, this will cause some
618             # unwanted releasing of connections.
619             }
620              
621             ## -----------------------------------------------
622             ## end Tree::Simple
623             ## -----------------------------------------------
624              
625             1;
626              
627             __END__