JAL-2157 JAL-1803 refactored Sequence.updatePDBIds, parse DBRefs with
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Fri, 16 Sep 2016 11:19:17 +0000 (12:19 +0100)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Fri, 16 Sep 2016 11:19:17 +0000 (12:19 +0100)
chain appended

src/jalview/datamodel/PDBEntry.java
src/jalview/datamodel/Sequence.java
src/jalview/datamodel/SequenceI.java
test/jalview/datamodel/PDBEntryTest.java
test/jalview/datamodel/SequenceTest.java
test/jalview/ext/jmol/JmolParserTest.java

index 198b4a6..1403595 100755 (executable)
@@ -26,6 +26,8 @@ import java.util.Hashtable;
 
 public class PDBEntry
 {
+  private static final int PDB_ID_LENGTH = 4;
+
   private String file;
 
   private String type;
@@ -72,10 +74,9 @@ public class PDBEntry
 
   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)
@@ -89,14 +90,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;
   }
 
   /**
@@ -120,6 +131,17 @@ public class PDBEntry
   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.type = type == null ? null : type.toString();
     this.file = filePath;
@@ -142,6 +164,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 file)
   {
     this.file = file;
@@ -224,52 +275,109 @@ 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)
+  protected 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 (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;
+    }
 
-    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)
     {
-      setType(newEntry.getType());
+      setFile(newFile);
+      setType(newType);
     }
-    if (getChainCode() == null
-            || (getChainCode() != null && getChainCode().length() == 0 && newEntry
-                    .getChainCode() != null))
+
+    /*
+     * 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)
     {
-      modified |= getChainCode() == null
-              || !newEntry.getChainCode().equals(getChainCode());
-      setChainCode(newEntry.getChainCode());
+      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();
       }
-      // TODO: getProperty -> Map<String,String>
       for (Object p : newEntry.getProperty().keySet())
       {
-        if (properties.get(p) == null
-                || !properties.get(p).equals(newEntry.getProperty().get(p)))
+        /*
+         * 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)))
         {
-          modified = true;
+          properties.put(p, newEntry.getProperty().get(p));
         }
-        properties.put(p, newEntry.getProperty().get(p));
       }
     }
-    return modified;
+    return true;
   }
 }
index 4f626a4..44522a8 100755 (executable)
@@ -412,72 +412,24 @@ public class Sequence extends ASequence implements SequenceI
   }
 
   @Override
-  public void addPDBId(PDBEntry entry)
+  public boolean addPDBId(PDBEntry entry)
   {
     if (pdbIds == null)
     {
       pdbIds = new Vector<PDBEntry>();
+      pdbIds.add(entry);
+      return true;
     }
-    if (!updatedPDBEntry(pdbIds, entry))
-    {
-      pdbIds.addElement(entry);
-    }
-  }
 
-  private static boolean updatedPDBEntry(List<PDBEntry> entries,
-          PDBEntry newEntry)
-  {
-    for (PDBEntry xtant : entries)
+    for (PDBEntry pdbe : pdbIds)
     {
-      if (xtant.getFile() != null && newEntry.getFile() != null
-              && !xtant.getFile().equals(newEntry.getFile()))
-      {
-        // different structure data, so leave alone.
-        continue;
-      }
-      // loop through to check whether we can find a matching ID
-
-      // either exact
-      if (!xtant.getId().equals(newEntry.getId()))
-      {
-        /* TODO: support stemming to group PDB IDs.
-        // or stemming, with exactly one alphanumeric character difference
-        if (xtant.getId().length() < newEntry.getId().length())
-        {
-          if (!newEntry.getId().startsWith(xtant.getId()))
-          {
-            continue;
-          }
-          // newEntry may be chain specific PDBEntry
-          // TODO: copy/update details from newEntry to xtant
-        }
-        else
-        {
-          if (!xtant.getId().startsWith(newEntry.getId()))
-          {
-            continue;
-          }
-          // xtant may be chain specific PDBEntry
-          // TODO: copy/update missing details from newEntry
-        }*/
-        continue;
-      }
-      if (xtant.getChainCode() != null && xtant.getChainCode().length() > 0
-              && newEntry.getChainCode() != null
-              && !newEntry.getChainCode().equals(xtant.getChainCode()))
+      if (pdbe.updateFrom(entry))
       {
-        // don't overwrite - multiple chain mappings for a sequence yield
-        // multiple PDBEntries
-        // each with different chaincode
-        continue;
+        return false;
       }
