JAL-2446 added contains, delete, checks for inclusion on add + tests
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Thu, 6 Apr 2017 15:08:36 +0000 (16:08 +0100)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Thu, 6 Apr 2017 15:08:36 +0000 (16:08 +0100)
src/jalview/datamodel/features/FeatureStore.java
src/jalview/datamodel/features/NCList.java
src/jalview/datamodel/features/NCNode.java
src/jalview/datamodel/features/SequenceFeatures.java
test/jalview/datamodel/features/FeatureStoreTest.java
test/jalview/datamodel/features/NCListTest.java
test/jalview/datamodel/features/NCNodeTest.java
test/jalview/datamodel/features/SequenceFeaturesTest.java

index f7757be..cb4bd6f 100644 (file)
@@ -59,66 +59,84 @@ public class FeatureStore
   public FeatureStore()
   {
     nonNestedFeatures = new ArrayList<SequenceFeature>();
-    // we only construct contactFeatures and the NCList if we need to
+    // we only construct nonPositionalFeatures, contactFeatures
+    // or the NCList if we need to
   }
 
   /**
-   * Add one entry to the data store
+   * Adds one sequence feature to the store, and returns true, unless the
+   * feature is already contained in the store, in which case this method
+   * returns false. Containment is determined by SequenceFeature.equals()
+   * comparison.
    * 
    * @param feature
    */
-  public void addFeature(SequenceFeature feature)
+  public boolean addFeature(SequenceFeature feature)
   {
+    boolean added = false;
+
     if (feature.isContactFeature())
     {
-      addContactFeature(feature);
+      added = addContactFeature(feature);
     }
     else if (feature.isNonPositional())
     {
-      addNonPositionalFeature(feature);
+      added = addNonPositionalFeature(feature);
     }
     else
     {
-      boolean added = addNonNestedFeature(feature);
-      if (!added)
+      if (!nonNestedFeatures.contains(feature))
       {
-        /*
-         * detected a nested feature - put it in the NCList structure
-         */
-        addNestedFeature(feature);
+        added = addNonNestedFeature(feature);
+        if (!added)
+        {
+          /*
+           * detected a nested feature - put it in the NCList structure
+           */
+          added = addNestedFeature(feature);
+        }
       }
     }
+
+    return added;
   }
 
   /**
    * Adds the feature to the list of non-positional features (with lazy
-   * instantiation of the list if it is null)
+   * instantiation of the list if it is null), and returns true. If the
+   * non-positional features already include the new feature (by equality test),
+   * then it is not added, and this method returns false.
    * 
    * @param feature
    */
-  protected void addNonPositionalFeature(SequenceFeature feature)
+  protected boolean addNonPositionalFeature(SequenceFeature feature)
   {
     if (nonPositionalFeatures == null)
     {
       nonPositionalFeatures = new ArrayList<SequenceFeature>();
     }
+    if (nonPositionalFeatures.contains(feature))
+    {
+      return false;
+    }
     nonPositionalFeatures.add(feature);
+    return true;
   }
 
   /**
    * Adds one feature to the NCList that can manage nested features (creating
-   * the NCList if necessary)
+   * the NCList if necessary), and returns true. If the feature is already
+   * stored in the NCList (by equality test), then it is not added, and this
+   * method returns false.
    */
-  protected synchronized void addNestedFeature(SequenceFeature feature)
+  protected synchronized boolean addNestedFeature(SequenceFeature feature)
   {
     if (nestedFeatures == null)
     {
       nestedFeatures = new NCList<SequenceFeature>(feature);
+      return true;
     }
-    else
-    {
-      nestedFeatures.add(feature);
-    }
+    return nestedFeatures.add(feature, false);
   }
 
   /**
@@ -225,13 +243,14 @@ public class FeatureStore
   /**
    * Add a contact feature to the lists that hold them ordered by start (first
    * contact) and by end (second contact) position, ensuring the lists remain
-   * ordered
+   * ordered, and returns true. If the contact feature lists already contain the
+   * given feature (by test for equality), does not add it and returns false.
    * 
    * @param feature
+   * @return
    */
