File Coverage

blib/lib/PomBase/Chobo/OntologyData.pm
Criterion Covered Total %
statement 131 143 91.6
branch 27 38 71.0
condition 6 11 54.5
subroutine 17 17 100.0
pod 2 12 16.6
total 183 221 82.8


line stmt bran cond sub pod time code
1             package PomBase::Chobo::OntologyData;
2              
3             =head1 NAME
4              
5             PomBase::Chobo::OntologyData - An in memory representation of an Ontology
6              
7             =head1 SYNOPSIS
8              
9             Objects of this class represent the part of an ontology that can be stored in
10             a Chado database.
11              
12             =head1 AUTHOR
13              
14             Kim Rutherford C<< >>
15              
16             =head1 BUGS
17              
18             Please report any bugs or feature requests to C.
19              
20             =head1 SUPPORT
21              
22             You can find documentation for this module with the perldoc command.
23              
24             perldoc PomBase::Chobo::OntologyData
25              
26             =over 4
27              
28             =back
29              
30             =head1 COPYRIGHT & LICENSE
31              
32             Copyright 2012 Kim Rutherford, all rights reserved.
33              
34             This program is free software; you can redistribute it and/or modify it
35             under the same terms as Perl itself.
36              
37             =head1 FUNCTIONS
38              
39             =cut
40              
41             our $VERSION = '0.039'; # VERSION
42              
43 4     4   82249 use Mouse;
  4         28887  
  4         24  
44              
45 4     4   3378 use Clone qw(clone);
  4         9846  
  4         241  
46 4     4   1714 use Try::Tiny;
  4         6496  
  4         223  
47 4     4   28 use Carp;
  4         11  
  4         191  
48              
49 4     4   2003 use PomBase::Chobo::OntologyTerm;
  4         14  
  4         8183  