-
-      xtant.updateFrom(newEntry);
-
-      return true;
     }
-    // if we got to the end of the loop, nothing was updated.
-    return false;
+    pdbIds.addElement(entry);
+    return true;
   }
 
   /**
@@ -1269,46 +1221,22 @@ public class Sequence extends ASequence implements SequenceI
     {
       return false;
     }
-    Vector newpdb = new Vector();
-    for (int i = 0; i < dbrefs.length; i++)
+    boolean added = false;
+    for (DBRefEntry dbr : dbrefs)
     {
-      if (DBRefSource.PDB.equals(dbrefs[i].getSource()))
+      if (DBRefSource.PDB.equals(dbr.getSource()))
       {
-        PDBEntry pdbe = new PDBEntry();
-        pdbe.setId(dbrefs[i].getAccessionId());
-        if (pdbIds == null || pdbIds.size() == 0)
-        {
-          newpdb.addElement(pdbe);
-        }
-        else
-        {
-          Enumeration en = pdbIds.elements();
-          boolean matched = false;
-          while (!matched && en.hasMoreElements())
-          {
-            PDBEntry anentry = (PDBEntry) en.nextElement();
-            if (anentry.getId().equals(pdbe.getId()))
-            {
-              matched = true;
-            }
-          }
-          if (!matched)
-          {
-            newpdb.addElement(pdbe);
-          }
-        }
-      }
-    }
-    if (newpdb.size() > 0)
-    {
-      Enumeration en = newpdb.elements();
-      while (en.hasMoreElements())
-      {
-        addPDBId((PDBEntry) en.nextElement());
+        /*
+         * 'Add' any PDB dbrefs as a PDBEntry - add is only performed if the
+         * PDB id is not already present in a 'matching' PDBEntry
+         * Constructor parses out a chain code if appended to the accession id
+         * (a fudge used to 'store' the chain code in the DBRef)
+         */
+        PDBEntry pdbe = new PDBEntry(dbr);
+        added |= addPDBId(pdbe);
       }
-      return true;
     }
-    return false;
+    return added;
   }
 
   @Override
index 55c59db..b7a291e 100755 (executable)
@@ -289,16 +289,18 @@ public interface SequenceI extends ASequenceI
   public Vector<PDBEntry> getAllPDBEntries();
 
   /**
-   * add entry to the *normalised* vector of PDBIds.
+   * Adds the entry to the *normalised* list of PDBIds.
    * 
-   * If a PDBEntry is passed with an entry.getID() string, as one already in the
-   * list, or one is added that appears to be the same but has a chain ID
+   * If a PDBEntry is passed with the same entry.getID() string as one already
+   * in the list, or one is added that appears to be the same but has a chain ID
    * appended, then the existing PDBEntry will be updated with the new
-   * attributes.
+   * attributes instead, unless the entries have distinct chain codes or
+   * associated structure files.
    * 
    * @param entry
+   * @return true if the entry was added, false if updated
    */
-  public void addPDBId(PDBEntry entry);
+  public boolean addPDBId(PDBEntry entry);
 
   /**
    * update the list of PDBEntrys to include any DBRefEntrys citing structural
index cf944b2..979fee4 100644 (file)
@@ -1,4 +1,5 @@
 /*
+    assertEquals(case7, case9);
  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
  * Copyright (C) $$Year-Rel$$ The Jalview Authors
  * 
@@ -27,6 +28,11 @@ import static org.testng.Assert.assertNotSame;
 import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertSame;
 import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import jalview.datamodel.PDBEntry.Type;
+
+import java.util.Hashtable;
 
 //import org.testng.Assert;
 import org.testng.annotations.AfterMethod;
@@ -52,23 +58,34 @@ public class PDBEntryTest
     PDBEntry pdbEntry = new PDBEntry("1xyz", "A", PDBEntry.Type.PDB,
             "x/y/z/File");
 
+    // id comparison is not case sensitive
     PDBEntry case1 = new PDBEntry("1XYZ", "A", PDBEntry.Type.PDB,
             "x/y/z/File");
+    // chain code comparison is not case sensitive
     PDBEntry case2 = new PDBEntry("1xyz", "a", PDBEntry.Type.PDB,
             "x/y/z/File");
+    // different type
     PDBEntry case3 = new PDBEntry("1xyz", "A", PDBEntry.Type.FILE,
             "x/y/z/File");
+    // different everything
     PDBEntry case4 = new PDBEntry(null, null, null, null);
+    // null id
     PDBEntry case5 = new PDBEntry(null, "A", PDBEntry.Type.PDB,
             "x/y/z/File");
+    // null chain
     PDBEntry case6 = new PDBEntry("1xyz", null, PDBEntry.Type.PDB,
             "x/y/z/File");
+    // null type
     PDBEntry case7 = new PDBEntry("1xyz", "A", null, "x/y/z/File");
+    // null file
     PDBEntry case8 = new PDBEntry("1xyz", "A", PDBEntry.Type.PDB, null);
+    // identical to case7
     PDBEntry case9 = new PDBEntry("1xyz", "A", null, "x/y/z/File");
+    // different file only
+    PDBEntry case10 = new PDBEntry("1xyz", "A", null, "a/b/c/File");
 
     /*
-     * assertions will invoke PDBEntry.equals()
+     * assertEquals will invoke PDBEntry.equals()
      */
     assertFalse(pdbEntry.equals(null));
     assertFalse(pdbEntry.equals("a"));
