Merge branch 'develop' into task/JAL-2196pdbeProperties
[jalview.git] / src / jalview / datamodel / PDBEntry.java
index 7c2d290..6a6ccd0 100755 (executable)
@@ -25,7 +25,6 @@ import jalview.util.CaseInsensitiveString;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.Hashtable;
-import java.util.Map.Entry;
 
 public class PDBEntry
 {
@@ -35,7 +34,9 @@ public class PDBEntry
    */
   private static final String CHAIN_ID = "chain_code";
 
-  Hashtable<String, Object> properties;
+  private Hashtable<String, Object> properties;
+
+  private static final int PDB_ID_LENGTH = 4;
 
   private String file;
 
@@ -77,10 +78,9 @@ public class PDBEntry
   }
 
 
-  /*
-   * (non-Javadoc)
-   * 
-   * @see java.lang.Object#equals(java.lang.Object)
+  /**
+   * Answers true if obj is a PDBEntry with the same id and chain code (both
+   * ignoring case), file, type and properties
    */
   @Override
   public boolean equals(Object obj)
@@ -94,14 +94,24 @@ public class PDBEntry
       return true;
     }
     PDBEntry o = (PDBEntry) obj;
-    return (type == o.type || (type != null && o.type != null && o.type
-            .equals(type)))
-            && (id == o.id || (id != null && o.id != null && o.id
-                    .equalsIgnoreCase(id)))
-            && (properties == o.properties || (properties != null
-                    && o.properties != null && properties
-                      .equals(o.properties)));
 
+    /*
+     * note that chain code is stored as a property wrapped by a 
+     * CaseInsensitiveString, so we are in effect doing a 
+     * case-insensitive comparison of chain codes
+     */
+    boolean idMatches = id == o.id
+            || (id != null && id.equalsIgnoreCase(o.id));
+    boolean fileMatches = file == o.file
+            || (file != null && file.equals(o.file));
+    boolean typeMatches = type == o.type
+            || (type != null && type.equals(o.type));
+    if (idMatches && fileMatches && typeMatches)
+    {
+      return properties == o.properties
+              || (properties != null && properties.equals(o.properties));
+    }
+    return false;
   }
 
   /**
@@ -125,8 +135,19 @@ public class PDBEntry
   public PDBEntry(String pdbId, String chain, PDBEntry.Type type,
           String filePath)
   {
+    init(pdbId, chain, type, filePath);
+  }
+
+  /**
+   * @param pdbId
+   * @param chain
+   * @param entryType
+   * @param filePath
+   */
+  void init(String pdbId, String chain, PDBEntry.Type entryType, String filePath)
+  {
     this.id = pdbId;
-    this.type = type == null ? null : type.toString();
+    this.type = entryType == null ? null : entryType.toString();
     this.file = filePath;
     setChainCode(chain);
   }
@@ -147,6 +168,35 @@ public class PDBEntry
     }
   }
 
+  /**
+   * Make a PDBEntry from a DBRefEntry. The accession code is used for the PDB
+   * id, but if it is 5 characters in length, the last character is removed and
+   * set as the chain code instead.
+   * 
+   * @param dbr
+   */
+  public PDBEntry(DBRefEntry dbr)
+  {
+    if (!DBRefSource.PDB.equals(dbr.getSource()))
+    {
+      throw new IllegalArgumentException("Invalid source: "
+              + dbr.getSource());
+    }
+
+    String pdbId = dbr.getAccessionId();
+    String chainCode = null;
+    if (pdbId.length() == PDB_ID_LENGTH + 1)
+    {
+      char chain = pdbId.charAt(PDB_ID_LENGTH);
+      if (('a' <= chain && chain <= 'z') || ('A' <= chain && chain <= 'Z'))
+      {
+        pdbId = pdbId.substring(0, PDB_ID_LENGTH);
+        chainCode = String.valueOf(chain);
+      }
+    }
+    init(pdbId, chainCode, null, null);
+  }
+
   public void setFile(String f)
   {
     this.file = f;
@@ -290,46 +340,103 @@ public class PDBEntry
   }
 
   /**
-   * update entry with details from another entry concerning the same PDB
-   * ID/file spec.
+   * Answers true if this object is either equivalent to, or can be 'improved'
+   * by, the given entry.
+   * <p>
+   * If newEntry has the same id (ignoring case), and doesn't have a conflicting
+   * file spec or chain code, then update this entry from its file and/or chain
+   * code.
    * 
    * @param newEntry
    * @return true if modifications were made
    */
   public boolean updateFrom(PDBEntry newEntry)
   {
-    boolean modified = false;
+    if (this.equals(newEntry))
+    {
+      return true;
+    }
+
+    String newId = newEntry.getId();
+    if (newId == null || getId() == null)
+    {
+      return false; // shouldn't happen
+    }
+
+    /*
+     * id has to match (ignoring case)
+     */
+    if (!getId().equalsIgnoreCase(newId))
+    {
+      return false;
+    }
+
+    /*
+     * Don't update if associated with different structure files
+     */
+    String newFile = newEntry.getFile();
+    if (newFile != null && getFile() != null && !newFile.equals(getFile()))
+    {
+      return false;
+    }
 
-    if (getFile() == null)
+    /*
+     * Don't update if associated with different chains (ignoring case)
+     */
+    String newChain = newEntry.getChainCode();
+    if (newChain != null && newChain.length() > 0 && getChainCode() != null
+            && getChainCode().length() > 0
+            && !getChainCode().equalsIgnoreCase(newChain))
     {
-      // update file and type of file
-      modified = newEntry.getFile() != null;
-      setFile(newEntry.getFile());
+      return false;
     }
-    if (newEntry.getType() != null && newEntry.getFile() != null
-            && newEntry.getFile().equals(getFile()))
+
+    /*
+     * set file path if not already set
+     */
+    String newType = newEntry.getType();
+    if (getFile() == null && newFile != null)
+    {
+      setFile(newFile);
+      setType(newType);
+    }
+
+    /*
+     * set file type if new entry has it and we don't
+     * (for the case where file was not updated)
+     */
+    if (getType() == null && newType != null)
     {
-      setType(newEntry.getType());
+      setType(newType);
     }
-    if (getChainCode() == null
-            || (getChainCode() != null && getChainCode().length() == 0 && newEntry
-                    .getChainCode() != null))
+
+    /*
+     * set chain if not already set (we excluded differing 
+     * chains earlier) (ignoring case change only)
+     */
+    if (newChain != null && newChain.length() > 0
+            && !newChain.equalsIgnoreCase(getChainCode()))
     {
-      modified |= (getChainCode() == null || !newEntry.getChainCode()
-              .equals(getChainCode()));
-      setChainCode(newEntry.getChainCode());
+      setChainCode(newChain);
     }
-    if (newEntry.properties != null)
+
+    /*
+     * copy any new or modified properties
+     */
+    Enumeration<String> newProps = newEntry.getProperties();
+    while (newProps.hasMoreElements())
     {
-      for (Entry<String, Object> entry : newEntry.properties.entrySet())
+      /*
+       * copy properties unless value matches; this defends against changing
+       * the case of chain_code which is wrapped in a CaseInsensitiveString
+       */
+      String key = newProps.nextElement();
+      Object value = newEntry.getProperty(key);
+      if (!value.equals(getProperty(key)))
       {
-        if (!entry.getValue().equals(getProperty(entry.getKey())))
-        {
-          modified = true;
-        }
-        setProperty(entry.getKey(), entry.getValue());
+        setProperty(key, value);
       }
     }
-    return modified;
+    return true;
   }
 }