-  protected synchronized void addContactFeature(SequenceFeature feature)
+  protected synchronized boolean addContactFeature(SequenceFeature feature)
   {
-    // TODO binary search for insertion points!
     if (contactFeatureStarts == null)
     {
       contactFeatureStarts = new ArrayList<SequenceFeature>();
@@ -240,10 +259,19 @@ public class FeatureStore
     {
       contactFeatureEnds = new ArrayList<SequenceFeature>();
     }
+
+    // TODO binary search for insertion points!
+    if (contactFeatureStarts.contains(feature))
+    {
+      return false;
+    }
+
     contactFeatureStarts.add(feature);
     Collections.sort(contactFeatureStarts, startOrdering);
     contactFeatureEnds.add(feature);
     Collections.sort(contactFeatureEnds, endOrdering);
+
+    return true;
   }
 
   /**
@@ -534,4 +562,51 @@ public class FeatureStore
     }
     return new ArrayList<SequenceFeature>(nonPositionalFeatures);
   }
+
+  /**
+   * Deletes the given feature from the store, returning true if it was found
+   * (and deleted), else false. This method makes no assumption that the feature
+   * is in the 'expected' place in the store, in case it has been modified since
+   * it was added.
+   * 
+   * @param sf
+   */
+  public boolean delete(SequenceFeature sf)
+  {
+    /*
+     * try the non-nested positional features first
+     */
+    boolean removed = nonNestedFeatures.remove(sf);
+
+    /*
+     * if not found, try contact positions (and if found, delete
+     * from both lists of contact positions)
+     */
+    if (!removed && contactFeatureStarts != null)
+    {
+      removed = contactFeatureStarts.remove(sf);
+      if (removed)
+      {
+        contactFeatureEnds.remove(sf);
+      }
+    }
+
+    /*
+     * if not found, try non-positional features
+     */
+    if (!removed && nonPositionalFeatures != null)
+    {
+      removed = nonPositionalFeatures.remove(sf);
+    }
+
+    /*
+     * if not found, try nested features
+     */
+    if (!removed && nestedFeatures != null)
+    {
+      removed = nestedFeatures.delete(sf);
+    }
+
+    return removed;
+  }
 }
index 02f94b6..17e08eb 100644 (file)
@@ -60,7 +60,7 @@ public class NCList<T extends ContiguousI>
      */
     Collections.sort(ranges, intervalSorter);
 
-    List<SubList> sublists = findSubranges(ranges);
+    List<SubList> sublists = buildSubranges(ranges);
 
     /*
      * convert each subrange to an NCNode consisting of a range and
@@ -95,7 +95,7 @@ public class NCList<T extends ContiguousI>
    * @param ranges
    * @return
    */
-  protected List<SubList> findSubranges(List<T> ranges)
+  protected List<SubList> buildSubranges(List<T> ranges)
   {
     List<SubList> sublists = new ArrayList<SubList>();
     
@@ -124,12 +124,31 @@ public class NCList<T extends ContiguousI>
   }
 
   /**
-   * Adds one entry to the stored set
+   * Adds one entry to the stored set (with duplicates allowed)
    * 
    * @param entry
    */
-  public synchronized void add(T entry)
+  public void add(T entry)
   {
+    add(entry, true);
+  }
+
+  /**
+   * Adds one entry to the stored set, and returns true, unless allowDuplicates
+   * is set to false and it is already contained (by object equality test), in
+   * which case it is not added and this method returns false.
+   * 
+   * @param entry
+   * @param allowDuplicates
+   * @return
+   */
+  public synchronized boolean add(T entry, boolean allowDuplicates)
+  {
+    if (!allowDuplicates && contains(entry))
+    {
+      return false;
+    }
+
     size++;
     long start = entry.getBegin();
     long end = entry.getEnd();
@@ -153,7 +172,7 @@ public class NCList<T extends ContiguousI>
        * all subranges precede this one - add it on the end
        */
       subranges.add(new NCNode<T>(entry));
-      return;
+      return true;
     }
 
     /*
@@ -175,7 +194,7 @@ public class NCList<T extends ContiguousI>
          * new entry lies between subranges j-1 j
          */
         subranges.add(j, new NCNode<T>(entry));
