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!
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();
+ }
}
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;
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));
+ }
}