public class PDBEntry
{
+ private static final int PDB_ID_LENGTH = 4;
+
private String file;
private String type;
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)
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;
}
/**
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;
}
}
+ /**
+ * 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;
}
/**
- * 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;
}
}
}
@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;
}
/**
{
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
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
/*
+ assertEquals(case7, case9);
* Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
* Copyright (C) $$Year-Rel$$ The Jalview Authors
*
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;
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"));
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...
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;
+ }
+ }
+
}
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));
+ }
}
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(