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