50              
51              
52             has terms_by_id => (is => 'rw', init_arg => undef, isa => 'HashRef',
53             default => sub { {} });
54             has terms_by_name => (is => 'rw', init_arg => undef, isa => 'HashRef',
55             default => sub { {} });
56             has terms_by_cv_name => (is => 'rw', init_arg => undef, isa => 'HashRef',
57             default => sub { {} });
58             has relationship_terms_by_cv_name => (is => 'rw', init_arg => undef, isa => 'HashRef',
59             default => sub { {} });
60             has terms_by_db_name => (is => 'rw', init_arg => undef, isa => 'HashRef',
61             default => sub { {} });
62             has metadata_by_namespace => (is => 'rw', init_arg => undef, isa => 'HashRef',
63             default => sub { {} });
64             has _term_relationships => (is => 'rw', init_arg => undef, isa => 'HashRef',
65             default => sub { {} });
66              
67             =head2 add
68              
69             Usage : $ontology_data->add(metadata => {..}, terms => [...]);
70             Function: Add some terms, often all terms from one OBO file
71             Args : metadata - the metadata for the terms
72             terms - an array of OntologyTerm objects
73             Return : Nothing, dies on error
74              
75             =cut
76              
77             sub add
78             {
79 11     11 1 23 my $self = shift;
80              
81 11         43 my %args = @_;
82              
83 11         28 my $metadata = $args{metadata};
84 11         23 my $terms = $args{terms};
85              
86 11         44 my $terms_by_id = $self->terms_by_id();
87 11         32 my $terms_by_name = $self->terms_by_name();
88 11         23 my $terms_by_cv_name = $self->terms_by_cv_name();
89 11         33 my $relationship_terms_by_cv_name = $self->relationship_terms_by_cv_name();
90              
91 11         28 my $metadata_by_namespace = $self->metadata_by_namespace();
92              
93 11         32 for my $term (@$terms) {
94 72         202 my @new_term_ids = ($term->{id});
95              
96 72         189 push @new_term_ids, map { $_->{id}; } $term->alt_ids();
  19         45  
97              
98 72         144 my @found_existing_terms = ();
99              
100 72         122 for my $id (@new_term_ids) {
101 91         163 my $existing_term = $terms_by_id->{$id};
102              
103 91 100       204 if (defined $existing_term) {
104 6 100       24 if (!grep { $_ == $existing_term } @found_existing_terms) {
  2         8  
105 4         12 push @found_existing_terms, $existing_term;
106             }
107             }
108             }
109              
110 72 50       160 if (@found_existing_terms > 1) {
111 0         0 die "two previously read terms match an alt_id field from:\n" .
112             $term->to_string() . "\n\nmatching term 1:\n" .
113             $found_existing_terms[0]->to_string() . "\n\nmatching term 2:\n" .
114             $found_existing_terms[1]->to_string() . "\n";
115             } else {
116 72 100       145 if (@found_existing_terms == 1) {
117 4         10 my $existing_term = $found_existing_terms[0];
118              
119 4 50 33     48 if (!$term->is_obsolete() && !$existing_term->is_obsolete()) {
120 4         15 my $old_namespace = $existing_term->namespace();
121              
122 4         21 $existing_term->merge($term);
123              
124 4 100       29 if ($old_namespace ne $existing_term->namespace()) {
125 2         11 delete $self->terms_by_cv_name()->{$old_namespace}->{$existing_term->name()};
126             }
127              
128 4         11 $term = $existing_term;
129             }
130             }
131             }
132              
133 72         178 for my $id_details ($term->alt_ids(),
134             { id => $term->{id},
135             db_name => $term->{db_name},
136             accession => $term->{accession},
137             } ) {
138 93         333 $terms_by_id->{$id_details->{id}} = $term;
139              
140 93         388 $self->terms_by_db_name()->{$id_details->{db_name}}->{$id_details->{accession}} = $term;
141             }
142              
143 72         238 my $def = $term->def();
144              
145             map {
146 60         79 my $def_dbxref = $_;
147 60 50       253 if ($def_dbxref =~ /^(.+?):(.*)/) {
148 60         186 my ($def_db_name, $def_accession) = ($1, $2);
149 60         266 $self->terms_by_db_name()->{$def_db_name}->{$def_accession} = $term;
150             } else {
151 0         0 die qq(can't parse dbxref from "def:" line: $def_dbxref);
152             }
153 72         104 } @{$def->{dbxrefs}};
  72         151  
154              
155             map {
156 72         190 my $xref = $_;
  13         34  
157              
158 13 50       77 if ($xref =~ /^(.+?):(.*)/) {
159 13         45 my ($def_db_name, $def_accession) = ($1, $2);
160 13         99 $self->terms_by_db_name()->{$def_db_name}->{$def_accession} = $term;
161             } else {
162 0         0 die qq(can't parse "xref:" line: $xref);
163             }
164             } $term->xrefs();
165              
166 72         146 my $name = $term->{name};
167              
168 72 50       135 if (defined $name) {
169 72 100 66     266 if (!exists $terms_by_name->{$name} ||
170 4         23 !grep { $_ == $term } @{$terms_by_name->{$name}}) {
  4         11  
171 68         93 push @{$terms_by_name->{$name}}, $term;
  68         241  
172             }
173             } else {
174 0         0 warn "term without a name tag ignored:\n", $term->to_string(), "\n\n";
175 0         0 next;
176             }
177              
178 72         190 my $term_namespace = $term->namespace();
179              
180 72 50       145 if (defined $term_namespace) {
181 72         170 my $existing_term_by_name = $terms_by_cv_name->{$term_namespace}->{$name};
182 72 50 66     200 if ($existing_term_by_name && $existing_term_by_name != $term) {
183             warn qq(more than one Term with the name "$name" in namespace "$term_namespace" -\n) .
184             "existing:\n" . $term->to_string() . "\n\nand:\n" .
185 0         0 $terms_by_cv_name->{$term_namespace}->{$name}->to_string() . "\n\n";
186             } else {
187 72         163 $terms_by_cv_name->{$term_namespace}->{$name} = $term;
188             }
189              
190 72 100       154 if ($term->{is_relationshiptype}) {
191 10         23 $relationship_terms_by_cv_name->{$term_namespace}->{$name} = $term;
192             }
193              
194 72 100       162 if (!exists $metadata_by_namespace->{$term_namespace}) {
195 12         214 $metadata_by_namespace->{$term_namespace} = clone $metadata;
196             }
197             }
198              
199 72 50       189 if ($term->{relationship}) {
200 72         151 for my $rel (@{$term->{relationship}}) {
  72         203  
201             my $key = $term->{id} . '<' . $rel->{relationship_name} .
202 64         204 '>' . $rel->{other_term};
203 64         319 $self->_term_relationships()->{$key} = 1;
204             }
205             }
206             }
207             }
208              
209             sub get_terms_by_name
210             {
211 1     1 0 3 my $self = shift;
212 1         4 my $name = shift;
213              
214 1   50     3 return @{$self->terms_by_name()->{$name} // []};
  1         9  
215             }
216              
217             sub get_term_by_id
218             {
219 35     35 0 17252 my $self = shift;
220 35         50 my $id = shift;
221              
222 35         102 return $self->terms_by_id()->{$id};
223             }
224              
225             sub get_cv_names
226             {
227 22     22 0 39 my $self = shift;
228              
229 22         36 return keys %{$self->terms_by_cv_name()};
  22         113  
230             }
231              
232             sub get_terms_by_cv_name
233             {
234 21     21 0 1443 my $self = shift;
235 21         43 my $cv_name = shift;
236              
237 21         32 return values %{$self->terms_by_cv_name()->{$cv_name}};
  21         178  
238             }
239              
240             sub get_db_names
241             {
242 5     5 0 1393 my $self = shift;
243              
244 5         9 return keys %{$self->terms_by_db_name()};
  5         46  
245             }
246              
247             sub accessions_by_db_name
248             {
249 10     10 0 15 my $self = shift;
250 10         19 my $db_name = shift;
251              
252 10         13 return sort keys %{$self->terms_by_db_name()->{$db_name}};
  10         53  
253             }
254              
255             sub get_terms
256             {
257 19     19 0 742 my $self = shift;
258              
259 19         48 return map { $self->get_terms_by_cv_name($_); } $self->get_cv_names();
  20         51  
260             }
261              
262             sub get_namespaces
263             {
264 3     3 0 3579 my $self = shift;
265              
266 3         5 return keys %{$self->metadata_by_namespace()};
  3         20  
267             }
268              
269             sub get_metadata_by_namespace
270             {
271 3     3 0 11 my $self = shift;
272 3         9 my $namespace = shift;
273              
274 3         26 return $self->metadata_by_namespace()->{$namespace};
275             }
276              
277             sub relationships
278             {
279 6     6 0 1681 my $self = shift;
280              
281 6 100       46 if ($self->{_relationships}) {
282 2         4 return @{$self->{_relationships}}
  2         21  
283             }
284              
285             $self->{_relationships} = [map {
286 32         175 my ($subject_id, $rel_name, $object_id) = /(.*)<(.*)>(.*)/;
287              
288 32         75 my $object_term = $self->get_term_by_id($object_id);
289              
290 32 50       70 if (!$object_term) {
291 0         0 my $subject_term = $self->get_term_by_id($subject_id);
292             warn qq(ignoring relation where object isn't defined: "$object_id" line ) .
293             $subject_term->{source_file_line_number} . ' of ' .
294 0         0 $subject_term->{source_file} . "\n";
295 0         0 ();
296             } else {
297 32         100 [$subject_id, $rel_name, $object_id];
298             }
299 4         17 } sort keys %{$self->_term_relationships()}];
  4         42  
300              
301 4         15 return @{$self->{_relationships}};
  4         19  
302             }
303              
304             =head2 finish
305              
306             Usage : $self->finish();
307             Function: remove namespaces that are empty due to merging and check that
308             objects and subjects of relationships exist
309              
310             =cut
311              
312             sub finish
313             {
314 3     3 1 13 my $self = shift;
315              
316 3         24 my @relationships = $self->relationships();
317              
318 3 50       12 if (@relationships == 0) {
319 0         0 warn "note: no relationships read\n";
320             }
321              
322             # find and remove namespaces that are empty due to merging
323             my @empty_namespaces =
324             map {
325 4 50       8 if (scalar(keys %{$self->terms_by_cv_name()->{$_}}) == 0) {
  4         27  
326 0         0 $_;
327             } else {
328 4         13 ();
329             }
330 3         7 } keys %{$self->terms_by_cv_name()};
  3         12  
331              
332             map {
333 3         12 delete $self->terms_by_cv_name()->{$_};
  0            
334             } @empty_namespaces;
335             }
336              
337             1;