Merge branch 'develop' into releases/Release_2_11_Branch
[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 URL getUrl(List<String> ids) throws MalformedURLException
102   {
103     return null; // not used
104   }
105
106   /**
107    * Calls the REST /map service to get the chromosomal coordinates (start/end)
108    * in 'toRef' that corresponding to the (start/end) queryRange in 'fromRef'
109    * 
110    * @param species
111    * @param chromosome
112    * @param fromRef
113    * @param toRef
114    * @param queryRange
115    * @return
116    * @see http://rest.ensemblgenomes.org/documentation/info/assembly_map
117    */
118   public int[] getAssemblyMapping(String species, String chromosome,
119           String fromRef, String toRef, int[] queryRange)
120   {
121     URL url = null;
122     BufferedReader br = null;
123
124     try
125     {
126       url = getAssemblyMapUrl(species, chromosome, fromRef, toRef, queryRange[0],
127               queryRange[1]);
128       br = getHttpResponse(url, null);
129       return (parseAssemblyMappingResponse(br));
130     } catch (Throwable t)
131     {
132       System.out.println("Error calling " + url + ": " + t.getMessage());
133       return null;
134     } finally
135     {
136       if (br != null)
137       {
138         try
139         {
140           br.close();
141         } catch (IOException e)
142         {
143           // ignore
144         }
145       }
146     }
147   }
148
149   /**
150    * Parses the JSON response from the /map/&lt;species&gt;/ REST service. The
151    * format is (with some fields omitted)
152    * 
153    * <pre>
154    *  {"mappings": 
155    *    [{
156    *       "original": {"end":45109016,"start":45051610},
157    *       "mapped"  : {"end":43186384,"start":43128978} 
158    *  }] }
159    * </pre>
160    * 
161    * @param br
162    * @return
163    */
164   protected int[] parseAssemblyMappingResponse(BufferedReader br)
165   {
166     int[] result = null;
167     JSONParser jp = new JSONParser();
168
169     try
170     {
171       JSONObject parsed = (JSONObject) jp.parse(br);
172       JSONArray mappings = (JSONArray) parsed.get(MAPPINGS);
173
174       Iterator rvals = mappings.iterator();
175       while (rvals.hasNext())
176       {
177         // todo check for "mapped"
178         JSONObject val = (JSONObject) rvals.next();
179         JSONObject mapped = (JSONObject) val.get(MAPPED);
180         int start = Integer.parseInt(mapped.get("start").toString());
181         int end = Integer.parseInt(mapped.get("end").toString());
182         String strand = mapped.get("strand").toString();
183         if ("1".equals(strand))
184         {
185           result = new int[] { start, end };
186         }
187         else
188         {
189           result = new int[] { end, start };
190         }
191       }
192     } catch (IOException | ParseException | NumberFormatException e)
193     {
194       // ignore
195     }
196     return result;
197   }
198
199   /**
200    * Calls the REST /map/cds/id service, and returns a DBRefEntry holding the
201    * returned chromosomal coordinates, or returns null if the call fails
202    * 
203    * @param division
204    *          e.g. Ensembl, EnsemblMetazoa
205    * @param accession
206    *          e.g. ENST00000592782, Y55B1AR.1.1
207    * @param start
208    * @param end
209    * @return
210    */
211   public GeneLociI getCdsMapping(String division, String accession,
212           int start, int end)
213   {
214     return getIdMapping(division, accession, start, end, CDS);
215   }
216
217   /**
218    * Calls the REST /map/cdna/id service, and returns a DBRefEntry holding the
219    * returned chromosomal coordinates, or returns null if the call fails
220    * 
221    * @param division
222    *          e.g. Ensembl, EnsemblMetazoa
223    * @param accession
224    *          e.g. ENST00000592782, Y55B1AR.1.1
225    * @param start
226    * @param end
227    * @return
228    */
229   public GeneLociI getCdnaMapping(String division, String accession,
230           int start, int end)
231   {
232     return getIdMapping(division, accession, start, end, CDNA);
233   }
234
235   GeneLociI getIdMapping(String division, String accession, int start,
236           int end, String cdsOrCdna)
237   {
238     URL url = null;
239     BufferedReader br = null;
240
241     try
242     {
243       String domain = new EnsemblInfo().getDomain(division);
244       if (domain != null)
245       {
246         url = getIdMapUrl(domain, accession, start, end, cdsOrCdna);
247         br = getHttpResponse(url, null);
248         if (br != null)
249         {
250           return (parseIdMappingResponse(br, accession, domain));
251         }
252       }
253       return null;
254     } catch (Throwable t)
255     {
256       System.out.println("Error calling " + url + ": " + t.getMessage());
257       return null;
258     } finally
259     {
260       if (br != null)
261       {
262         try
263         {
264           br.close();
265         } catch (IOException e)
266         {
267           // ignore
268         }
269       }
270     }
271   }
272
273   /**
274    * Constructs a URL to the /map/cds/<id> or /map/cdna/<id> REST service. The
275    * REST call is to either ensembl or ensemblgenomes, as determined from the
276    * division, e.g. Ensembl or EnsemblProtists.
277    * 
278    * @param domain
279    * @param accession
280    * @param start
281    * @param end
282    * @param cdsOrCdna
283    * @return
284    * @throws MalformedURLException
285    */
286   URL getIdMapUrl(String domain, String accession, int start, int end,
287           String cdsOrCdna) throws MalformedURLException
288   {
289     String url = String
290             .format("%s/map/%s/%s/%d..%d?include_original_region=1&content-type=application/json",
291                     domain, cdsOrCdna, accession, start, end);
292     return new URL(url);
293   }
294
295   /**
296    * Parses the JSON response from the /map/cds/ or /map/cdna REST service. The
297    * format is
298    * 
299    * <pre>
300    * {"mappings":
301    *   [
302    *    {"assembly_name":"TAIR10","end":2501311,"seq_region_name":"1","gap":0,
303    *     "strand":-1,"coord_system":"chromosome","rank":0,"start":2501114},
304    *    {"assembly_name":"TAIR10","end":2500815,"seq_region_name":"1","gap":0,
305    *     "strand":-1,"coord_system":"chromosome","rank":0,"start":2500714}
306    *   ]
307    * }
308    * </pre>
309    * 
310    * @param br
311    * @param accession
312    * @param domain
313    * @return
314    */
315   GeneLociI parseIdMappingResponse(BufferedReader br, String accession,
316           String domain)
317   {
318     JSONParser jp = new JSONParser();
319
320     try
321     {
322       JSONObject parsed = (JSONObject) jp.parse(br);
323       JSONArray mappings = (JSONArray) parsed.get(MAPPINGS);
324
325       Iterator rvals = mappings.iterator();
326       String assembly = null;
327       String chromosome = null;
328       int fromEnd = 0;
329       List<int[]> regions = new ArrayList<>();
330
331       while (rvals.hasNext())
332       {
333         JSONObject val = (JSONObject) rvals.next();
334         JSONObject original = (JSONObject) val.get("original");
335         fromEnd = Integer.parseInt(original.get("end").toString());
336
337         JSONObject mapped = (JSONObject) val.get(MAPPED);
338         int start = Integer.parseInt(mapped.get("start").toString());
339         int end = Integer.parseInt(mapped.get("end").toString());
340         String ass = mapped.get("assembly_name").toString();
341         if (assembly != null && !assembly.equals(ass))
342         {
343           System.err
344                   .println("EnsemblMap found multiple assemblies - can't resolve");
345           return null;
346         }
347         assembly = ass;
348         String chr = mapped.get("seq_region_name").toString();
349         if (chromosome != null && !chromosome.equals(chr))
350         {
351           System.err
352                   .println("EnsemblMap found multiple chromosomes - can't resolve");
353           return null;
354         }
355         chromosome = chr;
356         String strand = mapped.get("strand").toString();
357         if ("-1".equals(strand))
358         {
359           regions.add(new int[] { end, start });
360         }
361         else
362         {
363           regions.add(new int[] { start, end });
364         }
365       }
366
367       /*
368        * processed all mapped regions on chromosome, assemble the result,
369        * having first fetched the species id for the accession
370        */
371       final String species = new EnsemblLookup(domain)
372               .getSpecies(accession);
373       final String as = assembly;
374       final String chr = chromosome;
375       List<int[]> fromRange = Collections.singletonList(new int[] { 1,
376           fromEnd });
377       final MapList map = new MapList(fromRange, regions, 1, 1);
378       return new GeneLociI()
379       {
380
381         @Override
382         public String getSpeciesId()
383         {
384           return species == null ? "" : species;
385         }
386
387         @Override
388         public String getAssemblyId()
389         {
390           return as;
391         }
392
393         @Override
394         public String getChromosomeId()
395         {
396           return chr;
397         }
398
399         @Override
400         public MapList getMap()
401         {
402           return map;
403         }
404       };
405     } catch (IOException | ParseException | NumberFormatException e)
406     {
407       // ignore
408     }
409
410     return null;
411   }
412
413 }