JAL-2738 use GeneLocus extends DBRefEntry to hold chromosomal mappings
[jalview.git] / src / jalview / ext / ensembl / EnsemblMap.java
1 package jalview.ext.ensembl;
2
3 import jalview.datamodel.AlignmentI;
4 import jalview.datamodel.DBRefSource;
5 import jalview.datamodel.GeneLociI;
6 import jalview.datamodel.GeneLocus;
7 import jalview.datamodel.Mapping;
8 import jalview.util.MapList;
9
10 import java.io.BufferedReader;
11 import java.io.IOException;
12 import java.net.MalformedURLException;
13 import java.net.URL;
14 import java.util.ArrayList;
15 import java.util.Collections;
16 import java.util.Iterator;
17 import java.util.List;
18
19 import org.json.simple.JSONArray;
20 import org.json.simple.JSONObject;
21 import org.json.simple.parser.JSONParser;
22 import org.json.simple.parser.ParseException;
23
24 public class EnsemblMap extends EnsemblRestClient
25 {
26   private static final String MAPPED = "mapped";
27
28   private static final String MAPPINGS = "mappings";
29
30   private static final String CDS = "cds";
31
32   private static final String CDNA = "cdna";
33
34   /**
35    * Default constructor (to use rest.ensembl.org)
36    */
37   public EnsemblMap()
38   {
39     super();
40   }
41
42   /**
43    * Constructor given the target domain to fetch data from
44    * 
45    * @param
46    */
47   public EnsemblMap(String domain)
48   {
49     super(domain);
50   }
51
52   @Override
53   public String getDbName()
54   {
55     return DBRefSource.ENSEMBL;
56   }
57
58   @Override
59   public AlignmentI getSequenceRecords(String queries) throws Exception
60   {
61     return null; // not used
62   }
63
64   /**
65    * Constructs a URL of the format <code>
66    * http://rest.ensembl.org/map/human/GRCh38/17:45051610..45109016:1/GRCh37?content-type=application/json
67    * </code>
68    * 
69    * @param species
70    * @param chromosome
71    * @param fromRef
72    * @param toRef
73    * @param startPos
74    * @param endPos
75    * @return
76    * @throws MalformedURLException
77    */
78   protected URL getAssemblyMapUrl(String species, String chromosome, String fromRef,
79           String toRef, int startPos, int endPos)
80           throws MalformedURLException
81   {
82     /*
83      * start-end might be reverse strand - present forwards to the service
84      */
85     boolean forward = startPos <= endPos;
86     int start = forward ? startPos : endPos;
87     int end = forward ? endPos : startPos;
88     String strand = forward ? "1" : "-1";
89     String url = String.format(
90             "%s/map/%s/%s/%s:%d..%d:%s/%s?content-type=application/json",
91             getDomain(), species, fromRef, chromosome, start, end, strand,
92             toRef);
93     return new URL(url);
94   }
95
96   @Override
97   protected boolean useGetRequest()
98   {
99     return true;
100   }
101
102   @Override
103   protected URL getUrl(List<String> ids) throws MalformedURLException
104   {
105     return null; // not used
106   }
107
108   /**
109    * Calls the REST /map service to get the chromosomal coordinates (start/end)
110    * in 'toRef' that corresponding to the (start/end) queryRange in 'fromRef'
111    * 
112    * @param species
113    * @param chromosome
114    * @param fromRef
115    * @param toRef
116    * @param queryRange
117    * @return
118    * @see http://rest.ensemblgenomes.org/documentation/info/assembly_map
119    */
120   public int[] getAssemblyMapping(String species, String chromosome,
121           String fromRef, String toRef, int[] queryRange)
122   {
123     URL url = null;
124     BufferedReader br = null;
125
126     try
127     {
128       url = getAssemblyMapUrl(species, chromosome, fromRef, toRef, queryRange[0],
129               queryRange[1]);
130       br = getHttpResponse(url, null);
131       return (parseAssemblyMappingResponse(br));
132     } catch (Throwable t)
133     {
134       System.out.println("Error calling " + url + ": " + t.getMessage());
135       return null;
136     } finally
137     {
138       if (br != null)
139       {
140         try
141         {
142           br.close();
143         } catch (IOException e)
144         {
145           // ignore
146         }
147       }
148     }
149   }
150
151   /**
152    * Parses the JSON response from the /map/&lt;species&gt;/ REST service. The
153    * format is (with some fields omitted)
154    * 
155    * <pre>
156    *  {"mappings": 
157    *    [{
158    *       "original": {"end":45109016,"start":45051610},
159    *       "mapped"  : {"end":43186384,"start":43128978} 
160    *  }] }
161    * </pre>
162    * 
163    * @param br
164    * @return
165    */
166   protected int[] parseAssemblyMappingResponse(BufferedReader br)
167   {
168     int[] result = null;
169     JSONParser jp = new JSONParser();
170
171     try
172     {
173       JSONObject parsed = (JSONObject) jp.parse(br);
174       JSONArray mappings = (JSONArray) parsed.get(MAPPINGS);
175
176       Iterator rvals = mappings.iterator();
177       while (rvals.hasNext())
178       {
179         // todo check for "mapped"
180         JSONObject val = (JSONObject) rvals.next();
181         JSONObject mapped = (JSONObject) val.get(MAPPED);
182         int start = Integer.parseInt(mapped.get("start").toString());
183         int end = Integer.parseInt(mapped.get("end").toString());
184         String strand = mapped.get("strand").toString();
185         if ("1".equals(strand))
186         {
187           result = new int[] { start, end };
188         }
189         else
190         {
191           result = new int[] { end, start };
192         }
193       }
194     } catch (IOException | ParseException | NumberFormatException e)
195     {
196       // ignore
197     }
198     return result;
199   }
200
201   /**
202    * Calls the REST /map/cds/id service, and returns a DBRefEntry holding the
203    * returned chromosomal coordinates, or returns null if the call fails
204    * 
205    * @param division
206    *          e.g. Ensembl, EnsemblMetazoa
207    * @param accession
208    *          e.g. ENST00000592782, Y55B1AR.1.1
209    * @param start
210    * @param end
211    * @return
212    */
213   public GeneLociI getCdsMapping(String division, String accession,
214           int start, int end)
215   {
216     return getIdMapping(division, accession, start, end, CDS);
217   }
218
219   /**
220    * Calls the REST /map/cdna/id service, and returns a DBRefEntry holding the
221    * returned chromosomal coordinates, or returns null if the call fails
222    * 
223    * @param division
224    *          e.g. Ensembl, EnsemblMetazoa
225    * @param accession
226    *          e.g. ENST00000592782, Y55B1AR.1.1
227    * @param start
228    * @param end
229    * @return
230    */
231   public GeneLociI getCdnaMapping(String division, String accession,
232           int start, int end)
233   {
234     return getIdMapping(division, accession, start, end, CDNA);
235   }
236
237   GeneLociI getIdMapping(String division, String accession, int start,
238           int end, String cdsOrCdna)
239   {
240     URL url = null;
241     BufferedReader br = null;
242
243     try
244     {
245       String domain = new EnsemblInfo().getDomain(division);
246       if (domain != null)
247       {
248         url = getIdMapUrl(domain, accession, start, end, cdsOrCdna);
249         br = getHttpResponse(url, null);
250         if (br != null)
251         {
252           return (parseIdMappingResponse(br, accession, domain));
253         }
254       }
255       return null;
256     } catch (Throwable t)
257     {
258       System.out.println("Error calling " + url + ": " + t.getMessage());
259       return null;
260     } finally
261     {
262       if (br != null)
263       {
264         try
265         {
266           br.close();
267         } catch (IOException e)
268         {
269           // ignore
270         }
271       }
272     }
273   }
274
275   /**
276    * Constructs a URL to the /map/cds/<id> or /map/cdna/<id> REST service. The
277    * REST call is to either ensembl or ensemblgenomes, as determined from the
278    * division, e.g. Ensembl or EnsemblProtists.
279    * 
280    * @param domain
281    * @param accession
282    * @param start
283    * @param end
284    * @param cdsOrCdna
285    * @return
286    * @throws MalformedURLException
287    */
288   URL getIdMapUrl(String domain, String accession, int start, int end,
289           String cdsOrCdna) throws MalformedURLException
290   {
291     String url = String
292             .format("%s/map/%s/%s/%d..%d?include_original_region=1&content-type=application/json",
293                     domain, cdsOrCdna, accession, start, end);
294     return new URL(url);
295   }
296
297   /**
298    * Parses the JSON response from the /map/cds/ or /map/cdna REST service. The
299    * format is
300    * 
301    * <pre>
302    * {"mappings":
303    *   [
304    *    {"assembly_name":"TAIR10","end":2501311,"seq_region_name":"1","gap":0,
305    *     "strand":-1,"coord_system":"chromosome","rank":0,"start":2501114},
306    *    {"assembly_name":"TAIR10","end":2500815,"seq_region_name":"1","gap":0,
307    *     "strand":-1,"coord_system":"chromosome","rank":0,"start":2500714}
308    *   ]
309    * }
310    * </pre>
311    * 
312    * @param br
313    * @param accession
314    * @param domain
315    * @return
316    */
317   GeneLociI parseIdMappingResponse(BufferedReader br, String accession,
318           String domain)
319   {
320     JSONParser jp = new JSONParser();
321
322     try
323     {
324       JSONObject parsed = (JSONObject) jp.parse(br);
325       JSONArray mappings = (JSONArray) parsed.get(MAPPINGS);
326
327       Iterator rvals = mappings.iterator();
328       String assembly = null;
329       String chromosome = null;
330       int fromEnd = 0;
331       List<int[]> regions = new ArrayList<>();
332
333       while (rvals.hasNext())
334       {
335         JSONObject val = (JSONObject) rvals.next();
336         JSONObject original = (JSONObject) val.get("original");
337         fromEnd = Integer.parseInt(original.get("end").toString());
338
339         JSONObject mapped = (JSONObject) val.get(MAPPED);
340         int start = Integer.parseInt(mapped.get("start").toString());
341         int end = Integer.parseInt(mapped.get("end").toString());
342         String ass = mapped.get("assembly_name").toString();
343         if (assembly != null && !assembly.equals(ass))
344         {
345           System.err
346                   .println("EnsemblMap found multiple assemblies - can't resolve");
347           return null;
348         }
349         assembly = ass;
350         String chr = mapped.get("seq_region_name").toString();
351         if (chromosome != null && !chromosome.equals(chr))
352         {
353           System.err
354                   .println("EnsemblMap found multiple chromosomes - can't resolve");
355           return null;
356         }
357         chromosome = chr;
358         String strand = mapped.get("strand").toString();
359         if ("-1".equals(strand))
360         {
361           regions.add(new int[] { end, start });
362         }
363         else
364         {
365           regions.add(new int[] { start, end });
366         }
367       }
368
369       /*
370        * processed all mapped regions on chromosome, assemble the result,
371        * having first fetched the species id for the accession
372        */
373       final String species = new EnsemblLookup(domain)
374               .getSpecies(accession);
375       final String as = assembly;
376       final String chr = chromosome;
377       List<int[]> fromRange = Collections.singletonList(new int[] { 1,
378           fromEnd });
379       Mapping mapping = new Mapping(new MapList(fromRange, regions, 1, 1));
380       return new GeneLocus(species == null ? "" : species, as, chr,
381               mapping);
382     } catch (IOException | ParseException | NumberFormatException e)
383     {
384       // ignore
385     }
386
387     return null;
388   }
389
390 }