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