JAL-2210 formatting
[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
306               && refb.getSource() != null
307               && DBRefUtils.getCanonicalName(refb.getSource()).equals(
308                       DBRefUtils.getCanonicalName(refa.getSource())))
309       {
310         // We dont care about version
311         if (refa.getAccessionId() != null && refb.getAccessionId() != null
312         // FIXME should be && not || here?
313                 || refb.getAccessionId().equals(refa.getAccessionId()))
314         {
315           if ((refa.getMap() == null || refb.getMap() == null)
316                   || (refa.getMap() != null && refb.getMap() != null && refb
317                           .getMap().equals(refa.getMap())))
318           {
319             return true;
320           }
321         }
322       }
323       return false;
324     }
325   };
326
327   /**
328    * accession ID and DB must be identical. Version is ignored. No map on either
329    * or map but no maplist on either or maplist of map on a is the complement of
330    * maplist of map on b.
331    */
332   // TODO unused - remove?
333   public static DbRefComp matchDbAndIdAndComplementaryMapList = new DbRefComp()
334   {
335     @Override
336     public boolean matches(DBRefEntry refa, DBRefEntry refb)
337     {
338       if (refa.getSource() != null
339               && refb.getSource() != null
340               && DBRefUtils.getCanonicalName(refb.getSource()).equals(
341                       DBRefUtils.getCanonicalName(refa.getSource())))
342       {
343         // We dont care about version
344         if (refa.getAccessionId() != null && refb.getAccessionId() != null
345                 || refb.getAccessionId().equals(refa.getAccessionId()))
346         {
347           if ((refa.getMap() == null && refb.getMap() == null)
348                   || (refa.getMap() != null && refb.getMap() != null))
349           {
350             if ((refb.getMap().getMap() == null && refa.getMap().getMap() == null)
351                     || (refb.getMap().getMap() != null
352                             && refa.getMap().getMap() != null && refb
353                             .getMap().getMap().getInverse()
354                             .equals(refa.getMap().getMap())))
355             {
356               return true;
357             }
358           }
359         }
360       }
361       return false;
362     }
363   };
364
365   /**
366    * accession ID and DB must be identical. Version is ignored. No map on both
367    * or or map but no maplist on either or maplist of map on a is equivalent to
368    * the maplist of map on b.
369    */
370   // TODO unused - remove?
371   public static DbRefComp matchDbAndIdAndEquivalentMapList = new DbRefComp()
372   {
373     @Override
374     public boolean matches(DBRefEntry refa, DBRefEntry refb)
375     {
376       if (refa.getSource() != null
377               && refb.getSource() != null
378               && DBRefUtils.getCanonicalName(refb.getSource()).equals(
379                       DBRefUtils.getCanonicalName(refa.getSource())))
380       {
381         // We dont care about version
382         // if ((refa.getVersion()==null || refb.getVersion()==null)
383         // || refb.getVersion().equals(refa.getVersion()))
384         // {
385         if (refa.getAccessionId() != null && refb.getAccessionId() != null
386                 || refb.getAccessionId().equals(refa.getAccessionId()))
387         {
388           if (refa.getMap() == null && refb.getMap() == null)
389           {
390             return true;
391           }
392           if (refa.getMap() != null
393                   && refb.getMap() != null
394                   && ((refb.getMap().getMap() == null && refa.getMap()
395                           .getMap() == null) || (refb.getMap().getMap() != null
396                           && refa.getMap().getMap() != null && refb
397                           .getMap().getMap().equals(refa.getMap().getMap()))))
398           {
399             return true;
400           }
401         }
402       }
403       return false;
404     }
405   };
406
407   /**
408    * accession ID and DB must be identical, or null on a. Version is ignored. No
409    * map on either or map but no maplist on either or maplist of map on a is
410    * equivalent to the maplist of map on b.
411    */
412   public static DbRefComp matchDbAndIdAndEitherMapOrEquivalentMapList = new DbRefComp()
413   {
414     @Override
415     public boolean matches(DBRefEntry refa, DBRefEntry refb)
416     {
417       if (refa.getSource() != null
418               && refb.getSource() != null
419               && DBRefUtils.getCanonicalName(refb.getSource()).equals(
420                       DBRefUtils.getCanonicalName(refa.getSource())))
421       {
422         // We dont care about version
423
424         if (refa.getAccessionId() == null
425                 || refa.getAccessionId().equals(refb.getAccessionId()))
426         {
427           if (refa.getMap() == null || refb.getMap() == null)
428           {
429             return true;
430           }
431           if ((refa.getMap() != null && refb.getMap() != null)
432                   && (refb.getMap().getMap() == null && refa.getMap()
433                           .getMap() == null)
434                   || (refb.getMap().getMap() != null
435                           && refa.getMap().getMap() != null && (refb
436                           .getMap().getMap().equals(refa.getMap().getMap()))))
437           {
438             return true;
439           }
440         }
441       }
442       return false;
443     }
444   };
445
446   /**
447    * accession ID only must be identical.
448    */
449   public static DbRefComp matchId = new DbRefComp()
450   {
451     @Override
452     public boolean matches(DBRefEntry refa, DBRefEntry refb)
453     {
454       if (refa.getAccessionId() != null && refb.getAccessionId() != null
455               && refb.getAccessionId().equals(refa.getAccessionId()))
456       {
457         return true;
458       }
459       return false;
460     }
461   };
462
463   /**
464    * Parses a DBRefEntry and adds it to the sequence, also a PDBEntry if the
465    * database is PDB.
466    * <p>
467    * Used by file parsers to generate DBRefs from annotation within file (eg
468    * Stockholm)
469    * 
470    * @param dbname
471    * @param version
472    * @param acn
473    * @param seq
474    *          where to annotate with reference
475    * @return parsed version of entry that was added to seq (if any)
476    */
477   public static DBRefEntry parseToDbRef(SequenceI seq, String dbname,
478           String version, String acn)
479   {
480     DBRefEntry ref = null;
481     if (dbname != null)
482     {
483       String locsrc = DBRefUtils.getCanonicalName(dbname);
484       if (locsrc.equals(DBRefSource.PDB))
485       {
486         /*
487          * Check for PFAM style stockhom PDB accession id citation e.g.
488          * "1WRI A; 7-80;"
489          */
490         Regex r = new com.stevesoft.pat.Regex(
491                 "([0-9][0-9A-Za-z]{3})\\s*(.?)\\s*;\\s*([0-9]+)-([0-9]+)");
492         if (r.search(acn.trim()))
493         {
494           String pdbid = r.stringMatched(1);
495           String chaincode = r.stringMatched(2);
496           if (chaincode == null)
497           {
498             chaincode = " ";
499           }
500           // String mapstart = r.stringMatched(3);
501           // String mapend = r.stringMatched(4);
502           if (chaincode.equals(" "))
503           {
504             chaincode = "_";
505           }
506           // construct pdb ref.
507           ref = new DBRefEntry(locsrc, version, pdbid + chaincode);
508           PDBEntry pdbr = new PDBEntry();
509           pdbr.setId(pdbid);
510           pdbr.setType(PDBEntry.Type.PDB);
511           pdbr.setProperty(new Hashtable());
512           pdbr.setChainCode(chaincode);
513           // pdbr.getProperty().put("CHAIN", chaincode);
514           seq.addPDBId(pdbr);
515         }
516         else
517         {
518           System.err.println("Malformed PDB DR line:" + acn);
519         }
520       }
521       else
522       {
523         // default:
524         ref = new DBRefEntry(locsrc, version, acn);
525       }
526     }
527     if (ref != null)
528     {
529       seq.addDBRef(ref);
530     }
531     return ref;
532   }
533
534   /**
535    * Returns true if either object is null, or they are equal
536    * 
537    * @param o1
538    * @param o2
539    * @return
540    */
541   public static boolean nullOrEqual(Object o1, Object o2)
542   {
543     if (o1 == null || o2 == null)
544     {
545       return true;
546     }
547     return o1.equals(o2);
548   }
549
550   /**
551    * canonicalise source string before comparing. null is always wildcard
552    * 
553    * @param o1
554    *          - null or source string to compare
555    * @param o2
556    *          - null or source string to compare
557    * @return true if either o1 or o2 are null, or o1 equals o2 under
558    *         DBRefUtils.getCanonicalName
559    *         (o1).equals(DBRefUtils.getCanonicalName(o2))
560    */
561   public static boolean nullOrEqualSource(String o1, String o2)
562   {
563     if (o1 == null || o2 == null)
564     {
565       return true;
566     }
567     return DBRefUtils.getCanonicalName(o1).equals(
568             DBRefUtils.getCanonicalName(o2));
569   }
570
571   /**
572    * Selects just the DNA or protein references from a set of references
573    * 
574    * @param selectDna
575    *          if true, select references to 'standard' DNA databases, else to
576    *          'standard' peptide databases
577    * @param refs
578    *          a set of references to select from
579    * @return
580    */
581   public static DBRefEntry[] selectDbRefs(boolean selectDna,
582           DBRefEntry[] refs)
583   {
584     return selectRefs(refs, selectDna ? DBRefSource.DNACODINGDBS
585             : DBRefSource.PROTEINDBS);
586     // could attempt to find other cross
587     // refs here - ie PDB xrefs
588     // (not dna, not protein seq)
589   }
590
591   /**
592    * Returns the (possibly empty) list of those supplied dbrefs which have the
593    * specified source database, with a case-insensitive match of source name
594    * 
595    * @param dbRefs
596    * @param source
597    * @return
598    */
599   public static List<DBRefEntry> searchRefsForSource(DBRefEntry[] dbRefs,
600           String source)
601   {
602     List<DBRefEntry> matches = new ArrayList<DBRefEntry>();
603     if (dbRefs != null && source != null)
604     {
605       for (DBRefEntry dbref : dbRefs)
606       {
607         if (source.equalsIgnoreCase(dbref.getSource()))
608         {
609           matches.add(dbref);
610         }
611       }
612     }
613     return matches;
614   }
615
616   /**
617    * promote direct database references to primary for nucleotide or protein
618    * sequences if they have an appropriate primary ref
619    * <table>
620    * <tr>
621    * <td>Seq Type</td>
622    * <td>Primary DB</td>
623    * <td>Direct which will be promoted</td>
624    * </tr>
625    * <tr>
626    * <td>peptides</td>
627    * <td>Ensembl</td>
628    * <td>Uniprot</td>
629    * </tr>
630    * <tr>
631    * <td>peptides</td>
632    * <td>Ensembl</td>
633    * <td>Uniprot</td>
634    * </tr>
635    * <tr>
636    * <td>dna</td>
637    * <td>Ensembl</td>
638    * <td>ENA</td>
639    * </tr>
640    * </table>
641    * 
642    * @param sequence
643    */
644   public static void ensurePrimaries(SequenceI sequence)
645   {
646     List<DBRefEntry> pr = sequence.getPrimaryDBRefs();
647     if (pr.size() == 0)
648     {
649       // nothing to do
650       return;
651     }
652     List<DBRefEntry> selfs = new ArrayList<DBRefEntry>();
653     selfs.addAll(Arrays.asList(selectDbRefs(!sequence.isProtein(),
654             sequence.getDBRefs())));
655
656     // filter non-primary refs
657     for (DBRefEntry p : pr)
658     {
659       while (selfs.contains(p))
660       {
661         selfs.remove(p);
662       }
663     }
664     List<DBRefEntry> toPromote = new ArrayList<DBRefEntry>();
665
666     for (DBRefEntry p : pr)
667     {
668       List<String> promType = new ArrayList<String>();
669       if (sequence.isProtein())
670       {
671         switch (getCanonicalName(p.getSource()))
672         {
673         case DBRefSource.UNIPROT:
674           // case DBRefSource.UNIPROTKB:
675           // case DBRefSource.UP_NAME:
676           // search for and promote ensembl
677           promType.add(DBRefSource.ENSEMBL);
678           break;
679         case DBRefSource.ENSEMBL:
680           // search for and promote Uniprot
681           promType.add(DBRefSource.UNIPROT);
682           break;
683         }
684       }
685       else
686       {
687         // TODO: promote transcript refs
688       }
689
690       // collate candidates and promote them
691       DBRefEntry[] candidates = selectRefs(
692               selfs.toArray(new DBRefEntry[0]),
693               promType.toArray(new String[0]));
694       if (candidates != null)
695       {
696         for (DBRefEntry cand : candidates)
697         {
698           if (cand.hasMap())
699           {
700             if (cand.getMap().getTo() != null
701                     && cand.getMap().getTo() != sequence)
702             {
703               // can't promote refs with mappings to other sequences
704               continue;
705             }
706             if (cand.getMap().getMap().getFromLowest() != sequence
707                     .getStart()
708                     && cand.getMap().getMap().getFromHighest() != sequence
709                             .getEnd())
710             {
711               // can't promote refs with mappings from a region of this sequence
712               // - eg CDS
713               continue;
714             }
715           }
716           // and promote
717           cand.setVersion(p.getVersion() + " (promoted)");
718           selfs.remove(cand);
719           toPromote.add(cand);
720           if (!cand.isPrimaryCandidate())
721           {
722             System.out.println("Warning: Couldn't promote dbref "
723                     + cand.toString() + " for sequence "
724                     + sequence.toString());
725           }
726         }
727       }
728     }
729   }
730
731 }