JAL-1705 fetch Uniprot and PDB xrefs for Ensembl protein products
[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 entry, according
173    * to the given comparator. Returns null if no matches.
174    * 
175    * @param refs
176    *          an array of database references to search
177    * @param entry
178    *          an entry to compare against
179    * @param comparator
180    * @return
181    */
182   static DBRefEntry[] searchRefs(DBRefEntry[] refs, DBRefEntry entry,
183           DbRefComp comparator)
184   {
185     if (refs == null || entry == null)
186     {
187       return null;
188     }
189     List<DBRefEntry> rfs = new ArrayList<DBRefEntry>();
190     for (int i = 0; i < refs.length; i++)
191     {
192       if (comparator.matches(entry, refs[i]))
193       {
194         rfs.add(refs[i]);
195       }
196     }
197     return rfs.size() == 0 ? null : rfs.toArray(new DBRefEntry[rfs.size()]);
198   }
199
200   interface DbRefComp
201   {
202     public boolean matches(DBRefEntry refa, DBRefEntry refb);
203   }
204
205   /**
206    * match on all non-null fields in refa
207    */
208   // TODO unused - remove?
209   public static DbRefComp matchNonNullonA = new DbRefComp()
210   {
211     @Override
212     public boolean matches(DBRefEntry refa, DBRefEntry refb)
213     {
214       if (refa.getSource() == null
215               || refb.getSource().equals(refa.getSource()))
216       {
217         if (refa.getVersion() == null
218                 || refb.getVersion().equals(refa.getVersion()))
219         {
220           if (refa.getAccessionId() == null
221                   || refb.getAccessionId().equals(refa.getAccessionId()))
222           {
223             if (refa.getMap() == null
224                     || (refb.getMap() != null && refb.getMap().equals(
225                             refa.getMap())))
226             {
227               return true;
228             }
229           }
230         }
231       }
232       return false;
233     }
234   };
235
236   /**
237    * either field is null or field matches for all of source, version, accession
238    * id and map.
239    */
240   // TODO unused - remove?
241   public static DbRefComp matchEitherNonNull = new DbRefComp()
242   {
243     @Override
244     public boolean matches(DBRefEntry refa, DBRefEntry refb)
245     {
246       if (nullOrEqual(refa.getSource(), refb.getSource())
247               && nullOrEqual(refa.getVersion(), refb.getVersion())
248               && nullOrEqual(refa.getAccessionId(), refb.getAccessionId())
249               && nullOrEqual(refa.getMap(), refb.getMap()))
250       {
251         return true;
252       }
253       return false;
254     }
255   };
256
257   /**
258    * accession ID and DB must be identical. Version is ignored. Map is either
259    * not defined or is a match (or is compatible?)
260    */
261   // TODO unused - remove?
262   public static DbRefComp matchDbAndIdAndEitherMap = new DbRefComp()
263   {
264     @Override
265     public boolean matches(DBRefEntry refa, DBRefEntry refb)
266     {
267       if (refa.getSource() != null && refb.getSource() != null
268               && refb.getSource().equals(refa.getSource()))
269       {
270         // We dont care about version
271         if (refa.getAccessionId() != null && refb.getAccessionId() != null
272         // FIXME should be && not || here?
273                 || refb.getAccessionId().equals(refa.getAccessionId()))
274         {
275           if ((refa.getMap() == null || refb.getMap() == null)
276                   || (refa.getMap() != null && refb.getMap() != null && refb
277                           .getMap().equals(refa.getMap())))
278           {
279             return true;
280           }
281         }
282       }
283       return false;
284     }
285   };
286
287   /**
288    * accession ID and DB must be identical. Version is ignored. No map on either
289    * or map but no maplist on either or maplist of map on a is the complement of
290    * maplist of map on b.
291    */
292   // TODO unused - remove?
293   public static DbRefComp matchDbAndIdAndComplementaryMapList = new DbRefComp()
294   {
295     @Override
296     public boolean matches(DBRefEntry refa, DBRefEntry refb)
297     {
298       if (refa.getSource() != null && refb.getSource() != null
299               && refb.getSource().equals(refa.getSource()))
300       {
301         // We dont care about version
302         if (refa.getAccessionId() != null && refb.getAccessionId() != null
303                 || refb.getAccessionId().equals(refa.getAccessionId()))
304         {
305           if ((refa.getMap() == null && refb.getMap() == null)
306                   || (refa.getMap() != null && refb.getMap() != null))
307           {
308             if ((refb.getMap().getMap() == null && refa.getMap().getMap() == null)
309                     || (refb.getMap().getMap() != null
310                             && refa.getMap().getMap() != null && refb
311                             .getMap().getMap().getInverse()
312                             .equals(refa.getMap().getMap())))
313             {
314               return true;
315             }
316           }
317         }
318       }
319       return false;
320     }
321   };
322
323   /**
324    * accession ID and DB must be identical. Version is ignored. No map on both
325    * or or map but no maplist on either or maplist of map on a is equivalent to
326    * the maplist of map on b.
327    */
328   // TODO unused - remove?
329   public static DbRefComp matchDbAndIdAndEquivalentMapList = new DbRefComp()
330   {
331     @Override
332     public boolean matches(DBRefEntry refa, DBRefEntry refb)
333     {
334       if (refa.getSource() != null && refb.getSource() != null
335               && refb.getSource().equals(refa.getSource()))
336       {
337         // We dont care about version
338         // if ((refa.getVersion()==null || refb.getVersion()==null)
339         // || refb.getVersion().equals(refa.getVersion()))
340         // {
341         if (refa.getAccessionId() != null && refb.getAccessionId() != null
342                 || refb.getAccessionId().equals(refa.getAccessionId()))
343         {
344           if (refa.getMap() == null && refb.getMap() == null)
345           {
346             return true;
347           }
348           if (refa.getMap() != null
349                   && refb.getMap() != null
350                   && ((refb.getMap().getMap() == null && refa.getMap()
351                           .getMap() == null) || (refb.getMap().getMap() != null
352                           && refa.getMap().getMap() != null && refb
353                           .getMap().getMap().equals(refa.getMap().getMap()))))
354           {
355             return true;
356           }
357         }
358       }
359       return false;
360     }
361   };
362
363   /**
364    * accession ID and DB must be identical. Version is ignored. No map on either
365    * or map but no maplist on either or maplist of map on a is equivalent to the
366    * maplist of map on b.
367    */
368   public static DbRefComp matchDbAndIdAndEitherMapOrEquivalentMapList = new DbRefComp()
369   {
370     @Override
371     public boolean matches(DBRefEntry refa, DBRefEntry refb)
372     {
373       if (refa.getSource() != null && refb.getSource() != null
374               && refb.getSource().equals(refa.getSource()))
375       {
376         // We dont care about version
377         if (refa.getAccessionId() != null && refb.getAccessionId() != null
378                 && refb.getAccessionId().equals(refa.getAccessionId()))
379         {
380           if (refa.getMap() == null || refb.getMap() == null)
381           {
382             return true;
383           }
384           if ((refa.getMap() != null && refb.getMap() != null)
385                   && (refb.getMap().getMap() == null && refa.getMap()
386                           .getMap() == null)
387                   || (refb.getMap().getMap() != null
388                           && refa.getMap().getMap() != null && (refb
389                           .getMap().getMap().equals(refa.getMap().getMap()))))
390           { // getMap().getMap().containsEither(false,refa.getMap().getMap())
391             return true;
392           }
393         }
394       }
395       return false;
396     }
397   };
398
399   /**
400    * Parses a DBRefEntry and adds it to the sequence, also a PDBEntry if the
401    * database is PDB.
402    * <p>
403    * Used by file parsers to generate DBRefs from annotation within file (eg
404    * Stockholm)
405    * 
406    * @param dbname
407    * @param version
408    * @param acn
409    * @param seq
410    *          where to annotate with reference
411    * @return parsed version of entry that was added to seq (if any)
412    */
413   public static DBRefEntry parseToDbRef(SequenceI seq, String dbname,
414           String version, String acn)
415   {
416     DBRefEntry ref = null;
417     if (dbname != null)
418     {
419       String locsrc = DBRefUtils.getCanonicalName(dbname);
420       if (locsrc.equals(DBRefSource.PDB))
421       {
422         /*
423          * Check for PFAM style stockhom PDB accession id citation e.g.
424          * "1WRI A; 7-80;"
425          */
426         Regex r = new com.stevesoft.pat.Regex(
427                 "([0-9][0-9A-Za-z]{3})\\s*(.?)\\s*;\\s*([0-9]+)-([0-9]+)");
428         if (r.search(acn.trim()))
429         {
430           String pdbid = r.stringMatched(1);
431           String chaincode = r.stringMatched(2);
432           if (chaincode == null)
433           {
434             chaincode = " ";
435           }
436           // String mapstart = r.stringMatched(3);
437           // String mapend = r.stringMatched(4);
438           if (chaincode.equals(" "))
439           {
440             chaincode = "_";
441           }
442           // construct pdb ref.
443           ref = new DBRefEntry(locsrc, version, pdbid + chaincode);
444           PDBEntry pdbr = new PDBEntry();
445           pdbr.setId(pdbid);
446           pdbr.setType(PDBEntry.Type.PDB);
447           pdbr.setProperty(new Hashtable());
448           pdbr.setChainCode(chaincode);
449           // pdbr.getProperty().put("CHAIN", chaincode);
450           seq.addPDBId(pdbr);
451         }
452         else
453         {
454           System.err.println("Malformed PDB DR line:" + acn);
455         }
456       }
457       else
458       {
459         // default:
460         ref = new DBRefEntry(locsrc, version, acn);
461       }
462     }
463     if (ref != null)
464     {
465       seq.addDBRef(ref);
466     }
467     return ref;
468   }
469
470   /**
471    * Returns true if either object is null, or they are equal
472    * 
473    * @param o1
474    * @param o2
475    * @return
476    */
477   public static boolean nullOrEqual(Object o1, Object o2)
478   {
479     if (o1 == null || o2 == null)
480     {
481       return true;
482     }
483     return (o1 == null ? o2.equals(o1) : o1.equals(o2));
484   }
485
486 }