JAL-3446 unused imports removed
[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 java.util.Collections;
24 import java.util.Enumeration;
25 import java.util.Hashtable;
26
27 import jalview.util.CaseInsensitiveString;
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   /**
156    * Entry point when file is not known and fileType may be string
157    * @param pdbId
158    * @param chain may be null
159    * @param fileType "pdb", "mmcif", or "bcif"; null defaults to mmcif
160    */
161   public PDBEntry(String pdbId, String chain, String fileType) {
162     this.id = pdbId.toLowerCase();
163     setChainCode(chain); // I note that PDB Chains ARE case-sensitive now
164     if (fileType == null)
165       fileType = "mmcif";
166     switch (fileType.toLowerCase()) {
167     case "pdb":
168       this.type = Type.PDB.toString();
169       break;
170     case "mmcif":
171       this.type = Type.MMCIF.toString();
172       break;
173     default:
174     case "bcif":
175       System.out.println("format " + fileType + " has not been implemented; using mmCIF");
176       this.type = Type.MMCIF.toString();
177       break;
178     }
179   }
180   
181   public PDBEntry(String pdbId, String chain, PDBEntry.Type type,
182           String filePath)
183   {
184     init(pdbId, chain, type, filePath);
185   }
186
187   /**
188    * @param pdbId
189    * @param chain
190    * @param entryType
191    * @param filePath
192    */
193   void init(String pdbId, String chain, PDBEntry.Type entryType,
194           String filePath)
195   {
196     this.id = pdbId;
197     this.type = entryType == null ? null : entryType.toString();
198     this.file = filePath;
199     setChainCode(chain);
200   }
201
202   /**
203    * Copy constructor.
204    * 
205    * @param entry
206    */
207   public PDBEntry(PDBEntry entry)
208   {
209     file = entry.file;
210     type = entry.type;
211     id = entry.id;
212     if (entry.properties != null)
213     {
214       properties = (Hashtable<String, Object>) entry.properties.clone();
215     }
216   }
217
218   /**
219    * Make a PDBEntry from a DBRefEntry. The accession code is used for the PDB
220    * id, but if it is 5 characters in length, the last character is removed and
221    * set as the chain code instead.
222    * 
223    * @param dbr
224    */
225   public PDBEntry(DBRefEntry dbr)
226   {
227     if (!DBRefSource.PDB.equals(dbr.getSource()))
228     {
229       throw new IllegalArgumentException(
230               "Invalid source: " + dbr.getSource());
231     }
232
233     String pdbId = dbr.getAccessionId();
234     String chainCode = null;
235     if (pdbId.length() == PDB_ID_LENGTH + 1)
236     {
237       char chain = pdbId.charAt(PDB_ID_LENGTH);
238       if (('a' <= chain && chain <= 'z') || ('A' <= chain && chain <= 'Z'))
239       {
240         pdbId = pdbId.substring(0, PDB_ID_LENGTH);
241         chainCode = String.valueOf(chain);
242       }
243     }
244     init(pdbId, chainCode, null, null);
245   }
246
247   public void setFile(String f)
248   {
249     this.file = f;
250   }
251
252   public String getFile()
253   {
254     return file;
255   }
256
257   public void setType(String t)
258   {
259     this.type = t;
260   }
261
262   public void setType(PDBEntry.Type type)
263   {
264     this.type = type == null ? null : type.toString();
265   }
266
267   public String getType()
268   {
269     return type;
270   }
271
272   public void setId(String id)
273   {
274     this.id = id;
275   }
276
277   public String getId()
278   {
279     return id;
280   }
281
282   /**
283    * TODO 
284    * 
285    * @param key  "protocol" 
286    * @param value
287    */
288   public void setProperty(String key, Object value)
289   {
290     if (this.properties == null)
291     {
292       this.properties = new Hashtable<String, Object>();
293     }
294     properties.put(key, value);
295   }
296
297   public Object getProperty(String key)
298   {
299     return properties == null ? null : properties.get(key);
300   }
301
302   /**
303    * Returns an enumeration of the keys of this object's properties (or an empty
304    * enumeration if it has no properties)
305    * 
306    * @return
307    */
308   public Enumeration<String> getProperties()
309   {
310     if (properties == null)
311     {
312       return Collections.emptyEnumeration();
313     }
314     return properties.keys();
315   }
316
317   /**
318    * 
319    * @return null or a string for associated chain IDs
320    */
321   public String getChainCode()
322   {
323     return (properties == null || properties.get(CHAIN_ID) == null) ? null
324             : properties.get(CHAIN_ID).toString();
325   }
326
327   /**
328    * Sets a non-case-sensitive property for the given chain code. Two PDBEntry
329    * objects which differ only in the case of their chain code are considered
330    * equal. This avoids duplication of objects in lists of PDB ids.
331    * 
332    * @param chainCode
333    */
334   public void setChainCode(String chainCode)
335   {
336     if (chainCode == null)
337     {
338       deleteProperty(CHAIN_ID);
339     }
340     else
341     {
342       setProperty(CHAIN_ID, new CaseInsensitiveString(chainCode));
343     }
344   }
345
346   /**
347    * Deletes the property with the given key, and returns the deleted value (or
348    * null)
349    */
350   Object deleteProperty(String key)
351   {
352     Object result = null;
353     if (properties != null)
354     {
355       result = properties.remove(key);
356     }
357     return result;
358   }
359
360   @Override
361   public String toString()
362   {
363     return id;
364   }
365
366   /**
367    * Getter provided for Castor binding only. Application code should call
368    * getProperty() or getProperties() instead.
369    * 
370    * @deprecated
371    * @see #getProperty(String)
372    * @see #getProperties()
373    * @see jalview.ws.dbsources.Uniprot#getUniprotEntries
374    * @return
375    */
376   @Deprecated
377   public Hashtable<String, Object> getProps()
378   {
379     return properties;
380   }
381
382   /**
383    * Setter provided for Castor binding only. Application code should call
384    * setProperty() instead.
385    * 
386    * @deprecated
387    * @return
388    */
389   @Deprecated
390   public void setProps(Hashtable<String, Object> props)
391   {
392     properties = props;
393   }
394
395   /**
396    * Answers true if this object is either equivalent to, or can be 'improved'
397    * by, the given entry.
398    * <p>
399    * If newEntry has the same id (ignoring case), and doesn't have a conflicting
400    * file spec or chain code, then update this entry from its file and/or chain
401    * code.
402    * 
403    * @param newEntry
404    * @return true if modifications were made
405    */
406   public boolean updateFrom(PDBEntry newEntry)
407   {
408     if (this.equals(newEntry))
409     {
410       return true;
411     }
412
413     String newId = newEntry.getId();
414     if (newId == null || getId() == null)
415     {
416       return false; // shouldn't happen
417     }
418
419     /*
420      * id has to match (ignoring case)
421      */
422     if (!getId().equalsIgnoreCase(newId))
423     {
424       return false;
425     }
426
427     /*
428      * Don't update if associated with different structure files
429      */
430     String newFile = newEntry.getFile();
431     if (newFile != null && getFile() != null && !newFile.equals(getFile()))
432     {
433       return false;
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 (!value.equals(getProperty(key)))
489       {
490         setProperty(key, value);
491       }
492     }
493     return true;
494   }
495 }