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