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