JAL-1191 SequenceOntology wrapping/caching SO.OBO via BioJava library
[jalview.git] / src / jalview / io / gff / SequenceOntology.java
1 package jalview.io.gff;
2
3 import java.io.BufferedReader;
4 import java.io.FileInputStream;
5 import java.io.IOException;
6 import java.io.InputStream;
7 import java.io.InputStreamReader;
8 import java.util.ArrayList;
9 import java.util.HashMap;
10 import java.util.List;
11 import java.util.Map;
12 import java.util.NoSuchElementException;
13
14 import org.biojava.nbio.ontology.Ontology;
15 import org.biojava.nbio.ontology.Term;
16 import org.biojava.nbio.ontology.Term.Impl;
17 import org.biojava.nbio.ontology.Triple;
18 import org.biojava.nbio.ontology.io.OboParser;
19
20 /**
21  * A wrapper class that parses the Sequence Ontology and exposes useful access
22  * methods. This version uses the BioJava parser.
23  */
24 public class SequenceOntology
25 {
26   private static SequenceOntology instance = new SequenceOntology();
27
28   private Ontology ontology;
29
30   private Term isA;
31
32   /*
33    * lookup of terms by user readable name (NB not guaranteed unique)
34    */
35   private Map<String, Term> termsByDescription;
36
37   /*
38    * Map where key is a Term and value is a (possibly empty) list of 
39    * all Terms to which the key has a direct 'isA' relationship
40    */
41   private Map<Term, List<Term>> termIsA;
42
43   public static SequenceOntology getInstance()
44   {
45     return instance;
46   }
47
48   /**
49    * Private constructor to enforce use of singleton. Parses and caches the SO
50    * OBO data file.
51    */
52   private SequenceOntology()
53   {
54     termsByDescription = new HashMap<String, Term>();
55     termIsA = new HashMap<Term, List<Term>>();
56
57     OboParser parser = new OboParser();
58     InputStream inStream = null;
59     try
60     {
61       inStream = new FileInputStream(
62               "/Users/gmcarstairs/Documents/ontologies/so-xp-simple.obo");
63
64       BufferedReader oboFile = new BufferedReader(new InputStreamReader(
65               inStream));
66       ontology = parser.parseOBO(oboFile, "SO", "the SO ontology");
67       isA = ontology.getTerm("is_a");
68
69       storeTermNames();
70     } catch (Exception e)
71     {
72       e.printStackTrace();
73     } finally
74     {
75       if (inStream != null)
76       {
77         try
78         {
79           inStream.close();
80         } catch (IOException e)
81         {
82           // ignore
83         }
84       }
85     }
86   }
87
88   protected void storeTermNames()
89   {
90     for (Term term : ontology.getTerms())
91     {
92       if (term instanceof Impl)
93       {
94         String description = term.getDescription();
95         if (description != null)
96         {
97           // System.out.println(term.getName() + "=" + term.getDescription());
98           Term replaced = termsByDescription.put(description, term);
99           if (replaced != null)
100           {
101             System.err.println("Warning: " + term.getName()
102                     + " has replaced " + replaced.getName()
103                     + " for lookup of description "
104                     + description);
105           }
106         }
107       }
108     }
109   }
110
111   /**
112    * Test whether the given Sequence Ontology term is nucleotide_match (either
113    * directly or via is_a relationship)
114    * 
115    * @param soTerm
116    * @return
117    */
118   public boolean isNucleotideMatch(String soTerm)
119   {
120     return isA(soTerm, "nucleotide_match");
121   }
122
123   /**
124    * Test whether the given Sequence Ontology term is protein_match (either
125    * directly or via is_a relationship)
126    * 
127    * @param soTerm
128    * @return
129    */
130   public boolean isProteinMatch(String soTerm)
131   {
132     return isA(soTerm, "protein_match");
133   }
134
135   public boolean isPolypeptide(String soTerm)
136   {
137     return isA(soTerm, "polypeptide");
138   }
139
140   /**
141    * Returns true if the given term has a (direct or indirect) 'isA'
142    * relationship with the parent
143    * 
144    * @param child
145    * @param parent
146    * @return
147    */
148   public boolean isA(String child, String parent)
149   {
150     Term childTerm = getTerm(child);
151     Term parentTerm = getTerm(parent);
152
153     return termIsA(childTerm, parentTerm);
154   }
155
156   /**
157    * Returns true if the childTerm 'isA' parentTerm (directly or indirectly).
158    * 
159    * @param childTerm
160    * @param parentTerm
161    * @return
162    */
163   protected synchronized boolean termIsA(Term childTerm, Term parentTerm)
164   {
165     /*
166      * null child term arises from a misspelled SO description
167      */
168     if (childTerm == null || parentTerm == null)
169     {
170       return false;
171     }
172
173     /*
174      * recursive search endpoint:
175      */
176     if (childTerm == parentTerm)
177     {
178       return true;
179     }
180     /*
181      * lazy initialisation - find all of a term's parents the first
182      * time this is called, and save them in a map.
183      */
184     if (!termIsA.containsKey(childTerm))
185     {
186       findParents(childTerm);
187     }
188
189     List<Term> parents = termIsA.get(childTerm);
190     for (Term parent : parents)
191     {
192       if (termIsA(parent, parentTerm))
193       {
194         return true;
195       }
196     }
197
198     return false;
199   }
200
201   /**
202    * Finds all the 'isA' parents of the childTerm and stores them as a (possibly
203    * empty) list.
204    * 
205    * @param childTerm
206    */
207   protected synchronized void findParents(Term childTerm)
208   {
209     List<Term> result = new ArrayList<Term>();
210     for (Triple triple : ontology.getTriples(childTerm, null, isA))
211     {
212       Term parent = triple.getObject();
213       result.add(parent);
214
215       /*
216        * and search for the parent's parents recursively
217        */
218       findParents(parent);
219     }
220     termIsA.put(childTerm, result);
221   }
222
223   /**
224    * Returns the Term for a given name (e.g. "SO:0000735") or description (e.g.
225    * "sequence_location"), or null if not found.
226    * 
227    * @param child
228    * @return
229    */
230   protected Term getTerm(String nameOrDescription)
231   {
232     Term t = termsByDescription.get(nameOrDescription);
233     if (t == null)
234     {
235       try
236       {
237         t = ontology.getTerm(nameOrDescription);
238       } catch (NoSuchElementException e)
239       {
240         // not found
241       }
242     }
243     return t;
244   }
245 }