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   96 use strict;
  16         32  
  16         455  
3 16     16   73 use base 'Bio::Phylo::NeXML::Writable';
  16         34  
  16         1683  
4 16     16   106 use Bio::Phylo::Factory;
  16         30  
  16         105  
5 16     16   70 use Bio::Phylo::Util::Exceptions 'throw';
  16         30  
  16         698  
6 16     16   89 use Bio::Phylo::Util::CONSTANT qw'_DOMCREATOR_ _DATATYPE_ /looks_like/';
  16         37  
  16         5727  
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 objects and L
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 1310 my $class = shift;
55              
56             # constructor called with type string
57 837 100       1972 if ( $class eq __PACKAGE__ ) {
58 787         1909 my $type = ucfirst( lc(shift) );
59 787 50       1671 if ( not $type ) {
60 0         0 throw 'BadArgs' => "No subtype specified!";
61             }
62 787 100       1630 if ( $type eq 'Nucleotide' ) {
63 1         6 $logger->warn("'nucleotide' datatype requested, using 'dna'");
64 1         2 $type = 'Dna';
65             }
66 787         2108 return looks_like_class( __PACKAGE__ . '::' . $type )
67             ->SUPER::new(@_);
68             }
69              
70             # constructor called from type subclass
71             else {
72 50         130 my %args = looks_like_hash @_;
73             {
74 16     16   115 no strict 'refs';
  16         34  
  16         1761  
  0         0  
75 50         137 $args{'-lookup'} = ${"${class}::LOOKUP"}
76 50 50       64 if ${"${class}::LOOKUP"};
  50         193  
77 50         113 $args{'-missing'} = ${"${class}::MISSING"}
78 50 50       67 if ${"${class}::MISSING"};
  50         149  
79 50 100       72 $args{'-gap'} = ${"${class}::GAP"} if ${"${class}::GAP"};
  10         27  
  50         148  
80 16     16   106 use strict;
  16         41  
  16         936  
81             }
82 50         69 return $class->SUPER::new(%args);
  50         162  
83             }
84 16     16   87 }
  16         33  
  16         98  
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 1217 my ( $self, $lookup ) = @_;
112 756         1531 my $id = $self->get_id;
113              
114             # we have a value
115 756 50       1352 if ( defined $lookup ) {
116 756 50       1505 if ( looks_like_instance $lookup, 'HASH' ) {
117 756         1507 $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         1274 return $self;
129 16     16   4376 }
  16         38  
  16         66  
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 197 my ( $self, $missing ) = @_;
147 109         210 my $id = $self->get_id;
148 109 50       254 if ( $missing ne $self->get_gap ) {
149 109         201 $missing{$id} = $missing;
150             }
151             else {
152 0         0 throw 'BadArgs' =>
153             "Missing character '$missing' already in use as gap character";
154             }
155 109         232 return $self;
156 16     16   3597 }
  16         43  
  16         68  
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 136 my ( $self, $gap ) = @_;
174 69 50       183 if ( not $gap eq $self->get_missing ) {
175 69         199 $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         161 return $self;
182 16     16   3847 }
  16         38  
  16         69  
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 79 my ( $self, $metas ) = @_;
199 50         110 $meta{$self->get_id} = $metas;
200 50         109 return $self;
201 16     16   3202 }
  16         37  
  16         56  
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 1423 my $type = ref shift;
291 824         3334 $type =~ s/.*:://;
292 824         2432 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 109417 my ( $self, $symbol ) = @_;
386 82250         87773 my @states;
387 82250 50       107963 if ( my $lookup = $self->get_lookup ) {
388 82250 50       167968 if ( my $map = $lookup->{uc $symbol} ) {
389 82250         95115 @states = @{ $map };
  82250         125337  
390             }
391             }
392 82250         174110 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 96692 my $self = shift;
478 83130         122717 my $id = $self->get_id;
479 83130 100       126763 if ( exists $lookup{$id} ) {
480 82476         147256 return $lookup{$id};
481             }
482             else {
483 654         877 my $class = __PACKAGE__;
484 654         1293 $class .= '::' . $self->get_type;
485 654         2598 $logger->debug("datatype class is $class");
486 654 50       1613 if ( looks_like_class $class ) {
487 654         984 my $lookup;
488             {
489 16     16   18301 no strict 'refs';
  16         38  
  16         611  
  0         0  
490 654         828 $lookup = ${ $class . '::LOOKUP' };
  654         2153  
491 16     16   85 use strict;
  16         33  
  16         13076  
492             }
493 654         814 $self->set_lookup($lookup);
  654         2014  
494 654         1568 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 2210 my $self = shift;
514 1643         3075 my $missing = $missing{ $self->get_id };
515 1643 100       4719 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 1378 my $self = shift;
533 950         1700 my $gap = $gap{ $self->get_id };
534 950 100       2055 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 110 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 115 my ( $self, $symbol ) = @_;
601 65 100       105 if ( my $lookup = $self->get_lookup ) {
602 50         74 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         90  
605             }
606             }
607 26         56 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 2288 my $self = shift;
627 1451         1964 my @data;
628 1451         2777 ARG: for my $arg (@_) {
629 1451 50       6325 if ( ref $arg eq 'ARRAY' ) {
    100          
630 0         0 push @data, @{$arg};
  0         0  
631             }
632             elsif ( UNIVERSAL::can( $arg, 'get_char' ) ) {
633 743         1731 push @data, $arg->get_char;
634             }
635             else {
636 708 50       1999 if ( length($arg) > 1 ) {
637 0         0 push @data, @{ $self->split($arg) };
  0         0  
638             }
639             else {
640 708         10466 @data = @_;
641 708         1378 last ARG;
642             }
643             }
644             }
645 1451 100       4189 return 1 if not @data;
646 712         1701 my $lookup = $self->get_lookup;
647 712         1439 my @symbols = ( $self->get_missing, $self->get_gap, keys %{$lookup} );
  712         3005  
648 712         1557 my %symbols = map { $_ => 1 } grep { defined $_ } @symbols;
  12480         18786  
  12480         16983  
649 712         1910 CHAR_CHECK: for my $char (@data) {
650 82978 50       113794 next CHAR_CHECK if not defined $char;
651 82978 100       138608 next CHAR_CHECK if $symbols{ uc $char };
652 8         63 return 0;
653             }
654 704         10950 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 252 my ( $self, $model ) = @_;
674 119         535 $logger->info("Comparing datatype '$self' to '$model'");
675 119 100       281 return 1 if $self->get_id == $model->get_id;
676 34 50       81 return 0 if $self->get_type ne $model->get_type;
677              
678             # check strings
679 34         79 for my $prop (qw(get_type get_missing get_gap)) {
680 102         229 my ( $self_prop, $model_prop ) = ( $self->$prop, $model->$prop );
681 102 50 33     445 return 0
      33        
682             if defined $self_prop
683             && defined $model_prop
684             && $self_prop ne $model_prop;
685             }
686 34         90 my ( $s_lookup, $m_lookup ) = ( $self->get_lookup, $model->get_lookup );
687              
688             # one has lookup, other hasn't
689 34 50 33     141 if ( $s_lookup && !$m_lookup ) {
690 0         0 return 0;
691             }
692              
693             # both don't have lookup -> are continuous
694 34 0 33     89 if ( !$s_lookup && !$m_lookup ) {
695 0         0 return 1;
696             }
697              
698             # get keys
699 34         47 my @s_keys = keys %{$s_lookup};
  34         140  
700 34         61 my @m_keys = keys %{$m_lookup};
  34         96  
701              
702             # different number of keys
703 34 50       90 if ( scalar(@s_keys) != scalar(@m_keys) ) {
704 0         0 return 0;
705             }
706              
707             # compare keys
708 34         71 for my $key (@s_keys) {
709 448 50       694 if ( not exists $m_lookup->{$key} ) {
710 0         0 return 0;
711             }
712             else {
713              
714             # compare values
715 448         799 my ( %s_vals, %m_vals );
716 448         0 my ( @s_vals, @m_vals );
717 448         493 @s_vals = @{ $s_lookup->{$key} };
  448         761  
718 448         553 @m_vals = @{ $m_lookup->{$key} };
  448         629  
719              
720             # different number of vals
721 448 50       774 if ( scalar(@m_vals) != scalar(@s_vals) ) {
722 0         0 return 0;
723             }
724              
725             # make hashes to compare on vals
726 448         611 %s_vals = map { $_ => 1 } @s_vals;
  808         1383  
727 448         616 %m_vals = map { $_ => 1 } @m_vals;
  808         1191  
728 448         789 for my $val ( keys %s_vals ) {
729 808 50       1693 return 0 if not exists $m_vals{$val};
730             }
731             }
732             }
733 34         149 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 2218 my ( $self, $string ) = @_;
757 1237         69875 my @array = CORE::split( /\s*/, $string );
758 1237         17598 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 62 my ( $self, $array ) = @_;
776 40         50 return CORE::join( '', @{$array} );
  40         175  
777             }
778              
779             sub _cleanup : Destructor {
780 1672     1672   2416 my $self = shift;
781 1672         5954 $logger->debug("cleaning up '$self'");
782 1672         3408 my $id = $self->get_id;
783 1672         2793 for my $field (@fields) {
784 6688         10444 delete $field->{$id};
785             }
786 16     16   119 }
  16         36  
  16         86  
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 .= "\nget_tag . ">";
854             }
855 0         0 $xml .= "\nget_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 .= "\nget_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 ::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   142 sub _tag { 'states' }
1015 50     50   112 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
1026             for any user or developer questions and discussions.
1027              
1028             =over
1029              
1030             =item L
1031              
1032             This object inherits from L, so the methods defined
1033             therein are also applicable to L objects.
1034              
1035             =item L
1036              
1037             Also see the manual: L and L.
1038              
1039             =back
1040              
1041             =head1 CITATION
1042              
1043             If you use Bio::Phylo in published research, please cite it:
1044              
1045             B, B, B, B
1046             and B, 2011. Bio::Phylo - phyloinformatic analysis using Perl.
1047             I B<12>:63.
1048             L
1049              
1050             =cut
1051              
1052             }
1053             1;