673bcc0db15ea75b2d5317038f0f3b220d5e5930
[jalview.git] / src / jalview / gui / CrossRefAction.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.gui;
22
23 import jalview.analysis.AlignmentUtils;
24 import jalview.analysis.CrossRef;
25 import jalview.api.AlignmentViewPanel;
26 import jalview.api.FeatureSettingsModelI;
27 import jalview.bin.Cache;
28 import jalview.datamodel.Alignment;
29 import jalview.datamodel.AlignmentI;
30 import jalview.datamodel.DBRefEntry;
31 import jalview.datamodel.DBRefSource;
32 import jalview.datamodel.GeneLociI;
33 import jalview.datamodel.SequenceI;
34 import jalview.ext.ensembl.EnsemblInfo;
35 import jalview.ext.ensembl.EnsemblMap;
36 import jalview.io.gff.SequenceOntologyI;
37 import jalview.structure.StructureSelectionManager;
38 import jalview.util.DBRefUtils;
39 import jalview.util.MapList;
40 import jalview.util.MappingUtils;
41 import jalview.util.MessageManager;
42 import jalview.ws.SequenceFetcher;
43
44 import java.util.ArrayList;
45 import java.util.HashMap;
46 import java.util.List;
47 import java.util.Map;
48 import java.util.Set;
49
50 /**
51  * Factory constructor and runnable for discovering and displaying
52  * cross-references for a set of aligned sequences
53  * 
54  * @author jprocter
55  *
56  */
57 public class CrossRefAction implements Runnable
58 {
59   private AlignFrame alignFrame;
60
61   private SequenceI[] sel;
62
63   private final boolean _odna;
64
65   private String source;
66
67   List<AlignmentViewPanel> xrefViews = new ArrayList<>();
68
69   List<AlignmentViewPanel> getXrefViews()
70   {
71     return xrefViews;
72   }
73
74   @Override
75   public void run()
76   {
77     final long sttime = System.currentTimeMillis();
78     alignFrame.setProgressBar(MessageManager.formatMessage(
79             "status.searching_for_sequences_from", new Object[]
80             { source }), sttime);
81     try
82     {
83       AlignmentI alignment = alignFrame.getViewport().getAlignment();
84       AlignmentI dataset = alignment.getDataset() == null ? alignment
85               : alignment.getDataset();
86       boolean dna = alignment.isNucleotide();
87       if (_odna != dna)
88       {
89         System.err
90                 .println("Conflict: showProducts for alignment originally "
91                         + "thought to be " + (_odna ? "DNA" : "Protein")
92                         + " now searching for " + (dna ? "DNA" : "Protein")
93                         + " Context.");
94       }
95       AlignmentI xrefs = new CrossRef(sel, dataset)
96               .findXrefSequences(source, dna);
97       if (xrefs == null)
98       {
99         return;
100       }
101
102       /*
103        * try to look up chromosomal coordinates for nucleotide
104        * sequences (if not already retrieved)
105        */
106       findGeneLoci(xrefs.getSequences());
107
108       /*
109        * get display scheme (if any) to apply to features
110        */
111       FeatureSettingsModelI featureColourScheme = new SequenceFetcher()
112               .getFeatureColourScheme(source);
113
114       AlignmentI xrefsAlignment = makeCrossReferencesAlignment(dataset,
115               xrefs);
116       if (!dna)
117       {
118         xrefsAlignment = AlignmentUtils.makeCdsAlignment(
119                 xrefsAlignment.getSequencesArray(), dataset, sel);
120         xrefsAlignment.alignAs(alignment);
121       }
122
123       /*
124        * If we are opening a splitframe, make a copy of this alignment (sharing the same dataset
125        * sequences). If we are DNA, drop introns and update mappings
126        */
127       AlignmentI copyAlignment = null;
128
129       if (Cache.getDefault(Preferences.ENABLE_SPLIT_FRAME, true))
130       {
131         copyAlignment = copyAlignmentForSplitFrame(alignment, dataset, dna,
132                 xrefs, xrefsAlignment);
133         if (copyAlignment == null)
134         {
135           return; // failed
136         }
137       }
138
139       /*
140        * build AlignFrame(s) according to available alignment data
141        */
142       AlignFrame newFrame = new AlignFrame(xrefsAlignment,
143               AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
144       if (Cache.getDefault("HIDE_INTRONS", true))
145       {
146         newFrame.hideFeatureColumns(SequenceOntologyI.EXON, false);
147       }
148       String newtitle = String.format("%s %s %s",
149               dna ? MessageManager.getString("label.proteins")
150                       : MessageManager.getString("label.nucleotides"),
151               MessageManager.getString("label.for"), alignFrame.getTitle());
152       newFrame.setTitle(newtitle);
153
154       if (copyAlignment == null)
155       {
156         /*
157          * split frame display is turned off in preferences file
158          */
159         Desktop.addInternalFrame(newFrame, newtitle,
160                 AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
161         xrefViews.add(newFrame.alignPanel);
162         return; // via finally clause
163       }
164
165       AlignFrame copyThis = new AlignFrame(copyAlignment,
166               AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
167       copyThis.setTitle(alignFrame.getTitle());
168
169       boolean showSequenceFeatures = alignFrame.getViewport()
170               .isShowSequenceFeatures();
171       newFrame.setShowSeqFeatures(showSequenceFeatures);
172       copyThis.setShowSeqFeatures(showSequenceFeatures);
173       FeatureRenderer myFeatureStyling = alignFrame.alignPanel
174               .getSeqPanel().seqCanvas.getFeatureRenderer();
175
176       /*
177        * copy feature rendering settings to split frame
178        */
179       FeatureRenderer fr1 = newFrame.alignPanel.getSeqPanel().seqCanvas
180               .getFeatureRenderer();
181       fr1.transferSettings(myFeatureStyling);
182       fr1.findAllFeatures(true);
183       FeatureRenderer fr2 = copyThis.alignPanel.getSeqPanel().seqCanvas
184               .getFeatureRenderer();
185       fr2.transferSettings(myFeatureStyling);
186       fr2.findAllFeatures(true);
187
188       /*
189        * apply 'database source' feature configuration
190        * if any - first to the new splitframe view about to be displayed
191        */
192
193       newFrame.getViewport().applyFeaturesStyle(featureColourScheme);
194       copyThis.getViewport().applyFeaturesStyle(featureColourScheme);
195
196       /*
197        * and for JAL-3330 also to original alignFrame view(s)
198        * this currently trashes any original settings.
199        */
200       for (AlignmentViewPanel origpanel: alignFrame.getAlignPanels()) {
201         origpanel.getAlignViewport()
202                 .mergeFeaturesStyle(featureColourScheme);
203       }
204
205       SplitFrame sf = new SplitFrame(dna ? copyThis : newFrame,
206               dna ? newFrame : copyThis);
207
208       newFrame.setVisible(true);
209       copyThis.setVisible(true);
210       String linkedTitle = MessageManager
211               .getString("label.linked_view_title");
212       Desktop.addInternalFrame(sf, linkedTitle, -1, -1);
213       sf.adjustInitialLayout();
214
215       // finally add the top, then bottom frame to the view list
216       xrefViews.add(dna ? copyThis.alignPanel : newFrame.alignPanel);
217       xrefViews.add(!dna ? copyThis.alignPanel : newFrame.alignPanel);
218
219     } catch (OutOfMemoryError e)
220     {
221       new OOMWarning("whilst fetching crossreferences", e);
222     } catch (Throwable e)
223     {
224       Cache.log.error("Error when finding crossreferences", e);
225     } finally
226     {
227       alignFrame.setProgressBar(MessageManager.formatMessage(
228               "status.finished_searching_for_sequences_from", new Object[]
229               { source }), sttime);
230     }
231   }
232
233   /**
234    * Tries to add chromosomal coordinates to any nucleotide sequence which does
235    * not already have them. Coordinates are retrieved from Ensembl given an
236    * Ensembl identifier, either on the sequence itself or on a peptide sequence
237    * it has a reference to.
238    * 
239    * <pre>
240    * Example (human):
241    * - fetch EMBLCDS cross-references for Uniprot entry P30419
242    * - the EMBL sequences do not have xrefs to Ensembl
243    * - the Uniprot entry has xrefs to 
244    *    ENSP00000258960, ENSP00000468424, ENST00000258960, ENST00000592782
245    * - either of the transcript ids can be used to retrieve gene loci e.g.
246    *    http://rest.ensembl.org/map/cds/ENST00000592782/1..100000
247    * Example (invertebrate):
248    * - fetch EMBLCDS cross-references for Uniprot entry Q43517 (FER1_SOLLC)
249    * - the Uniprot entry has an xref to ENSEMBLPLANTS Solyc10g044520.1.1
250    * - can retrieve gene loci with
251    *    http://rest.ensemblgenomes.org/map/cds/Solyc10g044520.1.1/1..100000
252    * </pre>
253    * 
254    * @param sequences
255    */
256   public static void findGeneLoci(List<SequenceI> sequences)
257   {
258     Map<DBRefEntry, GeneLociI> retrievedLoci = new HashMap<>();
259     for (SequenceI seq : sequences)
260     {
261       findGeneLoci(seq, retrievedLoci);
262     }
263   }
264
265   /**
266    * Tres to find chromosomal coordinates for the sequence, by searching its
267    * direct and indirect cross-references for Ensembl. If the loci have already
268    * been retrieved, just reads them out of the map of retrievedLoci; this is
269    * the case of an alternative transcript for the same protein. Otherwise calls
270    * a REST service to retrieve the loci, and if successful, adds them to the
271    * sequence and to the retrievedLoci.
272    * 
273    * @param seq
274    * @param retrievedLoci
275    */
276   static void findGeneLoci(SequenceI seq,
277           Map<DBRefEntry, GeneLociI> retrievedLoci)
278   {
279     /*
280      * don't replace any existing chromosomal coordinates
281      */
282     if (seq == null || seq.isProtein() || seq.getGeneLoci() != null
283             || seq.getDBRefs() == null)
284     {
285       return;
286     }
287     
288     Set<String> ensemblDivisions = new EnsemblInfo().getDivisions();
289     
290     /*
291      * first look for direct dbrefs from sequence to Ensembl
292      */
293     String[] divisionsArray = ensemblDivisions
294             .toArray(new String[ensemblDivisions.size()]);
295     DBRefEntry[] seqRefs = seq.getDBRefs();
296     DBRefEntry[] directEnsemblRefs = DBRefUtils.selectRefs(seqRefs,
297             divisionsArray);
298     if (directEnsemblRefs != null)
299     {
300       for (DBRefEntry ensemblRef : directEnsemblRefs)
301       {
302         if (fetchGeneLoci(seq, ensemblRef, retrievedLoci))
303         {
304           return;
305         }
306       }
307     }
308
309     /*
310      * else look for indirect dbrefs from sequence to Ensembl
311      */
312     for (DBRefEntry dbref : seq.getDBRefs())
313     {
314       if (dbref.getMap() != null && dbref.getMap().getTo() != null)
315       {
316         DBRefEntry[] dbrefs = dbref.getMap().getTo().getDBRefs();
317         DBRefEntry[] indirectEnsemblRefs = DBRefUtils.selectRefs(dbrefs,
318                 divisionsArray);
319         if (indirectEnsemblRefs != null)
320         {
321           for (DBRefEntry ensemblRef : indirectEnsemblRefs)
322           {
323             if (fetchGeneLoci(seq, ensemblRef, retrievedLoci))
324             {
325               return;
326             }
327           }
328         }
329       }
330     }
331   }
332
333   /**
334    * Retrieves chromosomal coordinates for the Ensembl (or EnsemblGenomes)
335    * identifier in dbref. If successful, and the sequence length matches gene
336    * loci length, then add it to the sequence, and to the retrievedLoci map.
337    * Answers true if successful, else false.
338    * 
339    * @param seq
340    * @param dbref
341    * @param retrievedLoci
342    * @return
343    */
344   static boolean fetchGeneLoci(SequenceI seq, DBRefEntry dbref,
345           Map<DBRefEntry, GeneLociI> retrievedLoci)
346   {
347     String accession = dbref.getAccessionId();
348     String division = dbref.getSource();
349
350     /*
351      * hack: ignore cross-references to Ensembl protein ids
352      * (or use map/translation perhaps?)
353      * todo: is there an equivalent in EnsemblGenomes?
354      */
355     if (accession.startsWith("ENSP"))
356     {
357       return false;
358     }
359     EnsemblMap mapper = new EnsemblMap();
360
361     /*
362      * try CDS mapping first
363      */
364     GeneLociI geneLoci = mapper.getCdsMapping(division, accession, 1,
365             seq.getLength());
366     if (geneLoci != null)
367     {
368       MapList map = geneLoci.getMapping();
369       int mappedFromLength = MappingUtils.getLength(map.getFromRanges());
370       if (mappedFromLength == seq.getLength())
371       {
372         seq.setGeneLoci(geneLoci.getSpeciesId(), geneLoci.getAssemblyId(),
373                 geneLoci.getChromosomeId(), map);
374         retrievedLoci.put(dbref, geneLoci);
375         return true;
376       }
377     }
378
379     /*
380      * else try CDNA mapping
381      */
382     geneLoci = mapper.getCdnaMapping(division, accession, 1,
383             seq.getLength());
384     if (geneLoci != null)
385     {
386       MapList map = geneLoci.getMapping();
387       int mappedFromLength = MappingUtils.getLength(map.getFromRanges());
388       if (mappedFromLength == seq.getLength())
389       {
390         seq.setGeneLoci(geneLoci.getSpeciesId(), geneLoci.getAssemblyId(),
391                 geneLoci.getChromosomeId(), map);
392         retrievedLoci.put(dbref, geneLoci);
393         return true;
394       }
395     }
396
397     return false;
398   }
399
400   /**
401    * @param alignment
402    * @param dataset
403    * @param dna
404    * @param xrefs
405    * @param xrefsAlignment
406    * @return
407    */
408   protected AlignmentI copyAlignmentForSplitFrame(AlignmentI alignment,
409           AlignmentI dataset, boolean dna, AlignmentI xrefs,
410           AlignmentI xrefsAlignment)
411   {
412     AlignmentI copyAlignment;
413     boolean copyAlignmentIsAligned = false;
414     if (dna)
415     {
416       copyAlignment = AlignmentUtils.makeCdsAlignment(sel, dataset,
417               xrefsAlignment.getSequencesArray());
418       if (copyAlignment.getHeight() == 0)
419       {
420         JvOptionPane.showMessageDialog(alignFrame,
421                 MessageManager.getString("label.cant_map_cds"),
422                 MessageManager.getString("label.operation_failed"),
423                 JvOptionPane.OK_OPTION);
424         System.err.println("Failed to make CDS alignment");
425         return null;
426       }
427
428       /*
429        * pending getting Embl transcripts to 'align', 
430        * we are only doing this for Ensembl
431        */
432       // TODO proper criteria for 'can align as cdna'
433       if (DBRefSource.ENSEMBL.equalsIgnoreCase(source)
434               || AlignmentUtils.looksLikeEnsembl(alignment))
435       {
436         copyAlignment.alignAs(alignment);
437         copyAlignmentIsAligned = true;
438       }
439     }
440     else
441     {
442       copyAlignment = AlignmentUtils.makeCopyAlignment(sel,
443               xrefs.getSequencesArray(), dataset);
444     }
445     copyAlignment
446             .setGapCharacter(alignFrame.viewport.getGapCharacter());
447
448     StructureSelectionManager ssm = StructureSelectionManager
449             .getStructureSelectionManager(Desktop.instance);
450
451     /*
452      * register any new mappings for sequence mouseover etc
453      * (will not duplicate any previously registered mappings)
454      */
455     ssm.registerMappings(dataset.getCodonFrames());
456
457     if (copyAlignment.getHeight() <= 0)
458     {
459       System.err.println(
460               "No Sequences generated for xRef type " + source);
461       return null;
462     }
463
464     /*
465      * align protein to dna
466      */
467     if (dna && copyAlignmentIsAligned)
468     {
469       xrefsAlignment.alignAs(copyAlignment);
470     }
471     else
472     {
473       /*
474        * align cdna to protein - currently only if 
475        * fetching and aligning Ensembl transcripts!
476        */
477       // TODO: generalise for other sources of locus/transcript/cds data
478       if (dna && DBRefSource.ENSEMBL.equalsIgnoreCase(source))
479       {
480         copyAlignment.alignAs(xrefsAlignment);
481       }
482     }
483
484     return copyAlignment;
485   }
486
487   /**
488    * Makes an alignment containing the given sequences, and adds them to the
489    * given dataset, which is also set as the dataset for the new alignment
490    * 
491    * TODO: refactor to DatasetI method
492    * 
493    * @param dataset
494    * @param seqs
495    * @return
496    */
497   protected AlignmentI makeCrossReferencesAlignment(AlignmentI dataset,
498           AlignmentI seqs)
499   {
500     SequenceI[] sprods = new SequenceI[seqs.getHeight()];
501     for (int s = 0; s < sprods.length; s++)
502     {
503       sprods[s] = (seqs.getSequenceAt(s)).deriveSequence();
504       if (dataset.getSequences() == null || !dataset.getSequences()
505               .contains(sprods[s].getDatasetSequence()))
506       {
507         dataset.addSequence(sprods[s].getDatasetSequence());
508       }
509       sprods[s].updatePDBIds();
510     }
511     Alignment al = new Alignment(sprods);
512     al.setDataset(dataset);
513     return al;
514   }
515
516   /**
517    * Constructor
518    * 
519    * @param af
520    * @param seqs
521    * @param fromDna
522    * @param dbSource
523    */
524   CrossRefAction(AlignFrame af, SequenceI[] seqs, boolean fromDna,
525           String dbSource)
526   {
527     this.alignFrame = af;
528     this.sel = seqs;
529     this._odna = fromDna;
530     this.source = dbSource;
531   }
532
533   public static CrossRefAction getHandlerFor(final SequenceI[] sel,
534           final boolean fromDna, final String source,
535           final AlignFrame alignFrame)
536   {
537     return new CrossRefAction(alignFrame, sel, fromDna, source);
538   }
539
540 }