File Coverage

Bio/SeqFeature/Lite.pm
Criterion Covered Total %
statement 41 354 11.5
branch 19 190 10.0
condition 9 94 9.5
subroutine 5 71 7.0
pod 39 66 59.0
total 113 775 14.5


line stmt bran cond sub pod time code
1             package Bio::SeqFeature::Lite;
2              
3             =head1 NAME
4              
5             Bio::SeqFeature::Lite - Lightweight Bio::SeqFeatureI class
6              
7             =head1 SYNOPSIS
8              
9             # create a simple feature with no internal structure
10             $f = Bio::SeqFeature::Lite->new(-start => 1000,
11             -stop => 2000,
12             -type => 'transcript',
13             -name => 'alpha-1 antitrypsin',
14             -desc => 'an enzyme inhibitor',
15             );
16              
17             # create a feature composed of multiple segments, all of type "similarity"
18             $f = Bio::SeqFeature::Lite->new(-segments => [[1000,1100],[1500,1550],[1800,2000]],
19             -name => 'ABC-3',
20             -type => 'gapped_alignment',
21             -subtype => 'similarity');
22              
23             # build up a gene exon by exon
24             $e1 = Bio::SeqFeature::Lite->new(-start=>1,-stop=>100,-type=>'exon');
25             $e2 = Bio::SeqFeature::Lite->new(-start=>150,-stop=>200,-type=>'exon');
26             $e3 = Bio::SeqFeature::Lite->new(-start=>300,-stop=>500,-type=>'exon');
27             $f = Bio::SeqFeature::Lite->new(-segments=>[$e1,$e2,$e3],-type=>'gene');
28              
29             =head1 DESCRIPTION
30              
31             This is a simple Bio::SeqFeatureI-compliant object that is compatible
32             with Bio::Graphics::Panel. With it you can create lightweight feature
33             objects for drawing.
34              
35             All methods are as described in L with the following additions:
36              
37             =head2 The new() Constructor
38              
39             $feature = Bio::SeqFeature::Lite->new(@args);
40              
41             This method creates a new feature object. You can create a simple
42             feature that contains no subfeatures, or a hierarchically nested object.
43              
44             Arguments are as follows:
45              
46             -seq_id the reference sequence
47             -start the start position of the feature
48             -end the stop position of the feature
49             -stop an alias for end
50             -name the feature name (returned by seqname())
51             -type the feature type (returned by primary_tag())
52             -primary_tag the same as -type
53             -source the source tag
54             -score the feature score (for GFF compatibility)
55             -desc a description of the feature
56             -segments a list of subfeatures (see below)
57             -subtype the type to use when creating subfeatures
58             -strand the strand of the feature (one of -1, 0 or +1)
59             -phase the phase of the feature (0..2)
60             -seq a dna or protein sequence string to attach to feature
61             -id an alias for -name
62             -seqname an alias for -name
63             -display_id an alias for -name
64             -display_name an alias for -name (do you get the idea the API has changed?)
65             -primary_id unique database ID
66             -url a URL to link to when rendered with Bio::Graphics
67             -attributes a hashref of tag value attributes, in which the key is the tag
68             and the value is an array reference of values
69             -factory a reference to a feature factory, used for compatibility with
70             more obscure parts of Bio::DB::GFF
71              
72             The subfeatures passed in -segments may be an array of
73             Bio::SeqFeature::Lite objects, or an array of [$start,$stop]
74             pairs. Each pair should be a two-element array reference. In the
75             latter case, the feature type passed in -subtype will be used when
76             creating the subfeatures.
77              
78             If no feature type is passed, then it defaults to "feature".
79              
80             =head2 Non-SeqFeatureI methods
81              
82             A number of new methods are provided for compatibility with
83             Ace::Sequence, which has a slightly different API from SeqFeatureI:
84              
85             =over 4
86              
87             =item url()
88              
89             Get/set the URL that the graphical rendering of this feature will link to.
90              
91             =item add_segment(@segments)
92              
93             Add one or more segments (a subfeature). Segments can either be
94             Feature objects, or [start,stop] arrays, as in the -segments argument
95             to new(). The feature endpoints are automatically adjusted.
96              
97             =item segments()
98              
99             An alias for sub_SeqFeature().
100              
101             =item get_SeqFeatures()
102              
103             Alias for sub_SeqFeature()
104              
105             =item get_all_SeqFeatures()
106              
107             Alias for sub_SeqFeature()
108              
109             =item merged_segments()
110              
111             Another alias for sub_SeqFeature().
112              
113             =item stop()
114              
115             An alias for end().
116              
117             =item name()
118              
119             An alias for seqname().
120              
121             =item exons()
122              
123             An alias for sub_SeqFeature() (you don't want to know why!)
124              
125             =back
126              
127             =cut
128              
129 1     1   1783 use strict;
  1         5  
  1         52  
130              
131 1     1   10 use base qw(Bio::Root::Root Bio::SeqFeatureI Bio::LocationI Bio::SeqI);
  1         3  
  1         7152  
132              
133             *stop = \&end;
134             *info = \&name;
135             *seqname = \&name;
136             *exons = *sub_SeqFeature = *merged_segments = \&segments;
137             *get_all_SeqFeatures = *get_SeqFeatures = \&segments;
138             *method = \&primary_tag;
139             *source = \&source_tag;
140             *get_tag_values = \&each_tag_value;
141             *add_SeqFeature = \&add_segment;
142             *get_all_tags = \&all_tags;
143             *abs_ref = \&ref;
144              
145             # implement Bio::SeqI and FeatureHolderI interface
146              
147 0     0 1 0 sub primary_seq { return $_[0] }
148             sub annotation {
149 0     0 1 0 my ($obj,$value) = @_;
150 0 0       0 if( defined $value ) {
    0          
151 0 0       0 $obj->throw("object of class ".ref($value)." does not implement ".
152             "Bio::AnnotationCollectionI. Too bad.")
153             unless $value->isa("Bio::AnnotationCollectionI");
154 0         0 $obj->{'_annotation'} = $value;
155             } elsif( ! defined $obj->{'_annotation'}) {
156 0         0 $obj->{'_annotation'} = Bio::Annotation::Collection->new();
157             }
158 0         0 return $obj->{'_annotation'};
159             }
160             sub species {
161 0     0 1 0 my ($self, $species) = @_;
162 0 0       0 if ($species) {
163 0         0 $self->{'species'} = $species;
164             } else {
165 0         0 return $self->{'species'};
166             }
167             }
168 0     0 1 0 sub is_remote { return }
169 0 0   0 1 0 sub feature_count { return scalar @{shift->{segments} || []} }
  0         0  
170              
171 0     0 0 0 sub target { return; }
172 0     0 0 0 sub hit { shift->target }
173              
174             sub type {
175 0     0 0 0 my $self = shift;
176 0         0 my $method = $self->primary_tag;
177 0         0 my $source = $self->source_tag;
178 0 0       0 return $source ne '' ? "$method:$source" : $method;
179             }
180              
181             # usage:
182             # Bio::SeqFeature::Lite->new(
183             # -start => 1,
184             # -end => 100,
185             # -name => 'fred feature',
186             # -strand => +1);
187             #
188             # Alternatively, use -segments => [ [start,stop],[start,stop]...]
189             # to create a multisegmented feature.
190             sub new {
191 3     3 1 11 my $class= shift;
192 3 50       5 $class = ref($class) if ref $class;
193 3         8 my %arg = @_;
194              
195 3         6 my $self = bless {},$class;
196              
197 3   50     6 $arg{-strand} ||= 0;
198 3 50       10 if ($arg{-strand} =~ /^[\+\-\.]$/){
199 3 100       15 ($arg{-strand} eq "+") && ($self->{strand} = '1');
200 3 100       7 ($arg{-strand} eq "-") && ($self->{strand} = '-1');
201 3 100       7 ($arg{-strand} eq ".") && ($self->{strand} = '0');
202             } else {
203 0 0       0 $self->{strand} = $arg{-strand} ? ($arg{-strand} >= 0 ? +1 : -1) : 0;
    0          
204             }
205             $self->{name} = $arg{-name} || $arg{-seqname} || $arg{-display_id}
206 3   33     21 || $arg{-display_name} || $arg{-id};
207 3   50     11 $self->{type} = $arg{-type} || $arg{-primary_tag} || 'feature';
208 3 50       7 $self->{subtype} = $arg{-subtype} if exists $arg{-subtype};
209 3   50     17 $self->{source} = $arg{-source} || $arg{-source_tag} || '';
210 3 50       6 $self->{score} = $arg{-score} if exists $arg{-score};
211 3         3 $self->{start} = $arg{-start};
212 3 100       7 $self->{stop} = exists $arg{-end} ? $arg{-end} : $arg{-stop};
213 3   33     9 $self->{ref} = $arg{-seq_id} || $arg{-ref};
214 3   33     10 $self->{attributes} = $arg{-attributes} || $arg{-tag};
215 3         5 for my $option (qw(class url seq phase desc primary_id)) {
216 18 50       28 $self->{$option} = $arg{"-$option"} if exists $arg{"-$option"};
217             }
218              
219             # is_circular is needed for Bio::PrimarySeqI compliance
220 3   50     7 $self->{is_circular} = $arg{-is_circular} || 0;
221              
222             # fix start, stop
223 3 50 33     13 if (defined $self->{stop} && defined $self->{start}
      33        
224             && $self->{stop} < $self->{start}) {
225 0         0 @{$self}{'start','stop'} = @{$self}{'stop','start'};
  0         0  
  0         0  
226 0         0 $self->{strand} *= -1;
227             }
228              
229 3         3 my @segments;
230 3 50       6 if (my $s = $arg{-segments}) {
231             # NB: when $self ISA Bio::DB::SeqFeature the following invokes
232             # Bio::DB::SeqFeature::add_segment and not
233             # Bio::DB::SeqFeature::add_segment (as might be expected?)
234 0         0 $self->add_segment(@$s);
235             }
236              
237 3         7 $self;
238             }
239              
240             sub add_segment {
241 0     0 1 0 my $self = shift;
242 0   0     0 my $type = $self->{subtype} || $self->{type};
243 0   0     0 $self->{segments} ||= [];
244 0         0 my $ref = $self->seq_id;
245 0         0 my $name = $self->name;
246 0         0 my $class = $self->class;
247 0         0 my $source_tag = $self->source_tag;
248              
249 0   0     0 my $min_start = $self->start || 999_999_999_999;
250 0   0     0 my $max_stop = $self->end || -999_999_999_999;
251              
252 0         0 my @segments = @{$self->{segments}};
  0         0  
253              
254 0         0 for my $seg (@_) {
255 0 0       0 if (ref($seg) eq 'ARRAY') {
    0          
256 0         0 my ($start,$stop) = @{$seg};
  0         0  
257 0 0 0     0 next unless defined $start && defined $stop; # fixes an obscure bug somewhere above us
258 0         0 my $strand = $self->{strand};
259              
260 0 0       0 if ($start > $stop) {
261 0         0 ($start,$stop) = ($stop,$start);
262 0         0 $strand = -1;
263             }
264              
265             push @segments,$self->new(-start => $start,
266             -stop => $stop,
267             -strand => $strand,
268             -ref => $ref,
269             -type => $type,
270             -name => $name,
271             -class => $class,
272             -phase => $self->{phase},
273             -score => $self->{score},
274             -source_tag => $source_tag,
275             -attributes => $self->{attributes},
276 0         0 );
277 0 0       0 $min_start = $start if $start < $min_start;
278 0 0       0 $max_stop = $stop if $stop > $max_stop;
279              
280             } elsif (ref $seg) {
281 0         0 push @segments,$seg;
282              
283 0 0 0     0 $min_start = $seg->start if ($seg->start && $seg->start < $min_start);
284 0 0 0     0 $max_stop = $seg->end if ($seg->end && $seg->end > $max_stop);
285             }
286             }
287 0 0       0 if (@segments) {
288 0         0 local $^W = 0; # some warning of an uninitialized variable...
289 0         0 $self->{segments} = \@segments;
290 0   0     0 $self->{ref} ||= $self->{segments}[0]->seq_id;
291 0         0 $self->{start} = $min_start;
292 0         0 $self->{stop} = $max_stop;
293             }
294             }
295              
296             sub segments {
297 0     0 1 0 my $self = shift;
298 0 0       0 my $s = $self->{segments} or return wantarray ? () : 0;
    0          
299 0         0 @$s;
300             }
301             sub score {
302 0     0 0 0 my $self = shift;
303 0         0 my $d = $self->{score};
304 0 0       0 $self->{score} = shift if @_;
305 0         0 $d;
306             }
307             sub primary_tag {
308 0     0 1 0 my $self = shift;
309 0         0 my $d = $self->{type};
310 0 0       0 $self->{type} = shift if @_;
311 0         0 $d;
312             }
313             sub name {
314 0     0 1 0 my $self = shift;
315 0         0 my $d = $self->{name};
316 0 0       0 $self->{name} = shift if @_;
317 0         0 $d;
318             }
319 0     0 1 0 sub seq_id { shift->ref(@_) }
320             sub ref {
321 0     0 0 0 my $self = shift;
322 0         0 my $d = $self->{ref};
323 0 0       0 $self->{ref} = shift if @_;
324 0         0 $d;
325             }
326             sub start {
327 0     0 1 0 my $self = shift;
328 0         0 my $d = $self->{start};
329 0 0       0 $self->{start} = shift if @_;
330 0 0       0 if (my $rs = $self->{refseq}) {
331 0   0     0 my $strand = $rs->strand || 1;
332 0 0       0 return $strand >= 0 ? ($d - $rs->start + 1) : ($rs->end - $d + 1);
333             } else {
334 0         0 return $d;
335             }
336             }
337             sub end {
338 2     2 1 3 my $self = shift;
339 2         4 my $d = $self->{stop};
340 2 50       5 $self->{stop} = shift if @_;
341 2 50       4 if (my $rs = $self->{refseq}) {
342 0   0     0 my $strand = $rs->strand || 1;
343 0 0       0 return $strand >= 0 ? ($d - $rs->start + 1) : ($rs->end - $d + 1);
344             }
345 2         8 $d;
346             }
347             sub strand {
348 3     3 1 8 my $self = shift;
349 3         6 my $d = $self->{strand};
350 3 50       6 $self->{strand} = shift if @_;
351 3 50       6 if (my $rs = $self->{refseq}) {
352 0         0 my $rstrand = $rs->strand;
353 0 0       0 return 0 unless $d;
354 0 0       0 return 1 if $rstrand == $d;
355 0 0       0 return -1 if $rstrand != $d;
356             }
357 3         11 $d;
358             }
359              
360             # this does nothing, but it is here for compatibility reasons
361             sub absolute {
362 0     0 0   my $self = shift;
363 0           my $d = $self->{absolute};
364 0 0         $self->{absolute} = shift if @_;
365 0           $d;
366             }
367              
368             sub abs_start {
369 0     0 0   my $self = shift;
370 0           local $self->{refseq} = undef;
371 0           $self->start(@_);
372             }
373             sub abs_end {
374 0     0 0   my $self = shift;
375 0           local $self->{refseq} = undef;
376 0           $self->end(@_);
377             }
378             sub abs_strand {
379 0     0 0   my $self = shift;
380 0           local $self->{refseq} = undef;
381 0           $self->strand(@_);
382             }
383              
384             sub length {
385 0     0 1   my $self = shift;
386 0           return $self->end - $self->start + 1;
387             }
388              
389             #is_circular is needed for Bio::PrimarySeqI
390             sub is_circular {
391 0     0 1   my $self = shift;
392 0           my $d = $self->{is_circular};
393 0 0         $self->{is_circular} = shift if @_;
394 0           $d;
395             }
396              
397              
398             sub seq {
399 0     0 1   my $self = shift;
400 0 0         my $seq = exists $self->{seq} ? $self->{seq} : '';
401 0           return $seq;
402             }
403              
404             sub dna {
405 0     0 0   my $seq = shift->seq;
406 0 0         $seq = $seq->seq if CORE::ref($seq);
407 0           return $seq;
408             }
409              
410             =head2 display_name
411              
412             Title : display_name
413             Usage : $id = $obj->display_name or $obj->display_name($newid);
414             Function: Gets or sets the display id, also known as the common name of
415             the Seq object.
416              
417             The semantics of this is that it is the most likely string
418             to be used as an identifier of the sequence, and likely to
419             have "human" readability. The id is equivalent to the LOCUS
420             field of the GenBank/EMBL databanks and the ID field of the
421             Swissprot/sptrembl database. In fasta format, the >(\S+) is
422             presumed to be the id, though some people overload the id
423             to embed other information. Bioperl does not use any
424             embedded information in the ID field, and people are
425             encouraged to use other mechanisms (accession field for
426             example, or extending the sequence object) to solve this.
427              
428             Notice that $seq->id() maps to this function, mainly for
429             legacy/convenience issues.
430             Returns : A string
431             Args : None or a new id
432              
433              
434             =cut
435              
436 0     0 1   sub display_name { shift->name(@_) }
437              
438             *display_id = \&display_name;
439              
440             =head2 accession_number
441              
442             Title : accession_number
443             Usage : $unique_biological_key = $obj->accession_number;
444             Function: Returns the unique biological id for a sequence, commonly
445             called the accession_number. For sequences from established
446             databases, the implementors should try to use the correct
447             accession number. Notice that primary_id() provides the
448             unique id for the implementation, allowing multiple objects
449             to have the same accession number in a particular implementation.
450              
451             For sequences with no accession number, this method should return
452             "unknown".
453             Returns : A string
454             Args : None
455              
456              
457             =cut
458              
459             sub accession_number {
460 0     0 1   return 'unknown';
461             }
462              
463             =head2 alphabet
464              
465             Title : alphabet
466             Usage : if( $obj->alphabet eq 'dna' ) { /Do Something/ }
467             Function: Returns the type of sequence being one of
468             'dna', 'rna' or 'protein'. This is case sensitive.
469              
470             This is not called because this would cause
471             upgrade problems from the 0.5 and earlier Seq objects.
472              
473             Returns : a string either 'dna','rna','protein'. NB - the object must
474             make a call of the type - if there is no type specified it
475             has to guess.
476             Args : none
477             Status : Virtual
478              
479              
480             =cut
481              
482             sub alphabet{
483 0     0 1   return 'dna'; # no way this will be anything other than dna!
484             }
485              
486              
487              
488             =head2 desc
489              
490             Title : desc
491             Usage : $seqobj->desc($string) or $seqobj->desc()
492             Function: Sets or gets the description of the sequence
493             Example :
494             Returns : The description
495             Args : The description or none
496              
497              
498             =cut
499              
500             sub desc {
501 0     0 1   my $self = shift;
502 0           my ($d) = $self->notes;
503 0 0         $self->{desc} = shift if @_;
504 0           $d;
505             }
506              
507             sub attributes {
508 0     0 0   my $self = shift;
509 0 0         if (@_) {
510 0           return $self->get_tag_values(@_);
511             } else {
512 0 0         return $self->{attributes} ? %{$self->{attributes}} : ();
  0            
513             }
514             }
515              
516             sub primary_id {
517 0     0 1   my $self = shift;
518 0           my $d = $self->{primary_id};
519 0 0         $self->{primary_id} = shift if @_;
520 0           return $d;
521             # return $d if defined $d;
522             # return (overload::StrVal($self) =~ /0x([a-f0-9]+)/)[0];
523             }
524              
525             sub notes {
526 0     0 0   my $self = shift;
527 0           my $notes = $self->{desc};
528 0 0         return $notes if defined $notes;
529 0           return $self->attributes('Note');
530             }
531              
532             sub aliases {
533 0     0 0   my $self = shift;
534 0           return $self->attributes('Alias');
535             }
536              
537             sub low {
538 0     0 0   my $self = shift;
539 0 0         return $self->start < $self->end ? $self->start : $self->end;
540             }
541              
542             sub high {
543 0     0 0   my $self = shift;
544 0 0         return $self->start > $self->end ? $self->start : $self->end;
545             }
546              
547             =head2 location
548              
549             Title : location
550             Usage : my $location = $seqfeature->location()
551             Function: returns a location object suitable for identifying location
552             of feature on sequence or parent feature
553             Returns : Bio::LocationI object
554             Args : none
555              
556             =cut
557              
558             sub location {
559 0     0 1   my $self = shift;
560 0 0         require Bio::Location::Split unless Bio::Location::Split->can('new');
561 0           my $location;
562 0 0         if (my @segments = $self->segments) {
563 0           $location = Bio::Location::Split->new();
564 0           foreach (@segments) {
565 0           $location->add_sub_Location($_);
566             }
567             } else {
568 0           $location = $self;
569             }
570 0           $location;
571             }
572              
573             sub each_Location {
574 0     0 1   my $self = shift;
575 0 0         require Bio::Location::Simple unless Bio::Location::Simple->can('new');
576 0 0         if (my @segments = $self->segments) {
577             return map {
578 0           Bio::Location::Simple->new(-start => $_->start,
  0            
579             -end => $_->end,
580             -strand => $_->strand);
581             } @segments;
582             } else {
583 0           return Bio::Location::Simple->new(-start => $self->start,
584             -end => $self->end,
585             -strand => $self->strand);
586             }
587             }
588              
589             =head2 location_string
590              
591             Title : location_string
592             Usage : my $string = $seqfeature->location_string()
593             Function: Returns a location string in a format recognized by gbrowse
594             Returns : a string
595             Args : none
596              
597             This is a convenience function used by the generic genome browser. It
598             returns the location of the feature and its subfeatures in the compact
599             form "start1..end1,start2..end2,...". Use
600             $seqfeature-Elocation()-EtoFTString() to obtain a standard
601             GenBank/EMBL location representation.
602              
603             =cut
604              
605             sub location_string {
606 0     0 1   my $self = shift;
607 0 0         my @segments = $self->segments or return $self->to_FTstring;
608 0           join ',',map {$_->to_FTstring} @segments;
  0            
609             }
610              
611             sub coordinate_policy {
612 0 0   0 1   require Bio::Location::WidestCoordPolicy unless Bio::Location::WidestCoordPolicy->can('new');
613 0           return Bio::Location::WidestCoordPolicy->new();
614             }
615              
616 0     0 1   sub min_start { shift->low }
617 0     0 1   sub max_start { shift->low }
618 0     0 1   sub min_end { shift->high }
619 0     0 1   sub max_end { shift->high}
620 0     0 1   sub start_pos_type { 'EXACT' }
621 0     0 1   sub end_pos_type { 'EXACT' }
622             sub to_FTstring {
623 0     0 1   my $self = shift;
624 0           my $low = $self->min_start;
625 0           my $high = $self->max_end;
626 0           my $strand = $self->strand;
627 0 0 0       my $str = defined $strand && $strand<0 ? "complement($low..$high)" : "$low..$high";
628 0 0         if (my $id = $self->seq_id()) {
629 0           $str = $id . ":" . $str;
630             }
631 0           $str;
632             }
633             sub phase {
634 0     0 1   my $self = shift;
635 0           my $d = $self->{phase};
636 0 0         $self->{phase} = shift if @_;
637 0           $d;
638             }
639              
640             sub class {
641 0     0 0   my $self = shift;
642 0           my $d = $self->{class};
643 0 0         $self->{class} = shift if @_;
644 0 0         return defined($d) ? $d : 'Sequence'; # acedb is still haunting me - LS
645             }
646              
647             # set GFF dumping version
648             sub version {
649 0     0 0   my $self = shift;
650 0   0       my $d = $self->{gff3_version} || 2;
651 0 0         $self->{gff3_version} = shift if @_;
652 0           $d;
653             }
654              
655             sub gff_string {
656 0     0 1   my $self = shift;
657            
658 0 0         if ($self->version == 3) {
659 0           return $self->gff3_string(@_);
660             }
661            
662 0           my $recurse = shift;
663 0           my $name = $self->name;
664 0           my $class = $self->class;
665 0 0         my $group = "$class $name" if $name;
666 0           my $strand = ('-','.','+')[$self->strand+1];
667 0           my $string;
668 0 0 0       $string .= join("\t",
    0 0        
      0        
      0        
      0        
      0        
      0        
669             $self->ref||'.',$self->source||'.',$self->method||'.',
670             $self->start||'.',$self->stop||'.',
671             defined($self->score) ? $self->score : '.',
672             $strand||'.',
673             defined($self->phase) ? $self->phase : '.',
674             $group||''
675             );
676 0           $string .= "\n";
677 0 0         if ($recurse) {
678 0           foreach ($self->sub_SeqFeature) {
679 0           $string .= $_->gff_string($recurse);
680             }
681             }
682 0           $string;
683             }
684              
685             # Suggested strategy for dealing with the multiple parentage issue.
686             # First recurse through object tree and record parent tree.
687             # Then recurse again, skipping objects we've seen before.
688             sub gff3_string {
689 0     0 0   my ($self,$recurse,$parent_tree,$seenit,$force_id) = @_;
690 0   0       $parent_tree ||= {};
691 0   0       $seenit ||= {};
692 0           my @rsf = ();
693 0           my @parent_ids;
694              
695 0 0         if ($recurse) {
696 0 0         $self->_traverse($parent_tree) unless %$parent_tree; # this will record parents of all children
697 0 0         my $primary_id = defined $force_id ? $force_id : $self->_real_or_dummy_id;
698              
699 0 0         return if $seenit->{$primary_id}++;
700              
701 0           @rsf = $self->get_SeqFeatures;
702 0 0         if (@rsf) {
703             # Detect case in which we have a split location feature. In this case we
704             # skip to the grandchildren and trick them into thinking that our parent is theirs.
705 0           my %types = map {$_->primary_tag=>1} @rsf;
  0            
706 0           my @types = keys %types;
707 0 0 0       if (@types == 1 && $types[0] eq $self->primary_tag) {
708 0           return join ("\n",map {$_->gff3_string(1,$parent_tree,{},$primary_id)} @rsf);
  0            
709             }
710             }
711              
712 0           @parent_ids = keys %{$parent_tree->{$primary_id}};
  0            
713             }
714              
715 0           my $group = $self->format_attributes(\@parent_ids,$force_id);
716 0           my $name = $self->name;
717              
718 0           my $class = $self->class;
719 0           my $strand = ('-','.','+')[$self->strand+1];
720 0 0 0       my $p = join("\t",
    0 0        
      0        
      0        
      0        
      0        
      0        
721             $self->seq_id||'.',
722             $self->source||'.',
723             $self->method||'.',
724             $self->start||'.',
725             $self->stop||'.',
726             defined($self->score) ? $self->score : '.',
727             $strand||'.',
728             defined($self->phase) ? $self->phase : '.',
729             $group||'');
730             return join("\n",
731             $p,
732 0           map {$_->gff3_string(1,$parent_tree,$seenit)} @rsf);
  0            
733             }
734              
735             sub _real_or_dummy_id {
736 0     0     my $self = shift;
737 0           my $id = $self->primary_id;
738 0 0         return $id if defined $id;
739 0           return return (overload::StrVal($self) =~ /0x([a-f0-9]+)/)[0];
740             }
741              
742             sub _traverse {
743 0     0     my $self = shift;
744 0           my $tree = shift; # tree => {$child}{$parent} = 1
745 0           my $parent = shift;
746 0           my $id = $self->_real_or_dummy_id;
747 0 0         defined $id or return;
748 0 0         $tree->{$id}{$parent->_real_or_dummy_id}++ if $parent;
749 0           $_->_traverse($tree,$self) foreach $self->get_SeqFeatures;
750             }
751              
752 0     0 0   sub db { return }
753              
754             sub source_tag {
755 0     0 1   my $self = shift;
756 0           my $d = $self->{source};
757 0 0         $self->{source} = shift if @_;
758 0           $d;
759             }
760              
761             # This probably should be deleted. Not sure why it's here, but might
762             # have been added for Ace::Sequence::Feature-compliance.
763             sub introns {
764 0     0 0   my $self = shift;
765 0           return;
766             }
767              
768             sub has_tag {
769 0     0 1   my $self = shift;
770 0           my $tag = shift;
771 0           return exists $self->{attributes}{$tag};
772             }
773              
774             sub escape {
775 0     0 0   my $self = shift;
776 0           my $toencode = shift;
777 0           $toencode =~ s/([^a-zA-Z0-9_.:?^*\(\)\[\]@!+-])/uc sprintf("%%%02x",ord($1))/eg;
  0            
778 0           $toencode;
779             }
780              
781             sub all_tags {
782 0     0 0   my $self = shift;
783 0           return keys %{$self->{attributes}};
  0            
784             }
785              
786             sub add_tag_value {
787 0     0 0   my $self = shift;
788 0           my ($tag_name,@tag_values) = @_;
789 0           push @{$self->{attributes}{$tag_name}},@tag_values;
  0            
790             }
791              
792             sub remove_tag {
793 0     0 0   my $self = shift;
794 0           my $tag_name = shift;
795 0           delete $self->{attributes}{$tag_name};
796             }
797              
798             sub each_tag_value {
799 0     0 0   my $self = shift;
800 0           my $tag = shift;
801 0 0         my $value = $self->{attributes}{$tag} or return;
802 0           my $ref = CORE::ref $value;
803 0           return $ref && $ref eq 'ARRAY' ? @{$self->{attributes}{$tag}}
804 0 0 0       : $self->{attributes}{$tag};
805             }
806              
807             sub get_Annotations {
808 0     0 0   my $self = shift;
809 0           my $tag = shift;
810 0           my @values = $self->get_tag_values($tag);
811 0 0         return $values[0] if @values == 1;
812 0           return @values;
813             }
814              
815             sub format_attributes {
816 0     0 0   my $self = shift;
817 0           my $parent = shift;
818 0           my $fallback_id = shift;
819              
820 0           my @tags = $self->get_all_tags;
821 0           my @result;
822 0           for my $t (@tags) {
823 0           my @values = $self->get_tag_values($t);
824 0 0         push @result,join '=',$self->escape($t),join(',', map {$self->escape($_)} @values) if @values;
  0            
825             }
826             #my $id = $self->escape($self->_real_or_dummy_id) || $fallback_id;
827 0   0       my $id = $fallback_id || $self->escape($self->_real_or_dummy_id);
828              
829 0           my $parent_id;
830 0 0         if (@$parent) {
831 0           $parent_id = join (',',map {$self->escape($_)} @$parent);
  0            
832             }
833              
834 0           my $name = $self->display_name;
835 0 0         unshift @result,"ID=".$id if defined $id;
836 0 0         unshift @result,"Parent=".$parent_id if defined $parent_id;
837 0 0         unshift @result,"Name=".$self->escape($name) if defined $name;
838 0           return join ';',@result;
839             }
840              
841             =head2 clone
842              
843             Title : clone
844             Usage : my $feature = $seqfeature->clone
845             Function: Create a deep copy of the feature
846             Returns : A copy of the feature
847             Args : none
848              
849             =cut
850              
851             sub clone {
852 0     0 1   my $self = shift;
853 0           my %clone = %$self;
854             # overwrite attributes
855 0           my $clone = bless \%clone,CORE::ref($self);
856 0           $clone{attributes} = {};
857 0           for my $k (keys %{$self->{attributes}}) {
  0            
858 0           @{$clone{attributes}{$k}} = @{$self->{attributes}{$k}};
  0            
  0            
859             }
860 0           return $clone;
861             }
862              
863             =head2 refseq
864              
865             Title : refseq
866             Usage : $ref = $s->refseq([$newseq] [,$newseqclass])
867             Function: get/set reference sequence
868             Returns : current reference sequence
869             Args : new reference sequence and class (optional)
870             Status : Public
871              
872             This method will get or set the reference sequence. Called with no
873             arguments, it returns the current reference sequence. Called with any
874             Bio::SeqFeatureI object that provides the seq_id(), start(), end() and
875             strand() methods.
876              
877             The method will generate an exception if you attempt to set the
878             reference sequence to a sequence that has a different seq_id from the
879             current feature.
880              
881             =cut
882              
883             sub refseq {
884 0     0 1   my $self = shift;
885 0           my $d = $self->{refseq};
886 0 0         if (@_) {
887 0           my $newref = shift;
888 0 0         $self->throw("attempt to set refseq using a feature that does not share the same seq_id")
889             unless $newref->seq_id eq $self->seq_id;
890 0           $self->{refseq} = $newref;
891             }
892 0           return $d;
893             }
894              
895       0     sub DESTROY { }
896              
897             1;
898              
899             __END__