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