@@ -79,8 +96,17 @@ public class PDBEntryTest
     assertNotEquals(case5, pdbEntry);
     assertNotEquals(case6, pdbEntry);
     assertNotEquals(case7, pdbEntry);
-    assertEquals(case8, pdbEntry);
+    assertNotEquals(case8, pdbEntry);
+    assertEquals(case7, case9);
+    assertNotEquals(case9, case10);
+
+    // add properties
+    case7.getProperty().put("hello", "world");
+    assertNotEquals(case7, case9);
+    case9.getProperty().put("hello", "world");
     assertEquals(case7, case9);
+    case9.getProperty().put("hello", "WORLD");
+    assertNotEquals(case7, case9);
 
     /*
      * change string wrapper property to string...
@@ -122,4 +148,161 @@ public class PDBEntryTest
     assertTrue(PDBEntry.Type.FILE.matches("file"));
     assertFalse(PDBEntry.Type.FILE.matches("FILE "));
   }
+
+  @Test(groups = { "Functional" })
+  public void testUpdateFrom()
+  {
+    PDBEntry pdb1 = new PDBEntry("3A6S", null, null, null);
+    PDBEntry pdb2 = new PDBEntry("3A6S", null, null, null);
+    assertTrue(pdb1.updateFrom(pdb2));
+
+    /*
+     * mismatch of pdb id not allowed
+     */
+    pdb2 = new PDBEntry("1A70", "A", null, null);
+    assertFalse(pdb1.updateFrom(pdb2));
+    assertNull(pdb1.getChainCode());
+
+    /*
+     * match of pdb id is not case sensitive
+     */
+    pdb2 = new PDBEntry("3a6s", "A", null, null);
+    assertTrue(pdb1.updateFrom(pdb2));
+    assertEquals(pdb1.getChainCode(), "A");
+    assertEquals(pdb1.getId(), "3A6S");
+
+    /*
+     * add chain - with differing case for id
+     */
+    pdb1 = new PDBEntry("3A6S", null, null, null);
+    pdb2 = new PDBEntry("3a6s", "A", null, null);
+    assertTrue(pdb1.updateFrom(pdb2));
+    assertEquals(pdb1.getChainCode(), "A");
+
+    /*
+     * change of chain is not allowed
+     */
+    pdb2 = new PDBEntry("3A6S", "B", null, null);
+    assertFalse(pdb1.updateFrom(pdb2));
+    assertEquals(pdb1.getChainCode(), "A");
+
+    /*
+     * change chain from null
+     */
+    pdb1 = new PDBEntry("3A6S", null, null, null);
+    pdb2 = new PDBEntry("3A6S", "B", null, null);
+    assertTrue(pdb1.updateFrom(pdb2));
+    assertEquals(pdb1.getChainCode(), "B");
+
+    /*
+     * set file and type
+     */
+    pdb2 = new PDBEntry("3A6S", "B", Type.FILE, "filePath");
+    assertTrue(pdb1.updateFrom(pdb2));
+    assertEquals(pdb1.getFile(), "filePath");
+    assertEquals(pdb1.getType(), Type.FILE.toString());
+
+    /*
+     * change of file is not allowed
+     */
+    pdb1 = new PDBEntry("3A6S", null, null, "file1");
+    pdb2 = new PDBEntry("3A6S", "A", null, "file2");
+    assertFalse(pdb1.updateFrom(pdb2));
+    assertNull(pdb1.getChainCode());
+    assertEquals(pdb1.getFile(), "file1");
+
+    /*
+     * set type without change of file
+     */
+    pdb1 = new PDBEntry("3A6S", null, null, "file1");
+    pdb2 = new PDBEntry("3A6S", null, Type.PDB, "file1");
+    assertTrue(pdb1.updateFrom(pdb2));
+    assertEquals(pdb1.getType(), Type.PDB.toString());
+
+    /*
+     * set file with differing case of id and chain code
+     */
+    pdb1 = new PDBEntry("3A6S", "A", null, null);
+    pdb2 = new PDBEntry("3a6s", "a", Type.PDB, "file1");
+    assertTrue(pdb1.updateFrom(pdb2));
+    assertEquals(pdb1.getType(), Type.PDB.toString());
+    assertEquals(pdb1.getId(), "3A6S"); // unchanged
+    assertEquals(pdb1.getFile(), "file1"); // updated
+    assertEquals(pdb1.getChainCode(), "A"); // unchanged
+
+    /*
+     * changing nothing returns true
+     */
+    pdb1 = new PDBEntry("3A6S", "A", Type.PDB, "file1");
+    pdb2 = new PDBEntry("3A6S", null, null, null);
+    assertTrue(pdb1.updateFrom(pdb2));
+    assertEquals(pdb1.getChainCode(), "A");
+    assertEquals(pdb1.getType(), Type.PDB.toString());
+    assertEquals(pdb1.getFile(), "file1");
+
+    /*
+     * add and update properties only
+     */
+    pdb1 = new PDBEntry("3A6S", null, null, null);
+    pdb2 = new PDBEntry("3A6S", null, null, null);
+    // ughh properties not null if chain code has been set...
+    // JAL-2196 addresses this
+    pdb1.properties = new Hashtable();
+    pdb2.properties = new Hashtable();
+    pdb1.properties.put("destination", "mars");
+    pdb1.properties.put("hello", "world");
+    pdb2.properties.put("hello", "moon");
+    pdb2.properties.put("goodbye", "world");
+    assertTrue(pdb1.updateFrom(pdb2));
+    assertEquals(pdb1.properties.get("destination"), "mars");
+    assertEquals(pdb1.properties.get("hello"), "moon");
+    assertEquals(pdb1.properties.get("goodbye"), "world");
+
+    /*
+     * add properties only
+     */
+    pdb1 = new PDBEntry("3A6S", null, null, null);
+    pdb2 = new PDBEntry("3A6S", null, null, null);
+    pdb2.properties = new Hashtable();
+    pdb2.properties.put("hello", "moon");
+    assertTrue(pdb1.updateFrom(pdb2));
+    assertEquals(pdb1.properties.get("hello"), "moon");
+  }
+
+  @Test(groups = { "Functional" })
+  public void testConstructor_fromDbref()
+  {
+    PDBEntry pdb = new PDBEntry(new DBRefEntry("PDB", "0", "1A70"));
+    assertEquals(pdb.getId(), "1A70");
+    assertNull(pdb.getChainCode());
+    assertNull(pdb.getType());
+    assertNull(pdb.getFile());
+
+    /*
+     * from dbref with chain code appended
+     */
+    pdb = new PDBEntry(new DBRefEntry("PDB", "0", "1A70B"));
+    assertEquals(pdb.getId(), "1A70");
+    assertEquals(pdb.getChainCode(), "B");
+
+    /*
+     * from dbref with overlong accession
+     */
+    pdb = new PDBEntry(new DBRefEntry("PDB", "0", "1A70BC"));
+    assertEquals(pdb.getId(), "1A70BC");
+    assertNull(pdb.getChainCode());
+
+    /*
+     * from dbref which is not for PDB
+     */
+    try
+    {
+      pdb = new PDBEntry(new DBRefEntry("PDBe", "0", "1A70"));
+      fail("Expected exception");
+    } catch (IllegalArgumentException e)
+    {
+      // expected;
+    }
+  }
+
 }
