File Coverage

blib/lib/Bio/Das/Request/Features.pm
Criterion Covered Total %
statement 177 200 88.5
branch 65 114 57.0
condition 13 33 39.3
subroutine 32 37 86.4
pod 4 29 13.7
total 291 413 70.4


line stmt bran cond sub pod time code
1             package Bio::Das::Request::Features;
2             # $Id: Features.pm,v 1.16 2010/06/16 21:28:41 lstein Exp $
3             # this module issues and parses the types command, with arguments -dsn, -segment, -categories, -enumerate
4              
5 1     1   5 use strict;
  1         2  
  1         30  
6 1     1   5 use Bio::Das::Type;
  1         2  
  1         17  
7 1     1   546 use Bio::Das::Feature;
  1         3  
  1         40  
8 1     1   14 use Bio::Das::Segment;
  1         1  
  1         20  
9 1     1   5 use Bio::Das::Request;
  1         2  
  1         25  
10 1     1   6 use Bio::Das::Util 'rearrange';
  1         2  
  1         60  
11              
12 1     1   5 use vars '@ISA';
  1         2  
  1         2131  
13             @ISA = 'Bio::Das::Request';
14              
15             sub new {
16 2     2 1 3 my $pack = shift;
17 2         18 my ($dsn,$segments,$types,$categories,$feature_id,$group_id,$das,$fcallback,$scallback)
18             = rearrange([
19             ['dsn','dsns'],
20             ['segment','segments'],
21             ['type','types'],
22             ['category','categories'],
23             'feature_id',
24             'group_id',
25             'das',
26             ['callback','feature_callback'],
27             'segment_callback',
28             ],@_);
29 2         30 my $self = $pack->SUPER::new(
30             -dsn => $dsn,
31             -callback => $fcallback,
32             -args => {
33             segment => $segments,
34             category => $categories,
35             type => $types,
36             feature_id => $feature_id,
37             group_id => $group_id,
38             }
39             );
40 2 50       9 $self->{segment_callback} = $scallback if $scallback;
41 2 50       10 $self->das($das) if defined $das;
42 2         9 $self;
43             }
44              
45 61     61 1 133 sub command { 'features' }
46              
47             sub das {
48 5     5 0 12 my $self = shift;
49 5         99 my $d = $self->{das};
50 5 100       15 $self->{das} = shift if @_;
51 5         21 $d;
52             }
53              
54 3     3 0 12 sub segment_callback { shift->{segment_callback} }
55              
56             sub t_DASGFF {
57 4     4 0 8 my $self = shift;
58 4         9 my $attrs = shift;
59 4 100       15 if ($attrs) {
60 2         14 $self->clear_results;
61             }
62 4         48 delete $self->{tmp};
63             }
64              
65 4     4 0 25 sub t_GFF {
66             # nothing to do here -- probably should check version
67             }
68              
69             sub t_SEGMENT {
70 6     6 0 12 my $self = shift;
71 6         10 my $attrs = shift;
72 6 100       18 if ($attrs) { # segment section is starting
73 3         58 $self->{tmp}{current_segment} = Bio::Das::Segment->new($attrs->{id},$attrs->{start},
74             $attrs->{stop},$attrs->{version},
75             $self->das,$self->dsn
76             );
77 3         12 $self->{tmp}{current_feature} = undef;
78 3         30 $self->{tmp}{features} = [];
79             }
80              
81             else { # reached the end of the segment, so push result
82 3         13 $self->finish_segment();
83             }
84              
85             }
86              
87             sub finish_segment {
88 3     3 0 4 my $self = shift;
89              
90 3         13 $self->infer_parents_from_groups($self->{tmp}{features});
91 3         17 my $features = $self->build_object_hierarchy($self->{tmp}{features});
92              
93 3 50       15 if ($self->segment_callback) {
94 0         0 eval {$self->segment_callback->($self->{tmp}{current_segment}=>$features)};
  0         0  
95 0 0       0 warn $@ if $@;
96             } else {
97 3         17 $self->add_object($self->{tmp}{current_segment},$features);
98             }
99 3         11 delete $self->{tmp}{current_segment};
100 3         64 delete $self->{tmp}{features};
101             }
102              
103             # for features that have a <group> but no parent or parts,
104             # create inferred parents
105             sub infer_parents_from_groups {
106 3     3 0 4 my $self = shift;
107 3         4 my $f = shift;
108              
109 3         4 my (%inferred_parents,%group_types);
110 3         8 for my $feature (@$f) {
111              
112 204 50       411 my $group = $feature->group or next;
113 204 50       428 next if $feature->parent_id;
114 204 50       392 next if $feature->child_ids > 0;
115              
116 204         333 $group = "group_$group"; # avoid collisions
117              
118 204 100       541 unless ($inferred_parents{$group}) {
119 63         167 my $p = $inferred_parents{$group} = Bio::Das::Feature->new(
120             -segment => $feature->segment,
121             -id => $group,
122             -start => $feature->start,
123             -stop => $feature->stop
124             );
125 63         205 $p->orientation($feature->orientation);
126 63         179 $p->category('group');
127 63   33     127 my $gt = $feature->group_type || $feature->type;
128 63   66     195 my $type = $group_types{$gt}
129             ||= Bio::Das::Type->new($gt,$gt,'group');
130 63         155 $p->type($type);
131 63         133 $p->link($feature->link);
132 63         142 $p->label($feature->label);
133             }
134              
135 204         347 my $p = $inferred_parents{$group};
136 204 50       442 $p->start($feature->start) if $feature->start < $p->start;
137 204 100       482 $p->stop($feature->stop) if $feature->stop > $p->stop;
138 204         491 $feature->parent_id($group);
139 204         394 $p->add_child_id($feature->id);
140             }
141 3         51 push @$f,values %inferred_parents;
142             }
143              
144              
145             # this builds up hierarchical objects using their parent/child relationships
146             sub build_object_hierarchy {
147 3     3 0 6 my $self = shift;
148 3         4 my $f = shift;
149 3         9 my %id_to_feature = map {$_->id => $_} @$f;
  267         550  
150              
151 3         22 my @top_level;
152 3         10 for my $feature (@$f) {
153 267         666 my $parent_id = $feature->parent_id;
154 267 100 66     984 if (defined $parent_id
155             && (my $parent = $id_to_feature{$parent_id})) {
156 204         441 $parent->add_subfeature($feature);
157             } else {
158 63         354 push @top_level,$feature;
159             }
160             }
161 3         108 return \@top_level;
162             }
163              
164             sub cleanup {
165 2     2 1 4 my $self = shift;
166             # this fixes a problem in the UCSC server
167 2 50       11 $self->finish_segment if $self->{tmp}{current_segment};
168             }
169              
170             sub add_object {
171 3     3 0 6 my $self = shift;
172 3         4 push @{$self->{results}},@_;
  3         11  
173             }
174              
175              
176             # do nothing
177 0     0 0 0 sub t_UNKNOWNSEGMENT { }
178 0     0 0 0 sub t_ERRORSEGMENT { }
179              
180             sub t_FEATURE {
181 408     408 0 475 my $self = shift;
182 408         439 my $attrs = shift;
183              
184 408 100       614 if ($attrs) { # start of tag
185 204         827 my $feature = $self->{tmp}{current_feature} = Bio::Das::Feature->new($self->{tmp}{current_segment},
186             $attrs->{id}
187             );
188 204 50       501 $feature->label($attrs->{label}) if exists $attrs->{label};
189 204         1679 $self->{tmp}{type} = undef;
190             }
191              
192             else {
193             # feature is ending. This would be the place to do group aggregation
194 204         355 my $feature = $self->{tmp}{current_feature};
195 204         503 my $cft = $feature->type;
196              
197 204 50       606 if (!$cft->complete) {
198             # fix up broken das servers that don't set a method
199             # the id and method will be set to the same value
200 0 0 0     0 $cft->id($cft->method) if $cft->method && !$cft->id;
201 0 0 0     0 $cft->method($cft->id) if $cft->id && !$cft->method;
202             }
203              
204 204 50       548 if (my $callback = $self->callback) {
205 0         0 $callback->($feature);
206             } else {
207 204         200 push @{$self->{tmp}{features}},$feature;
  204         1232  
208             }
209             }
210             }
211              
212             sub t_TYPE {
213 408     408 0 493 my $self = shift;
214 408         439 my $attrs = shift;
215 408 50       1545 my $feature = $self->{tmp}{current_feature} or return;
216              
217 408   66     1702 my $cft = $self->{tmp}{type} ||= Bio::Das::Type->new();
218              
219 408 100       853 if ($attrs) { # tag starts
220 204         568 $cft->id($attrs->{id});
221 204 50       776 $cft->category($attrs->{category}) if $attrs->{category};
222 204 50 33     620 $cft->reference(1) if $attrs->{reference} && $attrs->{reference} eq 'yes';
223 204 50 33     521 $cft->has_subparts(1) if $attrs->{subparts} && $attrs->{subparts} eq 'yes';
224 204 50 33     1595 $cft->has_superparts(1) if $attrs->{superparts} && $attrs->{superparts} eq 'yes';
225             } else {
226              
227             # possibly add a label
228 204 50       466 if (my $label = $self->char_data) {
229 0         0 $cft->label($label);
230             }
231              
232 204         440 my $type = $self->_cache_types($cft);
233 204         655 $feature->type($type);
234             }
235             }
236              
237             sub t_METHOD {
238 408     408 0 516 my $self = shift;
239 408         460 my $attrs = shift;
240 408 50       1234 my $feature = $self->{tmp}{current_feature} or return;
241 408   33     1260 my $cft = $self->{tmp}{type} ||= Bio::Das::Type->new();
242              
243 408 100       754 if ($attrs) { # tag starts
244 204         575 $cft->method($attrs->{id});
245             }
246              
247             else { # tag ends
248              
249             # possibly add a label
250 204 50       477 if (my $label = $self->char_data) {
251 204         557 $cft->method_label($label);
252             }
253              
254 204 50       474 if ($cft->complete) {
255 204         409 my $type = $self->_cache_types($cft);
256 204         616 $feature->type($type);
257             }
258              
259             }
260             }
261              
262             sub t_PARENT {
263 0     0 0 0 my $self = shift;
264 0         0 my $attrs = shift;
265 0 0       0 my $feature = $self->{tmp}{current_feature} or return;
266 0 0       0 $feature->parent_id($attrs->{id}) if $attrs;
267             }
268              
269             sub t_PART {
270 0     0 0 0 my $self = shift;
271 0         0 my $attrs = shift;
272 0 0       0 my $feature = $self->{tmp}{current_feature} or return;
273 0 0       0 $feature->add_child_id($attrs->{id}) if $attrs;
274             }
275              
276             sub t_START {
277 408     408 0 472 my $self = shift;
278 408         451 my $attrs = shift;
279 408 50       2190 my $feature = $self->{tmp}{current_feature} or return;
280 408 100       2161 $feature->start($self->char_data) unless $attrs;
281             }
282              
283             sub t_END {
284 408     408 0 525 my $self = shift;
285 408         440 my $attrs = shift;
286 408 50       1227 my $feature = $self->{tmp}{current_feature} or return;
287 408 100       1964 $feature->stop($self->char_data) unless $attrs;
288             }
289              
290             sub t_SCORE {
291 408     408 0 476 my $self = shift;
292 408         410 my $attrs = shift;
293 408 50       1177 my $feature = $self->{tmp}{current_feature} or return;
294 408 100       1991 $feature->score($self->char_data) unless $attrs;
295             }
296              
297             sub t_ORIENTATION {
298 408     408 0 513 my $self = shift;
299 408         454 my $attrs = shift;
300 408 50       1171 my $feature = $self->{tmp}{current_feature} or return;
301 408 100       2197 $feature->orientation($self->char_data) unless $attrs;
302             }
303              
304             sub t_PHASE {
305 408     408 0 472 my $self = shift;
306 408         429 my $attrs = shift;
307 408 50       1188 my $feature = $self->{tmp}{current_feature} or return;
308 408 100       1972 $feature->phase($self->char_data) unless $attrs;
309             }
310              
311             sub t_GROUP {
312 408     408 0 461 my $self = shift;
313 408         401 my $attrs = shift;
314 408 50       1125 my $feature = $self->{tmp}{current_feature} or return;
315 408 100       1643 if($attrs) {
316 204         566 $feature->group_label( $attrs->{label} );
317 204         573 $feature->group_type( $attrs->{type} );
318 204         492 $feature->group( $attrs->{id} );
319             }
320             }
321              
322             sub t_LINK {
323 408     408 0 515 my $self = shift;
324 408         408 my $attrs = shift;
325 408 50       1194 my $feature = $self->{tmp}{current_feature} or return;
326 408 100       816 if($attrs) {
327 204         591 $feature->link( $attrs->{href} );
328             } else {
329 204         518 $feature->link_label( $self->char_data );
330             }
331             }
332              
333             sub t_NOTE {
334 0     0 0 0 my $self = shift;
335 0         0 my $attrs = shift;
336 0 0       0 my $feature = $self->{tmp}{current_feature} or return;
337 0 0       0 if ($attrs) {
338 0 0       0 $self->{tmp}{note_tag} = $attrs->{tag} if exists $attrs->{tag};
339             } else {
340 0         0 $feature->add_note($self->{tmp}{note_tag},$self->char_data);
341             }
342             }
343              
344             sub t_TARGET {
345 340     340 0 397 my $self = shift;
346 340         368 my $attrs = shift;
347 340 50       966 my $feature = $self->{tmp}{current_feature} or return;
348 340 100       676 if($attrs){
349 170         630 $feature->target($attrs->{id},$attrs->{start},$attrs->{stop});
350             } else {
351 170         405 $feature->target_label($self->char_data());
352             }
353             }
354              
355             sub _cache_types {
356 408     408   452 my $self = shift;
357 408         454 my $type = shift;
358 408         883 my $key = $type->_key;
359 408   66     1603 return $self->{cached_types}{$key} ||= $type;
360             }
361              
362             # override for segmentation behavior
363             sub results {
364 3     3 1 6 my $self = shift;
365 3 50       17 my %r = $self->SUPER::results or return;
366              
367             # in array context, return the list of types
368 3 100       12 return map { @{$_} } values %r if wantarray;
  1         1  
  1         9  
369              
370             # otherwise return ref to a hash
371 2         6 return \%r;
372             }
373              
374              
375             1;