JAL-2196 refactor PDBEntry.getProperty,setProperty,getProperties
[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.HashMap;
30 import java.util.HashSet;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Set;
34
35 import com.stevesoft.pat.Regex;
36
37 /**
38  * Utilities for handling DBRef objects and their collections.
39  */
40 public class DBRefUtils
41 {
42   /*
43    * lookup from lower-case form of a name to its canonical (standardised) form
44    */
45   private static Map<String, String> canonicalSourceNameLookup = new HashMap<String, String>();
46
47   private static Map<String, String> dasCoordinateSystemsLookup = new HashMap<String, String>();
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     dasCoordinateSystemsLookup.put("pdbresnum", DBRefSource.PDB);
76     dasCoordinateSystemsLookup.put("uniprot", DBRefSource.UNIPROT);
77     dasCoordinateSystemsLookup.put("embl", DBRefSource.EMBL);
78     // dasCoordinateSystemsLookup.put("embl", DBRefSource.EMBLCDS);
79   }
80
81   /**
82    * Returns those DBRefEntry objects whose source identifier (once converted to
83    * Jalview's canonical form) is in the list of sources to search for. Returns
84    * null if no matches found.
85    * 
86    * @param dbrefs
87    *          DBRefEntry objects to search
88    * @param sources
89    *          array of sources to select
90    * @return
91    */
92   public static DBRefEntry[] selectRefs(DBRefEntry[] dbrefs,
93           String[] sources)
94   {
95     if (dbrefs == null || sources == null)
96     {
97       return dbrefs;
98     }
99     HashSet<String> srcs = new HashSet<String>();
100     for (String src : sources)
101     {
102       srcs.add(src);
103     }
104
105     List<DBRefEntry> res = new ArrayList<DBRefEntry>();
106     for (DBRefEntry dbr : dbrefs)
107     {
108       String source = getCanonicalName(dbr.getSource());
109       if (srcs.contains(source))
110       {
111         res.add(dbr);
112       }
113     }
114
115     if (res.size() > 0)
116     {
117       DBRefEntry[] reply = new DBRefEntry[res.size()];
118       return res.toArray(reply);
119     }
120     return null;
121   }
122
123   /**
124    * isDasCoordinateSystem
125    * 
126    * @param string
127    *          String
128    * @param dBRefEntry
129    *          DBRefEntry
130    * @return boolean true if Source DBRefEntry is compatible with DAS
131    *         CoordinateSystem name
132    */
133
134   public static boolean isDasCoordinateSystem(String string,
135           DBRefEntry dBRefEntry)
136   {
137     if (string == null || dBRefEntry == null)
138     {
139       return false;
140     }
141     String coordsys = dasCoordinateSystemsLookup.get(string.toLowerCase());
142     return coordsys == null ? false : coordsys.equals(dBRefEntry
143             .getSource());
144   }
145
146   /**
147    * look up source in an internal list of database reference sources and return
148    * the canonical jalview name for the source, or the original string if it has
149    * no canonical form.
150    * 
151    * @param source
152    * @return canonical jalview source (one of jalview.datamodel.DBRefSource.*)
153    *         or original source
154    */
155   public static String getCanonicalName(String source)
156   {
157     if (source == null)
158     {
159       return null;
160     }
161     String canonical = canonicalSourceNameLookup.get(source.toLowerCase());
162     return canonical == null ? source : canonical;
163   }
164
165   /**
166    * Returns a (possibly empty) list of those references that match the given
167    * entry. Currently uses a comparator which matches if
168    * <ul>
169    * <li>database sources are the same</li>
170    * <li>accession ids are the same</li>
171    * <li>both have no mapping, or the mappings are the same</li>
172    * </ul>
173    * 
174    * @param ref
175    *          Set of references to search
176    * @param entry
177    *          pattern to match
178    * @return
179    */
180   public static List<DBRefEntry> searchRefs(DBRefEntry[] ref,
181           DBRefEntry entry)
182   {
183     return searchRefs(ref, entry,
184             matchDbAndIdAndEitherMapOrEquivalentMapList);
185   }
186
187   /**
188    * Returns a list of those references that match the given accession id
189    * <ul>
190    * <li>database sources are the same</li>
191    * <li>accession ids are the same</li>
192    * <li>both have no mapping, or the mappings are the same</li>
193    * </ul>
194    * 
195    * @param refs
196    *          Set of references to search
197    * @param accId
198    *          accession id to match
199    * @return
200    */
201   public static List<DBRefEntry> searchRefs(DBRefEntry[] refs, String accId)
202   {
203     return searchRefs(refs, new DBRefEntry("", "", accId), matchId);
204   }
205
206   /**
207    * Returns a (possibly empty) list of those references that match the given
208    * entry, according to the given comparator.
209    * 
210    * @param refs
211    *          an array of database references to search
212    * @param entry
213    *          an entry to compare against
214    * @param comparator
215    * @return
216    */
217   static List<DBRefEntry> searchRefs(DBRefEntry[] refs, DBRefEntry entry,
218           DbRefComp comparator)
219   {
220     List<DBRefEntry> rfs = new ArrayList<DBRefEntry>();
221     if (refs == null || entry == null)
222     {
223       return rfs;
224     }
225     for (int i = 0; i < refs.length; i++)
226     {
227       if (comparator.matches(entry, refs[i]))
228       {
229         rfs.add(refs[i]);
230       }
231     }
232     return rfs;
233   }
234
235   interface DbRefComp
236   {
237     public boolean matches(DBRefEntry refa, DBRefEntry refb);
238   }
239
240   /**
241    * match on all non-null fields in refa
242    */
243   // TODO unused - remove?
244   public static DbRefComp matchNonNullonA = new DbRefComp()
245   {
246     @Override
247     public boolean matches(DBRefEntry refa, DBRefEntry refb)
248     {
249       if (refa.getSource() == null
250               || DBRefUtils.getCanonicalName(refb.getSource()).equals(
251                       DBRefUtils.getCanonicalName(refa.getSource())))
252       {
253         if (refa.getVersion() == null
254                 || refb.getVersion().equals(refa.getVersion()))
255         {
256           if (refa.getAccessionId() == null
257                   || refb.getAccessionId().equals(refa.getAccessionId()))
258           {
259             if (refa.getMap() == null
260                     || (refb.getMap() != null && refb.getMap().equals(
261                             refa.getMap())))
262             {
263               return true;
264             }
265           }
266         }
267       }
268       return false;
269     }
270   };
271
272   /**
273    * either field is null or field matches for all of source, version, accession
274    * id and map.
275    */
276   // TODO unused - remove?
277   public static DbRefComp matchEitherNonNull = new DbRefComp()
278   {
279     @Override
280     public boolean matches(DBRefEntry refa, DBRefEntry refb)
281     {
282       if (nullOrEqualSource(refa.getSource(), refb.getSource())
283               && nullOrEqual(refa.getVersion(), refb.getVersion())
284               && nullOrEqual(refa.getAccessionId(), refb.getAccessionId())
285               && nullOrEqual(refa.getMap(), refb.getMap()))
286       {
287         return true;
288       }
289       return false;
290     }
291   };
292
293   /**
294    * accession ID and DB must be identical. Version is ignored. Map is either
295    * not defined or is a match (or is compatible?)
296    */
297   // TODO unused - remove?
298   public static DbRefComp matchDbAndIdAndEitherMap = new DbRefComp()
299   {
300     @Override
301     public boolean matches(DBRefEntry refa, DBRefEntry refb)
302     {
303       if (refa.getSource() != null && refb.getSource() != null
304               && DBRefUtils.getCanonicalName(refb.getSource()).equals(
305                       DBRefUtils.getCanonicalName(refa.getSource())))
306       {
307         // We dont care about version
308         if (refa.getAccessionId() != null && refb.getAccessionId() != null
309         // FIXME should be && not || here?
310                 || refb.getAccessionId().equals(refa.getAccessionId()))
311         {
312           if ((refa.getMap() == null || refb.getMap() == null)
313                   || (refa.getMap() != null && refb.getMap() != null && refb
314                           .getMap().equals(refa.getMap())))
315           {
316             return true;
317           }
318         }
319       }
320       return false;
321     }
322   };
323
324   /**
325    * accession ID and DB must be identical. Version is ignored. No map on either
326    * or map but no maplist on either or maplist of map on a is the complement of
327    * maplist of map on b.
328    */
329   // TODO unused - remove?
330   public static DbRefComp matchDbAndIdAndComplementaryMapList = new DbRefComp()
331   {
332     @Override
333     public boolean matches(DBRefEntry refa, DBRefEntry refb)
334     {
335       if (refa.getSource() != null && refb.getSource() != null
336               && DBRefUtils.getCanonicalName(refb.getSource()).equals(
337                       DBRefUtils.getCanonicalName(refa.getSource())))
338       {
339         // We dont care about version
340         if (refa.getAccessionId() != null && refb.getAccessionId() != null
341                 || refb.getAccessionId().equals(refa.getAccessionId()))
342         {
343           if ((refa.getMap() == null && refb.getMap() == null)
344                   || (refa.getMap() != null && refb.getMap() != null))
345           {
346             if ((refb.getMap().getMap() == null && refa.getMap().getMap() == null)
347                     || (refb.getMap().getMap() != null
348                             && refa.getMap().getMap() != null && refb
349                             .getMap().getMap().getInverse()
350                             .equals(refa.getMap().getMap())))
351             {
352               return true;
353             }
354           }
355         }
356       }
357       return false;
358     }
359   };
360
361   /**
362    * accession ID and DB must be identical. Version is ignored. No map on both
363    * or or map but no maplist on either or maplist of map on a is equivalent to
364    * the maplist of map on b.
365    */
366   // TODO unused - remove?
367   public static DbRefComp matchDbAndIdAndEquivalentMapList = new DbRefComp()
368   {
369     @Override
370     public boolean matches(DBRefEntry refa, DBRefEntry refb)
371     {
372       if (refa.getSource() != null && refb.getSource() != null
373               && DBRefUtils.getCanonicalName(refb.getSource()).equals(
374                       DBRefUtils.getCanonicalName(refa.getSource())))
375       {
376         // We dont care about version
377         // if ((refa.getVersion()==null || refb.getVersion()==null)
378         // || refb.getVersion().equals(refa.getVersion()))
379         // {
380         if (refa.getAccessionId() != null && refb.getAccessionId() != null
381                 || refb.getAccessionId().equals(refa.getAccessionId()))
382         {
383           if (refa.getMap() == null && refb.getMap() == null)
384           {
385             return true;
386           }
387           if (refa.getMap() != null
388                   && refb.getMap() != null
389                   && ((refb.getMap().getMap() == null && refa.getMap()
390                           .getMap() == null) || (refb.getMap().getMap() != null
391                           && refa.getMap().getMap() != null && refb
392                           .getMap().getMap().equals(refa.getMap().getMap()))))
393           {
394             return true;
395           }
396         }
397       }
398       return false;
399     }
400   };
401
402   /**
403    * accession ID and DB must be identical, or null on a. Version is ignored. No
404    * map on either or map but no maplist on either or maplist of map on a is
405    * equivalent to the maplist of map on b.
406    */
407   public static DbRefComp matchDbAndIdAndEitherMapOrEquivalentMapList = new DbRefComp()
408   {
409     @Override
410     public boolean matches(DBRefEntry refa, DBRefEntry refb)
411     {
412       if (refa.getSource() != null && refb.getSource() != null
413               && DBRefUtils.getCanonicalName(refb.getSource()).equals(
414                       DBRefUtils.getCanonicalName(refa.getSource())))
415       {
416         // We dont care about version
417
418         if (refa.getAccessionId() == null
419                 || refa.getAccessionId().equals(refb.getAccessionId()))
420         {
421           if (refa.getMap() == null || refb.getMap() == null)
422           {
423             return true;
424           }
425           if ((refa.getMap() != null && refb.getMap() != null)
426                   && (refb.getMap().getMap() == null && refa.getMap()
427                           .getMap() == null)
428                   || (refb.getMap().getMap() != null
429                           && refa.getMap().getMap() != null && (refb
430                           .getMap().getMap().equals(refa.getMap().getMap()))))
431           {
432             return true;
433           }
434         }
435       }
436       return false;
437     }
438   };
439
440   /**
441    * accession ID only must be identical.
442    */
443   public static DbRefComp matchId = new DbRefComp()
444   {
445     @Override
446     public boolean matches(DBRefEntry refa, DBRefEntry refb)
447     {
448       if (refa.getAccessionId() != null && refb.getAccessionId() != null
449               && refb.getAccessionId().equals(refa.getAccessionId()))
450       {
451         return true;
452       }
453       return false;
454     }
455   };
456
457   /**
458    * Parses a DBRefEntry and adds it to the sequence, also a PDBEntry if the
459    * database is PDB.
460    * <p>
461    * Used by file parsers to generate DBRefs from annotation within file (eg
462    * Stockholm)
463    * 
464    * @param dbname
465    * @param version
466    * @param acn
467    * @param seq
468    *          where to annotate with reference
469    * @return parsed version of entry that was added to seq (if any)
470    */
471   public static DBRefEntry parseToDbRef(SequenceI seq, String dbname,
472           String version, String acn)
473   {
474     DBRefEntry ref = null;
475     if (dbname != null)
476     {
477       String locsrc = DBRefUtils.getCanonicalName(dbname);
478       if (locsrc.equals(DBRefSource.PDB))
479       {
480         /*
481          * Check for PFAM style stockhom PDB accession id citation e.g.
482          * "1WRI A; 7-80;"
483          */
484         Regex r = new com.stevesoft.pat.Regex(
485                 "([0-9][0-9A-Za-z]{3})\\s*(.?)\\s*;\\s*([0-9]+)-([0-9]+)");
486         if (r.search(acn.trim()))
487         {
488           String pdbid = r.stringMatched(1);
489           String chaincode = r.stringMatched(2);
490           if (chaincode == null)
491           {
492             chaincode = " ";
493           }
494           // String mapstart = r.stringMatched(3);
495           // String mapend = r.stringMatched(4);
496           if (chaincode.equals(" "))
497           {
498             chaincode = "_";
499           }
500           // construct pdb ref.
501           ref = new DBRefEntry(locsrc, version, pdbid + chaincode);
502           PDBEntry pdbr = new PDBEntry();
503           pdbr.setId(pdbid);
504           pdbr.setType(PDBEntry.Type.PDB);
505           pdbr.setChainCode(chaincode);
506           seq.addPDBId(pdbr);
507         }
508         else
509         {
510           System.err.println("Malformed PDB DR line:" + acn);
511         }
512       }
513       else
514       {
515         // default:
516         ref = new DBRefEntry(locsrc, version, acn);
517       }
518     }
519     if (ref != null)
520     {
521       seq.addDBRef(ref);
522     }
523     return ref;
524   }
525
526   /**
527    * Returns true if either object is null, or they are equal
528    * 
529    * @param o1
530    * @param o2
531    * @return
532    */
533   public static boolean nullOrEqual(Object o1, Object o2)
534   {
535     if (o1 == null || o2 == null)
536     {
537       return true;
538     }
539     return o1.equals(o2);
540   }
541
542   /**
543    * canonicalise source string before comparing. null is always wildcard
544    * 
545    * @param o1
546    *          - null or source string to compare
547    * @param o2
548    *          - null or source string to compare
549    * @return true if either o1 or o2 are null, or o1 equals o2 under
550    *         DBRefUtils.getCanonicalName
551    *         (o1).equals(DBRefUtils.getCanonicalName(o2))
552    */
553   public static boolean nullOrEqualSource(String o1, String o2)
554   {
555     if (o1 == null || o2 == null)
556     {
557       return true;
558     }
559     return DBRefUtils.getCanonicalName(o1).equals(
560             DBRefUtils.getCanonicalName(o2));
561   }
562
563   /**
564    * Selects just the DNA or protein references from a set of references
565    * 
566    * @param selectDna
567    *          if true, select references to 'standard' DNA databases, else to
568    *          'standard' peptide databases
569    * @param refs
570    *          a set of references to select from
571    * @return
572    */
573   public static DBRefEntry[] selectDbRefs(boolean selectDna,
574           DBRefEntry[] refs)
575   {
576     return selectRefs(refs, selectDna ? DBRefSource.DNACODINGDBS
577             : DBRefSource.PROTEINDBS);
578     // could attempt to find other cross
579     // refs here - ie PDB xrefs
580     // (not dna, not protein seq)
581   }
582
583   /**
584    * Returns the (possibly empty) list of those supplied dbrefs which have the
585    * specified source database, with a case-insensitive match of source name
586    * 
587    * @param dbRefs
588    * @param source
589    * @return
590    */
591   public static List<DBRefEntry> searchRefsForSource(DBRefEntry[] dbRefs,
592           String source)
593   {
594     List<DBRefEntry> matches = new ArrayList<DBRefEntry>();
595     if (dbRefs != null && source != null)
596     {
597       for (DBRefEntry dbref : dbRefs)
598       {
599         if (source.equalsIgnoreCase(dbref.getSource()))
600         {
601           matches.add(dbref);
602         }
603       }
604     }
605     return matches;
606   }
607
608 }