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