File Coverage

blib/lib/Bio/Phylo/Listable.pm
Criterion Covered Total %
statement 155 209 74.1
branch 31 64 48.4
condition 17 28 60.7
subroutine 32 39 82.0
pod 22 22 100.0
total 257 362 70.9


line stmt bran cond sub pod time code
1             package Bio::Phylo::Listable;
2 51     51   344 use strict;
  51         91  
  51         1278  
3 51     51   14561 use Bio::Phylo::ListableRole;
  51         134  
  51         310  
4 51     51   323 use base 'Bio::Phylo::ListableRole';
  51         110  
  51         5115  
5 51     51   324 use Bio::Phylo::Util::Exceptions 'throw';
  51         97  
  51         2051  
6 51     51   289 use Bio::Phylo::Util::CONSTANT qw':all';
  51         99  
  51         44066  
7              
8             {
9             my $logger = __PACKAGE__->get_logger;
10              
11             # $fields array necessary for object destruction
12             my @fields = \(
13             my (
14             %entities, # XXX strong reference
15             %index,
16             %listeners,
17             %sets,
18             )
19             );
20              
21              
22             =head1 NAME
23              
24             Bio::Phylo::Listable - List of things, super class for many objects
25              
26             =head1 SYNOPSIS
27              
28             No direct usage, parent class. Methods documented here
29             are available for all objects that inherit from it.
30              
31             =head1 DESCRIPTION
32              
33             A listable object is an object that contains multiple smaller objects of the
34             same type. For example: a tree contains nodes, so it's a listable object.
35              
36             This class contains methods that are useful for all listable objects: Matrices
37             (i.e. sets of matrix objects), individual Matrix objects, Datum objects (i.e.
38             character state sequences), Taxa, Forest, Tree and Node objects.
39              
40             =head1 METHODS
41              
42             =head2 ARRAY METHODS
43              
44             =over
45              
46             =item insert()
47              
48             Pushes an object into its container.
49              
50             Type : Mutator
51             Title : insert
52             Usage : $obj->insert($other_obj);
53             Function: Pushes an object into its container.
54             Returns : A Bio::Phylo::Listable object.
55             Args : A Bio::Phylo::* object.
56              
57             =cut
58              
59             sub insert {
60 9206     9206 1 24319 my ( $self, @obj ) = @_;
61 9206 100 100     27710 if ( @obj and $self->can_contain(@obj) ) {
62 9193         17832 my $id = $self->get_id;
63 9193         12171 push @{ $entities{$id} }, @obj;
  9193         33778  
64 9193         15004 for (@obj) {
65 91795 100 66     161456 ref $_ && UNIVERSAL::can($_,'_set_container') && $_->_set_container($self);
66             }
67             $self->notify_listeners( 'insert', @obj )
68 9193 100 66     22177 if $listeners{$id} and @{ $listeners{$id} };
  8223         31358  
69 9193         23297 return $self;
70             }
71             else {
72 13         101 throw 'ObjectMismatch' => "Failed insertion: [@obj] in [$self]";
73             }
74             }
75              
76             =item insert_at_index()
77              
78             Inserts argument object in container at argument index.
79              
80             Type : Mutator
81             Title : insert_at_index
82             Usage : $obj->insert_at_index($other_obj, $i);
83             Function: Inserts $other_obj at index $i in container $obj
84             Returns : A Bio::Phylo::Listable object.
85             Args : A Bio::Phylo::* object.
86              
87             =cut
88              
89             sub insert_at_index {
90 8529     8529 1 14055 my ( $self, $obj, $index ) = @_;
91 8529         39626 $logger->debug("inserting '$obj' in '$self' at index $index");
92 8529 50 33     26542 if ( defined $obj and $self->can_contain($obj) ) {
93 8529         16723 my $id = $self->get_id;
94 8529         18608 $entities{$id}->[$index] = $obj;
95 8529 100       19453 if ( looks_like_implementor( $obj, '_set_container' ) ) {
96 8515         18856 $obj->_set_container($self);
97             }
98             $self->notify_listeners( 'insert_at_index', $obj )
99 8529 50 33     19012 if $listeners{$id} and @{ $listeners{$id} };
  0         0  
100 8529         19473 return $self;
101             }
102             else {
103 0         0 throw 'ObjectMismatch' => 'Failed insertion!';
104             }
105             }
106              
107             =item delete()
108              
109             Deletes argument from container.
110              
111             Type : Mutator
112             Title : delete
113             Usage : $obj->delete($other_obj);
114             Function: Deletes an object from its container.
115             Returns : A Bio::Phylo::Listable object.
116             Args : A Bio::Phylo::* object.
117             Note : Be careful with this method: deleting
118             a node from a tree like this will
119             result in undefined references in its
120             neighbouring nodes. Its children will
121             have their parent reference become
122             undef (instead of pointing to their
123             grandparent, as collapsing a node would
124             do). The same is true for taxon objects
125             that reference datum objects: if the
126             datum object is deleted from a matrix
127             (say), the taxon will now hold undefined
128             references.
129              
130             =cut
131              
132             sub delete {
133 652     652 1 961 my ( $self, $obj ) = @_;
134 652         1214 my $id = $self->get_id;
135 652 50       1535 if ( $self->can_contain($obj) ) {
136 652         1201 my $object_id = $obj->get_id;
137 652         852 my $occurence_counter = 0;
138 652 50       1386 if ( my $i = $index{$id} ) {
139 0         0 for my $j ( 0 .. $i ) {
140 0 0       0 if ( $entities{$id}->[$j]->get_id == $object_id ) {
141 0         0 $occurence_counter++;
142             }
143             }
144             }
145             my @modified =
146 652         798 grep { $_->get_id != $object_id } @{ $entities{$id} };
  62797         95692  
  652         1327  
147 652         2587 $entities{$id} = \@modified;
148 652         1351 $index{$id} -= $occurence_counter;
149             }
150             else {
151 0         0 throw 'ObjectMismatch' =>
152             "Invocant object cannot contain argument object";
153             }
154             $self->notify_listeners( 'delete', $obj )
155 652 100 66     1714 if $listeners{$id} and @{ $listeners{$id} };
  267         1340  
156 652         1433 return $self;
157             }
158              
159             =item clear()
160              
161             Empties container object.
162              
163             Type : Mutator
164             Title : clear
165             Usage : $obj->clear();
166             Function: Clears the container.
167             Returns : A Bio::Phylo::Listable object.
168             Args : Note.
169             Note :
170              
171             =cut
172              
173             sub clear {
174 745     745 1 1059 my $self = shift;
175 745         1447 my $id = $self->get_id;
176 745         1679 $entities{$id} = [];
177             $self->notify_listeners('clear')
178 745 100 66     1780 if $listeners{$id} and @{ $listeners{$id} };
  75         446  
179 745         1249 return $self;
180             }
181              
182             =item keep_entities()
183              
184             Keeps the container's contents specified by an array reference of indices.
185              
186             Type : Mutator
187             Title : keep_entities
188             Usage : $list->keep_entities([9,7,7,6]);
189             Function: Keeps a subset of contents
190             Returns : A Bio::Phylo::Listable object.
191             Args : An array reference of indices
192              
193             =cut
194              
195             sub keep_entities {
196 20     20 1 28 my ( $self, $indices_array_ref ) = @_;
197 20         36 my $id = $self->get_id;
198 20   50     40 my $ent = $entities{$id} || [];
199 20         24 my @contents = @{$ent};
  20         42  
200 20         27 my @pruned = @contents[ @{$indices_array_ref} ];
  20         41  
201 20         25 $entities{$id} = \@pruned;
202 20         54 return $self;
203             }
204              
205             =item get_entities()
206              
207             Returns a reference to an array of objects contained by the listable object.
208              
209             Type : Accessor
210             Title : get_entities
211             Usage : my @entities = @{ $obj->get_entities };
212             Function: Retrieves all entities in the container.
213             Returns : A reference to a list of Bio::Phylo::*
214             objects.
215             Args : none.
216              
217             =cut
218              
219             sub get_entities {
220 179433   100 179433 1 287346 return $entities{ $_[0]->get_id } || [];
221             }
222            
223 56     56   125 sub _get_things { $entities{shift->get_id} }
224             sub _set_things : Clonable DeepClonable {
225 56     56   104 my ( $self, $things ) = @_;
226 56         115 $entities{$self->get_id} = $things;
227 56         212 $self->notify_listeners( '_set_things', $things );
228 56         123 return $self;
229 51     51   374 }
  51         112  
  51         276  
230              
231             =back
232              
233             =head2 ITERATOR METHODS
234              
235             =over
236              
237             =item first()
238              
239             Jumps to the first element contained by the listable object.
240              
241             Type : Iterator
242             Title : first
243             Usage : my $first_obj = $obj->first;
244             Function: Retrieves the first
245             entity in the container.
246             Returns : A Bio::Phylo::* object
247             Args : none.
248              
249             =cut
250              
251             sub first {
252 73     73 1 200 my $self = shift;
253 73         223 my $id = $self->get_id;
254 73         224 $index{$id} = 0;
255 73         378 return $entities{$id}->[0];
256             }
257              
258             =item last()
259              
260             Jumps to the last element contained by the listable object.
261              
262             Type : Iterator
263             Title : last
264             Usage : my $last_obj = $obj->last;
265             Function: Retrieves the last
266             entity in the container.
267             Returns : A Bio::Phylo::* object
268             Args : none.
269              
270             =cut
271              
272             sub last {
273 1     1 1 2 my $self = shift;
274 1         4 my $id = $self->get_id;
275 1         2 $index{$id} = $#{ $entities{$id} };
  1         3  
276 1         4 return $entities{$id}->[-1];
277             }
278              
279             =item current()
280              
281             Returns the current focal element of the listable object.
282              
283             Type : Iterator
284             Title : current
285             Usage : my $current_obj = $obj->current;
286             Function: Retrieves the current focal
287             entity in the container.
288             Returns : A Bio::Phylo::* object
289             Args : none.
290              
291             =cut
292              
293             sub current {
294 0     0 1 0 my $self = shift;
295 0         0 my $id = $self->get_id;
296 0 0       0 if ( !defined $index{$id} ) {
297 0         0 $index{$id} = 0;
298             }
299 0         0 return $entities{$id}->[ $index{$id} ];
300             }
301              
302             =item next()
303              
304             Returns the next focal element of the listable object.
305              
306             Type : Iterator
307             Title : next
308             Usage : my $next_obj = $obj->next;
309             Function: Retrieves the next focal
310             entity in the container.
311             Returns : A Bio::Phylo::* object
312             Args : none.
313              
314             =cut
315              
316             sub next {
317 0     0 1 0 my $self = shift;
318 0         0 my $id = $self->get_id;
319 0 0       0 if ( !defined $index{$id} ) {
    0          
320 0         0 $index{$id} = 0;
321 0         0 return $entities{$id}->[ $index{$id} ];
322             }
323 0         0 elsif ( ( $index{$id} + 1 ) <= $#{ $entities{$id} } ) {
324 0         0 $index{$id}++;
325 0         0 return $entities{$id}->[ $index{$id} ];
326             }
327             else {
328 0         0 return;
329             }
330             }
331              
332             =item previous()
333              
334             Returns the previous element of the listable object.
335              
336             Type : Iterator
337             Title : previous
338             Usage : my $previous_obj = $obj->previous;
339             Function: Retrieves the previous
340             focal entity in the container.
341             Returns : A Bio::Phylo::* object
342             Args : none.
343              
344             =cut
345              
346             sub previous {
347 0     0 1 0 my $self = shift;
348 0         0 my $id = $self->get_id;
349              
350             # either undef or 0
351 0 0       0 if ( !$index{$id} ) {
    0          
352 0         0 return;
353             }
354             elsif ( 1 <= $index{$id} ) {
355 0         0 $index{$id}--;
356 0         0 return $entities{$id}->[ $index{$id} ];
357             }
358             else {
359 0         0 return;
360             }
361             }
362              
363             =item current_index()
364              
365             Returns the current internal index of the container.
366              
367             Type : Accessor
368             Title : current_index
369             Usage : my $last_index = $obj->current_index;
370             Function: Returns the current internal
371             index of the container or 0
372             Returns : An integer
373             Args : none.
374              
375             =cut
376              
377 0 0   0 1 0 sub current_index { $index{ ${ $_[0] } } || 0 }
  0         0  
378              
379 56     56   111 sub _get_index { $index{shift->get_id} }
380              
381             sub _set_index : Clonable {
382 56     56   92 my ( $self, $idx ) = @_;
383 56         129 $index{ $self->get_id } = $idx;
384 56         113 return $self;
385 51     51   27363 }
  51         109  
  51         224  
386              
387             =item last_index()
388              
389             Returns the highest valid index of the container.
390              
391             Type : Generic query
392             Title : last_index
393             Usage : my $last_index = $obj->last_index;
394             Function: Returns the highest valid
395             index of the container.
396             Returns : An integer
397             Args : none.
398              
399             =cut
400              
401 0     0 1 0 sub last_index { $#{ $entities{ ${ $_[0] } } } }
  0         0  
  0         0  
402              
403             =back
404              
405             =head2 UTILITY METHODS
406              
407             =over
408              
409             =item set_listener()
410              
411             Attaches a listener (code ref) which is executed when contents change.
412              
413             Type : Utility method
414             Title : set_listener
415             Usage : $object->set_listener( sub { my $object = shift; } );
416             Function: Attaches a listener (code ref) which is executed when contents change.
417             Returns : Invocant.
418             Args : A code reference.
419             Comments: When executed, the code reference will receive $object
420             (the container) as its first argument.
421              
422             =cut
423              
424             sub set_listener {
425 946     946 1 1983 my ( $self, $listener ) = @_;
426 946         2289 my $id = $self->get_id;
427 946 50       2481 if ( not $listeners{$id} ) {
428 946         2074 $listeners{$id} = [];
429             }
430 946 50       2454 if ( looks_like_instance( $listener, 'CODE' ) ) {
431 946         1478 push @{ $listeners{$id} }, $listener;
  946         2891  
432             }
433             else {
434 0         0 throw 'BadArgs' => "$listener not a CODE reference";
435             }
436             }
437             sub _set_listeners : Clonable {
438 56     56   93 my ( $self, $l ) = @_;
439 56         106 $listeners{$self->get_id} = $l;
440 56         130 return $self;
441 51     51   15349 }
  51         137  
  51         214  
442 56     56   129 sub _get_listeners { $listeners{shift->get_id} }
443              
444             =item notify_listeners()
445              
446             Notifies listeners of changed contents.
447              
448             Type : Utility method
449             Title : notify_listeners
450             Usage : $object->notify_listeners;
451             Function: Notifies listeners of changed contents.
452             Returns : Invocant.
453             Args : NONE.
454             Comments:
455              
456             =cut
457              
458             sub notify_listeners {
459 8621     8621 1 16986 my ( $self, @args ) = @_;
460 8621         16057 my $id = $self->get_id;
461 8621 100       17247 if ( $listeners{$id} ) {
462 8591         9819 for my $l ( @{ $listeners{$id} } ) {
  8591         15238  
463 8591         20529 $l->( $self, @args );
464             }
465             }
466 8621         12973 return $self;
467             }
468              
469             =back
470              
471             =head2 SETS MANAGEMENT
472              
473             Many Bio::Phylo objects are segmented, i.e. they contain one or more subparts
474             of the same type. For example, a matrix contains multiple rows; each row
475             contains multiple cells; a tree contains nodes, and so on. (Segmented objects
476             all inherit from Bio::Phylo::Listable, i.e. the class whose documentation you're
477             reading here.) In many cases it is useful to be able to define subsets of the
478             contents of segmented objects, for example sets of taxon objects inside a taxa
479             block. The Bio::Phylo::Listable object allows this through a number of methods
480             (add_set, remove_set, add_to_set, remove_from_set etc.). Those methods delegate
481             the actual management of the set contents to the L object.
482             Consult the documentation for L for a code sample.
483              
484             =over
485              
486             =item add_set()
487              
488             Type : Mutator
489             Title : add_set
490             Usage : $obj->add_set($set)
491             Function: Associates a Bio::Phylo::Set object with the container
492             Returns : Invocant
493             Args : A Bio::Phylo::Set object
494              
495             =cut
496              
497             # here we create a listener that updates the set
498             # object when the associated container changes
499             my $create_set_listeners = sub {
500             my ( $self, $set ) = @_;
501             my $listener = sub {
502             my ( $listable, $method, $obj ) = @_;
503             if ( $method eq 'delete' ) {
504             $listable->remove_from_set( $obj, $set );
505             }
506             elsif ( $method eq 'clear' ) {
507             $set->clear;
508             }
509             };
510             return $listener;
511             };
512              
513             sub add_set {
514 4     4 1 13 my ( $self, $set ) = @_;
515 4         14 my $listener = $create_set_listeners->( $self, $set );
516 4         19 $self->set_listener($listener);
517 4         14 my $id = $self->get_id;
518 4 50       24 $sets{$id} = {} if not $sets{$id};
519 4         16 my $setid = $set->get_id;
520 4         15 $sets{$id}->{$setid} = $set;
521 4         11 return $self;
522             }
523            
524             =item set_sets()
525              
526             Type : Mutator
527             Title : set_sets
528             Usage : $obj->set_sets([ $s1, $s2, $s3 ])
529             Function: Assigns all Bio::Phylo::Set objects to the container
530             Returns : Invocant
531             Args : An array ref of Bio::Phylo::Set objects
532              
533             =cut
534            
535             sub set_sets : Clonable {
536 56     56 1 101 my ( $self, $sets ) = @_;
537 56         106 my $id = $self->get_id;
538 56         122 $sets{$id} = {};
539 56 50       116 if ( $sets ) {
540 56         67 for my $set ( @{ $sets } ) {
  56         115  
541 0         0 $sets{$id}->{$set->get_id} = $set;
542             }
543             }
544 56         119 return $self;
545 51     51   22630 }
  51         100  
  51         206  
546              
547             =item remove_set()
548              
549             Type : Mutator
550             Title : remove_set
551             Usage : $obj->remove_set($set)
552             Function: Removes association between a Bio::Phylo::Set object and the container
553             Returns : Invocant
554             Args : A Bio::Phylo::Set object
555              
556             =cut
557              
558             sub remove_set {
559 0     0 1 0 my ( $self, $set ) = @_;
560 0         0 my $id = $self->get_id;
561 0 0       0 $sets{$id} = {} if not $sets{$id};
562 0         0 my $setid = $set->get_id;
563 0         0 delete $sets{$id}->{$setid};
564 0         0 return $self;
565             }
566              
567             =item get_sets()
568              
569             Type : Accessor
570             Title : get_sets
571             Usage : my @sets = @{ $obj->get_sets() };
572             Function: Retrieves all associated Bio::Phylo::Set objects
573             Returns : Invocant
574             Args : None
575              
576             =cut
577              
578             sub get_sets {
579 60     60 1 130 my $self = shift;
580 60         124 my $id = $self->get_id;
581 60 100       192 $sets{$id} = {} if not $sets{$id};
582 60         94 return [ values %{ $sets{$id} } ];
  60         183  
583             }
584              
585             =item is_in_set()
586              
587             Type : Test
588             Title : is_in_set
589             Usage : @do_something if $listable->is_in_set($obj,$set);
590             Function: Returns whether or not the first argument is listed in the second argument
591             Returns : Boolean
592             Args : $obj - an object that may, or may not be in $set
593             $set - the Bio::Phylo::Set object to query
594             Notes : This method makes two assumptions:
595             i) the $set object is associated with the container,
596             i.e. add_set($set) has been called previously
597             ii) the $obj object is part of the container
598             If either assumption is violated a warning message
599             is printed.
600              
601             =cut
602              
603             sub is_in_set {
604 22     22 1 140 my ( $self, $obj, $set ) = @_;
605 22 50 33     57 if ( looks_like_object($set,_SET_) and $sets{ $self->get_id }->{ $set->get_id } ) {
606 22         58 my $i = $self->get_index_of($obj);
607 22 50       38 if ( defined $i ) {
608 22 100       46 return $set->get_by_index($i) ? 1 : 0;
609             }
610             else {
611 0         0 $logger->warn("Container doesn't contain that object.");
612             }
613             }
614             else {
615 0         0 $logger->warn("That set is not associated with this container.");
616             }
617             }
618              
619             =item add_to_set()
620              
621             Type : Mutator
622             Title : add_to_set
623             Usage : $listable->add_to_set($obj,$set);
624             Function: Adds first argument to the second argument
625             Returns : Invocant
626             Args : $obj - an object to add to $set
627             $set - the Bio::Phylo::Set object to add to
628             Notes : this method assumes that $obj is already
629             part of the container. If that assumption is
630             violated a warning message is printed.
631              
632             =cut
633              
634             sub add_to_set {
635 14     14 1 26 my ( $self, $obj, $set ) = @_;
636 14         29 my $id = $self->get_id;
637 14 50       37 $sets{$id} = {} if not $sets{$id};
638 14         40 my $i = $self->get_index_of($obj);
639 14 50       28 if ( defined $i ) {
640 14         39 $set->insert_at_index( 1 => $i );
641 14         27 my $set_id = $set->get_id;
642 14 50       34 if ( not exists $sets{$id}->{$set_id} ) {
643 0         0 my $listener = $create_set_listeners->( $self, $set );
644 0         0 $self->set_listener($listener);
645             }
646 14         24 $sets{$id}->{$set_id} = $set;
647             }
648             else {
649 0         0 $logger->warn(
650             "Container doesn't contain the object you're adding to the set."
651             );
652             }
653 14         28 return $self;
654             }
655              
656             =item remove_from_set()
657              
658             Type : Mutator
659             Title : remove_from_set
660             Usage : $listable->remove_from_set($obj,$set);
661             Function: Removes first argument from the second argument
662             Returns : Invocant
663             Args : $obj - an object to remove from $set
664             $set - the Bio::Phylo::Set object to remove from
665             Notes : this method assumes that $obj is already
666             part of the container. If that assumption is
667             violated a warning message is printed.
668              
669             =cut
670              
671             sub remove_from_set {
672 0     0 1   my ( $self, $obj, $set ) = @_;
673 0           my $id = $self->get_id;
674 0 0         $sets{$id} = {} if not $sets{$id};
675 0           my $i = $self->get_index_of($obj);
676 0 0         if ( defined $i ) {
677 0           $set->insert_at_index( $i => 0 );
678 0           $sets{$id}->{ $set->get_id } = $set;
679             }
680             else {
681 0           $logger->warn(
682             "Container doesn't contain the object you're adding to the set."
683             );
684             }
685 0           return $self;
686             }
687              
688             =begin comment
689              
690             Type : Internal method
691             Title : _cleanup
692             Usage : $listable->_cleanup;
693             Function: Called during object destruction, for cleanup of instance data
694             Returns :
695             Args :
696              
697             =end comment
698              
699             =cut
700              
701             sub _cleanup : Destructor {
702 28053     28053   34555 my $self = shift;
703 28053         44407 my $id = $self->get_id;
704 28053         40684 for my $field (@fields) {
705 112212         173297 delete $field->{$id};
706             }
707 51     51   29225 }
  51         115  
  51         216  
