JAL-1807 explicit imports (jalview.util)
[jalview.git] / src / jalview / analysis / CrossRef.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.analysis;
22
23 import jalview.datamodel.AlignedCodonFrame;
24 import jalview.datamodel.Alignment;
25 import jalview.datamodel.AlignmentI;
26 import jalview.datamodel.DBRefEntry;
27 import jalview.datamodel.DBRefSource;
28 import jalview.datamodel.Mapping;
29 import jalview.datamodel.Sequence;
30 import jalview.datamodel.SequenceI;
31 import jalview.util.Comparison;
32 import jalview.util.DBRefUtils;
33 import jalview.ws.SequenceFetcher;
34 import jalview.ws.seqfetcher.ASequenceFetcher;
35
36 import java.util.ArrayList;
37 import java.util.List;
38 import java.util.Vector;
39
40 /**
41  * Functions for cross-referencing sequence databases. user must first specify
42  * if cross-referencing from protein or dna (set dna==true)
43  * 
44  * @author JimP
45  * 
46  */
47 public class CrossRef
48 {
49   /**
50    * Select just the DNA or protein references for a protein or dna sequence
51    * 
52    * @param fromDna
53    *          if true, select references from DNA (i.e. Protein databases), else
54    *          DNA database references
55    * @param refs
56    *          a set of references to select from
57    * @return
58    */
59   public static DBRefEntry[] findXDbRefs(boolean fromDna, DBRefEntry[] refs)
60   {
61     return DBRefUtils.selectRefs(refs, fromDna ? DBRefSource.PROTEINDBS
62             : DBRefSource.DNACODINGDBS);
63     // could attempt to find other cross
64     // refs here - ie PDB xrefs
65     // (not dna, not protein seq)
66   }
67
68   /**
69    * @param dna
70    *          true if seqs are DNA seqs
71    * @param seqs
72    * @return a list of sequence database cross reference source types
73    */
74   public static String[] findSequenceXrefTypes(boolean dna, SequenceI[] seqs)
75   {
76     return findSequenceXrefTypes(dna, seqs, null);
77   }
78
79   /**
80    * Indirect references are references from other sequences from the dataset to
81    * any of the direct DBRefEntrys on the given sequences.
82    * 
83    * @param dna
84    *          true if seqs are DNA seqs
85    * @param seqs
86    * @return a list of sequence database cross reference source types
87    */
88   public static String[] findSequenceXrefTypes(boolean dna,
89           SequenceI[] seqs, AlignmentI dataset)
90   {
91     String[] dbrefs = null;
92     List<String> refs = new ArrayList<String>();
93     for (int s = 0; s < seqs.length; s++)
94     {
95       if (seqs[s] != null)
96       {
97         SequenceI dss = seqs[s];
98         while (dss.getDatasetSequence() != null)
99         {
100           dss = dss.getDatasetSequence();
101         }
102         DBRefEntry[] rfs = findXDbRefs(dna, dss.getDBRef());
103         for (int r = 0; rfs != null && r < rfs.length; r++)
104         {
105           if (!refs.contains(rfs[r].getSource()))
106           {
107             refs.add(rfs[r].getSource());
108           }
109         }
110         if (dataset != null)
111         {
112           // search for references to this sequence's direct references.
113           DBRefEntry[] lrfs = CrossRef
114                   .findXDbRefs(!dna, seqs[s].getDBRef());
115           List<SequenceI> rseqs = new ArrayList<SequenceI>();
116           CrossRef.searchDatasetXrefs(seqs[s], !dna, lrfs, dataset, rseqs,
117                   null); // don't need to specify codon frame for mapping here
118           for (SequenceI rs : rseqs)
119           {
120             DBRefEntry[] xrs = findXDbRefs(dna, rs.getDBRef()); // not used??
121             for (int r = 0; rfs != null && r < rfs.length; r++)
122             {
123               if (!refs.contains(rfs[r].getSource()))
124               {
125                 refs.add(rfs[r].getSource());
126               }
127             }
128           }
129         }
130       }
131     }
132     if (refs.size() > 0)
133     {
134       dbrefs = new String[refs.size()];
135       refs.toArray(dbrefs);
136     }
137     return dbrefs;
138   }
139
140   /*
141    * if (dna) { if (rfs[r].hasMap()) { // most likely this is a protein cross
142    * reference if (!refs.contains(rfs[r].getSource())) {
143    * refs.addElement(rfs[r].getSource()); } } }
144    */
145   public static boolean hasCdnaMap(SequenceI[] seqs)
146   {
147     String[] reftypes = findSequenceXrefTypes(false, seqs);
148     for (int s = 0; s < reftypes.length; s++)
149     {
150       if (reftypes.equals(DBRefSource.EMBLCDS))
151       {
152         return true;
153         // no map
154       }
155     }
156     return false;
157   }
158
159   public static SequenceI[] getCdnaMap(SequenceI[] seqs)
160   {
161     Vector cseqs = new Vector();
162     for (int s = 0; s < seqs.length; s++)
163     {
164       DBRefEntry[] cdna = findXDbRefs(true, seqs[s].getDBRef());
165       for (int c = 0; c < cdna.length; c++)
166       {
167         if (cdna[c].getSource().equals(DBRefSource.EMBLCDS))
168         {
169           System.err
170                   .println("TODO: unimplemented sequence retrieval for coding region sequence.");
171           // TODO: retrieve CDS dataset sequences
172           // need global dataset sequence retriever/resolver to reuse refs
173           // and construct Mapping entry.
174           // insert gaps in CDS according to peptide gaps.
175           // add gapped sequence to cseqs
176         }
177       }
178     }
179     if (cseqs.size() > 0)
180     {
181       SequenceI[] rsqs = new SequenceI[cseqs.size()];
182       cseqs.copyInto(rsqs);
183       return rsqs;
184     }
185     return null;
186
187   }
188
189   /**
190    * 
191    * @param dna
192    * @param seqs
193    * @return
194    */
195   public static Alignment findXrefSequences(SequenceI[] seqs, boolean dna,
196           String source)
197   {
198     return findXrefSequences(seqs, dna, source, null);
199   }
200
201   /**
202    * 
203    * @param seqs
204    * @param dna
205    * @param source
206    * @param dataset
207    *          alignment to search for product sequences.
208    * @return products (as dataset sequences)
209    */
210   public static Alignment findXrefSequences(SequenceI[] seqs, boolean dna,
211           String source, AlignmentI dataset)
212   {
213     List<SequenceI> rseqs = new ArrayList<SequenceI>();
214     Alignment ral = null;
215     AlignedCodonFrame cf = new AlignedCodonFrame(); // nominal width
216     for (int s = 0; s < seqs.length; s++)
217     {
218       SequenceI dss = seqs[s];
219       while (dss.getDatasetSequence() != null)
220       {
221         dss = dss.getDatasetSequence();
222       }
223       boolean found = false;
224       DBRefEntry[] xrfs = CrossRef.findXDbRefs(dna, dss.getDBRef());
225       if ((xrfs == null || xrfs.length == 0) && dataset != null)
226       {
227         System.out.println("Attempting to find ds Xrefs refs.");
228         DBRefEntry[] lrfs = CrossRef.findXDbRefs(!dna, seqs[s].getDBRef());
229         // less ambiguous would be a 'find primary dbRefEntry' method.
230         // filter for desired source xref here
231         found = CrossRef.searchDatasetXrefs(dss, !dna, lrfs, dataset,
232                 rseqs, cf);
233       }
234       for (int r = 0; xrfs != null && r < xrfs.length; r++)
235       {
236         if (source != null && !source.equals(xrfs[r].getSource()))
237         {
238           continue;
239         }
240         if (xrfs[r].hasMap())
241         {
242           if (xrfs[r].getMap().getTo() != null)
243           {
244             SequenceI rsq = new Sequence(xrfs[r].getMap().getTo());
245             rseqs.add(rsq);
246             if (xrfs[r].getMap().getMap().getFromRatio() != xrfs[r]
247                     .getMap().getMap().getToRatio())
248             {
249               // get sense of map correct for adding to product alignment.
250               if (dna)
251               {
252                 // map is from dna seq to a protein product
253                 cf.addMap(dss, rsq, xrfs[r].getMap().getMap());
254               }
255               else
256               {
257                 // map should be from protein seq to its coding dna
258                 cf.addMap(rsq, dss, xrfs[r].getMap().getMap().getInverse());
259               }
260             }
261             found = true;
262           }
263         }
264         if (!found)
265         {
266           // do a bit more work - search for sequences with references matching
267           // xrefs on this sequence.
268           if (dataset != null)
269           {
270             found |= searchDataset(dss, xrfs[r], dataset, rseqs, cf); // ,false,!dna);
271             if (found)
272              {
273               xrfs[r] = null; // we've recovered seqs for this one.
274             }
275           }
276         }
277       }
278       if (!found)
279       {
280         if (xrfs != null && xrfs.length > 0)
281         {
282           // Try and get the sequence reference...
283           /*
284            * Ideal world - we ask for a sequence fetcher implementation here if
285            * (jalview.io.RunTimeEnvironment.getSequenceFetcher()) (
286            */
287           ASequenceFetcher sftch = new SequenceFetcher();
288           SequenceI[] retrieved = null;
289           int l = xrfs.length;
290           for (int r = 0; r < xrfs.length; r++)
291           {
292             // filter out any irrelevant or irretrievable references
293             if (xrfs[r] == null
294                     || ((source != null && !source.equals(xrfs[r]
295                             .getSource())) || !sftch.isFetchable(xrfs[r]
296                             .getSource())))
297             {
298               l--;
299               xrfs[r] = null;
300             }
301           }
302           if (l > 0)
303           {
304             System.out
305                     .println("Attempting to retrieve cross referenced sequences.");
306             DBRefEntry[] t = new DBRefEntry[l];
307             l = 0;
308             for (int r = 0; r < xrfs.length; r++)
309             {
310               if (xrfs[r] != null)
311               {
312                 t[l++] = xrfs[r];
313               }
314             }
315             xrfs = t;
316             try
317             {
318               retrieved = sftch.getSequences(xrfs); // problem here is we don't
319               // know which of xrfs
320               // resulted in which
321               // retrieved element
322             } catch (Exception e)
323             {
324               System.err
325                       .println("Problem whilst retrieving cross references for Sequence : "
326                               + seqs[s].getName());
327               e.printStackTrace();
328             }
329             if (retrieved != null)
330             {
331               for (int rs = 0; rs < retrieved.length; rs++)
332               {
333                 // TODO: examine each sequence for 'redundancy'
334                 DBRefEntry[] dbr = retrieved[rs]
335                         .getDBRef();
336                 if (dbr != null && dbr.length > 0)
337                 {
338                   for (int di = 0; di < dbr.length; di++)
339                   {
340                     // find any entry where we should put in the sequence being
341                     // cross-referenced into the map
342                     Mapping map = dbr[di].getMap();
343                     if (map != null)
344                     {
345                       if (map.getTo() != null && map.getMap() != null)
346                       {
347                         // should search the local dataset to find any existing
348                         // candidates for To !
349                         try
350                         {
351                           // compare ms with dss and replace with dss in mapping
352                           // if map is congruent
353                           SequenceI ms = map.getTo();
354                           int sf = map.getMap().getToLowest();
355                           int st = map.getMap().getToHighest();
356                           SequenceI mappedrg = ms.getSubSequence(sf, st);
357                           SequenceI loc = dss.getSubSequence(sf, st);
358                           if (mappedrg.getLength() > 0
359                                   && mappedrg.getSequenceAsString().equals(
360                                           loc.getSequenceAsString()))
361                           {
362                             System.err
363                                     .println("Mapping updated for retrieved crossreference");
364                             // method to update all refs of existing To on
365                             // retrieved sequence with dss and merge any props
366                             // on To onto dss.
367                             map.setTo(dss);
368                           }
369                         } catch (Exception e)
370                         {
371                           System.err
372                                   .println("Exception when consolidating Mapped sequence set...");
373                           e.printStackTrace(System.err);
374                         }
375                       }
376                     }
377                   }
378                 }
379                 retrieved[rs].updatePDBIds();
380                 rseqs.add(retrieved[rs]);
381               }
382             }
383           }
384         }
385       }
386     }
387     if (rseqs.size() > 0)
388     {
389       SequenceI[] rsqs = new SequenceI[rseqs.size()];
390       rseqs.toArray(rsqs);
391       ral = new Alignment(rsqs);
392       if (cf != null && cf.getProtMappings() != null)
393       {
394         ral.addCodonFrame(cf);
395       }
396     }
397     return ral;
398   }
399
400   /**
401    * find references to lrfs in the cross-reference set of each sequence in
402    * dataset (that is not equal to sequenceI) Identifies matching DBRefEntry
403    * based on source and accession string only - Map and Version are nulled.
404    * 
405    * @param sequenceI
406    * @param lrfs
407    * @param dataset
408    * @param rseqs
409    * @return true if matches were found.
410    */
411   private static boolean searchDatasetXrefs(SequenceI sequenceI,
412           boolean dna, DBRefEntry[] lrfs, AlignmentI dataset,
413           List<SequenceI> rseqs,
414           AlignedCodonFrame cf)
415   {
416     boolean found = false;
417     if (lrfs == null)
418     {
419       return false;
420     }
421     for (int i = 0; i < lrfs.length; i++)
422     {
423       DBRefEntry xref = new DBRefEntry(lrfs[i]);
424       // add in wildcards
425       xref.setVersion(null);
426       xref.setMap(null);
427       found = searchDataset(sequenceI, xref, dataset, rseqs, cf, false, dna);
428     }
429     return found;
430   }
431
432   /**
433    * search a given sequence dataset for references matching cross-references to
434    * the given sequence
435    * 
436    * @param sequenceI
437    * @param xrf
438    * @param dataset
439    * @param rseqs
440    *          set of unique sequences
441    * @param cf
442    * @return true if one or more unique sequences were found and added
443    */
444   public static boolean searchDataset(SequenceI sequenceI, DBRefEntry xrf,
445           AlignmentI dataset, List<SequenceI> rseqs, AlignedCodonFrame cf)
446   {
447     return searchDataset(sequenceI, xrf, dataset, rseqs, cf, true, false);
448   }
449
450   /**
451    * TODO: generalise to different protein classifications Search dataset for
452    * DBRefEntrys matching the given one (xrf) and add the associated sequence to
453    * rseq.
454    * 
455    * @param sequenceI
456    * @param xrf
457    * @param dataset
458    * @param rseqs
459    * @param direct
460    *          - search all references or only subset
461    * @param dna
462    *          search dna or protein xrefs (if direct=false)
463    * @return true if relationship found and sequence added.
464    */
465   public static boolean searchDataset(SequenceI sequenceI, DBRefEntry xrf,
466           AlignmentI dataset, List<SequenceI> rseqs, AlignedCodonFrame cf,
467           boolean direct, boolean dna)
468   {
469     boolean found = false;
470     SequenceI[] typer = new SequenceI[1];
471     if (dataset == null)
472     {
473       return false;
474     }
475     if (dataset.getSequences() == null)
476     {
477       System.err.println("Empty dataset sequence set - NO VECTOR");
478       return false;
479     }
480     List<SequenceI> ds;
481     synchronized (ds = dataset.getSequences())
482     {
483       for (SequenceI nxt : ds)
484       {
485         if (nxt != null)
486         {
487           if (nxt.getDatasetSequence() != null)
488           {
489             System.err
490                     .println("Implementation warning: getProducts passed a dataset alignment without dataset sequences in it!");
491           }
492           if (nxt != sequenceI && nxt != sequenceI.getDatasetSequence())
493           {
494             // check if this is the correct sequence type
495             {
496               typer[0] = nxt;
497               boolean isDna = Comparison.isNucleotide(typer);
498               if ((direct && isDna == dna) || (!direct && isDna != dna))
499               {
500                 // skip this sequence because it is same molecule type
501                 continue;
502               }
503             }
504
505             // look for direct or indirect references in common
506             DBRefEntry[] poss = nxt.getDBRef(), cands = null;
507             if (direct)
508             {
509               cands = DBRefUtils.searchRefs(poss, xrf);
510             }
511             else
512             {
513               poss = CrossRef.findXDbRefs(dna, poss); //
514               cands = DBRefUtils.searchRefs(poss, xrf);
515             }
516             if (cands != null)
517             {
518               if (!rseqs.contains(nxt))
519               {
520                 rseqs.add(nxt);
521                 boolean foundmap = cf != null;
522                 // don't search if we aren't given a codon map object
523                 for (int r = 0; foundmap && r < cands.length; r++)
524                 {
525                   if (cands[r].hasMap())
526                   {
527                     if (cands[r].getMap().getTo() != null
528                             && cands[r].getMap().getMap().getFromRatio() != cands[r]
529                                     .getMap().getMap().getToRatio())
530                     {
531                       foundmap = true;
532                       // get sense of map correct for adding to product
533                       // alignment.
534                       if (dna)
535                       {
536                         // map is from dna seq to a protein product
537                         cf.addMap(sequenceI, nxt, cands[r].getMap()
538                                 .getMap());
539                       }
540                       else
541                       {
542                         // map should be from protein seq to its coding dna
543                         cf.addMap(nxt, sequenceI, cands[r].getMap()
544                                 .getMap().getInverse());
545                       }
546                     }
547                   }
548                 }
549                 // TODO: add mapping between sequences if necessary
550                 found = true;
551               }
552             }
553
554           }
555         }
556       }
557     }
558     return found;
559   }
560
561   /**
562    * precalculate different products that can be found for seqs in dataset and
563    * return them.
564    * 
565    * @param dna
566    * @param seqs
567    * @param dataset
568    * @param fake
569    *          - don't actually build lists - just get types
570    * @return public static Object[] buildXProductsList(boolean dna, SequenceI[]
571    *         seqs, AlignmentI dataset, boolean fake) { String types[] =
572    *         jalview.analysis.CrossRef.findSequenceXrefTypes( dna, seqs,
573    *         dataset); if (types != null) { System.out.println("Xref Types for:
574    *         "+(dna ? "dna" : "prot")); for (int t = 0; t < types.length; t++) {
575    *         System.out.println("Type: " + types[t]); SequenceI[] prod =
576    *         jalview.analysis.CrossRef.findXrefSequences(seqs, dna, types[t]);
577    *         System.out.println("Found " + ((prod == null) ? "no" : "" +
578    *         prod.length) + " products"); if (prod!=null) { for (int p=0;
579    *         p<prod.length; p++) { System.out.println("Prod "+p+":
580    *         "+prod[p].getDisplayId(true)); } } } } else {
581    *         System.out.println("Trying getProducts for
582    *         "+al.getSequenceAt(0).getDisplayId(true));
583    *         System.out.println("Search DS Xref for: "+(dna ? "dna" : "prot"));
584    *         // have a bash at finding the products amongst all the retrieved
585    *         sequences. SequenceI[] prod =
586    *         jalview.analysis.CrossRef.findXrefSequences(al
587    *         .getSequencesArray(), dna, null, ds); System.out.println("Found " +
588    *         ((prod == null) ? "no" : "" + prod.length) + " products"); if
589    *         (prod!=null) { // select non-equivalent sequences from dataset list
590    *         for (int p=0; p<prod.length; p++) { System.out.println("Prod "+p+":
591    *         "+prod[p].getDisplayId(true)); } } } }
592    */
593 }