f414a9c4c87d0b0336b88b408821633e17585c8d
[jalview.git] / src / jalview / util / DBRefUtils.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.util;
22
23 import jalview.datamodel.DBRefEntry;
24 import jalview.datamodel.DBRefSource;
25 import jalview.datamodel.PDBEntry;
26 import jalview.datamodel.SequenceI;
27
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.HashMap;
31 import java.util.HashSet;
32 import java.util.Hashtable;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Set;
36
37 import com.stevesoft.pat.Regex;
38
39 /**
40  * Utilities for handling DBRef objects and their collections.
41  */
42 public class DBRefUtils
43 {
44   /*
45    * lookup from lower-case form of a name to its canonical (standardised) form
46    */
47   private static Map<String, String> canonicalSourceNameLookup = new HashMap<String, String>();
48
49   private static Map<String, String> dasCoordinateSystemsLookup = new HashMap<String, String>();
50
51   static
52   {
53     // TODO load these from a resource file?
54     canonicalSourceNameLookup.put("uniprotkb/swiss-prot",
55             DBRefSource.UNIPROT);
56     canonicalSourceNameLookup.put("uniprotkb/trembl", DBRefSource.UNIPROT);
57
58     // Ensembl values for dbname in xref REST service:
59     canonicalSourceNameLookup.put("uniprot/sptrembl", DBRefSource.UNIPROT);
60     canonicalSourceNameLookup.put("uniprot/swissprot", DBRefSource.UNIPROT);
61
62     canonicalSourceNameLookup.put("pdb", DBRefSource.PDB);
63     canonicalSourceNameLookup.put("ensembl", DBRefSource.ENSEMBL);
64     // Ensembl Gn and Tr are for Ensembl genomic and transcript IDs as served
65     // from ENA.
66     canonicalSourceNameLookup.put("ensembl-tr", DBRefSource.ENSEMBL);
67     canonicalSourceNameLookup.put("ensembl-gn", DBRefSource.ENSEMBL);
68
69     // Make sure we have lowercase entries for all canonical string lookups
70     Set<String> keys = canonicalSourceNameLookup.keySet();
71     for (String k : keys)
72     {
73       canonicalSourceNameLookup.put(k.toLowerCase(),
74               canonicalSourceNameLookup.get(k));
75     }
76
77     dasCoordinateSystemsLookup.put("pdbresnum", DBRefSource.PDB);
78     dasCoordinateSystemsLookup.put("uniprot", DBRefSource.UNIPROT);
79     dasCoordinateSystemsLookup.put("embl", DBRefSource.EMBL);
80     // dasCoordinateSystemsLookup.put("embl", DBRefSource.EMBLCDS);
81   }
82
83   /**
84    * Returns those DBRefEntry objects whose source identifier (once converted to
85    * Jalview's canonical form) is in the list of sources to search for. Returns
86    * null if no matches found.
87    * 
88    * @param dbrefs
89    *          DBRefEntry objects to search
90    * @param sources
91    *          array of sources to select
92    * @return
93    */
94   public static DBRefEntry[] selectRefs(DBRefEntry[] dbrefs,
95           String[] sources)
96   {
97     if (dbrefs == null || sources == null)
98     {
99       return dbrefs;
100     }
101     HashSet<String> srcs = new HashSet<String>();
102     for (String src : sources)
103     {
104       srcs.add(src);
105     }
106
107     List<DBRefEntry> res = new ArrayList<DBRefEntry>();
108     for (DBRefEntry dbr : dbrefs)
109     {
110       String source = getCanonicalName(dbr.getSource());
111       if (srcs.contains(source))
112       {
113         res.add(dbr);
114       }
115     }
116
117     if (res.size() > 0)
118     {
119       DBRefEntry[] reply = new DBRefEntry[res.size()];
120       return res.toArray(reply);
121     }
122     return null;
123   }
124
125   /**
126    * isDasCoordinateSystem
127    * 
128    * @param string
129    *          String
130    * @param dBRefEntry
131    *          DBRefEntry
132    * @return boolean true if Source DBRefEntry is compatible with DAS
133    *         CoordinateSystem name
134    */
135
136   public static boolean isDasCoordinateSystem(String string,
137           DBRefEntry dBRefEntry)
138   {
139     if (string == null || dBRefEntry == null)
140     {
141       return false;
142     }
143     String coordsys = dasCoordinateSystemsLookup.get(string.toLowerCase());
144     return coordsys == null ? false : coordsys.equals(dBRefEntry
145             .getSource());
146   }
147
148   /**
149    * look up source in an internal list of database reference sources and return
150    * the canonical jalview name for the source, or the original string if it has
151    * no canonical form.
152    * 
153    * @param source
154    * @return canonical jalview source (one of jalview.datamodel.DBRefSource.*)
155    *         or original source
156    */
157   public static String getCanonicalName(String source)
158   {
159     if (source == null)
160     {
161       return null;
162     }
163     String canonical = canonicalSourceNameLookup.get(source.toLowerCase());
164     return canonical == null ? source : canonical;
165   }
166
167   /**
168    * Returns a (possibly empty) list of those references that match the given
169    * entry. Currently uses a comparator which matches if
170    * <ul>
171    * <li>database sources are the same</li>
172    * <li>accession ids are the same</li>
173    * <li>both have no mapping, or the mappings are the same</li>
174    * </ul>
175    * 
176    * @param ref
177    *          Set of references to search
178    * @param entry
179    *          pattern to match
180    * @return
181    */
182   public static List<DBRefEntry> searchRefs(DBRefEntry[] ref,
183           DBRefEntry entry)
184   {
185     return searchRefs(ref, entry,
186             matchDbAndIdAndEitherMapOrEquivalentMapList);
187   }
188
189   /**
190    * Returns a list of those references that match the given accession id
191    * <ul>
192    * <li>database sources are the same</li>
193    * <li>accession ids are the same</li>
194    * <li>both have no mapping, or the mappings are the same</li>
195    * </ul>
196    * 
197    * @param refs
198    *          Set of references to search
199    * @param accId
200    *          accession id to match
201    * @return
202    */
203   public static List<DBRefEntry> searchRefs(DBRefEntry[] refs, String accId)
204   {
205     return searchRefs(refs, new DBRefEntry("", "", accId), matchId);
206   }
207
208   /**
209    * Returns a (possibly empty) list of those references that match the given
210    * entry, according to the given comparator.
211    * 
212    * @param refs
213    *          an array of database references to search
214    * @param entry
215    *          an entry to compare against
216    * @param comparator
217    * @return
218    */
219   static List<DBRefEntry> searchRefs(DBRefEntry[] refs, DBRefEntry entry,
220           DbRefComp comparator)
221   {
222     List<DBRefEntry> rfs = new ArrayList<DBRefEntry>();
223     if (refs == null || entry == null)
224     {
225       return rfs;
226     }
227     for (int i = 0; i < refs.length; i++)
228     {
229       if (comparator.matches(entry, refs[i]))
230       {
231         rfs.add(refs[i]);
232       }
233     }
234     return rfs;
235   }
236
237   interface DbRefComp
238   {
239     public boolean matches(DBRefEntry refa, DBRefEntry refb);
240   }
241
242   /**
243    * match on all non-null fields in refa
244    */
245   // TODO unused - remove?
246   public static DbRefComp matchNonNullonA = new DbRefComp()
247   {
248     @Override
249     public boolean matches(DBRefEntry refa, DBRefEntry refb)
250     {
251       if (refa.getSource() == null
252               || DBRefUtils.getCanonicalName(refb.getSource()).equals(
253                       DBRefUtils.getCanonicalName(refa.getSource())))
254       {
255         if (refa.getVersion() == null
256                 || refb.getVersion().equals(refa.getVersion()))
257         {
258           if (refa.getAccessionId() == null
259                   || refb.getAccessionId().equals(refa.getAccessionId()))
260           {
261             if (refa.getMap() == null
262                     || (refb.getMap() != null && refb.getMap().equals(
263                             refa.getMap())))
264             {
265               return true;
266             }
267           }
268         }
269       }
270       return false;
271     }
272   };
273
274   /**
275    * either field is null or field matches for all of source, version, accession
276    * id and map.
277    */
278   // TODO unused - remove?
279   public static DbRefComp matchEitherNonNull = new DbRefComp()
280   {
281     @Override
282     public boolean matches(DBRefEntry refa, DBRefEntry refb)
283     {
284       if (nullOrEqualSource(refa.getSource(), refb.getSource())
285               && nullOrEqual(refa.getVersion(), refb.getVersion())
286               && nullOrEqual(refa.getAccessionId(), refb.getAccessionId())
287               && nullOrEqual(refa.getMap(), refb.getMap()))
288       {
289         return true;
290       }
291       return false;
292     }
293   };
294
295   /**
296    * accession ID and DB must be identical. Version is ignored. Map is either
297    * not defined or is a match (or is compatible?)
298    */
299   // TODO unused - remove?
300   public static DbRefComp matchDbAndIdAndEitherMap = new DbRefComp()
301   {
302     @Override
303     public boolean matches(DBRefEntry refa, DBRefEntry refb)
304     {
305       if (refa.getSource() != null && refb.getSource() != null
306               && DBRefUtils.getCanonicalName(refb.getSource()).equals(
307                       DBRefUtils.getCanonicalName(refa.getSource())))
308       {
309         // We dont care about version
310         if (refa.getAccessionId() != null && refb.getAccessionId() != null
311         // FIXME should be && not || here?
312                 || refb.getAccessionId().equals(refa.getAccessionId()))
313         {
314           if ((refa.getMap() == null || refb.getMap() == null)
315                   || (refa.getMap() != null && refb.getMap() != null && refb
316                           .getMap().equals(refa.getMap())))
317           {
318             return true;
319           }
320         }
321       }
322       return false;
323     }
324   };
325
326   /**
327    * accession ID and DB must be identical. Version is ignored. No map on either
328    * or map but no maplist on either or maplist of map on a is the complement of
329    * maplist of map on b.
330    */
331   // TODO unused - remove?
332   public static DbRefComp matchDbAndIdAndComplementaryMapList = new DbRefComp()
333   {
334     @Override
335     public boolean matches(DBRefEntry refa, DBRefEntry refb)
336     {
337       if (refa.getSource() != null && refb.getSource() != null
338               && DBRefUtils.getCanonicalName(refb.getSource()).equals(
339                       DBRefUtils.getCanonicalName(refa.getSource())))
340       {
341         // We dont care about version
342         if (refa.getAccessionId() != null && refb.getAccessionId() != null
343                 || refb.getAccessionId().equals(refa.getAccessionId()))
344         {
345           if ((refa.getMap() == null && refb.getMap() == null)
346                   || (refa.getMap() != null && refb.getMap() != null))
347           {
348             if ((refb.getMap().getMap() == null && refa.getMap().getMap() == null)
349                     || (refb.getMap().getMap() != null
350                             && refa.getMap().getMap() != null && refb
351                             .getMap().getMap().getInverse()
352                             .equals(refa.getMap().getMap())))
353             {
354               return true;
355             }
356           }
357         }
358       }
359       return false;
360     }
361   };
362
363   /**
364    * accession ID and DB must be identical. Version is ignored. No map on both
365    * or or map but no maplist on either or maplist of map on a is equivalent to
366    * the maplist of map on b.
367    */
368   // TODO unused - remove?
369   public static DbRefComp matchDbAndIdAndEquivalentMapList = new DbRefComp()
370   {
371     @Override
372     public boolean matches(DBRefEntry refa, DBRefEntry refb)
373     {
374       if (refa.getSource() != null && refb.getSource() != null
375               && DBRefUtils.getCanonicalName(refb.getSource()).equals(
376                       DBRefUtils.getCanonicalName(refa.getSource())))
377       {
378         // We dont care about version
379         // if ((refa.getVersion()==null || refb.getVersion()==null)
380         // || refb.getVersion().equals(refa.getVersion()))
381         // {
382         if (refa.getAccessionId() != null && refb.getAccessionId() != null
383                 || refb.getAccessionId().equals(refa.getAccessionId()))
384         {
385           if (refa.getMap() == null && refb.getMap() == null)
386           {
387             return true;
388           }
389           if (refa.getMap() != null
390                   && refb.getMap() != null
391                   && ((refb.getMap().getMap() == null && refa.getMap()
392                           .getMap() == null) || (refb.getMap().getMap() != null
393                           && refa.getMap().getMap() != null && refb
394                           .getMap().getMap().equals(refa.getMap().getMap()))))
395           {
396             return true;
397           }
398         }
399       }
400       return false;
401     }
402   };
403
404   /**
405    * accession ID and DB must be identical, or null on a. Version is ignored. No
406    * map on either or map but no maplist on either or maplist of map on a is
407    * equivalent to the maplist of map on b.
408    */
409   public static DbRefComp matchDbAndIdAndEitherMapOrEquivalentMapList = new DbRefComp()
410   {
411     @Override
412     public boolean matches(DBRefEntry refa, DBRefEntry refb)
413     {
414       if (refa.getSource() != null && refb.getSource() != null
415               && DBRefUtils.getCanonicalName(refb.getSource()).equals(
416                       DBRefUtils.getCanonicalName(refa.getSource())))
417       {
418         // We dont care about version
419
420         if (refa.getAccessionId() == null
421                 || refa.getAccessionId().equals(refb.getAccessionId()))
422         {
423           if (refa.getMap() == null || refb.getMap() == null)
424           {
425             return true;
426           }
427           if ((refa.getMap() != null && refb.getMap() != null)
428                   && (refb.getMap().getMap() == null && refa.getMap()
429                           .getMap() == null)
430                   || (refb.getMap().getMap() != null
431                           && refa.getMap().getMap() != null && (refb
432                           .getMap().getMap().equals(refa.getMap().getMap()))))
433           {
434             return true;
435           }
436         }
437       }
438       return false;
439     }
440   };
441
442   /**
443    * accession ID only must be identical.
444    */
445   public static DbRefComp matchId = new DbRefComp()
446   {
447     @Override
448     public boolean matches(DBRefEntry refa, DBRefEntry refb)
449     {
450       if (refa.getAccessionId() != null && refb.getAccessionId() != null
451               && refb.getAccessionId().equals(refa.getAccessionId()))
452       {
453         return true;
454       }
455       return false;
456     }
457   };
458
459   /**
460    * Parses a DBRefEntry and adds it to the sequence, also a PDBEntry if the
461    * database is PDB.
462    * <p>
463    * Used by file parsers to generate DBRefs from annotation within file (eg
464    * Stockholm)
465    * 
466    * @param dbname
467    * @param version
468    * @param acn
469    * @param seq
470    *          where to annotate with reference
471    * @return parsed version of entry that was added to seq (if any)
472    */
473   public static DBRefEntry parseToDbRef(SequenceI seq, String dbname,
474           String version, String acn)
475   {
476     DBRefEntry ref = null;
477     if (dbname != null)
478     {
479       String locsrc = DBRefUtils.getCanonicalName(dbname);
480       if (locsrc.equals(DBRefSource.PDB))
481       {
482         /*
483          * Check for PFAM style stockhom PDB accession id citation e.g.
484          * "1WRI A; 7-80;"
485          */
486         Regex r = new com.stevesoft.pat.Regex(
487                 "([0-9][0-9A-Za-z]{3})\\s*(.?)\\s*;\\s*([0-9]+)-([0-9]+)");
488         if (r.search(acn.trim()))
489         {
490           String pdbid = r.stringMatched(1);
491           String chaincode = r.stringMatched(2);
492           if (chaincode == null)
493           {
494             chaincode = " ";
495           }
496           // String mapstart = r.stringMatched(3);
497           // String mapend = r.stringMatched(4);
498           if (chaincode.equals(" "))
499           {
500             chaincode = "_";
501           }
502           // construct pdb ref.
503           ref = new DBRefEntry(locsrc, version, pdbid + chaincode);
504           PDBEntry pdbr = new PDBEntry();
505           pdbr.setId(pdbid);
506           pdbr.setType(PDBEntry.Type.PDB);
507           pdbr.setProperty(new Hashtable());
508           pdbr.setChainCode(chaincode);
509           // pdbr.getProperty().put("CHAIN", chaincode);
510           seq.addPDBId(pdbr);
511         }
512         else
513         {
514           System.err.println("Malformed PDB DR line:" + acn);
515         }
516       }
517       else
518       {
519         // default:
520         ref = new DBRefEntry(locsrc, version, acn);
521       }
522     }
523     if (ref != null)
524     {
525       seq.addDBRef(ref);
526     }
527     return ref;
528   }
529
530   /**
531    * Returns true if either object is null, or they are equal
532    * 
533    * @param o1
534    * @param o2
535    * @return
536    */
537   public static boolean nullOrEqual(Object o1, Object o2)
538   {
539     if (o1 == null || o2 == null)
540     {
541       return true;
542     }
543     return o1.equals(o2);
544   }
545
546   /**
547    * canonicalise source string before comparing. null is always wildcard
548    * 
549    * @param o1
550    *          - null or source string to compare
551    * @param o2
552    *          - null or source string to compare
553    * @return true if either o1 or o2 are null, or o1 equals o2 under
554    *         DBRefUtils.getCanonicalName
555    *         (o1).equals(DBRefUtils.getCanonicalName(o2))
556    */
557   public static boolean nullOrEqualSource(String o1, String o2)
558   {
559     if (o1 == null || o2 == null)
560     {
561       return true;
562     }
563     return DBRefUtils.getCanonicalName(o1).equals(
564             DBRefUtils.getCanonicalName(o2));
565   }
566
567   /**
568    * Selects just the DNA or protein references from a set of references
569    * 
570    * @param selectDna
571    *          if true, select references to 'standard' DNA databases, else to
572    *          'standard' peptide databases
573    * @param refs
574    *          a set of references to select from
575    * @return
576    */
577   public static DBRefEntry[] selectDbRefs(boolean selectDna,
578           DBRefEntry[] refs)
579   {
580     return selectRefs(refs, selectDna ? DBRefSource.DNACODINGDBS
581             : DBRefSource.PROTEINDBS);
582     // could attempt to find other cross
583     // refs here - ie PDB xrefs
584     // (not dna, not protein seq)
585   }
586
587   /**
588    * Returns the (possibly empty) list of those supplied dbrefs which have the
589    * specified source database, with a case-insensitive match of source name
590    * 
591    * @param dbRefs
592    * @param source
593    * @return
594    */
595   public static List<DBRefEntry> searchRefsForSource(DBRefEntry[] dbRefs,
596           String source)
597   {
598     List<DBRefEntry> matches = new ArrayList<DBRefEntry>();
599     if (dbRefs != null && source != null)
600     {
601       for (DBRefEntry dbref : dbRefs)
602       {
603         if (source.equalsIgnoreCase(dbref.getSource()))
604         {
605           matches.add(dbref);
606         }
607       }
608     }
609     return matches;
610   }
611
612   /**
613    * promote direct database references to primary for nucleotide or protein
614    * sequences if they have an appropriate primary ref
615    * <table>
616    * <tr>
617    * <td>Seq Type</td>
618    * <td>Primary DB</td>
619    * <td>Direct which will be promoted</td>
620    * </tr>
621    * <tr>
622    * <td>peptides</td>
623    * <td>Ensembl</td>
624    * <td>Uniprot</td>
625    * </tr>
626    * <tr>
627    * <td>peptides</td>
628    * <td>Ensembl</td>
629    * <td>Uniprot</td>
630    * </tr>
631    * <tr>
632    * <td>dna</td>
633    * <td>Ensembl</td>
634    * <td>ENA</td>
635    * </tr>
636    * </table>
637    * 
638    * @param sequence
639    */
640   public static void ensurePrimaries(SequenceI sequence)
641   {
642     List<DBRefEntry> pr = sequence.getPrimaryDBRefs();
643     if (pr.size() == 0)
644     {
645       // nothing to do
646       return;
647     }
648     List<DBRefEntry> selfs = new ArrayList<DBRefEntry>();
649     selfs.addAll(Arrays.asList(selectDbRefs(!sequence.isProtein(),
650             sequence.getDBRefs())));
651
652     // filter non-primary refs
653     for (DBRefEntry p : pr)
654     {
655       while (selfs.contains(p))
656       {
657         selfs.remove(p);
658       }
659     }
660     List<DBRefEntry> toPromote = new ArrayList<DBRefEntry>();
661
662     for (DBRefEntry p : pr)
663     {
664       List<String> promType = new ArrayList<String>();
665       if (sequence.isProtein())
666       {
667         switch (getCanonicalName(p.getSource()))
668         {
669         case DBRefSource.UNIPROT:
670           // case DBRefSource.UNIPROTKB:
671           // case DBRefSource.UP_NAME:
672           // search for and promote ensembl
673           promType.add(DBRefSource.ENSEMBL);
674           break;
675         case DBRefSource.ENSEMBL:
676           // search for and promote Uniprot
677           promType.add(DBRefSource.UNIPROT);
678           break;
679         }
680       }
681       else
682       {
683         // TODO: promote transcript refs
684       }
685
686       // collate candidates and promote them
687       DBRefEntry[] candidates = selectRefs(
688               selfs.toArray(new DBRefEntry[0]),
689               promType.toArray(new String[0]));
690       if (candidates != null)
691       {
692         for (DBRefEntry cand : candidates)
693         {
694           if (cand.hasMap())
695           {
696             if (cand.getMap().getTo() != null
697                     && cand.getMap().getTo() != sequence)
698             {
699               // can't promote refs with mappings to other sequences
700               continue;
701             }
702             if (cand.getMap().getMap().getFromLowest() != sequence
703                     .getStart()
704                     && cand.getMap().getMap().getFromHighest() != sequence
705                             .getEnd())
706             {
707               // can't promote refs with mappings from a region of this sequence
708               // - eg CDS
709               continue;
710             }
711           }
712           // and promote
713           cand.setVersion(p.getVersion() + " (promoted)");
714           selfs.remove(cand);
715           toPromote.add(cand);
716           if (!cand.isPrimaryCandidate())
717           {
718             System.out.println("Warning: Couldn't promote dbref "
719                     + cand.toString() + " for sequence "
720                     + sequence.toString());
721           }
722         }
723       }
724     }
725   }
726
727 }