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