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