-        return;
+        return true;
       }
 
       if (subrange.getStart() <= start && subrange.getEnd() >= end)
@@ -184,7 +203,7 @@ public class NCList<T extends ContiguousI>
          * push new entry inside this subrange as it encloses it
          */
         subrange.add(entry);
-        return;
+        return true;
       }
       
       if (start <= subrange.getStart())
@@ -214,7 +233,7 @@ public class NCList<T extends ContiguousI>
              * entry encloses one or more preceding subranges
              */
             addEnclosingRange(entry, firstEnclosed, lastEnclosed);
-            return;
+            return true;
           }
           else
           {
@@ -223,7 +242,7 @@ public class NCList<T extends ContiguousI>
              * so just add it 
              */
             subranges.add(j, new NCNode<T>(entry));
-            return;
+            return true;
           }
         }
       }
@@ -245,9 +264,51 @@ public class NCList<T extends ContiguousI>
     {
       subranges.add(new NCNode<T>(entry));
     }
+
+    return true;
   }
   
   /**
+   * Answers true if this NCList contains the given entry (by object equality
+   * test), else false
+   * 
+   * @param entry
+   * @return
+   */
+  public boolean contains(T entry)
+  {
+    /*
+     * find the first sublist that might overlap, i.e. 
+     * the first whose end position is >= from
+     */
+    int candidateIndex = findFirstOverlap(entry.getBegin());
+
+    if (candidateIndex == -1)
+    {
+      return false;
+    }
+
+    int to = entry.getEnd();
+
+    for (int i = candidateIndex; i < subranges.size(); i++)
+    {
+      NCNode<T> candidate = subranges.get(i);
+      if (candidate.getStart() > to)
+      {
+        /*
+         * we are past the end of our target range
+         */
+        break;
+      }
+      if (candidate.contains(entry))
+      {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
    * Update the tree so that the range of the new entry encloses subranges i to
    * j (inclusive). That is, replace subranges i-j (inclusive) with a new
    * subrange that contains them.
@@ -296,8 +357,7 @@ public class NCList<T extends ContiguousI>
    * @param to
    * @param result
    */
-  protected void findOverlaps(long from, long to,
- List<T> result)
+  protected void findOverlaps(long from, long to, List<T> result)
   {
     /*
      * find the first sublist that might overlap, i.e. 
@@ -494,4 +554,70 @@ public class NCList<T extends ContiguousI>
       subrange.getEntries(result);
     }
   }
+
+  /**
+   * Deletes the given entry from the store, returning true if it was found (and
+   * deleted), else false. This method makes no assumption that the entry is in
+   * the 'expected' place in the store, in case it has been modified since it
+   * was added. Only the first 'same object' match is deleted, not 'equal' or
+   * multiple objects.
+   * 
+   * @param entry
+   */
+  public synchronized boolean delete(T entry)
+  {
+    if (entry == null)
+    {
+      return false;
+    }
+    for (int i = 0; i < subranges.size(); i++)
+    {
+      NCNode<T> subrange = subranges.get(i);
+      NCList<T> subRegions = subrange.getSubRegions();
+
+      if (subrange.getRegion() == entry)
+      {
+        /*
+         * if the subrange is rooted on this entry, promote its
+         * subregions (if any) to replace the subrange here 
+         */
+        subranges.remove(i);
+        if (subRegions != null)
+        {
+          subranges.addAll(i, subRegions.subranges);
+        }
+        size--;
+        return true;
+      }
+      else
+      {
+        if (subRegions != null && subRegions.delete(entry))
+        {
+          size--;
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Answers true if this contains no ranges
+   * 
+   * @return
+   */
+  public boolean isEmpty()
+  {
+    return getSize() == 0;
+  }
+
+  /**
+   * Answer the list of subranges held in this NCList
+   * 
+   * @return
+   */
+  List<NCNode<T>> getSubregions()
+  {
+    return subranges;
+  }
 }
index 6af913a..0755614 100644 (file)
@@ -178,4 +178,44 @@ class NCNode<V extends ContiguousI>
       subregions.getEntries(entries);
     }
   }
+
+  /**
+   * Answers true if this object contains the given entry (by object equals
+   * test), else false
+   * 
+   * @param entry
+   * @return
+   */
+  boolean contains(V entry)
+  {
+    if (entry == null)
+    {
+      return false;
+    }
+    if (entry.equals(region))
+    {
+      return true;
+    }
+    return subregions == null ? false : subregions.contains(entry);
+  }
+
+  /**
+   * Answers the 'root' region modelled by this object
+   * 
+   * @return
+   */
+  V getRegion()
+  {
+    return region;
+  }
+
+  /**
+   * Answers the (possibly null) contained regions within this object
+   * 
+   * @return
+   */
+  NCList<V> getSubRegions()
+  {
+    return subregions;
+  }
 }
index c947407..64aa63e 100644 (file)
@@ -34,11 +34,14 @@ public class SequenceFeatures
   }
 
   /**
-   * Add one sequence feature to the store
+   * Adds one sequence feature to the store, and returns true, unless the
+   * feature is already contained in the store, in which case this method
+   * returns false. Containment is determined by SequenceFeature.equals()
+   * comparison.
    * 
    * @param sf
    */
-  public void add(SequenceFeature sf)
+  public boolean add(SequenceFeature sf)
   {
     String type = sf.getType();
 
@@ -46,7 +49,7 @@ public class SequenceFeatures
     {
       featureStore.put(type, new FeatureStore());
     }
-    featureStore.get(type).addFeature(sf);
+    return featureStore.get(type).addFeature(sf);
   }
 
   /**
@@ -167,4 +170,24 @@ public class SequenceFeatures
     }
     return result;
   }
+
+  /**
+   * Deletes the given feature from the store, returning true if it was found
+   * (and deleted), else false. This method makes no assumption that the feature
+   * is in the 'expected' place in the store, in case it has been modified since
+   * it was added.
+   * 
+   * @param sf
+   */
+  public boolean delete(SequenceFeature sf)
+  {
+    for (FeatureStore featureSet : featureStore.values())
+    {
+      if (featureSet.delete(sf))
+      {
+        return true;
+      }
+    }
+    return false;
+  }
 }
index 7147c51..88a7616 100644 (file)
@@ -19,7 +19,8 @@ public class FeatureStoreTest
     FeatureStore fs = new FeatureStore();
     fs.addFeature(new SequenceFeature("", "", 10, 20, Float.NaN,
             null));
-    fs.addFeature(new SequenceFeature("", "", 10, 20, Float.NaN, null));
+    // same range different description
+    fs.addFeature(new SequenceFeature("", "desc", 10, 20, Float.NaN, null));
     fs.addFeature(new SequenceFeature("", "", 15, 25, Float.NaN, null));
     fs.addFeature(new SequenceFeature("", "", 20, 35, Float.NaN, null));
 
@@ -49,7 +50,10 @@ public class FeatureStoreTest
     SequenceFeature sf1 = addFeature(fs, 10, 50);
     SequenceFeature sf2 = addFeature(fs, 10, 40);
     SequenceFeature sf3 = addFeature(fs, 20, 30);
-    SequenceFeature sf4 = addFeature(fs, 20, 30);
+    // fudge feature at same location but different group (so is added)
+    SequenceFeature sf4 = new SequenceFeature("", "", 20, 30, Float.NaN,
+            "different group");
+    fs.addFeature(sf4);
     SequenceFeature sf5 = addFeature(fs, 35, 36);
 
     List<SequenceFeature> overlaps = fs.findOverlappingFeatures(1, 9);
@@ -237,8 +241,8 @@ public class FeatureStoreTest
     SequenceFeature sf1 = new SequenceFeature("Metal", "desc", 10, 20,
             Float.NaN, null);
     store.addFeature(sf1);
-    // same range
-    SequenceFeature sf2 = new SequenceFeature("Metal", "desc", 10, 20,
+    // same range, different description
+    SequenceFeature sf2 = new SequenceFeature("Metal", "desc2", 10, 20,
             Float.NaN, null);
     store.addFeature(sf2);
     // discontiguous range
@@ -272,4 +276,94 @@ public class FeatureStoreTest
     assertTrue(features.contains(sf6));
     assertTrue(features.contains(sf7));
   }
+
+  @Test(groups = "Functional")
+  public void testDelete()
+  {
+    FeatureStore store = new FeatureStore();
+    SequenceFeature sf1 = addFeature(store, 10, 20);
+    assertTrue(store.getFeatures().contains(sf1));
+
+    /*
+     * simple deletion
+     */
+    assertTrue(store.delete(sf1));
+    assertTrue(store.getFeatures().isEmpty());
+
+    /*
+     * non-positional feature deletion
+     */
+    SequenceFeature sf2 = addFeature(store, 0, 0);
+    assertTrue(store.getFeatures().contains(sf2));
+    assertTrue(store.delete(sf2));
+    assertTrue(store.getFeatures().isEmpty());
+
+    /*
+     * contact feature deletion
+     */
+    SequenceFeature sf3 = new SequenceFeature("", "Disulphide Bond", 11,
+            23, Float.NaN, null);
+    store.addFeature(sf3);
+    assertEquals(store.getFeatures().size(), 1);
+    assertTrue(store.getFeatures().contains(sf3));
+    assertTrue(store.delete(sf3));
+    assertTrue(store.getFeatures().isEmpty());
+
+    /*
+     * nested feature deletion
+     */
+    SequenceFeature sf4 = addFeature(store, 20, 30);
+    SequenceFeature sf5 = addFeature(store, 22, 26); // to NCList
+    SequenceFeature sf6 = addFeature(store, 23, 24); // child of sf5
+    SequenceFeature sf7 = addFeature(store, 25, 25); // sibling of sf6
+    SequenceFeature sf8 = addFeature(store, 24, 24); // child of sf6
+    SequenceFeature sf9 = addFeature(store, 23, 23); // child of sf6
+    assertEquals(store.getFeatures().size(), 6);
+
+    // delete a node with children - they take its place
+    assertTrue(store.delete(sf6)); // sf8, sf9 should become children of sf5
+    assertEquals(store.getFeatures().size(), 5);
+    assertFalse(store.getFeatures().contains(sf6));
+
+    // delete a node with no children
+    assertTrue(store.delete(sf7));
+    assertEquals(store.getFeatures().size(), 4);
+    assertFalse(store.getFeatures().contains(sf7));
+
+    // delete root of NCList
+    assertTrue(store.delete(sf5));
+    assertEquals(store.getFeatures().size(), 3);
+    assertFalse(store.getFeatures().contains(sf5));
+
+    // continue the killing fields
+    assertTrue(store.delete(sf4));
+    assertEquals(store.getFeatures().size(), 2);
+    assertFalse(store.getFeatures().contains(sf4));
+
+    assertTrue(store.delete(sf9));
+    assertEquals(store.getFeatures().size(), 1);
+    assertFalse(store.getFeatures().contains(sf9));
+
+    assertTrue(store.delete(sf8));
+    assertTrue(store.getFeatures().isEmpty());
+  }
+
+  @Test(groups = "Functional")
+  public void testAddFeature()
+  {
+    FeatureStore fs = new FeatureStore();
+
+    SequenceFeature sf1 = new SequenceFeature("Cath", "", 10, 20,
+            Float.NaN, null);
+    SequenceFeature sf2 = new SequenceFeature("Cath", "", 10, 20,
+            Float.NaN, null);
+
+    assertTrue(fs.addFeature(sf1));
+
+    /*
+     * re-adding the same or an identical feature should fail
+     */
+    assertFalse(fs.addFeature(sf1));
+    assertFalse(fs.addFeature(sf2));
+  }
 }
index ca4288c..e0d3659 100644 (file)
@@ -1,7 +1,11 @@
 package jalview.datamodel.features;
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertSame;
 import static org.testng.Assert.assertTrue;
 
+import jalview.datamodel.SequenceFeature;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -355,7 +359,46 @@ public class NCListTest
   @Test(groups = "Functional")
   public void testDelete()
   {
-    assertTrue(false, "todo");
+    List<Range> ranges = new ArrayList<Range>();
+    Range r1 = new Range(20, 30);
+    ranges.add(r1);
+    NCList<Range> ncl = new NCList<Range>(ranges);
+    assertTrue(ncl.getEntries().contains(r1));
+
+    Range r2 = new Range(20, 30);
+    assertFalse(ncl.delete(null)); // null argument
+    assertFalse(ncl.delete(r2)); // never added
+    assertTrue(ncl.delete(r1)); // success
+    assertTrue(ncl.getEntries().isEmpty());
+
+    /*
+     * tests where object.equals() == true
+     */
+    NCList<SequenceFeature> features = new NCList<SequenceFeature>();
+    SequenceFeature sf1 = new SequenceFeature("type", "desc", 1, 10, 2f,
+            "group");
+    SequenceFeature sf2 = new SequenceFeature("type", "desc", 1, 10, 2f,
+            "group");
+    features.add(sf1);
+    assertEquals(sf1, sf2); // sf1.equals(sf2)
+    assertFalse(features.delete(sf2)); // equality is not enough for deletion
+    assertTrue(features.getEntries().contains(sf1)); // still there!
+    assertTrue(features.delete(sf1));
+    assertTrue(features.getEntries().isEmpty()); // gone now
+
+    /*
+     * test with duplicate objects in NCList
+     */
+    features.add(sf1);
+    features.add(sf1);
+    assertEquals(features.getEntries().size(), 2);
+    assertSame(features.getEntries().get(0), sf1);
+    assertSame(features.getEntries().get(1), sf1);
+    assertTrue(features.delete(sf1)); // first match only is deleted
+    assertTrue(features.contains(sf1));
+    assertEquals(features.getSize(), 1);
+    assertTrue(features.delete(sf1));
+    assertTrue(features.getEntries().isEmpty());
   }
 
   @Test(groups = "Functional")
@@ -389,4 +432,40 @@ public class NCListTest
     assertEquals(ncl.toString(), "[15-25, 20-30, 25-35, 40-50, 45-55]");
     assertTrue(ncl.isValid());
   }
