File Coverage

blib/lib/PomBase/Chobo/OntologyTerm.pm
Criterion Covered Total %
statement 138 149 92.6
branch 50 60 83.3
condition 34 46 73.9
subroutine 16 16 100.0
pod 2 7 28.5
total 240 278 86.3


line stmt bran cond sub pod time code
1             package PomBase::Chobo::OntologyTerm;
2              
3             =head1 NAME
4              
5             PomBase::Chobo::OntologyTerm - Code for holding term data read from an OBO file
6              
7             =head1 SYNOPSIS
8              
9             =head1 AUTHOR
10              
11             Kim Rutherford C<< >>
12              
13             =head1 BUGS
14              
15             Please report any bugs or feature requests to C.
16              
17             =head1 SUPPORT
18              
19             You can find documentation for this module with the perldoc command.
20              
21             perldoc PomBase::Chobo::OntologyTerm
22              
23             =over 4
24              
25             =back
26              
27             =head1 COPYRIGHT & LICENSE
28              
29             Copyright 2012 Kim Rutherford, all rights reserved.
30              
31             This program is free software; you can redistribute it and/or modify it
32             under the same terms as Perl itself.
33              
34             =head1 FUNCTIONS
35              
36             =cut
37              
38             our $VERSION = '0.037'; # VERSION
39              
40 5     5   67630 use Mouse;
  5         23041  
  5         29  
41 5     5   1678 use Carp;
  5         9  
  5         268  
42              
43 5     5   1954 use PomBase::Chobo::OntologyConf;
  5         19  
  5         157  
44              
45 5     5   347 use Clone qw(clone);
  5         2037  
  5         202  
46 5     5   2035 use Data::Compare;
  5         47424  
  5         31  
47 5     5   19618 use List::Compare;
  5         95201  
  5         1258  
