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