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   645471 use strict;
  12         110  
  12         334  
4 12     12   57 use warnings;
  12         18  
  12         531  
5              
6             our $VERSION = '1.34';
7              
8 12     12   66 use Scalar::Util qw(blessed);
  12         20  
  12         1586  
9              
10             ## -----------------------------------------------
11             ## Tree::Simple
12             ## -----------------------------------------------
13              
14             my $USE_WEAK_REFS;
15              
16             sub import {
17 12     12   95 shift;
18 12 50       12458 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   85 use constant ROOT => "root";
  12         35  
  12         35001  
27              
28             ### constructor
29              
30             sub new {
31 136     136 1 13158 my ($_class, $node, $parent) = @_;
32 136   66     418 my $class = ref($_class) || $_class;
33 136         263 my $tree = bless({}, $class);
34 136         334 $tree->_init($node, $parent, []);
35 133         309 return $tree;
36             }
37              
38             ### -----------------------------------------------
39             ### methods
40             ### -----------------------------------------------
41              
42             ## -----------------------------------------------
43             ## private methods
44              
45             sub _init {
46 136     136   222 my ($self, $node, $parent, $children) = @_;
47             # set the value of the unique id
48 136         815 ($self->{_uid}) = ("$self" =~ /\((.*?)\)$/);
49             # set the value of the node
50 136         235 $self->{_node} = $node;
51             # and set the value of _children
52 136         167 $self->{_children} = $children;
53 136         181 $self->{_height} = 1;
54 136         190 $self->{_width} = 1;
55             # Now check our $parent value
56 136 100       236 if (defined($parent)) {
57 9 100 100     70 if (blessed($parent) && $parent->isa("Tree::Simple")) {
    100          
58             # and set it as our parent
59 3         9 $parent->addChild($self);
60             }
61             elsif ($parent eq $self->ROOT) {
62 3         17 $self->_setParent( $self->ROOT );
63             }
64             else {
65 3         22 die "Insufficient Arguments : parent argument must be a Tree::Simple object";
66             }
67             }
68             else {
69 127         301 $self->_setParent( $self->ROOT );
70             }
71             }
72              
73             sub _setParent {
74 263     263   490 my ($self, $parent) = @_;
75 263 100 100     1225 (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         434 $self->{_parent} = $parent;
79 259 100       517 if ($parent eq $self->ROOT) {
80 143         356 $self->{_depth} = -1;
81             }
82             else {
83 116 50       190 weaken($self->{_parent}) if $USE_WEAK_REFS;
84 116         185 $self->{_depth} = $parent->getDepth() + 1;
85             }
86             }
87              
88             sub _detachParent {
89 18 50   18   22 return if $USE_WEAK_REFS;
90 18         19 my ($self) = @_;
91 18         28 $self->{_parent} = undef;
92             }
93              
94             sub _setHeight {
95 142     142   221 my ($self, $child) = @_;
96 142         239 my $child_height = $child->getHeight();
97 142 100       323 return if ($self->{_height} >= $child_height + 1);
98 66         108 $self->{_height} = $child_height + 1;
99              
100             # and now bubble up to the parent (unless we are the root)
101 66 100       136 $self->getParent()->_setHeight($self) unless $self->isRoot();
102             }
103              
104             sub _setWidth {
105 107     107   153 my ($self, $child_width) = @_;
106 107         142 $self->{_width} += $child_width;
107             # and now bubble up to the parent (unless we are the root)
108 107 100       196 $self->getParent()->_setWidth($child_width) unless $self->isRoot();
109             }
110              
111             ## -----------------------------------------------
112             ## mutators
113              
114             sub setNodeValue {
115 23     23 1 65 my ($self, $node_value) = @_;
116 23 100       60 (defined($node_value)) || die "Insufficient Arguments : must supply a value for node";
117 22         44 $self->{_node} = $node_value;
118             }
119              
120             sub setUID {
121 2     2 1 46 my ($self, $uid) = @_;
122 2 100       16 ($uid) || die "Insufficient Arguments : Custom Unique ID's must be a true value";
123 1         2 $self->{_uid} = $uid;
124             }
125              
126             ## -----------------------------------------------
127             ## child methods
128              
129             sub addChild {
130 36     36 1 6637 splice @_, 1, 0, $_[0]->getChildCount;
131 36         139 goto &insertChild;
132             }
133              
134             sub addChildren {
135 21     21 1 434 splice @_, 1, 0, $_[0]->getChildCount;
136 21         69 goto &insertChildren;
137             }
138              
139             sub generateChild
140             {
141 3     3 1 520 return $_[0]->addChild($_[0]->new($_[1]) );
142             }
143              
144             sub _insertChildAt {
145 81     81   4968 my ($self, $index, @trees) = @_;
146              
147 81 100       183 (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         132 my $max = $self->getChildCount();
153 79 100       165 ($index <= $max)
154             || die "Index Out of Bounds : got ($index) expected no more than ("
155             . $self->getChildCount() . ")";
156              
157 77 100       199 (@trees)
158             || die "Insufficient Arguments : no tree(s) to insert";
159              
160 74         116 my($new_width) = 0;
161              
162 74         134 foreach my $tree (@trees) {
163 127 100 100     710 (blessed($tree) && $tree->isa("Tree::Simple"))
164             || die "Insufficient Arguments : Child must be a Tree::Simple object";
165 116         285 $tree->_setParent($self);
166 116         285 $self->_setHeight($tree);
167              
168 116         209 $new_width += getWidth($tree);
169              
170 116 100       211 $tree->fixDepth() unless $tree->isLeaf();
171             }
172              
173 63 100       116 $self -> _setWidth($new_width - ($self -> isLeaf ? 1 : 0) );
174              
175             # if index is zero, use this optimization
176 63 100       156 if ($index == 0) {
    100          
177 44         60 unshift @{$self->{_children}} => @trees;
  44         99  
178             }
179             # if index is equal to the number of children
180             # then use this optimization
181             elsif ($index == $max) {
182 11         16 push @{$self->{_children}} => @trees;
  11         27  
183             }
184             # otherwise do some heavy lifting here
185             else {
186 8         12 splice @{$self->{_children}}, $index, 0, @trees;
  8         22  
187             }
188              
189 63         173 $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 846 my ($self, $index) = @_;
200 16 100       51 (defined($index))
201             || die "Insufficient Arguments : Cannot remove child without index.";
202 15 100       30 ($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       28 ($index < $self->getChildCount())
207             || die "Index Out of Bounds : got ($index) expected no more than ("
208             . $self->getChildCount() . ")";
209 13         19 my $removed_child;
210             # if index is zero, use this optimization
211 13 100       31 if ($index == 0) {
    100          
212 8         16 $removed_child = shift @{$self->{_children}};
  8         164  
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         4 $removed_child = pop @{$self->{_children}};
  2         4  
218             }
219             # otherwise do some heavy lifting here
220             else {
221 3         5 $removed_child = $self->{_children}->[$index];
222 3         4 splice @{$self->{_children}}, $index, 1;
  3         27  
223             }
224             # make sure we fix the height
225 13         70 $self->fixHeight();
226 13         38 $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         51 $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       24 $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         70 return $removed_child;
240             }
241              
242             sub removeChild {
243 13     13 1 134 my ($self, $child_to_remove) = @_;
244 13 100       43 (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       36 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     107 (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         19 my $index = 0;
255 9         28 foreach my $child ($self->getAllChildren()) {
256 11 100       51 ("$child" eq "$child_to_remove") && return $self->removeChildAt($index);
257 3         5 $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 24 my ($self) = @_;
264 14 100       64 return -1 if $self->{_parent} eq $self->ROOT;
265 13         23 my $index = 0;
266 13         30 foreach my $sibling ($self->{_parent}->getAllChildren()) {
267 27 100       99 ("$sibling" eq "$self") && return $index;
268 14         22 $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 32 my ($self, @args) = @_;
283 2 100       5 (!$self->isRoot())
284             || die "Insufficient Arguments : cannot add a sibling to a ROOT tree";
285 1         5 $self->{_parent}->addChild(@args);
286             }
287              
288             sub addSiblings {
289 2     2 1 440 my ($self, @args) = @_;
290 2 100       7 (!$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 425 my ($self, @args) = @_;
297 4 100       9 (!$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 21 sub getUID { $_[0]{_uid} }
314 216     216 1 4429 sub getParent { $_[0]{_parent} }
315 217     217 1 2102 sub getDepth { $_[0]{_depth} }
316 298     298 1 1658 sub getNodeValue { $_[0]{_node} }
317 280     280 1 538 sub getWidth { $_[0]{_width} }
318 259     259 1 1270 sub getHeight { $_[0]{_height} }
319              
320             # for backwards compatibility
321             *height = \&getHeight;
322              
323 480     480 1 2048 sub getChildCount { $#{$_[0]{_children}} + 1 }
  480         1567  
324              
325             sub getChild {
326 108     108 1 7497 my ($self, $index) = @_;
327 108 100       232 (defined($index))
328             || die "Insufficient Arguments : Cannot get child without index";
329 107         342 return $self->{_children}->[$index];
330             }
331              
332             sub getAllChildren {
333 381     381 1 2388 my ($self) = @_;
334             return wantarray ?
335 371         1042 @{$self->{_children}}
336             :
337 381 100       549 $self->{_children};
338             }
339              
340             sub getSibling {
341 11     11 1 43 my ($self, $index) = @_;
342 11 100       23 (!$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 5065 my ($self) = @_;
349 33 100       54 (!$self->isRoot())
350             || die "Insufficient Arguments : cannot get siblings from a ROOT tree";
351 32         72 $self->getParent()->getAllChildren();
352             }
353              
354             sub getSiblingCount
355             {
356 5     5 1 604 my($self) = @_;
357              
358 5 50       13 return $self->isRoot ? 0 : $#{$self->getAllSiblings};
  5         14  
359              
360             } # End of getSiblingCount.
361              
362             ## -----------------------------------------------
363             ## informational
364              
365 288     288 1 437 sub isLeaf { $_[0]->getChildCount == 0 }
366              
367             sub isRoot {
368 374     374 1 23117 my ($self) = @_;
369 374   66     2010 return (!defined($self->{_parent}) || $self->{_parent} eq $self->ROOT);
370             }
371              
372             sub size {
373 21     21 1 556 my ($self) = @_;
374 21         20 my $size = 1;
375 21         28 foreach my $child ($self->getAllChildren()) {
376 20         27 $size += $child->size();
377             }
378 21         25 return $size;
379             }
380              
381             sub isFirstChild
382             {
383 4     4 0 656 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 869 my($self) = @_;
392              
393 4 50       10 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 33 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   71 my ($tree) = @_;
425 41 50       63 return if $tree->isRoot();
426 41         90 $tree->{_depth} = $tree->getParent()->getDepth() + 1;
427             }
428 19         113 );
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 36 my ($self) = @_;
437             # we must find the tallest sub-tree
438             # and use that to define the height
439 21         25 my $max_height = 0;
440 21 100       40 unless ($self->isLeaf()) {
441 17         34 foreach my $child ($self->getAllChildren()) {
442 62         89 my $child_height = $child->getHeight();
443 62 100       116 $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       60 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 52 my ($self) = @_;
459 29         36 my $fixed_width = 0;
460 29         46 $fixed_width += $_->getWidth() foreach $self->getAllChildren();
461 29         41 $self->{_width} = $fixed_width;
462 29 100       54 $self->getParent()->fixWidth() unless $self->isRoot();
463             }
464              
465             sub traverse {
466 225     225 1 3772 my ($self, $func, $post) = @_;
467 225 100       353 (defined($func)) || die "Insufficient Arguments : Cannot traverse without traversal function";
468 224 100       380 (ref($func) eq "CODE") || die "Incorrect Object Type : traversal function is not a function";
469 222 100 100     337 (ref($post) eq "CODE") || die "Incorrect Object Type : post traversal function is not a function"
470             if defined($post);
471 220         290 foreach my $child ($self->getAllChildren()) {
472 191   100     276 my $ret = $func->($child) || '';
473              
474             # Propagate up the stack.
475 191 100       356 return $ret if 'ABORT' eq $ret;
476              
477 190   100     272 $ret = $child->traverse($func, $post) || '';
478 190 100       323 return $ret if 'ABORT' eq $ret;
479              
480 189 100       381 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 523 my ($self, $visitor) = @_;
489             # it must be a blessed reference and ...
490 11 100 100     103 (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         23 $visitor->visit($self);
497             }
498              
499             ## -----------------------------------------------
500             ## cloning
501              
502             sub clone {
503 33     33 1 47 my ($self) = @_;
504             # first clone the value in the node
505 33         44 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         61 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       55 map { $_->clone() } $self->getAllChildren()
  29         55  
523             ) unless $self->isLeaf();
524             # return the clone
525 33         69 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 9 my ($self) = @_;
532 2         4 my $cloned_tree = { %{$self} };
  2         15  
533 2         6 bless($cloned_tree, ref($self));
534             # just clone the node (if you can)
535 2         6 $cloned_tree->setNodeValue(_cloneNode($self->getNodeValue()));
536 2         5 return $cloned_tree;
537             }
538              
539             # this is a helper function which
540             # recursively clones the node
541             sub _cloneNode {
542 44     44   61 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       88 $seen = {} unless defined $seen;
547             # now here we go...
548 44         49 my $clone;
549             # if it is not a reference, then lets just return it
550 44 100       99 return $node unless ref($node);
551             # if it is in the cache, then return that
552 10 50       19 return $seen->{$node} if exists ${$seen}{$node};
  10         28  
553             # if it is an object, then ...
554 10 100       22 if (blessed($node)) {
555             # see if we can clone it
556 4 100       28 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     34 if (ref($node) eq "SCALAR" || ref($node) eq "REF") {
    100          
    100          
569 3         4 my $var = "";
570 3         5 $clone = \$var;
571 3         4 ${$clone} = _cloneNode(${$node}, $seen);
  3         6  
  3         17  
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         2 $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         1 foreach my $key (keys %{$node}) {
  1         4  
583 2         11 $clone->{$key} = _cloneNode($node->{$key}, $seen);
584             }
585             }
586             else {
587             # all other ref types are not copied
588 1         2 $clone = $node;
589             }
590             }
591             # store the clone in the cache and
592 10         22 $seen->{$node} = $clone;
593             # then return the clone
594 10         20 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   2011 return if $USE_WEAK_REFS;
606 25         41 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       66 if ($self->{_children}) {
612 25         52 foreach my $child (@{$self->{_children}}) {
  25         665  
613 18 50       30 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__