48              
49             has id => (is => 'ro', isa => 'Int', required => 1);
50             has cvterm_id => (is => 'ro', isa => 'Int', required => 0);
51             has cv_id => (is => 'ro', isa => 'Int', required => 0);
52             has name => (is => 'ro', isa => 'Str');
53             has def => (is => 'ro', isa => 'Str');
54             has namespace => (is => 'ro', isa => 'Str');
55             has comment => (is => 'ro', isa => 'Str');
56             has alt_id => (is => 'ro', isa => 'ArrayRef');
57             has subset => (is => 'ro', isa => 'ArrayRef');
58             has is_relationshiptype => (is => 'ro', isa => 'Bool');
59             has is_obsolete => (is => 'ro', isa => 'Bool');
60             has replaced_by => (is => 'ro', isa => 'Str');
61             has consider => (is => 'ro', isa => 'Str');
62             has property_value => (is => 'ro', isa => 'ArrayRef');
63             has source_file => (is => 'ro', isa => 'Str', required => 1);
64             has source_file_line_number => (is => 'ro', isa => 'Str', required => 1);
65             has metadata => (is => 'ro');
66              
67             our @field_names;
68             our %field_conf;
69              
70             BEGIN {
71 5     5   77 %field_conf = %PomBase::Chobo::OntologyConf::field_conf;
72 5         18 @field_names = qw(id name);
73              
74 5 100       25 for my $field_name (sort grep { $_ ne 'id' && $_ ne 'name' } keys %field_conf) {
  75         264  
75 65         6838 push @field_names, $field_name;
76             }
77             }
78              
79             sub synonyms
80             {
81 6     6 0 11 my $self = shift;
82              
83 6   100     9 return @{$self->{synonym} // []};
  6         46  
84             }
85              
86             sub alt_ids
87             {
88 149     149 0 197 my $self = shift;
89              
90             return map {
91 41         89 my $val = $_;
92              
93 41 100       1430 if ($val =~ /(\S+):(\S+)/) {
94             {
95 37         272 id => $val,
96             db_name => $1,
97             accession => $2,
98             };
99             } else {
100 4         6 my $db_name;
101 4 50 33     37 if (defined $self->metadata()->{ontology} &&
102             $self->metadata()->{ontology} eq 'ro') {
103 0         0 $db_name = 'OBO_REL'
104             } else {
105 4         16 $db_name = '_global';
106             }
107             {
108 4         26 id => $val,
109             db_name => $db_name,
110             accession => $val,
111             };
112             }
113 149   50     181 } @{$self->{alt_id} // []};
  149         567  
114             }
115              
116             sub property_values
117             {
118 6     6 0 1036 my $self = shift;
119              
120 6   100     10 return @{$self->{property_value} // []};
  6         38  
121             }
122              
123             sub subsets
124             {
125 2     2 0 2501 my $self = shift;
126              
127 2   100     5 return @{$self->{subset} // []};
  2         30  
128             }
129              
130             =head2 make_object
131              
132             Usage : my $object = PomBase::Chobo::OntologyTerm->make_object($args);
133             Function: Turn $args into an OntologyTerm
134              
135             =cut
136              
137             sub make_object
138             {
139 79     79 1 9420 my $class = shift;
140 79         100 my $object = shift;
141 79         95 my $options = shift;
142              
143 79 50       148 if (!defined $object) {
144 0         0 croak "no argument passed to new()";
145             }
146              
147 79 100 66     302 if ($object->{def} && $object->{def}->{dbxrefs} && $object->{alt_id}) {
      66        
148 10         15 for my $alt_id (@{$object->{alt_id}}) {
  10         33  
149             # filter alt_ids from the definition xrefs to avoid:
150             # duplicate key value violates unique constraint "cvterm_dbxref_c1"
151             # see also: https://github.com/kimrutherford/go-ontology/commit/92dca313a69ffb073c226b94242faa8f321efcf2
152 15         47 @{$object->{def}->{dbxrefs}} =
153             grep {
154 11         15 my $xref = $_;
155 11         84 $alt_id ne $xref;
156 15         49 } @{$object->{def}->{dbxrefs}};
  15         31  
157             }
158             }
159              
160 79 100 66     195 if ($object->{is_obsolete} && $object->{name} && $object->{name} !~ /^obsolete/i) {
      66        
161 2         12 $object->{name} = "OBSOLETE " . $object->{id} . " " . $object->{name};
162             }
163              
164 79 50 66     164 if ($object->{is_relationshiptype} && $object->{name}) {
165 10         23 $object->{name} =~ s/ /_/g;
166             }
167              
168 79         116 $object->{_namespace_from_metadata} = 0;
169              
170 79 100       139 if ($options) {
171 72 100       128 if ($options->{namespace_from_metadata}) {
172 34         52 $object->{_namespace_from_metadata} = 1;
173             }
174             }
175              
176 79   100     242 $object->{alt_id} //= [];
177              
178 79         123 my ($db_name, $accession);
179              
180 79 100       457 unless (($db_name, $accession) = $object->{id} =~ /^(\S+):(.+?)\s*$/) {
181 11 50       27 if ($object->{id} eq 'part_of') {
182             # special case to make sure all the part_of terms are merged - the "part_of"
183             # in the GO and FYPO OBO files has the namespace "external" (and a variety of
184             # others) and the ID is "part_of"
185             # we normalise the id and namespace to match RO
186 0         0 $db_name = 'BFO';
187 0         0 $accession = '0000050';
188              
189 0         0 $object->{id} = "$db_name:$accession";
190 0         0 $object->{namespace} = "relationship";
191             } else {
192 11         19 $db_name = '_global';
193 11         16 $accession = $object->{id};
194             }
195             }
196              
197 79         160 $object->{accession} = $accession;
198 79         144 $object->{db_name} = $db_name;
199              
200 79 50       141 if (!defined $object->{source_file}) {
201 0         0 confess "source_file attribute of object is required\n";
202             }
203              
204 79 50       153 if (!defined $object->{source_file_line_number}) {
205 0         0 confess "source_file_line attribute of object is required\n";
206             }
207              
208 79         213 return bless $object, $class;
209             }
210              
211             =head2 merge
212              
213             Usage : my $merged_term = $term->merge($other_term);
214             Function: Attempt to merge $other_term into this term. Only merges if at least
215             one of the ID or alt_ids from this term match the ID or an alt_id
216             from $other_term
217             Args : $other_term - the term to merge with
218             Return : undef - if no id from this term matches one from $other_term
219             $self - if there is a match
220             =cut
221              
222             sub merge
223             {
224 14     14 1 3477 my $self = shift;
225 14         23 my $other_term = shift;
226              
227 14 100       40 return if $self == $other_term;
228              
229 13         36 my $lc = List::Compare->new([$self->{id}, @{$self->{alt_id}}],
230 13         28 [$other_term->{id}, @{$other_term->{alt_id}}]);
  13         89  
231              
232 13 50       1636 if (scalar($lc->get_intersection()) == 0) {
233 0         0 return undef;
234             }
235              
236 13         187 my @new_alt_id = List::Compare->new([$lc->get_union()], [$self->id()])->get_unique(1);
237              
238 13         1646 $self->{alt_id} = \@new_alt_id;
239              
240             my $merge_field = sub {
241 26     26   36 my $name = shift;
242 26         59 my $other_term = shift;
243              
244 26         46 my $field_conf = $PomBase::Chobo::OntologyConf::field_conf{$name};
245              
246 26 50       45 if (defined $field_conf) {
247 26 100 100     124 if (defined $field_conf->{type} &&
      66        
248             ($field_conf->{type} eq 'SINGLE' || $field_conf->{type} eq 'SINGLE_HASH')) {
249 15         25 my $res = undef;
250 15 100       40 if (defined $field_conf->{merge}) {
251 7         25 $res = $field_conf->{merge}->($self, $other_term);
252             }
253              
254 15 100       41 if (defined $res) {
255 1         5 $self->{$name} = $res;
256             } else {
257 14         23 my $new_field_value = $other_term->{$name};
258              
259 14 100       33 if (defined $new_field_value) {
260 10 100 66     52 if (!defined $self->{$name} ||
      100        
261             ($name eq 'namespace' &&
262             $self->{_namespace_from_metadata})) {
263 7         23 $self->{$name} = $new_field_value;
264             } else {
265 3 50 33     11 if ($name ne 'namespace' || !$other_term->{_namespace_from_metadata}) {
266             warn qq|new "$name" tag of this stanza (from |,
267             $other_term->source_file(), " line ",
268             $other_term->source_file_line_number(), ") ",
269             "differs from previously ",
270             "seen value (from ", $self->source_file(),
271             " line ", $self->source_file_line_number(), q|) "|,
272 3         33 $self->{$name}, '" ',
273             qq(- ignoring new value: "$new_field_value"\n\n),
274             "while merging: \n" . $other_term->to_string() . "\n\n",
275             "into existing term:\n",
276             $self->to_string(), "\n\n";
277             }
278             }
279             } else {
280             # no merging to do
281             }
282             }
283             } else {
284 11         19 my $new_field_value = $other_term->{$name};
285 11         28 for my $single_value (@$new_field_value) {
286 8 100       24 if (!grep { Compare($_, $single_value) } @{$self->{$name}}) {
  5         181  
  8         34  
287 7         518 push @{$self->{$name}}, clone $single_value;
  7         65  
288             }
289             }
290             }
291             } else {
292 0         0 die "unhandled field in merge(): $name\n";
293             }
294 13         68 };
295              
296 13         32 for my $field_name (@field_names) {
297 195 100 100     6053 next if $field_name eq 'id' or $field_name eq 'alt_id';
298              
299 169 100       462 if (!Compare($self->{$field_name}, $other_term->{$field_name})) {
300 26         1101 $merge_field->($field_name, $other_term);
301             }
302             }
303              
304 13         2079 return $self;
305             }
306              
307             sub to_string
308             {
309 12     12 0 1449 my $self = shift;
310              
311 12         18 my @lines = ();
312              
313 12 50       33 if ($self->is_relationshiptype()) {
314 0         0 push @lines, "[Typedef]";
315             } else {
316 12         18 push @lines, "[Term]";
317             }
318              
319             my $line_maker = sub {
320 60     60   72 my $name = shift;
321 60         68 my $value = shift;
322              
323 60         70 my @ret_lines = ();
324              
325 60 100       83 if (ref $value) {
326 26         34 my @values;
327 26 100       47 if ($field_conf{$name}->{type} eq 'SINGLE_HASH') {
328 2         8 push @values, $value;
329             } else {
330 24         39 @values = @$value;
331             }
332 26         33 for my $single_value (@values) {
333 41         57 my $to_string_proc = $field_conf{$name}->{to_string};
334 41         41 my $value_as_string;
335 41 100       65 if (defined $to_string_proc) {
336 19         39 $value_as_string = $to_string_proc->($single_value);
337             } else {
338 22         26 $value_as_string = $single_value;
339             }
340 41         96 push @ret_lines, "$name: $value_as_string";
341             }
342             } else {
343 34         64 push @ret_lines, "$name: $value";
344             }
345              
346 60         134 return @ret_lines;
347 12         55 };
348              
349 12         22 for my $field_name (@field_names) {
350 180         236 my $field_value = $self->{$field_name};
351              
352 180 100       250 if (defined $field_value) {
353 60         86 push @lines, $line_maker->($field_name, $field_value);
354             }
355             }
356              
357 12         265 return join "\n", @lines;
358             }
359              
360             1;