708              
709             =back
710              
711             =cut
712              
713             # podinherit_insert_token
714              
715             =head1 SEE ALSO
716              
717             There is a mailing list at L
718             for any user or developer questions and discussions.
719              
720             Also see the manual: L and L.
721              
722             =head2 Objects inheriting from Bio::Phylo::Listable
723              
724             =over
725              
726             =item L
727              
728             Iterate over a set of trees.
729              
730             =item L
731              
732             Iterate over nodes in a tree.
733              
734             =item L
735              
736             Iterate of children of a node.
737              
738             =item L
739              
740             Iterate over a set of matrices.
741              
742             =item L
743              
744             Iterate over the datum objects in a matrix.
745              
746             =item L
747              
748             Iterate over the characters in a datum.
749              
750             =item L
751              
752             Iterate over a set of taxa.
753              
754             =back
755              
756             =head2 Superclasses
757              
758             =over
759              
760             =item L
761              
762             This object inherits from L, so methods
763             defined there are also applicable here.
764              
765             =back
766              
767             =head1 CITATION
768              
769             If you use Bio::Phylo in published research, please cite it:
770              
771             B, B, B, B
772             and B, 2011. Bio::Phylo - phyloinformatic analysis using Perl.
773             I B<12>:63.
774             L
775              
776             =cut
777              
778             }
779             1;