97fb08ddae8428f89008b165fe6909ad99d369c4
[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 needed or can we
50     // use FileFormatI for PDB, MMCIF?
51     PDB("pdb", "xml"), MMCIF("mmcif", "mmcif"), FILE("?", "?");
52     String ext;
53
54     String format;
55
56     private Type(String fmt, String ex)
57     {
58       format = fmt;
59       ext = ex;
60     }
61
62     public String getFormat()
63     {
64       return format;
65     }
66     public String getExtension()
67     {
68       return ext;
69     }
70
71     /**
72      * case insensitive matching for Type enum
73      * 
74      * @param value
75      * @return
76      */
77     public static Type getType(String value)
78     {
79       for (Type t : Type.values())
80       {
81         if (t.toString().equalsIgnoreCase(value))
82         {
83           return t;
84         }
85       }
86       return null;
87     }
88
89     /**
90      * case insensitive equivalence for strings resolving to PDBEntry type
91      * 
92      * @param t
93      * @return
94      */
95     public boolean matches(String t)
96     {
97       return (this.toString().equalsIgnoreCase(t));
98     }
99   }
100
101   /**
102    * Answers true if obj is a PDBEntry with the same id and chain code (both
103    * ignoring case), file, type and properties
104    */
105   @Override
106   public boolean equals(Object obj)
107   {
108     if (obj == null || !(obj instanceof PDBEntry))
109     {
110       return false;
111     }
112     if (obj == this)
113     {
114       return true;
115     }
116     PDBEntry o = (PDBEntry) obj;
117
118     /*
119      * note that chain code is stored as a property wrapped by a 
120      * CaseInsensitiveString, so we are in effect doing a 
121      * case-insensitive comparison of chain codes
122      */
123     boolean idMatches = id == o.id
124             || (id != null && id.equalsIgnoreCase(o.id));
125     boolean fileMatches = file == o.file
126             || (file != null && file.equals(o.file));
127     boolean typeMatches = type == o.type
128             || (type != null && type.equals(o.type));
129     if (idMatches && fileMatches && typeMatches)
130     {
131       return properties == o.properties
132               || (properties != null && properties.equals(o.properties));
133     }
134     return false;
135   }
136
137   /**
138    * Default constructor
139    */
140   public PDBEntry()
141   {
142   }
143
144
145   public PDBEntry(String pdbId, String chain, PDBEntry.Type type,
146           String filePath)
147   {
148     init(pdbId, chain, type, filePath);
149   }
150
151   /**
152    * @param pdbId
153    * @param chain
154    * @param entryType
155    * @param filePath
156    */
157   void init(String pdbId, String chain, PDBEntry.Type entryType, String filePath)
158   {
159     this.id = pdbId;
160     this.type = entryType == null ? null : entryType.toString();
161     this.file = filePath;
162     setChainCode(chain);
163   }
164
165   /**
166    * Copy constructor.
167    * 
168    * @param entry
169    */
170   public PDBEntry(PDBEntry entry)
171   {
172     file = entry.file;
173     type = entry.type;
174     id = entry.id;
175     if (entry.properties != null)
176     {
177       properties = (Hashtable<String, Object>) entry.properties.clone();
178     }
179   }
180
181   /**
182    * Make a PDBEntry from a DBRefEntry. The accession code is used for the PDB
183    * id, but if it is 5 characters in length, the last character is removed and
184    * set as the chain code instead.
185    * 
186    * @param dbr
187    */
188   public PDBEntry(DBRefEntry dbr)
189   {
190     if (!DBRefSource.PDB.equals(dbr.getSource()))
191     {
192       throw new IllegalArgumentException("Invalid source: "
193               + dbr.getSource());
194     }
195
196     String pdbId = dbr.getAccessionId();
197     String chainCode = null;
198     if (pdbId.length() == PDB_ID_LENGTH + 1)
199     {
200       char chain = pdbId.charAt(PDB_ID_LENGTH);
201       if (('a' <= chain && chain <= 'z') || ('A' <= chain && chain <= 'Z'))
202       {
203         pdbId = pdbId.substring(0, PDB_ID_LENGTH);
204         chainCode = String.valueOf(chain);
205       }
206     }
207     init(pdbId, chainCode, null, null);
208   }
209
210   public void setFile(String f)
211   {
212     this.file = f;
213   }
214
215   public String getFile()
216   {
217     return file;
218   }
219
220   public void setType(String t)
221   {
222     this.type = t;
223   }
224
225   public void setType(PDBEntry.Type type)
226   {
227     this.type = type == null ? null : type.toString();
228   }
229
230   public String getType()
231   {
232     return type;
233   }
234
235   public void setId(String id)
236   {
237     this.id = id;
238   }
239
240   public String getId()
241   {
242     return id;
243   }
244
245   public void setProperty(String key, Object value)
246   {
247     if (this.properties == null)
248     {
249       this.properties = new Hashtable<String, Object>();
250     }
251     properties.put(key, value);
252   }
253
254   public Object getProperty(String key)
255   {
256     return properties == null ? null : properties.get(key);
257   }
258
259   /**
260    * Returns an enumeration of the keys of this object's properties (or an empty
261    * enumeration if it has no properties)
262    * 
263    * @return
264    */
265   public Enumeration<String> getProperties()
266   {
267     if (properties == null)
268     {
269       return Collections.emptyEnumeration();
270     }
271     return properties.keys();
272   }
273
274   /**
275    * 
276    * @return null or a string for associated chain IDs
277    */
278   public String getChainCode()
279   {
280     return (properties == null || properties.get(CHAIN_ID) == null) ? null
281             : properties.get(CHAIN_ID).toString();
282   }
283
284   /**
285    * Sets a non-case-sensitive property for the given chain code. Two PDBEntry
286    * objects which differ only in the case of their chain code are considered
287    * equal. This avoids duplication of objects in lists of PDB ids.
288    * 
289    * @param chainCode
290    */
291   public void setChainCode(String chainCode)
292   {
293     if (chainCode == null)
294     {
295       deleteProperty(CHAIN_ID);
296     }
297     else
298     {
299       setProperty(CHAIN_ID, new CaseInsensitiveString(chainCode));
300     }
301   }
302
303   /**
304    * Deletes the property with the given key, and returns the deleted value (or
305    * null)
306    */
307   Object deleteProperty(String key)
308   {
309     Object result = null;
310     if (properties != null)
311     {
312       result = properties.remove(key);
313     }
314     return result;
315   }
316
317   @Override
318   public String toString()
319   {
320     return id;
321   }
322
323   /**
324    * Getter provided for Castor binding only. Application code should call
325    * getProperty() or getProperties() instead.
326    * 
327    * @deprecated
328    * @see #getProperty(String)
329    * @see #getProperties()
330    * @see jalview.ws.dbsources.Uniprot#getUniprotEntries
331    * @return
332    */
333   @Deprecated
334   public Hashtable<String, Object> getProps()
335   {
336     return properties;
337   }
338
339   /**
340    * Setter provided for Castor binding only. Application code should call
341    * setProperty() instead.
342    * 
343    * @deprecated
344    * @return
345    */
346   @Deprecated
347   public void setProps(Hashtable<String, Object> props)
348   {
349     properties = props;
350   }
351
352   /**
353    * Answers true if this object is either equivalent to, or can be 'improved'
354    * by, the given entry.
355    * <p>
356    * If newEntry has the same id (ignoring case), and doesn't have a conflicting
357    * file spec or chain code, then update this entry from its file and/or chain
358    * code.
359    * 
360    * @param newEntry
361    * @return true if modifications were made
362    */
363   public boolean updateFrom(PDBEntry newEntry)
364   {
365     if (this.equals(newEntry))
366     {
367       return true;
368     }
369
370     String newId = newEntry.getId();
371     if (newId == null || getId() == null)
372     {
373       return false; // shouldn't happen
374     }
375
376     /*
377      * id has to match (ignoring case)
378      */
379     if (!getId().equalsIgnoreCase(newId))
380     {
381       return false;
382     }
383
384     /*
385      * Don't update if associated with different structure files
386      */
387     String newFile = newEntry.getFile();
388     if (newFile != null && getFile() != null && !newFile.equals(getFile()))
389     {
390       return false;
391     }
392
393     /*
394      * Don't update if associated with different chains (ignoring case)
395      */
396     String newChain = newEntry.getChainCode();
397     if (newChain != null && newChain.length() > 0 && getChainCode() != null
398             && getChainCode().length() > 0
399             && !getChainCode().equalsIgnoreCase(newChain))
400     {
401       return false;
402     }
403
404     /*
405      * set file path if not already set
406      */
407     String newType = newEntry.getType();
408     if (getFile() == null && newFile != null)
409     {
410       setFile(newFile);
411       setType(newType);
412     }
413
414     /*
415      * set file type if new entry has it and we don't
416      * (for the case where file was not updated)
417      */
418     if (getType() == null && newType != null)
419     {
420       setType(newType);
421     }
422
423     /*
424      * set chain if not already set (we excluded differing 
425      * chains earlier) (ignoring case change only)
426      */
427     if (newChain != null && newChain.length() > 0
428             && !newChain.equalsIgnoreCase(getChainCode()))
429     {
430       setChainCode(newChain);
431     }
432
433     /*
434      * copy any new or modified properties
435      */
436     Enumeration<String> newProps = newEntry.getProperties();
437     while (newProps.hasMoreElements())
438     {
439       /*
440        * copy properties unless value matches; this defends against changing
441        * the case of chain_code which is wrapped in a CaseInsensitiveString
442        */
443       String key = newProps.nextElement();
444       Object value = newEntry.getProperty(key);
445       if (!value.equals(getProperty(key)))
446       {
447         setProperty(key, value);
448       }
449     }
450     return true;
451   }
452 }