b3e17bf43e29fc6c30786639e931cfada239d8a9
[jalview.git] / src / jalview / datamodel / PDBEntry.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.datamodel;
22
23 import jalview.util.CaseInsensitiveString;
24
25 import java.util.Collections;
26 import java.util.Enumeration;
27 import java.util.Hashtable;
28
29 public class PDBEntry
30 {
31
32   /**
33    * constant for storing chain code in properties table
34    */
35   private static final String CHAIN_ID = "chain_code";
36
37   private Hashtable<String, Object> properties;
38
39   private static final int PDB_ID_LENGTH = 4;
40
41   private String file;
42
43   private String type;
44
45   private String id;
46
47   public enum Type
48   {
49     // TODO is FILE needed; if not is this enum needed, or can we
50     // use FileFormatI for PDB, MMCIF?
51     PDB("pdb", "pdb"), MMCIF("mmcif", "cif"), FILE("?", "?");
52
53     /*
54      * file extension for cached structure file; must be one that
55      * is recognised by Chimera 'open' command
56      * @see https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/filetypes.html
57      */
58     String ext;
59
60     /*
61      * format specifier used in dbfetch request
62      * @see http://www.ebi.ac.uk/Tools/dbfetch/dbfetch/dbfetch.databases#pdb
63      */
64     String format;
65
66     private Type(String fmt, String ex)
67     {
68       format = fmt;
69       ext = ex;
70     }
71
72     public String getFormat()
73     {
74       return format;
75     }
76
77     public String getExtension()
78     {
79       return ext;
80     }
81
82     /**
83      * case insensitive matching for Type enum
84      * 
85      * @param value
86      * @return
87      */
88     public static Type getType(String value)
89     {
90       for (Type t : Type.values())
91       {
92         if (t.toString().equalsIgnoreCase(value))
93         {
94           return t;
95         }
96       }
97       return null;
98     }
99
100     /**
101      * case insensitive equivalence for strings resolving to PDBEntry type
102      * 
103      * @param t
104      * @return
105      */
106     public boolean matches(String t)
107     {
108       return (this.toString().equalsIgnoreCase(t));
109     }
110   }
111
112   /**
113    * Answers true if obj is a PDBEntry with the same id and chain code (both
114    * ignoring case), file, type and properties
115    */
116   @Override
117   public boolean equals(Object obj)
118   {
119     if (obj == null || !(obj instanceof PDBEntry))
120     {
121       return false;
122     }
123     if (obj == this)
124     {
125       return true;
126     }
127     PDBEntry o = (PDBEntry) obj;
128
129     /*
130      * note that chain code is stored as a property wrapped by a 
131      * CaseInsensitiveString, so we are in effect doing a 
132      * case-insensitive comparison of chain codes
133      */
134     boolean idMatches = id == o.id
135             || (id != null && id.equalsIgnoreCase(o.id));
136     boolean fileMatches = file == o.file
137             || (file != null && file.equals(o.file));
138     boolean typeMatches = type == o.type
139             || (type != null && type.equals(o.type));
140     if (idMatches && fileMatches && typeMatches)
141     {
142       return properties == o.properties
143               || (properties != null && properties.equals(o.properties));
144     }
145     return false;
146   }
147
148   /**
149    * Default constructor
150    */
151   public PDBEntry()
152   {
153   }
154
155   public PDBEntry(String pdbId, String chain, PDBEntry.Type type,
156           String filePath)
157   {
158     init(pdbId, chain, type, filePath);
159   }
160
161   /**
162    * @param pdbId
163    * @param chain
164    * @param entryType
165    * @param filePath
166    */
167   void init(String pdbId, String chain, PDBEntry.Type entryType,
168           String filePath)
169   {
170     this.id = pdbId;
171     this.type = entryType == null ? null : entryType.toString();
172     this.file = filePath;
173     setChainCode(chain);
174   }
175
176   /**
177    * Copy constructor.
178    * 
179    * @param entry
180    */
181   public PDBEntry(PDBEntry entry)
182   {
183     file = entry.file;
184     type = entry.type;
185     id = entry.id;
186     if (entry.properties != null)
187     {
188       properties = (Hashtable<String, Object>) entry.properties.clone();
189     }
190   }
191
192   /**
193    * Make a PDBEntry from a DBRefEntry. The accession code is used for the PDB
194    * id, but if it is 5 characters in length, the last character is removed and
195    * set as the chain code instead.
196    * 
197    * @param dbr
198    */
199   public PDBEntry(DBRefEntry dbr)
200   {
201     if (!DBRefSource.PDB.equals(dbr.getSource()))
202     {
203       throw new IllegalArgumentException(
204               "Invalid source: " + dbr.getSource());
205     }
206
207     String pdbId = dbr.getAccessionId();
208     String chainCode = null;
209     if (pdbId.length() == PDB_ID_LENGTH + 1)
210     {
211       char chain = pdbId.charAt(PDB_ID_LENGTH);
212       if (('a' <= chain && chain <= 'z') || ('A' <= chain && chain <= 'Z'))
213       {
214         pdbId = pdbId.substring(0, PDB_ID_LENGTH);
215         chainCode = String.valueOf(chain);
216       }
217     }
218     init(pdbId, chainCode, null, null);
219   }
220
221   public void setFile(String f)
222   {
223     this.file = f;
224   }
225
226   public String getFile()
227   {
228     return file;
229   }
230
231   public void setType(String t)
232   {
233     this.type = t;
234   }
235
236   public void setType(PDBEntry.Type type)
237   {
238     this.type = type == null ? null : type.toString();
239   }
240
241   public String getType()
242   {
243     return type;
244   }
245
246   public void setId(String id)
247   {
248     this.id = id;
249   }
250
251   public String getId()
252   {
253     return id;
254   }
255
256   public void setProperty(String key, Object value)
257   {
258     if (this.properties == null)
259     {
260       this.properties = new Hashtable<String, Object>();
261     }
262     properties.put(key, value);
263   }
264
265   public Object getProperty(String key)
266   {
267     return properties == null ? null : properties.get(key);
268   }
269
270   /**
271    * Returns an enumeration of the keys of this object's properties (or an empty
272    * enumeration if it has no properties)
273    * 
274    * @return
275    */
276   public Enumeration<String> getProperties()
277   {
278     if (properties == null)
279     {
280       return Collections.emptyEnumeration();
281     }
282     return properties.keys();
283   }
284
285   /**
286    * 
287    * @return null or a string for associated chain IDs
288    */
289   public String getChainCode()
290   {
291     return (properties == null || properties.get(CHAIN_ID) == null) ? null
292             : properties.get(CHAIN_ID).toString();
293   }
294
295   /**
296    * Sets a non-case-sensitive property for the given chain code. Two PDBEntry
297    * objects which differ only in the case of their chain code are considered
298    * equal. This avoids duplication of objects in lists of PDB ids.
299    * 
300    * @param chainCode
301    */
302   public void setChainCode(String chainCode)
303   {
304     if (chainCode == null)
305     {
306       deleteProperty(CHAIN_ID);
307     }
308     else
309     {
310       setProperty(CHAIN_ID, new CaseInsensitiveString(chainCode));
311     }
312   }
313
314   /**
315    * Deletes the property with the given key, and returns the deleted value (or
316    * null)
317    */
318   Object deleteProperty(String key)
319   {
320     Object result = null;
321     if (properties != null)
322     {
323       result = properties.remove(key);
324     }
325     return result;
326   }
327
328   @Override
329   public String toString()
330   {
331     return id;
332   }
333
334   /**
335    * Getter provided for Castor binding only. Application code should call
336    * getProperty() or getProperties() instead.
337    * 
338    * @deprecated
339    * @see #getProperty(String)
340    * @see #getProperties()
341    * @see jalview.ws.dbsources.Uniprot#getUniprotEntries
342    * @return
343    */
344   @Deprecated
345   public Hashtable<String, Object> getProps()
346   {
347     return properties;
348   }
349
350   /**
351    * Setter provided for Castor binding only. Application code should call
352    * setProperty() instead.
353    * 
354    * @deprecated
355    * @return
356    */
357   @Deprecated
358   public void setProps(Hashtable<String, Object> props)
359   {
360     properties = props;
361   }
362
363   /**
364    * Answers true if this object is either equivalent to, or can be 'improved'
365    * by, the given entry.
366    * <p>
367    * If newEntry has the same id (ignoring case), and doesn't have a conflicting
368    * file spec or chain code, then update this entry from its file and/or chain
369    * code.
370    * 
371    * @param newEntry
372    * @return true if modifications were made
373    */
374   public boolean updateFrom(PDBEntry newEntry)
375   {
376     if (this.equals(newEntry))
377     {
378       return true;
379     }
380
381     String newId = newEntry.getId();
382     if (newId == null || getId() == null)
383     {
384       return false; // shouldn't happen
385     }
386
387     boolean idMatches = getId().equalsIgnoreCase(newId);
388
389     /*
390      * Don't update if associated with different structure files
391      */
392     String newFile = newEntry.getFile();
393     if (newFile != null && getFile() != null)
394     {
395       if (!newFile.equals(getFile()))
396       {
397         return false;
398       }
399       else
400       {
401         // files match.
402         if (!idMatches)
403         {
404           // this shouldn't happen, but could do if the id from the
405           // file is not the same as the id from the authority that provided
406           // the file
407           return false;
408         }
409       }
410     }
411     else
412     {
413       // one has data, one doesn't ..
414       if (!idMatches)
415       {
416         return false;
417       } // otherwise maybe can update
418     }
419
420     /*
421      * Don't update if associated with different chains (ignoring case)
422      */
423     String newChain = newEntry.getChainCode();
424     if (newChain != null && newChain.length() > 0 && getChainCode() != null
425             && getChainCode().length() > 0
426             && !getChainCode().equalsIgnoreCase(newChain))
427     {
428       return false;
429     }
430
431     /*
432      * set file path if not already set
433      */
434     String newType = newEntry.getType();
435     if (getFile() == null && newFile != null)
436     {
437       setFile(newFile);
438       setType(newType);
439     }
440
441     /*
442      * set file type if new entry has it and we don't
443      * (for the case where file was not updated)
444      */
445     if (getType() == null && newType != null)
446     {
447       setType(newType);
448     }
449
450     /*
451      * set chain if not already set (we excluded differing 
452      * chains earlier) (ignoring case change only)
453      */
454     if (newChain != null && newChain.length() > 0
455             && !newChain.equalsIgnoreCase(getChainCode()))
456     {
457       setChainCode(newChain);
458     }
459
460     /*
461      * copy any new or modified properties
462      */
463     Enumeration<String> newProps = newEntry.getProperties();
464     while (newProps.hasMoreElements())
465     {
466       /*
467        * copy properties unless value matches; this defends against changing
468        * the case of chain_code which is wrapped in a CaseInsensitiveString
469        */
470       String key = newProps.nextElement();
471       Object value = newEntry.getProperty(key);
472       if (!value.equals(getProperty(key)))
473       {
474         setProperty(key, value);
475       }
476     }
477     return true;
478   }
479   
480   private boolean _hasProperty(final String key)
481   {
482     return (properties != null && properties.containsKey(key));
483   }
484
485   private static final String RETRIEVE_FROM = "RETRIEVE_FROM";
486
487   private static final String PROVIDER = "PROVIDER";
488
489   private static final String MODELPAGE = "PROVIDERPAGE";
490
491   /**
492    * Permanent URI for retrieving the original structure data
493    * 
494    * @param urlStr
495    */
496   public void setRetrievalUrl(String urlStr)
497   {
498     setProperty(RETRIEVE_FROM, urlStr);
499   }
500
501   public boolean hasRetrievalUrl()
502   {
503     return _hasProperty(RETRIEVE_FROM);
504   }
505
506   /**
507    * get the Permanent URI for retrieving the original structure data
508    */
509   public String getRetrievalUrl()
510   {
511     return (String) getProperty(RETRIEVE_FROM);
512   }
513
514   /**
515    * Data provider name - from 3D Beacons
516    * 
517    * @param provider
518    */
519   public void setProvider(String provider)
520   {
521     setProperty(PROVIDER, provider);
522   }
523
524   /**
525    * Get Data provider name - from 3D Beacons
526    * 
527    */
528   public String getProvider()
529   {
530     return (String) getProperty(PROVIDER);
531   }
532
533   /**
534    * Permanent URI for retrieving the original structure data
535    * 
536    * @param urlStr
537    */
538   public void setProviderPage(String urlStr)
539   {
540     setProperty(MODELPAGE, urlStr);
541   }
542
543   /**
544    * get the Permanent URI for retrieving the original structure data
545    */
546   public String getProviderPage()
547   {
548     return (String) getProperty(MODELPAGE);
549   }
550
551   public boolean hasProviderPage()
552   {
553     return _hasProperty(MODELPAGE);
554   }
555
556   public boolean hasProvider()
557   {
558     return _hasProperty(PROVIDER);
559   }
560 }