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