JAL-2157 JAL-1803 refactored Sequence.updatePDBIds, parse DBRefs with
[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.Hashtable;
26
27 public class PDBEntry
28 {
29   private static final int PDB_ID_LENGTH = 4;
30
31   private String file;
32
33   private String type;
34
35   private String id;
36
37   public enum Type
38   {
39     PDB, MMCIF, FILE;
40     /**
41      * case insensitive matching for Type enum
42      * 
43      * @param value
44      * @return
45      */
46     public static Type getType(String value)
47     {
48       for (Type t : Type.values())
49       {
50         if (t.toString().equalsIgnoreCase(value))
51         {
52           return t;
53         }
54       }
55       return null;
56     }
57
58     /**
59      * case insensitive equivalence for strings resolving to PDBEntry type
60      * 
61      * @param t
62      * @return
63      */
64     public boolean matches(String t)
65     {
66       return (this.toString().equalsIgnoreCase(t));
67     }
68   }
69
70   /**
71    * constant for storing chain code in properties table
72    */
73   private static final String CHAIN_ID = "chain_code";
74
75   Hashtable properties;
76
77   /**
78    * Answers true if obj is a PDBEntry with the same id and chain code (both
79    * ignoring case), file, type and properties
80    */
81   @Override
82   public boolean equals(Object obj)
83   {
84     if (obj == null || !(obj instanceof PDBEntry))
85     {
86       return false;
87     }
88     if (obj == this)
89     {
90       return true;
91     }
92     PDBEntry o = (PDBEntry) obj;
93
94     /*
95      * note that chain code is stored as a property wrapped by a 
96      * CaseInsensitiveString, so we are in effect doing a 
97      * case-insensitive comparison of chain codes
98      */
99     boolean idMatches = id == o.id
100             || (id != null && id.equalsIgnoreCase(o.id));
101     boolean fileMatches = file == o.file
102             || (file != null && file.equals(o.file));
103     boolean typeMatches = type == o.type
104             || (type != null && type.equals(o.type));
105     if (idMatches && fileMatches && typeMatches)
106     {
107       return properties == o.properties
108               || (properties != null && properties.equals(o.properties));
109     }
110     return false;
111   }
112
113   /**
114    * Default constructor
115    */
116   public PDBEntry()
117   {
118   }
119
120   /**
121    * Constructor given file path and PDB id.
122    * 
123    * @param filePath
124    */
125   // public PDBEntry(String filePath, String pdbId)
126   // {
127   // this.file = filePath;
128   // this.id = pdbId;
129   // }
130
131   public PDBEntry(String pdbId, String chain, PDBEntry.Type type,
132           String filePath)
133   {
134     init(pdbId, chain, type, filePath);
135   }
136
137   /**
138    * @param pdbId
139    * @param chain
140    * @param type
141    * @param filePath
142    */
143   void init(String pdbId, String chain, PDBEntry.Type type, String filePath)
144   {
145     this.id = pdbId;
146     this.type = type == null ? null : type.toString();
147     this.file = filePath;
148     setChainCode(chain);
149   }
150
151   /**
152    * Copy constructor.
153    * 
154    * @param entry
155    */
156   public PDBEntry(PDBEntry entry)
157   {
158     file = entry.file;
159     type = entry.type;
160     id = entry.id;
161     if (entry.properties != null)
162     {
163       properties = (Hashtable) entry.properties.clone();
164     }
165   }
166
167   /**
168    * Make a PDBEntry from a DBRefEntry. The accession code is used for the PDB
169    * id, but if it is 5 characters in length, the last character is removed and
170    * set as the chain code instead.
171    * 
172    * @param dbr
173    */
174   public PDBEntry(DBRefEntry dbr)
175   {
176     if (!DBRefSource.PDB.equals(dbr.getSource()))
177     {
178       throw new IllegalArgumentException("Invalid source: "
179               + dbr.getSource());
180     }
181
182     String pdbId = dbr.getAccessionId();
183     String chainCode = null;
184     if (pdbId.length() == PDB_ID_LENGTH + 1)
185     {
186       char chain = pdbId.charAt(PDB_ID_LENGTH);
187       if (('a' <= chain && chain <= 'z') || ('A' <= chain && chain <= 'Z'))
188       {
189         pdbId = pdbId.substring(0, PDB_ID_LENGTH);
190         chainCode = String.valueOf(chain);
191       }
192     }
193     init(pdbId, chainCode, null, null);
194   }
195
196   public void setFile(String file)
197   {
198     this.file = file;
199   }
200
201   public String getFile()
202   {
203     return file;
204   }
205
206   public void setType(String t)
207   {
208     this.type = t;
209   }
210
211   public void setType(PDBEntry.Type type)
212   {
213     this.type = type == null ? null : type.toString();
214   }
215
216   public String getType()
217   {
218     return type;
219   }
220
221   public void setId(String id)
222   {
223     this.id = id;
224   }
225
226   public String getId()
227   {
228     return id;
229   }
230
231   public void setProperty(Hashtable property)
232   {
233     this.properties = property;
234   }
235
236   public Hashtable getProperty()
237   {
238     return properties;
239   }
240
241   /**
242    * 
243    * @return null or a string for associated chain IDs
244    */
245   public String getChainCode()
246   {
247     return (properties == null || properties.get(CHAIN_ID) == null) ? null
248             : properties.get(CHAIN_ID).toString();
249   }
250
251   public void setChainCode(String chainCode)
252   {
253     if (properties == null)
254     {
255       if (chainCode == null)
256       {
257         // nothing to do.
258         return;
259       }
260       properties = new Hashtable();
261     }
262     if (chainCode == null)
263     {
264       properties.remove(CHAIN_ID);
265       return;
266     }
267     // update property for non-null chainCode
268     properties.put(CHAIN_ID, new CaseInsensitiveString(chainCode));
269   }
270
271   @Override
272   public String toString()
273   {
274     return id;
275   }
276
277   /**
278    * Answers true if this object is either equivalent to, or can be 'improved'
279    * by, the given entry.
280    * <p>
281    * If newEntry has the same id (ignoring case), and doesn't have a conflicting
282    * file spec or chain code, then update this entry from its file and/or chain
283    * code.
284    * 
285    * @param newEntry
286    * @return true if modifications were made
287    */
288   protected boolean updateFrom(PDBEntry newEntry)
289   {
290     if (this.equals(newEntry))
291     {
292       return true;
293     }
294
295     String newId = newEntry.getId();
296     if (newId == null || getId() == null)
297     {
298       return false; // shouldn't happen
299     }
300
301     /*
302      * id (less any chain code) has to match (ignoring case)
303      */
304     if (!getId().equalsIgnoreCase(newId))
305     {
306       return false;
307     }
308
309     /*
310      * Don't update if associated with different structure files
311      */
312     String newFile = newEntry.getFile();
313     if (newFile != null && getFile() != null && !newFile.equals(getFile()))
314     {
315       return false;
316     }
317
318     /*
319      * Don't update if associated with different chains (ignoring case)
320      */
321     String newChain = newEntry.getChainCode();
322     if (newChain != null && newChain.length() > 0 && getChainCode() != null
323             && getChainCode().length() > 0
324             && !getChainCode().equalsIgnoreCase(newChain))
325     {
326       return false;
327     }
328
329     /*
330      * set file path if not already set
331      */
332     String newType = newEntry.getType();
333     if (getFile() == null && newFile != null)
334     {
335       setFile(newFile);
336       setType(newType);
337     }
338
339     /*
340      * set file type if new entry has it and we don't
341      * (for the case where file was not updated)
342      */
343     if (getType() == null && newType != null)
344     {
345       setType(newType);
346     }
347
348     /*
349      * set chain if not already set (we excluded differing 
350      * chains earlier) (ignoring case change only)
351      */
352     if (newChain != null && newChain.length() > 0
353             && !newChain.equalsIgnoreCase(getChainCode()))
354     {
355       setChainCode(newChain);
356     }
357
358     /*
359      * copy any new properties; notice this may include chain_code,
360      * but we excluded differing chain codes earlier
361      */
362     if (newEntry.getProperty() != null)
363     {
364       if (properties == null)
365       {
366         properties = new Hashtable();
367       }
368       for (Object p : newEntry.getProperty().keySet())
369       {
370         /*
371          * copy properties unless value matches; this defends against changing
372          * the case of chain_code which is wrapped in a CaseInsensitiveString
373          */
374         Object value = newEntry.getProperty().get(p);
375         if (!value.equals(properties.get(p)))
376         {
377           properties.put(p, newEntry.getProperty().get(p));
378         }
379       }
380     }
381     return true;
382   }
383 }