+
+  /**
+   * Test the contains method (which uses object equals test)
+   */
+  @Test(groups = "Functional")
+  public void testContains()
+  {
+    NCList<SequenceFeature> ncl = new NCList<SequenceFeature>();
+    SequenceFeature sf1 = new SequenceFeature("type", "desc", 1, 10, 2f,
+            "group");
+    SequenceFeature sf2 = new SequenceFeature("type", "desc", 1, 10, 2f,
+            "group");
+    SequenceFeature sf3 = new SequenceFeature("type", "desc", 1, 10, 2f,
+            "anothergroup");
+    ncl.add(sf1);
+
+    assertTrue(ncl.contains(sf1));
+    assertTrue(ncl.contains(sf2)); // sf1.equals(sf2)
+    assertFalse(ncl.contains(sf3)); // !sf1.equals(sf3)
+
+    /*
+     * make some deeper structure in the NCList
+     */
+    SequenceFeature sf4 = new SequenceFeature("type", "desc", 2, 9, 2f,
+            "group");
+    ncl.add(sf4);
+    assertTrue(ncl.contains(sf4));
+    SequenceFeature sf5 = new SequenceFeature("type", "desc", 4, 5, 2f,
+            "group");
+    SequenceFeature sf6 = new SequenceFeature("type", "desc", 6, 8, 2f,
+            "group");
+    ncl.add(sf5);
+    ncl.add(sf6);
+    assertTrue(ncl.contains(sf5));
+    assertTrue(ncl.contains(sf6));
+  }
 }
