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