From b6e08d03ffec50725f18b77c174c94e5e49647e7 Mon Sep 17 00:00:00 2001 From: gmungoc Date: Fri, 12 Feb 2016 16:42:34 +0000 Subject: [PATCH] JAL-1705 JAL-1686 stronger SequenceFeature.equals() test --- src/jalview/datamodel/SequenceFeature.java | 90 ++++++++++++++++++++++- test/jalview/datamodel/SequenceFeatureTest.java | 88 ++++++++++++++++++++++ 2 files changed, 175 insertions(+), 3 deletions(-) diff --git a/src/jalview/datamodel/SequenceFeature.java b/src/jalview/datamodel/SequenceFeature.java index 252f46c..8146400 100755 --- a/src/jalview/datamodel/SequenceFeature.java +++ b/src/jalview/datamodel/SequenceFeature.java @@ -133,23 +133,93 @@ public class SequenceFeature this.featureGroup = featureGroup; } - public boolean equals(SequenceFeature sf) + /** + * Two features are considered equal if they have the same type, group, + * description, start, end, phase, strand, and (if present) 'Name', ID' and + * 'Parent' attributes. + * + * Note we need to check Parent to distinguish the same exon occurring in + * different transcripts (in Ensembl GFF). This allows assembly of transcript + * sequences from their component exon regions. + */ + @Override + public boolean equals(Object o) + { + return equals(o, false); + } + + /** + * Overloaded method allows the equality test to optionally ignore the + * 'Parent' attribute of a feature. This supports avoiding adding many + * superficially duplicate 'exon' or CDS features to genomic or protein + * sequence. + * + * @param o + * @param ignoreParent + * @return + */ + public boolean equals(Object o, boolean ignoreParent) { + if (o == null || !(o instanceof SequenceFeature)) + { + return false; + } + + SequenceFeature sf = (SequenceFeature) o; if (begin != sf.begin || end != sf.end || score != sf.score) { return false; } - if (!(type + description + featureGroup).equals(sf.type - + sf.description + sf.featureGroup)) + if (getStrand() != sf.getStrand()) { return false; } + if (!(type + description + featureGroup + getPhase()).equals(sf.type + + sf.description + sf.featureGroup + sf.getPhase())) + { + return false; + } + if (!equalAttribute(getValue("ID"), sf.getValue("ID"))) + { + return false; + } + if (!equalAttribute(getValue("Name"), sf.getValue("Name"))) + { + return false; + } + if (!ignoreParent) + { + if (!equalAttribute(getValue("Parent"), sf.getValue("Parent"))) + { + return false; + } + } return true; } /** + * Returns true if both values are null, are both non-null and equal + * + * @param att1 + * @param att2 + * @return + */ + protected static boolean equalAttribute(Object att1, Object att2) + { + if (att1 == null && att2 == null) + { + return true; + } + if (att1 != null) + { + return att1.equals(att2); + } + return att2.equals(att1); + } + + /** * DOCUMENT ME! * * @return DOCUMENT ME! @@ -378,4 +448,18 @@ public class SequenceFeature return String.format("%d %d %s %s", getBegin(), getEnd(), getType(), getDescription()); } + + /** + * Overridden to ensure that whenever two objects are equal, they have the + * same hashCode + */ + @Override + public int hashCode() + { + String s = getType() + getDescription() + getFeatureGroup() + + getValue("ID") + getValue("Name") + getValue("Parent") + + getPhase(); + return s.hashCode() + getBegin() + getEnd() + (int) getScore() + + getStrand(); + } } diff --git a/test/jalview/datamodel/SequenceFeatureTest.java b/test/jalview/datamodel/SequenceFeatureTest.java index d488a76..82b260e 100644 --- a/test/jalview/datamodel/SequenceFeatureTest.java +++ b/test/jalview/datamodel/SequenceFeatureTest.java @@ -1,8 +1,10 @@ package jalview.datamodel; import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.assertNull; import static org.testng.AssertJUnit.assertSame; +import static org.testng.AssertJUnit.assertTrue; import org.testng.annotations.Test; @@ -62,4 +64,90 @@ public class SequenceFeatureTest sf.setValue("STRAND", "."); assertEquals(0, sf.getStrand()); } + + /** + * Tests for equality, and that equal objects have the same hashCode + */ + @Test(groups = { "Functional" }) + public void testEqualsAndHashCode() + { + SequenceFeature sf1 = new SequenceFeature("type", "desc", 22, 33, + 12.5f, "group"); + sf1.setValue("ID", "id"); + sf1.setValue("Name", "name"); + sf1.setValue("Parent", "parent"); + sf1.setStrand("+"); + sf1.setPhase("1"); + SequenceFeature sf2 = new SequenceFeature("type", "desc", 22, 33, + 12.5f, "group"); + sf2.setValue("ID", "id"); + sf2.setValue("Name", "name"); + sf2.setValue("Parent", "parent"); + sf2.setStrand("+"); + sf2.setPhase("1"); + + assertFalse(sf1.equals(null)); + assertTrue(sf1.equals(sf2)); + assertTrue(sf2.equals(sf1)); + assertEquals(sf1.hashCode(), sf2.hashCode()); + + // changing type breaks equals: + sf2.setType("Type"); + assertFalse(sf1.equals(sf2)); + + // changing description breaks equals: + sf2.setType("type"); + sf2.setDescription("Desc"); + assertFalse(sf1.equals(sf2)); + + // changing start position breaks equals: + sf2.setDescription("desc"); + sf2.setBegin(21); + assertFalse(sf1.equals(sf2)); + + // changing end position breaks equals: + sf2.setBegin(22); + sf2.setEnd(32); + assertFalse(sf1.equals(sf2)); + + // changing feature group breaks equals: + sf2.setEnd(33); + sf2.setFeatureGroup("Group"); + assertFalse(sf1.equals(sf2)); + + // changing ID breaks equals: + sf2.setFeatureGroup("group"); + sf2.setValue("ID", "id2"); + assertFalse(sf1.equals(sf2)); + + // changing Name breaks equals: + sf2.setValue("ID", "id"); + sf2.setValue("Name", "Name"); + assertFalse(sf1.equals(sf2)); + + // changing Parent breaks equals: + sf2.setValue("Name", "name"); + sf1.setValue("Parent", "Parent"); + assertFalse(sf1.equals(sf2)); + + // changing strand breaks equals: + sf1.setValue("Parent", "parent"); + sf2.setStrand("-"); + assertFalse(sf1.equals(sf2)); + + // changing phase breaks equals: + sf2.setStrand("+"); + sf1.setPhase("2"); + assertFalse(sf1.equals(sf2)); + + // restore equality as sanity check: + sf1.setPhase("1"); + assertTrue(sf1.equals(sf2)); + assertTrue(sf2.equals(sf1)); + assertEquals(sf1.hashCode(), sf2.hashCode()); + + // changing status doesn't change equals: + sf1.setStatus("new"); + assertTrue(sf1.equals(sf2)); + } } -- 1.7.10.2