index da0aa4e..73f957e 100644 (file)
@@ -1,8 +1,11 @@
 package jalview.datamodel.features;
 
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertTrue;
 
+import jalview.datamodel.SequenceFeature;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -75,4 +78,25 @@ public class NCNodeTest
     assertTrue(entries.contains(r1));
     assertTrue(entries.contains(r2));
   }
+
+  /**
+   * Tests for the contains method (uses entry.equals() test)
+   */
+  @Test(groups = "Functional")
+  public void testContains()
+  {
+    SequenceFeature sf1 = new SequenceFeature("type", "desc", 1, 10, 2f,
+            "group");
+    SequenceFeature sf2 = new SequenceFeature("type", "desc", 1, 10, 2f,
+            "group");
+    SequenceFeature sf3 = new SequenceFeature("type", "desc", 1, 10, 2f,
+            "anothergroup");
+    NCNode<SequenceFeature> node = new NCNode<SequenceFeature>(sf1);
+
+    assertFalse(node.contains(null));
+    assertTrue(node.contains(sf1));
+    assertTrue(node.contains(sf2)); // sf1.equals(sf2)
+    assertFalse(node.contains(sf3)); // !sf1.equals(sf3)
+  }
+
 }
index 7edd67d..d340490 100644 (file)
@@ -19,8 +19,8 @@ public class SequenceFeaturesTest
     SequenceFeature sf1 = new SequenceFeature("Metal", "desc", 10, 20,
             Float.NaN, null);
     store.add(sf1);
