JAL-1705 JAL-1686 stronger SequenceFeature.equals() test
[jalview.git] / src / jalview / datamodel / SequenceFeature.java
index 252f46c..8146400 100755 (executable)
@@ -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();
+  }
 }