index c7e53a9..8c5073b 100644 (file)
@@ -884,4 +884,87 @@ public class SequenceTest
     assertTrue(primaryDBRefs.contains(dbr1));
     assertTrue(primaryDBRefs.contains(dbr3));
   }
+
+  /**
+   * Test the method that updates the list of PDBEntry from any new DBRefEntry
+   * for PDB
+   */
+  @Test(groups = { "Functional" })
+  public void testUpdatePDBIds()
+  {
+    PDBEntry pdbe1 = new PDBEntry("3A6S", null, null, null);
+    seq.addPDBId(pdbe1);
+    seq.addDBRef(new DBRefEntry("Ensembl", "8", "ENST1234"));
+    seq.addDBRef(new DBRefEntry("PDB", "0", "1A70"));
+    seq.addDBRef(new DBRefEntry("PDB", "0", "4BQGa"));
+    seq.addDBRef(new DBRefEntry("PDB", "0", "3a6sB"));
+    // 7 is not a valid chain code:
+    seq.addDBRef(new DBRefEntry("PDB", "0", "2GIS7"));
+    
+    seq.updatePDBIds();
+    List<PDBEntry> pdbIds = seq.getAllPDBEntries();
+    assertEquals(4, pdbIds.size());
+    assertSame(pdbe1, pdbIds.get(0));
+    // chain code got added to 3A6S:
+    assertEquals("B", pdbe1.getChainCode());
+    assertEquals("1A70", pdbIds.get(1).getId());
+    // 4BQGA is parsed into id + chain
+    assertEquals("4BQG", pdbIds.get(2).getId());
+    assertEquals("a", pdbIds.get(2).getChainCode());
+    assertEquals("2GIS7", pdbIds.get(3).getId());
+    assertNull(pdbIds.get(3).getChainCode());
+  }
+
+  /**
+   * Test the method that either adds a pdbid or updates an existing one
+   */
+  @Test(groups = { "Functional" })
+  public void testAddPDBId()
+  {
+    PDBEntry pdbe = new PDBEntry("3A6S", null, null, null);
+    seq.addPDBId(pdbe);
+    assertEquals(1, seq.getAllPDBEntries().size());
+    assertSame(pdbe, seq.getPDBEntry("3A6S"));
+    assertSame(pdbe, seq.getPDBEntry("3a6s")); // case-insensitive
+
+    // add the same entry
+    seq.addPDBId(pdbe);
+    assertEquals(1, seq.getAllPDBEntries().size());
+    assertSame(pdbe, seq.getPDBEntry("3A6S"));
+
+    // add an identical entry
+    seq.addPDBId(new PDBEntry("3A6S", null, null, null));
+    assertEquals(1, seq.getAllPDBEntries().size());
+    assertSame(pdbe, seq.getPDBEntry("3A6S"));
+
+    // add a different entry
+    PDBEntry pdbe2 = new PDBEntry("1A70", null, null, null);
+    seq.addPDBId(pdbe2);
+    assertEquals(2, seq.getAllPDBEntries().size());
+    assertSame(pdbe, seq.getAllPDBEntries().get(0));
+    assertSame(pdbe2, seq.getAllPDBEntries().get(1));
+
+    // update pdbe with chain code, file, type
+    PDBEntry pdbe3 = new PDBEntry("3a6s", "A", Type.PDB, "filepath");
+    seq.addPDBId(pdbe3);
+    assertEquals(2, seq.getAllPDBEntries().size());
+    assertSame(pdbe, seq.getAllPDBEntries().get(0)); // updated in situ
+    assertEquals("3A6S", pdbe.getId()); // unchanged
+    assertEquals("A", pdbe.getChainCode()); // updated
+    assertEquals(Type.PDB.toString(), pdbe.getType()); // updated
+    assertEquals("filepath", pdbe.getFile()); // updated
+    assertSame(pdbe2, seq.getAllPDBEntries().get(1));
+
+    // add with a different file path
+    PDBEntry pdbe4 = new PDBEntry("3a6s", "A", Type.PDB, "filepath2");
+    seq.addPDBId(pdbe4);
+    assertEquals(3, seq.getAllPDBEntries().size());
+    assertSame(pdbe4, seq.getAllPDBEntries().get(2));
+
+    // add with a different chain code
+    PDBEntry pdbe5 = new PDBEntry("3a6s", "B", Type.PDB, "filepath");
+    seq.addPDBId(pdbe5);
+    assertEquals(4, seq.getAllPDBEntries().size());
+    assertSame(pdbe5, seq.getAllPDBEntries().get(3));
+  }
 }
index 8788609..a014ef8 100644 (file)
@@ -162,7 +162,8 @@ public class JmolParserTest
 
   private void checkFirstAAIsAssoc(SequenceI sq)
   {
-    assertTrue("No secondary structure assigned for protein sequence.",
+    assertTrue("No secondary structure assigned for protein sequence for "
+            + sq.getName(),
             sq.getAnnotation() != null && sq.getAnnotation().length >= 1
                     && sq.getAnnotation()[0].hasIcons);
     assertTrue(