File Coverage

blib/lib/Bio/Phylo/Matrices/Datatype.pm
Criterion Covered Total %
statement 185 416 44.4
branch 44 126 34.9
condition 6 30 20.0
subroutine 34 44 77.2
pod 24 24 100.0
total 293 640 45.7


line stmt bran cond sub pod time code
1             package Bio::Phylo::Matrices::Datatype;
2 16     16   93 use strict;
  16         31  
  16         463  
3 16     16   72 use base 'Bio::Phylo::NeXML::Writable';
  16         29  
  16         1884  
4 16     16   94 use Bio::Phylo::Factory;
  16         27  
  16         89  
5 16     16   74 use Bio::Phylo::Util::Exceptions 'throw';
  16         26  
  16         613  
6 16     16   83 use Bio::Phylo::Util::CONSTANT qw'_DOMCREATOR_ _DATATYPE_ /looks_like/';
  16         29  
  16         3962  
7             {
8             my $logger = __PACKAGE__->get_logger;
9             my $fac = Bio::Phylo::Factory->new();
10             my @fields = \( my ( %lookup, %missing, %gap, %meta ) );
11              
12             =head1 NAME
13              
14             Bio::Phylo::Matrices::Datatype - Validator of character state data
15              
16             =head1 SYNOPSIS
17              
18             # No direct usage
19              
20             =head1 DESCRIPTION
21              
22             This is a superclass for objects that validate character data. Objects that
23             inherit from this class (typically those in the
24             Bio::Phylo::Matrices::Datatype::* namespace) can check strings and arrays of
25             character data for invalid symbols, and split and join strings and arrays
26             in a way appropriate for the type (on whitespace for continuous data,
27             on single characters for categorical data).
28             L<Bio::Phylo::Matrices::Matrix> objects and L<Bio::Phylo::Matrices::Datum>
29             internally delegate validation of their contents to these datatype objects;
30             there is no normal usage in which you'd have to deal with datatype objects
31             directly.
32              
33             =head1 METHODS
34              
35             =head2 CONSTRUCTOR
36              
37             =over
38              
39             =item new()
40              
41             Datatype constructor.
42              
43             Type : Constructor
44             Title : new
45             Usage : No direct usage, is called by TypeSafeData classes;
46             Function: Instantiates a Datatype object
47             Returns : a Bio::Phylo::Matrices::Datatype child class
48             Args : $type (optional, one of continuous, custom, dna,
49             mixed, protein, restriction, rna, standard)
50              
51             =cut
52              
53             sub new : Constructor {
54 837     837 1 1883 my $class = shift;
55              
56             # constructor called with type string
57 837 100       2255 if ( $class eq __PACKAGE__ ) {
58 787         2538 my $type = ucfirst( lc(shift) );
59 787 50       1994 if ( not $type ) {
60 0         0 throw 'BadArgs' => "No subtype specified!";
61             }
62 787 100       2257 if ( $type eq 'Nucleotide' ) {
63 1         4 $logger->warn("'nucleotide' datatype requested, using 'dna'");
64 1         2 $type = 'Dna';
65             }
66 787         3170 return looks_like_class( __PACKAGE__ . '::' . $type )
67             ->SUPER::new(@_);
68             }
69              
70             # constructor called from type subclass
71             else {
72 50         144 my %args = looks_like_hash @_;
73             {
74 16     16   97 no strict 'refs';
  16         32  
  16         1443  
  0         0  
75 50         143 $args{'-lookup'} = ${"${class}::LOOKUP"}
76 50 50       78 if ${"${class}::LOOKUP"};
  50         208  
77 50         127 $args{'-missing'} = ${"${class}::MISSING"}
78 50 50       64 if ${"${class}::MISSING"};
  50         160  
79 50 100       71 $args{'-gap'} = ${"${class}::GAP"} if ${"${class}::GAP"};
  10         36  
  50         154  
80 16     16   86 use strict;
  16         34  
  16         848  
81             }
82 50         73 return $class->SUPER::new(%args);
  50         166  
83             }
84 16     16   80 }
  16         35  
  16         82  
85              
86             =back
87            
88             =head2 MUTATORS
89            
90             =over
91              
92             =item set_lookup()
93              
94             Sets state lookup table.
95              
96             Type : Mutator
97             Title : set_lookup
98             Usage : $obj->set_lookup($hashref);
99             Function: Sets the state lookup table.
100             Returns : Modified object.
101             Args : Argument must be a hash
102             reference that maps allowed
103             single character symbols
104             (including ambiguity symbols)
105             onto the equivalent set of
106             non-ambiguous symbols
107              
108             =cut
109              
110             sub set_lookup : Clonable {
111 756     756 1 1982 my ( $self, $lookup ) = @_;
112 756         1979 my $id = $self->get_id;
113              
114             # we have a value
115 756 50       1927 if ( defined $lookup ) {
116 756 50       2364 if ( looks_like_instance $lookup, 'HASH' ) {
117 756         2191 $lookup{$id} = $lookup;
118             }
119             else {
120 0         0 throw 'BadArgs' => "lookup must be a hash reference";
121             }
122             }
123              
124             # no value, so must be a reset
125             else {
126 0         0 $lookup{$id} = $self->get_lookup;
127             }
128 756         2099 return $self;
129 16     16   4098 }
  16         31  
  16         52  
130              
131             =item set_missing()
132              
133             Sets missing data symbol.
134              
135             Type : Mutator
136             Title : set_missing
137             Usage : $obj->set_missing('?');
138             Function: Sets the symbol for missing data
139             Returns : Modified object.
140             Args : Argument must be a single
141             character, default is '?'
142              
143             =cut
144              
145             sub set_missing : Clonable {
146 109     109 1 211 my ( $self, $missing ) = @_;
147 109         251 my $id = $self->get_id;
148 109 50       247 if ( $missing ne $self->get_gap ) {
149 109         218 $missing{$id} = $missing;
150             }
151             else {
152 0         0 throw 'BadArgs' =>
153             "Missing character '$missing' already in use as gap character";
154             }
155 109         222 return $self;
156 16     16   3691 }
  16         31  
  16         55  
157              
158             =item set_gap()
159              
160             Sets gap symbol.
161              
162             Type : Mutator
163             Title : set_gap
164             Usage : $obj->set_gap('-');
165             Function: Sets the symbol for gaps
166             Returns : Modified object.
167             Args : Argument must be a single
168             character, default is '-'
169              
170             =cut
171              
172             sub set_gap : Clonable {
173 69     69 1 146 my ( $self, $gap ) = @_;
174 69 50       192 if ( not $gap eq $self->get_missing ) {
175 69         208 $gap{ $self->get_id } = $gap;
176             }
177             else {
178 0         0 throw 'BadArgs' =>
179             "Gap character '$gap' already in use as missing character";
180             }
181 69         171 return $self;
182 16     16   3499 }
  16         34  
  16         62  
183              
184             =item set_metas_for_states()
185              
186             Assigns all metadata annotations for all state symbols
187              
188             Type : Mutator
189             Title : set_metas_for_states
190             Usage : $obj->set_metas_for_states({ $state => [ $m1, $m2 ] });
191             Function: Assigns all metadata annotations for all state symbols
192             Returns : Modified object.
193             Args : A hash reference of state symbols with metadata arrays
194              
195             =cut
196            
197             sub set_metas_for_states : Clonable {
198 50     50 1 94 my ( $self, $metas ) = @_;
199 50         121 $meta{$self->get_id} = $metas;
200 50         126 return $self;
201 16     16   2833 }
  16         32  
  16         63  
202            
203             =item add_meta_for_state()
204              
205             Adds a metadata annotation for a state symbol
206              
207             Type : Mutator
208             Title : add_meta_for_state
209             Usage : $obj->add_meta_for_state($meta,$state);
210             Function: Adds a metadata annotation for a state symbol
211             Returns : Modified object.
212             Args : A Bio::Phylo::NeXML::Meta object and a state symbol
213              
214             =cut
215              
216             sub add_meta_for_state {
217 0     0 1 0 my ( $self, $meta, $state ) = @_;
218 0 0       0 if ( my $lookup = $self->get_lookup ) {
219 0 0       0 if ( exists $lookup->{$state} ) {
220 0         0 my $id = $self->get_id;
221 0 0       0 $meta{$id} = {} if not $meta{$id};
222 0 0       0 $meta{$id}->{$state} = [] if not $meta{$id}->{$state};
223 0         0 push @{ $meta{$id}->{$state} }, $meta;
  0         0  
224             }
225             else {
226 0         0 $logger->warn(
227             "State '$state' is unknown, can't add annotation");
228             }
229             }
230             else {
231 0         0 $logger->warn(
232             "This data type has no categorical states to annotate");
233             }
234 0         0 return $self;
235             }
236              
237             =item remove_meta_for_state()
238              
239             Removes a metadata annotation for a state symbol
240              
241             Type : Mutator
242             Title : remove_meta_for_state
243             Usage : $obj->remove_meta_for_state($meta,$state);
244             Function: Removes a metadata annotation for a state symbol
245             Returns : Modified object.
246             Args : A Bio::Phylo::NeXML::Meta object and a state symbol
247              
248             =cut
249              
250             sub remove_meta_for_state {
251 0     0 1 0 my ( $self, $meta, $state ) = @_;
252 0         0 my $id = $self->get_id;
253 0 0 0     0 if ( $meta{$id} && $meta{$id}->{$state} ) {
254 0         0 my $meta_array = $meta{$id}->{$state};
255 0         0 my $meta_id = $meta->get_id;
256 0         0 DICT: for my $i ( 0 .. $#{$meta_array} ) {
  0         0  
257 0 0       0 if ( $meta_array->[$i]->get_id == $meta_id ) {
258 0         0 splice @{$meta_array}, $i, 1;
  0         0  
259 0         0 last DICT;
260             }
261             }
262             }
263             else {
264 0         0 $logger->warn(
265             "There are no annotations to remove for state '$state'");
266             }
267 0         0 return $self;
268             }
269              
270             =back
271              
272             =head2 ACCESSORS
273              
274             =over
275              
276             =item get_type()
277              
278             Gets data type as string.
279              
280             Type : Accessor
281             Title : get_type
282             Usage : my $type = $obj->get_type;
283             Function: Returns the object's datatype
284             Returns : A string
285             Args : None
286              
287             =cut
288              
289             sub get_type {
290 824     824 1 1810 my $type = ref shift;
291 824         4758 $type =~ s/.*:://;
292 824         3240 return $type;
293             }
294              
295             =item get_ids_for_special_symbols()
296              
297             Gets state-to-id mapping for missing and gap symbols
298              
299             Type : Accessor
300             Title : get_ids_for_special_symbols
301             Usage : my %ids = %{ $obj->get_ids_for_special_symbols };
302             Function: Returns state-to-id mapping
303             Returns : A hash reference, keyed on symbol, with UID values
304             Args : Optional, a boolean:
305             true => prefix state ids with 's'
306             false => keep ids numerical
307              
308             =cut
309              
310             sub get_ids_for_special_symbols {
311 0     0 1 0 my $self = shift;
312 0         0 my $ids_for_states = $self->get_ids_for_states;
313 0         0 my @indices = sort { $a <=> $b } values %{$ids_for_states};
  0         0  
  0         0  
314 0         0 my $max_id = $indices[-1];
315 0         0 my ( $missing, $gap ) = ( $self->get_missing, $self->get_gap );
316 0         0 my $ids_for_special_symbols = {};
317 0 0       0 if ( $_[0] ) {
318 0         0 $ids_for_special_symbols->{$gap} = 's' . ++$max_id;
319 0         0 $ids_for_special_symbols->{$missing} = 's' . ++$max_id;
320             }
321             else {
322 0         0 $ids_for_special_symbols->{$gap} = ++$max_id;
323 0         0 $ids_for_special_symbols->{$missing} = ++$max_id;
324             }
325 0         0 return $ids_for_special_symbols;
326             }
327              
328             =item get_ids_for_states()
329              
330             Gets state-to-id mapping
331              
332             Type : Accessor
333             Title : get_ids_for_states
334             Usage : my %ids = %{ $obj->get_ids_for_states };
335             Function: Returns state-to-id mapping
336             Returns : A hash reference, keyed on symbol, with UID values
337             Args : Optional, a boolean:
338             true => prefix state ids with 's'
339             false => keep ids numerical
340             Note : This returns a mapping to alphanumeric states; special
341             symbols (for missing data and gaps) are handled separately
342              
343             =cut
344              
345             sub get_ids_for_states {
346 0     0 1 0 my $self = shift;
347 0         0 $logger->debug("getting ids for state set $self");
348 0 0       0 if ( my $lookup = $self->get_lookup ) {
349 0         0 my $ids_for_states = {};
350 0         0 my ( @symbols, %tmp_cats, $i );
351              
352             # build a list of state symbols: what properties will this
353             # list have? Symbols will be present in order of the
354             # size of the state set to which they belong; within
355             # each of these ranks, the symbols will be in lexical
356             # order.
357 0   0     0 push( @{ $tmp_cats{ @{ $lookup->{$_} } } ||= [] }, $_ )
  0         0  
358 0         0 for grep /^\d+|[a-zA-Z]/, keys %{$lookup};
  0         0  
359 0         0 push( @symbols, sort { $a cmp $b } @{ $tmp_cats{$_} } )
  0         0  
360 0         0 for sort { $a <=> $b } keys %tmp_cats;
  0         0  
361             $ids_for_states->{$_} = ( $_[0] ? 's' : '' ) . ( ++$i )
362 0 0       0 for (@symbols);
363 0         0 return $ids_for_states;
364             }
365 0         0 return {};
366             }
367              
368             =item get_states_for_symbol()
369              
370             Gets set of fundamental states for an ambiguity symbol
371              
372             Type : Accessor
373             Title : get_states_for_symbol
374             Usage : my @states = @{ $obj->get_states_for_symbol('N') };
375             Function: Returns the set of states for an ambiguity symbol
376             Returns : An array ref of symbols
377             Args : An ambiguity symbol
378             Comments: If supplied argument is a fundamental state, an array
379             ref with just that state is returned, e.g. 'A' returns
380             ['A'] for DNA and RNA
381              
382             =cut
383              
384             sub get_states_for_symbol {
385 82250     82250 1 151914 my ( $self, $symbol ) = @_;
386 82250         115358 my @states;
387 82250 50       138676 if ( my $lookup = $self->get_lookup ) {
388 82250 50       227901 if ( my $map = $lookup->{uc $symbol} ) {
389 82250         123580 @states = @{ $map };
  82250         165362  
390             }
391             }
392 82250         235066 return \@states;
393             }
394              
395             =item get_symbol_for_states()
396              
397             Gets ambiguity symbol for a set of states
398              
399             Type : Accessor
400             Title : get_symbol_for_states
401             Usage : my $state = $obj->get_symbol_for_states('A','C');
402             Function: Returns the ambiguity symbol for a set of states
403             Returns : A symbol (SCALAR)
404             Args : A set of symbols
405             Comments: If no symbol exists in the lookup
406             table for the given set of states,
407             a new - numerical - one is created
408              
409             =cut
410              
411             sub get_symbol_for_states {
412 0     0 1 0 my $self = shift;
413 0         0 my @syms = @_;
414 0         0 my $lookup = $self->get_lookup;
415 0 0       0 if ($lookup) {
416 0         0 my @lookup_syms = keys %{$lookup};
  0         0  
417 0         0 SYM: for my $sym (@lookup_syms) {
418 0         0 my @states = @{ $lookup->{$sym} };
  0         0  
419 0 0       0 if ( scalar @syms == scalar @states ) {
420 0         0 my $seen_all = 0;
421 0         0 for my $i ( 0 .. $#syms ) {
422 0         0 my $seen = 0;
423 0         0 for my $j ( 0 .. $#states ) {
424 0 0       0 if ( $syms[$i] eq $states[$j] ) {
425 0         0 $seen++;
426 0         0 $seen_all++;
427             }
428             }
429 0 0       0 next SYM if not $seen;
430             }
431              
432             # found existing symbol
433 0 0       0 return $sym if $seen_all == scalar @syms;
434             }
435             }
436              
437             # create new symbol
438 0         0 my $sym;
439 0 0       0 if ( $self->get_type !~ /standard/i ) {
440 0         0 my $sym = 0;
441 0         0 while ( exists $lookup->{$sym} ) {
442 0         0 $sym++;
443             }
444             }
445             else {
446 0         0 LETTER: for my $char ( 'A' .. 'Z' ) {
447 0 0       0 if ( not exists $lookup->{$char} ) {
448 0         0 $sym = $char;
449 0         0 last LETTER;
450             }
451             }
452             }
453 0         0 $lookup->{$sym} = \@syms;
454 0         0 $self->set_lookup($lookup);
455 0         0 return $sym;
456             }
457             else {
458 0         0 $logger->info("No lookup table!");
459 0         0 return;
460             }
461             }
462              
463             =item get_lookup()
464              
465             Gets state lookup table.
466              
467             Type : Accessor
468             Title : get_lookup
469             Usage : my $lookup = $obj->get_lookup;
470             Function: Returns the object's lookup hash
471             Returns : A hash reference
472             Args : None
473              
474             =cut
475              
476             sub get_lookup {
477 83130     83130 1 129371 my $self = shift;
478 83130         166390 my $id = $self->get_id;
479 83130 100       176279 if ( exists $lookup{$id} ) {
480 82476         199977 return $lookup{$id};
481             }
482             else {
483 654         1595 my $class = __PACKAGE__;
484 654         2522 $class .= '::' . $self->get_type;
485 654         3550 $logger->debug("datatype class is $class");
486 654 50       2455 if ( looks_like_class $class ) {
487 654         1225 my $lookup;
488             {
489 16     16   17132 no strict 'refs';
  16         35  
  16         598  
  0         0  
490 654         1075 $lookup = ${ $class . '::LOOKUP' };
  654         3156  
491 16     16   79 use strict;
  16         27  
  16         12390  
492             }
493 654         1097 $self->set_lookup($lookup);
  654         2563  
494 654         1829 return $lookup;
495             }
496             }
497             }
498              
499             =item get_missing()
500              
501             Gets missing data symbol.
502              
503             Type : Accessor
504             Title : get_missing
505             Usage : my $missing = $obj->get_missing;
506             Function: Returns the object's missing data symbol
507             Returns : A string
508             Args : None
509              
510             =cut
511              
512             sub get_missing {
513 1643     1643 1 2906 my $self = shift;
514 1643         3994 my $missing = $missing{ $self->get_id };
515 1643 100       6208 return defined $missing ? $missing : '?';
516             }
517              
518             =item get_gap()
519              
520             Gets gap symbol.
521              
522             Type : Accessor
523             Title : get_gap
524             Usage : my $gap = $obj->get_gap;
525             Function: Returns the object's gap symbol
526             Returns : A string
527             Args : None
528              
529             =cut
530              
531             sub get_gap {
532 950     950 1 1566 my $self = shift;
533 950         2143 my $gap = $gap{ $self->get_id };
534 950 100       2644 return defined $gap ? $gap : '-';
535             }
536              
537             =item get_meta_for_state()
538              
539             Gets metadata annotations (if any) for the provided state symbol
540              
541             Type : Accessor
542             Title : get_meta_for_state
543             Usage : my @meta = @{ $obj->get_meta_for_state };
544             Function: Gets metadata annotations for a state symbol
545             Returns : An array reference of Bio::Phylo::NeXML::Meta objects
546             Args : A state symbol
547              
548             =cut
549              
550             sub get_meta_for_state {
551 0     0 1 0 my ( $self, $state ) = @_;
552 0         0 my $id = $self->get_id;
553 0 0 0     0 if ( $meta{$id} && $meta{$id}->{$state} ) {
554 0         0 return $meta{$id}->{$state};
555             }
556 0         0 return [];
557             }
558              
559             =item get_metas_for_states()
560              
561             Gets metadata annotations (if any) for all state symbols
562              
563             Type : Accessor
564             Title : get_metas_for_states
565             Usage : my @meta = @{ $obj->get_metas_for_states };
566             Function: Gets metadata annotations for state symbols
567             Returns : An array reference of Bio::Phylo::NeXML::Meta objects
568             Args : None
569              
570             =cut
571            
572 50     50 1 111 sub get_metas_for_states { $meta{shift->get_id} }
573              
574             =back
575              
576             =head2 TESTS
577              
578             =over
579              
580             =item is_ambiguous()
581              
582             Tests whether the supplied state symbol represents an ambiguous (polymorphic
583             or uncertain) state. For example, for the most commonly-used alphabet for
584             DNA states, the symbol 'N' represents complete uncertainty, the actual state
585             could be any of 'A', 'C', 'G' or 'T', and so this method would return a true
586             value.
587              
588             Type : Test
589             Title : is_ambiguous
590             Usage : if ( $obj->is_ambiguous('N') ) {
591             # do something
592             }
593             Function: Returns true if argument is an ambiguous state symbol
594             Returns : BOOLEAN
595             Args : A state symbol
596              
597             =cut
598              
599             sub is_ambiguous {
600 65     65 1 95 my ( $self, $symbol ) = @_;
601 65 100       88 if ( my $lookup = $self->get_lookup ) {
602 50         70 my $mapping = $lookup->{uc $symbol};
603 50 100 66     121 if ( $mapping and ref $mapping eq 'ARRAY' ) {
604 39         44 return scalar(@{$mapping}) > 1;
  39         86  
605             }
606             }
607 26         50 return 0;
608             }
609              
610             =item is_valid()
611              
612             Validates argument.
613              
614             Type : Test
615             Title : is_valid
616             Usage : if ( $obj->is_valid($datum) ) {
617             # do something
618             }
619             Function: Returns true if $datum only contains valid characters
620             Returns : BOOLEAN
621             Args : A Bio::Phylo::Matrices::Datum object
622              
623             =cut
624              
625             sub is_valid {
626 1451     1451 1 2634 my $self = shift;
627 1451         2510 my @data;
628 1451         4210 ARG: for my $arg (@_) {
629 1451 50       8880 if ( ref $arg eq 'ARRAY' ) {
    100          
630 0         0 push @data, @{$arg};
  0         0  
631             }
632             elsif ( UNIVERSAL::can( $arg, 'get_char' ) ) {
633 743         2572 push @data, $arg->get_char;
634             }
635             else {
636 708 50       3205 if ( length($arg) > 1 ) {
637 0         0 push @data, @{ $self->split($arg) };
  0         0  
638             }
639             else {
640 708         17230 @data = @_;
641 708         1964 last ARG;
642             }
643             }
644             }
645 1451 100       5873 return 1 if not @data;
646 712         2461 my $lookup = $self->get_lookup;
647 712         2037 my @symbols = ( $self->get_missing, $self->get_gap, keys %{$lookup} );
  712         4762  
648 712         1916 my %symbols = map { $_ => 1 } grep { defined $_ } @symbols;
  12480         24278  
  12480         21641  
649 712         2360 CHAR_CHECK: for my $char (@data) {
650 82978 50       143250 next CHAR_CHECK if not defined $char;
651 82978 100       181110 next CHAR_CHECK if $symbols{ uc $char };
652 8         43 return 0;
653             }
654 704         15401 return 1;
655             }
656              
657             =item is_same()
658              
659             Compares data type objects.
660              
661             Type : Test
662             Title : is_same
663             Usage : if ( $obj->is_same($obj1) ) {
664             # do something
665             }
666             Function: Returns true if $obj1 contains the same validation rules
667             Returns : BOOLEAN
668             Args : A Bio::Phylo::Matrices::Datatype::* object
669              
670             =cut
671              
672             sub is_same {
673 119     119 1 213 my ( $self, $model ) = @_;
674 119         496 $logger->info("Comparing datatype '$self' to '$model'");
675 119 100       266 return 1 if $self->get_id == $model->get_id;
676 34 50       76 return 0 if $self->get_type ne $model->get_type;
677              
678             # check strings
679 34         77 for my $prop (qw(get_type get_missing get_gap)) {
680 102         223 my ( $self_prop, $model_prop ) = ( $self->$prop, $model->$prop );
681 102 50 33     442 return 0
      33        
682             if defined $self_prop
683             && defined $model_prop
684             && $self_prop ne $model_prop;
685             }
686 34         72 my ( $s_lookup, $m_lookup ) = ( $self->get_lookup, $model->get_lookup );
687              
688             # one has lookup, other hasn't
689 34 50 33     133 if ( $s_lookup && !$m_lookup ) {
690 0         0 return 0;
691             }
692              
693             # both don't have lookup -> are continuous
694 34 0 33     63 if ( !$s_lookup && !$m_lookup ) {
695 0         0 return 1;
696             }
697              
698             # get keys
699 34         49 my @s_keys = keys %{$s_lookup};
  34         114  
700 34         58 my @m_keys = keys %{$m_lookup};
  34         83  
701              
702             # different number of keys
703 34 50       78 if ( scalar(@s_keys) != scalar(@m_keys) ) {
704 0         0 return 0;
705             }
706              
707             # compare keys
708 34         56 for my $key (@s_keys) {
709 448 50       709 if ( not exists $m_lookup->{$key} ) {
710 0         0 return 0;
711             }
712             else {
713              
714             # compare values
715 448         747 my ( %s_vals, %m_vals );
716 448         0 my ( @s_vals, @m_vals );
717 448         473 @s_vals = @{ $s_lookup->{$key} };
  448         692  
718 448         528 @m_vals = @{ $m_lookup->{$key} };
  448         628  
719              
720             # different number of vals
721 448 50       702 if ( scalar(@m_vals) != scalar(@s_vals) ) {
722 0         0 return 0;
723             }
724              
725             # make hashes to compare on vals
726 448         621 %s_vals = map { $_ => 1 } @s_vals;
  808         1308  
727 448         591 %m_vals = map { $_ => 1 } @m_vals;
  808         1200  
728 448         771 for my $val ( keys %s_vals ) {
729 808 50       1624 return 0 if not exists $m_vals{$val};
730             }
731             }
732             }
733 34         136 return 1;
734             }
735              
736             =back
737              
738             =head2 UTILITY METHODS
739              
740             =over
741              
742             =item split()
743              
744             Splits argument string of characters following appropriate rules.
745              
746             Type : Utility method
747             Title : split
748             Usage : $obj->split($string)
749             Function: Splits $string into characters
750             Returns : An array reference of characters
751             Args : A string
752              
753             =cut
754              
755             sub split {
756 1237     1237 1 3500 my ( $self, $string ) = @_;
757 1237         97972 my @array = CORE::split( /\s*/, $string );
758 1237         29191 return \@array;
759             }
760              
761             =item join()
762              
763             Joins argument array ref of characters following appropriate rules.
764              
765             Type : Utility method
766             Title : join
767             Usage : $obj->join($arrayref)
768             Function: Joins $arrayref into a string
769             Returns : A string
770             Args : An array reference
771              
772             =cut
773              
774             sub join {
775 40     40 1 70 my ( $self, $array ) = @_;
776 40         48 return CORE::join( '', @{$array} );
  40         180  
777             }
778              
779             sub _cleanup : Destructor {
780 1672     1672   2949 my $self = shift;
781 1672         8500 $logger->debug("cleaning up '$self'");
782 1672         4724 my $id = $self->get_id;
783 1672         3638 for my $field (@fields) {
784 6688         13610 delete $field->{$id};
785             }
786 16     16   112 }
  16         34  
  16         73  
787              
788             =back
789              
790             =head2 SERIALIZERS
791              
792             =over
793              
794             =item to_xml()
795              
796             Writes data type definitions to xml
797              
798             Type : Serializer
799             Title : to_xml
800             Usage : my $xml = $obj->to_xml
801             Function: Writes data type definitions to xml
802             Returns : An xml string representation of data type definition
803             Args : None
804              
805             =cut
806              
807             sub to_xml {
808 0     0 1 0 my $self = shift;
809 0         0 $logger->debug("writing $self to xml");
810 0         0 my $xml = '';
811 0   0     0 my $normalized = $_[0] || {};
812 0         0 my $polymorphism = $_[1];
813 0 0       0 if ( my $lookup = $self->get_lookup ) {
814 0         0 $xml .= "\n" . $self->get_xml_tag;
815 0         0 $logger->debug($xml);
816 0         0 my $id_for_state = $self->get_ids_for_states(1);
817             my @states = sort {
818 0         0 my ( $m, $n );
819 0         0 ($m) = $id_for_state->{$a} =~ /([0-9]+)/;
820 0         0 ($n) = $id_for_state->{$b} =~ /([0-9]+)/;
821 0         0 $m <=> $n
822 0         0 } keys %{$id_for_state};
  0         0  
823 0         0 for my $state (@states) {
824 0         0 $xml .=
825             $self->_state_to_xml( $state, $id_for_state, $lookup,
826             $normalized, $polymorphism );
827             }
828 0         0 my ( $missing, $gap ) = ( $self->get_missing, $self->get_gap );
829 0         0 my $special = $self->get_ids_for_special_symbols;
830 0 0       0 if ( %{$special} ) {
  0         0  
831 0         0 my $uss =
832             $fac->create_xmlwritable( '-tag' => 'uncertain_state_set' );
833 0         0 my $mbr = $fac->create_xmlwritable(
834             '-tag' => 'member',
835             '-identifiable' => 0
836             );
837             $uss->set_attributes(
838 0         0 'id' => "s" . $special->{$gap},
839             'symbol' => '-'
840             );
841 0         0 $xml .= "\n" . $uss->get_xml_tag(1);
842             $uss->set_attributes(
843 0         0 'id' => "s" . $special->{$missing},
844             'symbol' => '?'
845             );
846 0         0 $xml .= "\n" . $uss->get_xml_tag();
847 0         0 for (@states) {
848 0         0 $mbr->set_attributes( 'state' => $id_for_state->{$_} );
849 0         0 $xml .= "\n" . $mbr->get_xml_tag(1);
850             }
851 0         0 $mbr->set_attributes( 'state' => "s" . $special->{$gap} );
852 0         0 $xml .= "\n" . $mbr->get_xml_tag(1);
853 0         0 $xml .= "\n</" . $uss->get_tag . ">";
854             }
855 0         0 $xml .= "\n</" . $self->get_tag . ">";
856             }
857 0         0 return $xml;
858             }
859              
860             sub _state_to_xml {
861 0     0   0 my ( $self, $state, $id_for_state, $lookup, $normalized, $polymorphism )
862             = @_;
863 0         0 my $state_id = $id_for_state->{$state};
864 0         0 my @mapping = @{ $lookup->{$state} };
  0         0  
865             my $symbol =
866 0 0       0 exists $normalized->{$state} ? $normalized->{$state} : $state;
867 0         0 my $xml = '';
868 0         0 my $unambiguous = scalar @mapping <= 1;
869 0 0       0 my $tag =
    0          
870             $unambiguous ? 'state'
871             : $polymorphism ? 'polymorphic_state_set'
872             : 'uncertain_state_set';
873 0         0 my $elt = $fac->create_xmlwritable(
874             '-tag' => $tag,
875             '-xml_id' => $state_id,
876             '-attributes' => { 'symbol' => $symbol }
877             );
878 0         0 $elt->add_meta($_) for @{ $self->get_meta_for_state($state) };
  0         0  
879              
880 0 0       0 if ($unambiguous) {
881 0         0 $xml .= "\n" . $elt->get_xml_tag(1);
882             }
883             else {
884 0         0 $xml .= "\n" . $elt->get_xml_tag();
885 0         0 for (@mapping) {
886             $xml .= $fac->create_xmlwritable(
887             '-tag' => 'member',
888             '-identifiable' => 0,
889 0         0 '-attributes' => { 'state' => $id_for_state->{$_} }
890             )->get_xml_tag(1);
891             }
892 0         0 $xml .= "\n</" . $elt->get_tag . ">";
893             }
894 0         0 return $xml;
895             }
896              
897             =item to_dom()
898              
899             Analog to to_xml.
900              
901             Type : Serializer
902             Title : to_dom
903             Usage : $type->to_dom
904             Function: Generates a DOM subtree from the invocant
905             and its contained objects
906             Returns : an <XML Package>::Element object
907             Args : none
908              
909             =cut
910              
911             sub to_dom {
912 0     0 1 0 my $self = shift;
913 0         0 my $dom = $_[0];
914 0         0 my @args = @_;
915              
916             # handle dom factory object...
917 0 0 0     0 if ( looks_like_instance( $dom, 'SCALAR' )
918             && $dom->_type == _DOMCREATOR_ )
919             {
920 0         0 splice( @args, 0, 1 );
921             }
922             else {
923 0         0 $dom = $Bio::Phylo::NeXML::DOM::DOM;
924 0 0       0 unless ($dom) {
925 0         0 throw 'BadArgs' => 'DOM factory object not provided';
926             }
927             }
928 0         0 my $elt;
929 0   0     0 my $normalized = $args[0] || {};
930 0         0 my $polymorphism = $args[1];
931 0 0       0 if ( my $lookup = $self->get_lookup ) {
932 0         0 $elt = $self->get_dom_elt($dom);
933 0         0 my $id_for_state = $self->get_ids_for_states;
934             my @states = sort {
935 0         0 my ( $m, $n );
936 0         0 ($m) = $id_for_state->{$a} =~ /([0-9]+)/;
937 0         0 ($n) = $id_for_state->{$b} =~ /([0-9]+)/;
938 0         0 $m <=> $n
939 0         0 } keys %{$id_for_state};
  0         0  
940 0         0 keys %{$id_for_state};
  0         0  
941 0         0 my $max_id = 0;
942 0         0 for my $state (@states) {
943 0         0 my $state_id = $id_for_state->{$state};
944 0         0 $id_for_state->{$state} = 's' . $state_id;
945 0         0 $max_id = $state_id;
946             }
947 0         0 for my $state (@states) {
948 0         0 $elt->set_child(
949             $self->_state_to_dom(
950             $dom, $state, $id_for_state,
951             $lookup, $normalized, $polymorphism
952             )
953             );
954             }
955 0         0 my ( $missing, $gap ) = ( $self->get_missing, $self->get_gap );
956 0         0 my $special = $self->get_ids_for_special_symbols;
957 0 0       0 if ( %{$special} ) {
  0         0  
958 0         0 my $uss;
959 0         0 $uss = $dom->create_element( '-tag' => 'uncertain_state_set' );
960 0         0 $uss->set_attributes( 'id' => 's' . $special->{$gap} );
961 0         0 $uss->set_attributes( 'symbol' => '-' );
962 0         0 $elt->set_child($uss);
963 0         0 $uss = $dom->create_element( '-tag' => 'uncertain_state_set' );
964 0         0 $uss->set_attributes( 'id' => 's' . $special->{$missing} );
965 0         0 $uss->set_attributes( 'symbol' => '?' );
966 0         0 my $mbr;
967              
968 0         0 for (@states) {
969 0         0 $mbr = $dom->create_element( '-tag' => 'member' );
970 0         0 $mbr->set_attributes( 'state' => $id_for_state->{$_} );
971 0         0 $uss->set_child($mbr);
972             }
973 0         0 $mbr = $dom->create_element( '-tag' => 'member' );
974 0         0 $mbr->set_attributes( 'state' => 's' . $special->{$gap} );
975 0         0 $uss->set_child($mbr);
976 0         0 $elt->set_child($uss);
977             }
978             }
979 0         0 return $elt;
980             }
981              
982             sub _state_to_dom {
983 0     0   0 my ( $self, $dom, $state, $id_for_state, $lookup, $normalized,
984             $polymorphism )
985             = @_;
986 0         0 my $state_id = $id_for_state->{$state};
987 0         0 my @mapping = @{ $lookup->{$state} };
  0         0  
988             my $symbol =
989 0 0       0 exists $normalized->{$state} ? $normalized->{$state} : $state;
990 0         0 my $elt;
991              
992             # has ambiguity mappings
993 0 0       0 if ( scalar @mapping > 1 ) {
994 0 0       0 my $tag =
995             $polymorphism ? 'polymorphic_state_set' : 'uncertain_state_set';
996 0         0 $elt = $dom->create_element( '-tag' => $tag );
997 0         0 $elt->set_attributes( 'id' => $state_id );
998 0         0 $elt->set_attributes( 'symbol' => $symbol );
999 0         0 for my $map (@mapping) {
1000 0         0 my $mbr = $dom->create_element( '-tag' => 'member' );
1001 0         0 $mbr->set_attributes( 'state' => $id_for_state->{$map} );
1002 0         0 $elt->set_child($mbr);
1003             }
1004             }
1005              
1006             # no ambiguity
1007             else {
1008 0         0 $elt = $dom->create_element( '-tag' => 'state' );
1009 0         0 $elt->set_attributes( 'id' => $state_id );
1010 0         0 $elt->set_attributes( 'symbol' => $symbol );
1011             }
1012 0         0 return $elt;
1013             }
1014 50     50   136 sub _tag { 'states' }
1015 50     50   126 sub _type { _DATATYPE_ }
1016              
1017             =back
1018              
1019             =cut
1020              
1021             # podinherit_insert_token
1022              
1023             =head1 SEE ALSO
1024              
1025             There is a mailing list at L<https://groups.google.com/forum/#!forum/bio-phylo>
1026             for any user or developer questions and discussions.
1027              
1028             =over
1029              
1030             =item L<Bio::Phylo>
1031              
1032             This object inherits from L<Bio::Phylo>, so the methods defined
1033             therein are also applicable to L<Bio::Phylo::Matrices::Datatype> objects.
1034              
1035             =item L<Bio::Phylo::Manual>
1036              
1037             Also see the manual: L<Bio::Phylo::Manual> and L<http://rutgervos.blogspot.com>.
1038              
1039             =back
1040              
1041             =head1 CITATION
1042              
1043             If you use Bio::Phylo in published research, please cite it:
1044              
1045             B<Rutger A Vos>, B<Jason Caravas>, B<Klaas Hartmann>, B<Mark A Jensen>
1046             and B<Chase Miller>, 2011. Bio::Phylo - phyloinformatic analysis using Perl.
1047             I<BMC Bioinformatics> B<12>:63.
1048             L<http://dx.doi.org/10.1186/1471-2105-12-63>
1049              
1050             =cut
1051              
1052             }
1053             1;