-    // same range
-    SequenceFeature sf2 = new SequenceFeature("Metal", "desc", 10, 20,
+    // same range, different description
+    SequenceFeature sf2 = new SequenceFeature("Metal", "desc2", 10, 20,
             Float.NaN, null);
     store.add(sf2);
     // discontiguous range
@@ -170,8 +170,8 @@ public class SequenceFeaturesTest
     SequenceFeature sf6 = new SequenceFeature("Disulfide bond", "desc", 18,
             45, Float.NaN, null);
     store.add(sf6);
-    // on more non-positional
-    SequenceFeature sf7 = new SequenceFeature("Pfam", "desc", 0, 0,
+    // one more non-positional, different description
+    SequenceFeature sf7 = new SequenceFeature("Pfam", "desc2", 0, 0,
             Float.NaN, null);
     store.add(sf7);
   
@@ -283,4 +283,18 @@ public class SequenceFeaturesTest
     assertEquals(overlaps.size(), 1);
     assertTrue(overlaps.contains(sf13));
   }
+
+  @Test(groups = "Functional")
+  public void testDelete()
+  {
+    SequenceFeatures sf = new SequenceFeatures();
+    SequenceFeature sf1 = addFeature(sf, "Pfam", 10, 50);
+    assertTrue(sf.getFeatures().contains(sf1));
+
+    assertFalse(sf.delete(null));
+    SequenceFeature sf2 = new SequenceFeature("Cath", "", 10, 15, 0f, null);
+    assertFalse(sf.delete(sf2)); // not added, can't delete it
+    assertTrue(sf.delete(sf1));
+    assertTrue(sf.getFeatures().isEmpty());
+  }
 }