2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
21 package jalview.ext.so;
23 import java.io.BufferedInputStream;
24 import java.io.BufferedReader;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.InputStreamReader;
28 import java.text.ParseException;
29 import java.util.ArrayList;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.List;
34 import java.util.NoSuchElementException;
35 import java.util.zip.ZipEntry;
36 import java.util.zip.ZipInputStream;
38 import org.biojava.nbio.ontology.Ontology;
39 import org.biojava.nbio.ontology.Term;
40 import org.biojava.nbio.ontology.Term.Impl;
41 import org.biojava.nbio.ontology.Triple;
42 import org.biojava.nbio.ontology.io.OboParser;
43 import org.biojava.nbio.ontology.utils.Annotation;
45 import jalview.bin.Console;
46 import jalview.io.gff.SequenceOntologyI;
49 * A wrapper class that parses the Sequence Ontology and exposes useful access
50 * methods. This version uses the BioJava parser.
52 public class SequenceOntology implements SequenceOntologyI
55 * the parsed Ontology data as modelled by BioJava
57 private Ontology ontology;
60 * the ontology term for the isA relationship
65 * lookup of terms by user readable name (NB not guaranteed unique)
67 private Map<String, Term> termsByDescription;
70 * Map where key is a Term and value is a (possibly empty) list of
71 * all Terms to which the key has an 'isA' relationship, either
72 * directly or indirectly (A isA B isA C)
74 private Map<Term, List<Term>> termIsA;
76 private List<String> termsFound;
78 private List<String> termsNotFound;
81 * Package private constructor to enforce use of singleton. Parses and caches
82 * the SO OBO data file.
84 public SequenceOntology()
86 termsFound = new ArrayList<String>();
87 termsNotFound = new ArrayList<String>();
88 termsByDescription = new HashMap<String, Term>();
89 termIsA = new HashMap<Term, List<Term>>();
91 loadOntologyZipFile("so-xp-simple.obo");
95 * Loads the given ontology file from a zip file with ".zip" appended
99 protected void loadOntologyZipFile(String ontologyFile)
101 long now = System.currentTimeMillis();
102 ZipInputStream zipStream = null;
105 String zipFile = ontologyFile + ".zip";
106 InputStream inStream = this.getClass()
107 .getResourceAsStream("/" + zipFile);
108 zipStream = new ZipInputStream(new BufferedInputStream(inStream));
110 while ((entry = zipStream.getNextEntry()) != null)
112 if (entry.getName().equals(ontologyFile))
114 loadOboFile(zipStream);
117 long elapsed = System.currentTimeMillis() - now;
118 Console.info("Loaded Sequence Ontology from " + zipFile + " ("
120 } catch (Exception e)
125 closeStream(zipStream);
130 * Closes the input stream, swallowing all exceptions
134 protected void closeStream(InputStream is)
141 } catch (IOException e)
149 * Reads, parses and stores the OBO file data
152 * @throws ParseException
153 * @throws IOException
155 protected void loadOboFile(InputStream is)
156 throws ParseException, IOException
158 BufferedReader oboFile = new BufferedReader(new InputStreamReader(is));
159 OboParser parser = new OboParser();
160 ontology = parser.parseOBO(oboFile, "SO", "the SO ontology");
161 isA = ontology.getTerm("is_a");
166 * Stores a lookup table of terms by description. Note that description is not
167 * guaranteed unique. Where duplicate descriptions are found, try to discard
168 * the term that is flagged as obsolete. However we do store obsolete terms
169 * where there is no duplication of description.
171 protected void storeTermNames()
173 for (Term term : ontology.getTerms())
175 if (term instanceof Impl)
177 String description = term.getDescription();
178 if (description != null)
180 Term replaced = termsByDescription.get(description);
181 if (replaced != null)
183 boolean newTermIsObsolete = isObsolete(term);
184 boolean oldTermIsObsolete = isObsolete(replaced);
185 if (newTermIsObsolete && !oldTermIsObsolete)
187 Console.debug("Ignoring " + term.getName()
188 + " as obsolete and duplicated by "
189 + replaced.getName());
192 else if (!newTermIsObsolete && oldTermIsObsolete)
194 Console.debug("Ignoring " + replaced.getName()
195 + " as obsolete and duplicated by " + term.getName());
199 Console.debug("Warning: " + term.getName() + " has replaced "
200 + replaced.getName() + " for lookup of '"
201 + description + "'");
204 termsByDescription.put(description, term);
211 * Answers true if the term has property "is_obsolete" with value true, else
217 public static boolean isObsolete(Term term)
219 Annotation ann = term.getAnnotation();
224 if (Boolean.TRUE.equals(ann.getProperty("is_obsolete")))
228 } catch (NoSuchElementException e)
230 // fall through to false
237 * Test whether the given Sequence Ontology term is nucleotide_match (either
238 * directly or via is_a relationship)
241 * SO name or description
244 public boolean isNucleotideMatch(String soTerm)
246 return isA(soTerm, NUCLEOTIDE_MATCH);
250 * Test whether the given Sequence Ontology term is protein_match (either
251 * directly or via is_a relationship)
254 * SO name or description
257 public boolean isProteinMatch(String soTerm)
259 return isA(soTerm, PROTEIN_MATCH);
263 * Test whether the given Sequence Ontology term is polypeptide (either
264 * directly or via is_a relationship)
267 * SO name or description
270 public boolean isPolypeptide(String soTerm)
272 return isA(soTerm, POLYPEPTIDE);
276 * Returns true if the given term has a (direct or indirect) 'isA'
277 * relationship with the parent
284 public boolean isA(String child, String parent)
286 if (child == null || parent == null)
291 * optimise trivial checks like isA("CDS", "CDS")
293 if (child.equals(parent))
299 Term childTerm = getTerm(child);
300 if (childTerm != null)
308 Term parentTerm = getTerm(parent);
310 return termIsA(childTerm, parentTerm);
314 * Records a valid term queried for, for reporting purposes
318 private void termFound(String term)
320 synchronized (termsFound)
322 if (!termsFound.contains(term))
324 termsFound.add(term);
330 * Records an invalid term queried for, for reporting purposes
334 private void termNotFound(String term)
336 synchronized (termsNotFound)
338 if (!termsNotFound.contains(term))
340 Console.error("SO term " + term + " invalid");
341 termsNotFound.add(term);
347 * Returns true if the childTerm 'isA' parentTerm (directly or indirectly).
353 protected synchronized boolean termIsA(Term childTerm, Term parentTerm)
356 * null term could arise from a misspelled SO description
358 if (childTerm == null || parentTerm == null)
364 * recursive search endpoint:
366 if (childTerm == parentTerm)
372 * lazy initialisation - find all of a term's parents (recursively)
373 * the first time this is called, and save them in a map.
375 if (!termIsA.containsKey(childTerm))
377 findParents(childTerm);
380 List<Term> parents = termIsA.get(childTerm);
381 for (Term parent : parents)
383 if (termIsA(parent, parentTerm))
386 * add (great-)grandparents to parents list as they are discovered,
387 * for faster lookup next time
389 if (!parents.contains(parentTerm))
391 parents.add(parentTerm);
401 * Finds all the 'isA' parents of the childTerm and stores them as a (possibly
406 protected synchronized void findParents(Term childTerm)
408 List<Term> result = new ArrayList<Term>();
409 for (Triple triple : ontology.getTriples(childTerm, null, isA))
411 Term parent = triple.getObject();
415 * and search for the parent's parents recursively
419 termIsA.put(childTerm, result);
423 * Returns the Term for a given name (e.g. "SO:0000735") or description (e.g.
424 * "sequence_location"), or null if not found.
429 protected Term getTerm(String nameOrDescription)
431 Term t = termsByDescription.get(nameOrDescription);
436 t = ontology.getTerm(nameOrDescription);
437 } catch (NoSuchElementException e)
445 public boolean isSequenceVariant(String soTerm)
447 return isA(soTerm, SEQUENCE_VARIANT);
451 * Sorts (case-insensitive) and returns the list of valid terms queried for
454 public List<String> termsFound()
456 synchronized (termsFound)
458 Collections.sort(termsFound, String.CASE_INSENSITIVE_ORDER);
464 * Sorts (case-insensitive) and returns the list of invalid terms queried for
467 public List<String> termsNotFound()
469 synchronized (termsNotFound)
471 Collections.sort(termsNotFound, String.CASE_INSENSITIVE_ORDER);
472 return termsNotFound;