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