JAL-2157 JAL-1803 refactored Sequence.updatePDBIds, parse DBRefs with
[jalview.git] / src / jalview / datamodel / PDBEntry.java
index 98c78fc..1403595 100755 (executable)
  */
 package jalview.datamodel;
 
+import jalview.util.CaseInsensitiveString;
+
 import java.util.Hashtable;
 
 public class PDBEntry
 {
+  private static final int PDB_ID_LENGTH = 4;
+
   private String file;
 
   private String type;
 
   private String id;
 
-  private String chainCode;
-
   public enum Type
   {
-    PDB, FILE
+    PDB, MMCIF, FILE;
+    /**
+     * case insensitive matching for Type enum
+     * 
+     * @param value
+     * @return
+     */
+    public static Type getType(String value)
+    {
+      for (Type t : Type.values())
+      {
+        if (t.toString().equalsIgnoreCase(value))
+        {
+          return t;
+        }
+      }
+      return null;
+    }
+
+    /**
+     * case insensitive equivalence for strings resolving to PDBEntry type
+     * 
+     * @param t
+     * @return
+     */
+    public boolean matches(String t)
+    {
+      return (this.toString().equalsIgnoreCase(t));
+    }
   }
+
+  /**
+   * constant for storing chain code in properties table
+   */
+  private static final String CHAIN_ID = "chain_code";
+
   Hashtable properties;
 
-  /*
-   * (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)
@@ -55,20 +90,24 @@ public class PDBEntry
       return true;
     }
     PDBEntry o = (PDBEntry) obj;
-    return /*
-            * (file == o.file || (file != null && o.file != null && o.file
-            * .equals(file))) &&
-            */
-    (type == o.type || (type != null && o.type != null && o.type
-                    .equals(type)))
-            && (id == o.id || (id != null && o.id != null && o.id
-                    .equalsIgnoreCase(id)))
-            && (chainCode == o.chainCode || (chainCode != null
-                    && o.chainCode != null && o.chainCode
-                      .equalsIgnoreCase(chainCode)))
-            && (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;
   }
 
   /**
@@ -88,14 +127,25 @@ public class PDBEntry
   // this.file = filePath;
   // this.id = pdbId;
   // }
-  
+
   public PDBEntry(String pdbId, String chain, PDBEntry.Type type,
           String filePath)
   {
+    init(pdbId, chain, type, filePath);
+  }
+
+  /**
+   * @param pdbId
+   * @param chain
+   * @param type
+   * @param filePath
+   */
+  void init(String pdbId, String chain, PDBEntry.Type type, String filePath)
+  {
     this.id = pdbId;
-    this.chainCode = chain;
     this.type = type == null ? null : type.toString();
     this.file = filePath;
+    setChainCode(chain);
   }
 
   /**
@@ -108,13 +158,41 @@ public class PDBEntry
     file = entry.file;
     type = entry.type;
     id = entry.id;
-    chainCode = entry.chainCode;
     if (entry.properties != null)
     {
       properties = (Hashtable) entry.properties.clone();
     }
   }
 
+  /**
+   * 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 file)
   {
     this.file = file;
@@ -129,9 +207,10 @@ public class PDBEntry
   {
     this.type = t;
   }
+
   public void setType(PDBEntry.Type type)
   {
-    this.type = type.toString();
+    this.type = type == null ? null : type.toString();
   }
 
   public String getType()
@@ -159,14 +238,146 @@ public class PDBEntry
     return properties;
   }
 
+  /**
+   * 
+   * @return null or a string for associated chain IDs
+   */
   public String getChainCode()
   {
-    return chainCode;
+    return (properties == null || properties.get(CHAIN_ID) == null) ? null
+            : properties.get(CHAIN_ID).toString();
   }
 
   public void setChainCode(String chainCode)
   {
-    this.chainCode = chainCode;
+    if (properties == null)
+    {
+      if (chainCode == null)
+      {
+        // nothing to do.
+        return;
+      }
+      properties = new Hashtable();
+    }
+    if (chainCode == null)
+    {
+      properties.remove(CHAIN_ID);
+      return;
+    }
+    // update property for non-null chainCode
+    properties.put(CHAIN_ID, new CaseInsensitiveString(chainCode));
+  }
+
+  @Override
+  public String toString()
+  {
+    return id;
   }
 
+  /**
+   * 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
+   */
+  protected boolean updateFrom(PDBEntry newEntry)
+  {
+    if (this.equals(newEntry))
+    {
+      return true;
+    }
+
+    String newId = newEntry.getId();
+    if (newId == null || getId() == null)
+    {
+      return false; // shouldn't happen
+    }
+
+    /*
+     * id (less any chain code) 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;
+    }
+
+    /*
+     * 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))
+    {
+      return false;
+    }
+
+    /*
+     * 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(newType);
+    }
+
+    /*
+     * set chain if not already set (we excluded differing 
+     * chains earlier) (ignoring case change only)
+     */
+    if (newChain != null && newChain.length() > 0
+            && !newChain.equalsIgnoreCase(getChainCode()))
+    {
+      setChainCode(newChain);
+    }
+
+    /*
+     * copy any new properties; notice this may include chain_code,
+     * but we excluded differing chain codes earlier
+     */
+    if (newEntry.getProperty() != null)
+    {
+      if (properties == null)
+      {
+        properties = new Hashtable();
+      }
+      for (Object p : newEntry.getProperty().keySet())
+      {
+        /*
+         * copy properties unless value matches; this defends against changing
+         * the case of chain_code which is wrapped in a CaseInsensitiveString
+         */
+        Object value = newEntry.getProperty().get(p);
+        if (!value.equals(properties.get(p)))
+        {
+          properties.put(p, newEntry.getProperty().get(p));
+        }
+      }
+    }
+    return true;
+  }
 }