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