JAL-3225 save auto formatting settings in org.eclipse.jdt.ui.prefs. Not yet put...
[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.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       {
170         return null;
171       }
172       while (rvals.hasNext())
173       {
174         // todo check for "mapped"
175         Map<String, Object> val = (Map<String, Object>) rvals.next();
176         Map<String, Object> mapped = (Map<String, Object>) val.get(MAPPED);
177         int start = Integer.parseInt(mapped.get("start").toString());
178         int end = Integer.parseInt(mapped.get("end").toString());
179         String strand = mapped.get("strand").toString();
180         if ("1".equals(strand))
181         {
182           result = new int[] { start, end };
183         }
184         else
185         {
186           result = new int[] { end, start };
187         }
188       }
189     } catch (IOException | ParseException | NumberFormatException e)
190     {
191       // ignore
192     }
193     return result;
194   }
195
196   /**
197    * Calls the REST /map/cds/id service, and returns a DBRefEntry holding the
198    * returned chromosomal coordinates, or returns null if the call fails
199    * 
200    * @param division
201    *          e.g. Ensembl, EnsemblMetazoa
202    * @param accession
203    *          e.g. ENST00000592782, Y55B1AR.1.1
204    * @param start
205    * @param end
206    * @return
207    */
208   public GeneLociI getCdsMapping(String division, String accession,
209           int start, int end)
210   {
211     return getIdMapping(division, accession, start, end, CDS);
212   }
213
214   /**
215    * Calls the REST /map/cdna/id service, and returns a DBRefEntry holding the
216    * returned chromosomal coordinates, or returns null if the call fails
217    * 
218    * @param division
219    *          e.g. Ensembl, EnsemblMetazoa
220    * @param accession
221    *          e.g. ENST00000592782, Y55B1AR.1.1
222    * @param start
223    * @param end
224    * @return
225    */
226   public GeneLociI getCdnaMapping(String division, String accession,
227           int start, int end)
228   {
229     return getIdMapping(division, accession, start, end, CDNA);
230   }
231
232   GeneLociI getIdMapping(String division, String accession, int start,
233           int end, String cdsOrCdna)
234   {
235     URL url = null;
236     try
237     {
238       String domain = new EnsemblInfo().getDomain(division);
239       if (domain != null)
240       {
241         url = getIdMapUrl(domain, accession, start, end, cdsOrCdna);
242         return (parseIdMappingResponse(url, accession, domain));
243       }
244       return null;
245     } catch (Throwable t)
246     {
247       System.out.println("Error calling " + url + ": " + t.getMessage());
248       return null;
249     }
250   }
251
252   /**
253    * Constructs a URL to the /map/cds/<id> or /map/cdna/<id> REST service. The
254    * REST call is to either ensembl or ensemblgenomes, as determined from the
255    * division, e.g. Ensembl or EnsemblProtists.
256    * 
257    * @param domain
258    * @param accession
259    * @param start
260    * @param end
261    * @param cdsOrCdna
262    * @return
263    * @throws MalformedURLException
264    */
265   URL getIdMapUrl(String domain, String accession, int start, int end,
266           String cdsOrCdna) throws MalformedURLException
267   {
268     String url = String
269             .format("%s/map/%s/%s/%d..%d?include_original_region=1&content-type=application/json",
270                     domain, cdsOrCdna, accession, start, end);
271     return new URL(url);
272   }
273
274   /**
275    * Parses the JSON response from the /map/cds/ or /map/cdna REST service. The
276    * format is
277    * 
278    * <pre>
279    * {"mappings":
280    *   [
281    *    {"assembly_name":"TAIR10","end":2501311,"seq_region_name":"1","gap":0,
282    *     "strand":-1,"coord_system":"chromosome","rank":0,"start":2501114},
283    *    {"assembly_name":"TAIR10","end":2500815,"seq_region_name":"1","gap":0,
284    *     "strand":-1,"coord_system":"chromosome","rank":0,"start":2500714}
285    *   ]
286    * }
287    * </pre>
288    * 
289    * @param br
290    * @param accession
291    * @param domain
292    * @return
293    */
294   @SuppressWarnings("unchecked")
295 GeneLociI parseIdMappingResponse(URL url, String accession,
296           String domain)
297   {
298
299     try
300     {
301       Iterator<Object> rvals = (Iterator<Object>) getJSON(url, null, -1, MODE_ITERATOR, MAPPINGS);
302       if (rvals == null)
303       {
304         return null;
305       }
306       String assembly = null;
307       String chromosome = null;
308       int fromEnd = 0;
309       List<int[]> regions = new ArrayList<>();
310
311       while (rvals.hasNext())
312       {
313         Map<String, Object> val = (Map<String, Object>) rvals.next();
314         Map<String, Object> original = (Map<String, Object>) val.get("original");
315         fromEnd = Integer.parseInt(original.get("end").toString());
316
317         Map<String, Object> mapped = (Map<String, Object>) val.get(MAPPED);
318         int start = Integer.parseInt(mapped.get("start").toString());
319         int end = Integer.parseInt(mapped.get("end").toString());
320         String ass = mapped.get("assembly_name").toString();
321         if (assembly != null && !assembly.equals(ass))
322         {
323           System.err
324                   .println("EnsemblMap found multiple assemblies - can't resolve");
325           return null;
326         }
327         assembly = ass;
328         String chr = mapped.get("seq_region_name").toString();
329         if (chromosome != null && !chromosome.equals(chr))
330         {
331           System.err
332                   .println("EnsemblMap found multiple chromosomes - can't resolve");
333           return null;
334         }
335         chromosome = chr;
336         String strand = mapped.get("strand").toString();
337         if ("-1".equals(strand))
338         {
339           regions.add(new int[] { end, start });
340         }
341         else
342         {
343           regions.add(new int[] { start, end });
344         }
345       }
346
347       /*
348        * processed all mapped regions on chromosome, assemble the result,
349        * having first fetched the species id for the accession
350        */
351       final String species = new EnsemblLookup(domain)
352               .getSpecies(accession);
353       final String as = assembly;
354       final String chr = chromosome;
355       List<int[]> fromRange = Collections.singletonList(new int[] { 1,
356           fromEnd });
357       Mapping mapping = new Mapping(new MapList(fromRange, regions, 1, 1));
358       return new GeneLocus(species == null ? "" : species, as, chr,
359               mapping);
360     } catch (IOException | ParseException | NumberFormatException e)
361     {
362       // ignore
363     }
364
365     return null;
366   }
367
368 }