JAL-2446 various get and find methods added with test coverage
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Wed, 5 Apr 2017 08:14:28 +0000 (09:14 +0100)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Wed, 5 Apr 2017 08:14:28 +0000 (09:14 +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 [new file with mode: 0644]
test/jalview/datamodel/features/Range.java [new file with mode: 0644]
test/jalview/datamodel/features/SequenceFeaturesTest.java [new file with mode: 0644]

index bd94c8a..f7757be 100644 (file)
@@ -9,7 +9,8 @@ import java.util.List;
 
 /**
  * A data store for a set of sequence features that supports efficient lookup of
- * features overlapping a given range.
+ * features overlapping a given range. Intended for (but not limited to) storage
+ * of features for one sequence and feature type.
  * 
  * @author gmcarstairs
  *
@@ -21,6 +22,11 @@ public class FeatureStore
   Comparator<ContiguousI> endOrdering = new RangeComparator(false);
 
   /*
+   * Non-positional features have no (zero) start/end position.
+   */
+  List<SequenceFeature> nonPositionalFeatures;
+
+  /*
    * An ordered list of features, with the promise that no feature in the list 
    * properly contains any other. This constraint allows bounded linear search
    * of the list for features overlapping a region.
@@ -67,6 +73,10 @@ public class FeatureStore
     {
       addContactFeature(feature);
     }
+    else if (feature.isNonPositional())
+    {
+      addNonPositionalFeature(feature);
+    }
     else
     {
       boolean added = addNonNestedFeature(feature);
@@ -81,6 +91,21 @@ public class FeatureStore
   }
 
   /**
+   * Adds the feature to the list of non-positional features (with lazy
+   * instantiation of the list if it is null)
+   * 
+   * @param feature
+   */
+  protected void addNonPositionalFeature(SequenceFeature feature)
+  {
+    if (nonPositionalFeatures == null)
+    {
+      nonPositionalFeatures = new ArrayList<SequenceFeature>();
+    }
+    nonPositionalFeatures.add(feature);
+  }
+
+  /**
    * Adds one feature to the NCList that can manage nested features (creating
    * the NCList if necessary)
    */
@@ -370,10 +395,6 @@ public class FeatureStore
       }
       int start = sf.getBegin();
       int end = sf.getEnd();
-      if (sf.isContactFeature())
-      {
-        end = start;
-      }
       if (start <= to && end >= from)
       {
         result.add(sf);
@@ -442,4 +463,75 @@ public class FeatureStore
       }
     }
   }
+
+  /**
+   * Answers a list of all features stored (including any non-positional
+   * features), in no guaranteed order
+   * 
+   * @return
+   */
+  public List<SequenceFeature> getFeatures()
+  {
+    /*
+     * add non-nested features (may be all features for many cases)
+     */
+    List<SequenceFeature> result = new ArrayList<SequenceFeature>();
+    result.addAll(nonNestedFeatures);
+
+    /*
+     * add any contact features - from the list by start position
+     */
+    if (contactFeatureStarts != null)
+    {
+      result.addAll(contactFeatureStarts);
+    }
+
+    /*
+     * add any non-positional features
+     */
+    if (nonPositionalFeatures != null)
+    {
+      result.addAll(nonPositionalFeatures);
+    }
+
+    /*
+     * add any nested features
+     */
+    if (nestedFeatures != null)
+    {
+      result.addAll(nestedFeatures.getEntries());
+    }
+
+    return result;
+  }
+
+  /**
+   * Answers a list of all contact features. If there are none, returns an
+   * immutable empty list.
+   * 
+   * @return
+   */
+  public List<SequenceFeature> getContactFeatures()
+  {
+    if (contactFeatureStarts == null)
+    {
+      return Collections.emptyList();
+    }
+    return new ArrayList<SequenceFeature>(contactFeatureStarts);
+  }
+
+  /**
+   * Answers a list of all non-positional features. If there are none, returns
+   * an immutable empty list.
+   * 
+   * @return
+   */
+  public List<SequenceFeature> getNonPositionalFeatures()
+  {
+    if (nonPositionalFeatures == null)
+    {
+      return Collections.emptyList();
+    }
+    return new ArrayList<SequenceFeature>(nonPositionalFeatures);
+  }
 }
index 40b062a..6a9f750 100644 (file)
@@ -9,7 +9,10 @@ import java.util.List;
  * An adapted implementation of NCList as described in the paper
  * 
  * <pre>
- * todo
+ * Nested Containment List (NCList): a new algorithm for accelerating
+ * interval query of genome alignment and interval databases
+ * - Alexander V. Alekseyenko, Christopher J. Lee
+ * https://doi.org/10.1093/bioinformatics/btl647
  * </pre>
  */
 public class NCList<T extends ContiguousI>
@@ -312,7 +315,7 @@ public class NCList<T extends ContiguousI>
          */
         break;
       }
-      candidate.addOverlaps(from, to, result);
+      candidate.findOverlaps(from, to, result);
     }
 
   }
@@ -461,4 +464,29 @@ public class NCList<T extends ContiguousI>
   {
     return size;
   }
+
+  /**
+   * Returns a list of all entries stored
+   * 
+   * @return
+   */
+  public List<T> getEntries()
+  {
+    List<T> result = new ArrayList<T>();
+    getEntries(result);
+    return result;
+  }
+
+  /**
+   * Adds all contained entries to the given list
+   * 
+   * @param result
+   */
+  void getEntries(List<T> result)
+  {
+    for (NCNode<T> subrange : subranges)
+    {
+      subrange.getEntries(result);
+    }
+  }
 }
index ab10f67..6af913a 100644 (file)
@@ -18,6 +18,9 @@ class NCNode<V extends ContiguousI>
 
   private V region;
 
+  /*
+   * null, or an object holding contained subregions of this nodes region
+   */
   private NCList<V> subregions;
 
   /**
@@ -46,7 +49,7 @@ class NCNode<V extends ContiguousI>
   {
     region = entry;
     subregions = newNCList;
-    // size = 1 + newNCList.size();
+    size = 1 + newNCList.getSize();
   }
 
   /**
@@ -112,7 +115,7 @@ class NCNode<V extends ContiguousI>
    * @param to
    * @param result
    */
-  void addOverlaps(long from, long to, List<V> result)
+  void findOverlaps(long from, long to, List<V> result)
   {
     if (region.getBegin() <= to && region.getEnd() >= from)
     {
@@ -161,4 +164,18 @@ class NCNode<V extends ContiguousI>
     }
     return subregions.isValid(getStart(), getEnd());
   }
+
+  /**
+   * Adds all contained entries to the given list
+   * 
+   * @param entries
+   */
+  void getEntries(List<V> entries)
+  {
+    entries.add(region);
+    if (subregions != null)
+    {
+      subregions.getEntries(entries);
+    }
+  }
 }
index d177566..c947407 100644 (file)
@@ -2,16 +2,26 @@ package jalview.datamodel.features;
 
 import jalview.datamodel.SequenceFeature;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+/**
+ * A class that stores sequence features in a way that supports efficient
+ * querying by type and location (overlap). Intended for (but not limited to)
+ * storage of features for one sequence.
+ * 
+ * @author gmcarstairs
+ *
+ */
 public class SequenceFeatures
 {
 
   /*
    * map from feature type to structured store of features for that type
+   * null types are permitted (but not a good idea!)
    */
   private Map<String, FeatureStore> featureStore;
 
@@ -31,6 +41,7 @@ public class SequenceFeatures
   public void add(SequenceFeature sf)
   {
     String type = sf.getType();
+
     if (featureStore.get(type) == null)
     {
       featureStore.put(type, new FeatureStore());
@@ -57,4 +68,103 @@ public class SequenceFeatures
     }
     return features.findOverlappingFeatures(from, to);
   }
+
+  /**
+   * Answers a list of all features stored (including non-positional), in no
+   * particular guaranteed order
+   * 
+   * @return
+   */
+  public List<SequenceFeature> getFeatures()
+  {
+    List<SequenceFeature> result = new ArrayList<SequenceFeature>();
+    for (FeatureStore featureSet : featureStore.values())
+    {
+      result.addAll(featureSet.getFeatures());
+    }
+    return result;
+  }
+
+  /**
+   * Answers a list of all non-positional features stored, in no particular
+   * guaranteed order
+   * 
+   * @return
+   */
+  public List<SequenceFeature> getNonPositionalFeatures()
+  {
+    List<SequenceFeature> result = new ArrayList<SequenceFeature>();
+    for (FeatureStore featureSet : featureStore.values())
+    {
+      result.addAll(featureSet.getNonPositionalFeatures());
+    }
+    return result;
+  }
+
+  /**
+   * Answers a list of all contact features stored, in no particular guaranteed
+   * order
+   * 
+   * @return
+   */
+  public List<SequenceFeature> getContactFeatures()
+  {
+    List<SequenceFeature> result = new ArrayList<SequenceFeature>();
+    for (FeatureStore featureSet : featureStore.values())
+    {
+      result.addAll(featureSet.getContactFeatures());
+    }
+    return result;
+  }
+
+  /**
+   * Answers a list of all features of the given type (including
+   * non-positional), in no particular guaranteed order
+   * 
+   * @return
+   */
+  public List<SequenceFeature> getFeatures(String type)
+  {
+    List<SequenceFeature> result = new ArrayList<SequenceFeature>();
+    FeatureStore featureSet = featureStore.get(type);
+    if (featureSet != null)
+    {
+      result.addAll(featureSet.getFeatures());
+    }
+    return result;
+  }
+
+  /**
+   * Answers a list of all contact features of the given type, in no particular
+   * guaranteed order
+   * 
+   * @return
+   */
+  public List<SequenceFeature> getContactFeatures(String type)
+  {
+    List<SequenceFeature> result = new ArrayList<SequenceFeature>();
+    FeatureStore featureSet = featureStore.get(type);
+    if (featureSet != null)
+    {
+      result.addAll(featureSet.getContactFeatures());
+    }
+    return result;
+  }
+
+  /**
+   * Answers a list of all non-positional features of the given type, in no
+   * particular guaranteed order
+   * 
+   * @return
+   */
+  public List<SequenceFeature> getNonPositionalFeatures(String type)
+  {
+    List<SequenceFeature> result = new ArrayList<SequenceFeature>();
+    FeatureStore featureSet = featureStore.get(type);
+    if (featureSet != null)
+    {
+      result.addAll(featureSet.getNonPositionalFeatures());
+    }
+    return result;
+  }
 }
index e355aaf..7147c51 100644 (file)
@@ -229,4 +229,47 @@ public class FeatureStoreTest
             null);
     assertFalse(fs.addNonNestedFeature(sf10));
   }
+
+  @Test(groups = "Functional")
+  public void testGetFeatures()
+  {
+    FeatureStore store = new FeatureStore();
+    SequenceFeature sf1 = new SequenceFeature("Metal", "desc", 10, 20,
+            Float.NaN, null);
+    store.addFeature(sf1);
+    // same range
+    SequenceFeature sf2 = new SequenceFeature("Metal", "desc", 10, 20,
+            Float.NaN, null);
+    store.addFeature(sf2);
+    // discontiguous range
+    SequenceFeature sf3 = new SequenceFeature("Metal", "desc", 30, 40,
+            Float.NaN, null);
+    store.addFeature(sf3);
+    // overlapping range
+    SequenceFeature sf4 = new SequenceFeature("Metal", "desc", 15, 35,
+            Float.NaN, null);
+    store.addFeature(sf4);
+    // enclosing range
+    SequenceFeature sf5 = new SequenceFeature("Metal", "desc", 5, 50,
+            Float.NaN, null);
+    store.addFeature(sf5);
+    // non-positional feature
+    SequenceFeature sf6 = new SequenceFeature("Metal", "desc", 0, 0,
+            Float.NaN, null);
+    store.addFeature(sf6);
+    // contact feature
+    SequenceFeature sf7 = new SequenceFeature("Disulphide bond", "desc",
+            18, 45, Float.NaN, null);
+    store.addFeature(sf7);
+
+    List<SequenceFeature> features = store.getFeatures();
+    assertEquals(features.size(), 7);
+    assertTrue(features.contains(sf1));
+    assertTrue(features.contains(sf2));
+    assertTrue(features.contains(sf3));
+    assertTrue(features.contains(sf4));
+    assertTrue(features.contains(sf5));
+    assertTrue(features.contains(sf6));
+    assertTrue(features.contains(sf7));
+  }
 }
index 367e424..cb3a133 100644 (file)
@@ -12,35 +12,6 @@ import org.testng.annotations.Test;
 
 public class NCListTest
 {
-  class Range implements ContiguousI
-  {
-    int start;
-
-    int end;
-
-    @Override
-    public int getBegin()
-    {
-      return start;
-    }
-
-    @Override
-    public int getEnd()
-    {
-      return end;
-    }
-
-    Range(int i, int j)
-    {
-      start = i;
-      end = j;
-    }
-
-    @Override
-    public String toString() {
-      return String.valueOf(start) + "-" + String.valueOf(end);
-    }
-  }
 
   /**
    * A basic sanity test of the constructor
@@ -202,4 +173,39 @@ public class NCListTest
       System.out.println(ncl.prettyPrint());
     }
   }
+
+  @Test(groups = "Functional")
+  public void testGetEntries()
+  {
+    List<Range> ranges = new ArrayList<Range>();
+    Range r1 = new Range(20, 20);
+    Range r2 = new Range(10, 20);
+    Range r3 = new Range(15, 30);
+    Range r4 = new Range(10, 30);
+    Range r5 = new Range(11, 19);
+    Range r6 = new Range(10, 20);
+    ranges.add(r1);
+    ranges.add(r2);
+    ranges.add(r3);
+    ranges.add(r4);
+    ranges.add(r5);
+    ranges.add(r6);
+  
+    NCList<Range> ncl = new NCList<Range>(ranges);
+    Range r7 = new Range(1, 100);
+    ncl.add(r7);
+
+    List<Range> contents = ncl.getEntries();
+    assertEquals(contents.size(), 7);
+    assertTrue(contents.contains(r1));
+    assertTrue(contents.contains(r2));
+    assertTrue(contents.contains(r3));
+    assertTrue(contents.contains(r4));
+    assertTrue(contents.contains(r5));
+    assertTrue(contents.contains(r6));
+    assertTrue(contents.contains(r7));
+
+    ncl = new NCList<Range>();
+    assertTrue(ncl.getEntries().isEmpty());
+  }
 }
diff --git a/test/jalview/datamodel/features/NCNodeTest.java b/test/jalview/datamodel/features/NCNodeTest.java
new file mode 100644 (file)
index 0000000..da0aa4e
--- /dev/null
@@ -0,0 +1,78 @@
+package jalview.datamodel.features;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.testng.annotations.Test;
+
+public class NCNodeTest
+{
+  @Test(groups = "Functional")
+  public void testAdd()
+  {
+    Range r1 = new Range(10, 20);
+    NCNode<Range> node = new NCNode<Range>(r1);
+    assertEquals(node.getStart(), 10);
+    Range r2 = new Range(10, 15);
+    node.add(r2);
+
+    List<Range> contents = new ArrayList<Range>();
+    node.getEntries(contents);
+    assertEquals(contents.size(), 2);
+    assertTrue(contents.contains(r1));
+    assertTrue(contents.contains(r2));
+  }
+
+  @Test(
+    groups = "Functional",
+    expectedExceptions = { IllegalArgumentException.class })
+  public void testAdd_invalidRangeStart()
+  {
+    Range r1 = new Range(10, 20);
+    NCNode<Range> node = new NCNode<Range>(r1);
+    assertEquals(node.getStart(), 10);
+    Range r2 = new Range(9, 15);
+    node.add(r2);
+  }
+
+  @Test(
+    groups = "Functional",
+    expectedExceptions = { IllegalArgumentException.class })
+  public void testAdd_invalidRangeEnd()
+  {
+    Range r1 = new Range(10, 20);
+    NCNode<Range> node = new NCNode<Range>(r1);
+    assertEquals(node.getStart(), 10);
+    Range r2 = new Range(12, 21);
+    node.add(r2);
+  }
+
+  @Test(groups = "Functional")
+  public void testGetEntries()
+  {
+    Range r1 = new Range(10, 20);
+    NCNode<Range> node = new NCNode<Range>(r1);
+    List<Range> entries = new ArrayList<Range>();
+
+    node.getEntries(entries);
+    assertEquals(entries.size(), 1);
+    assertTrue(entries.contains(r1));
+
+    // clearing the returned list does not affect the NCNode
+    entries.clear();
+    node.getEntries(entries);
+    assertEquals(entries.size(), 1);
+    assertTrue(entries.contains(r1));
+
+    Range r2 = new Range(15, 18);
+    node.add(r2);
+    entries.clear();
+    node.getEntries(entries);
+    assertEquals(entries.size(), 2);
+    assertTrue(entries.contains(r1));
+    assertTrue(entries.contains(r2));
+  }
+}
diff --git a/test/jalview/datamodel/features/Range.java b/test/jalview/datamodel/features/Range.java
new file mode 100644 (file)
index 0000000..701ec2a
--- /dev/null
@@ -0,0 +1,32 @@
+package jalview.datamodel.features;
+
+class Range implements ContiguousI
+{
+  int start;
+
+  int end;
+
+  @Override
+  public int getBegin()
+  {
+    return start;
+  }
+
+  @Override
+  public int getEnd()
+  {
+    return end;
+  }
+
+  Range(int i, int j)
+  {
+    start = i;
+    end = j;
+  }
+
+  @Override
+  public String toString()
+  {
+    return String.valueOf(start) + "-" + String.valueOf(end);
+  }
+}
diff --git a/test/jalview/datamodel/features/SequenceFeaturesTest.java b/test/jalview/datamodel/features/SequenceFeaturesTest.java
new file mode 100644 (file)
index 0000000..7edd67d
--- /dev/null
@@ -0,0 +1,286 @@
+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.List;
+
+import org.testng.annotations.Test;
+
+public class SequenceFeaturesTest
+{
+  @Test(groups = "Functional")
+  public void testGetFeatures()
+  {
+    SequenceFeatures store = new SequenceFeatures();
+    SequenceFeature sf1 = new SequenceFeature("Metal", "desc", 10, 20,
+            Float.NaN, null);
+    store.add(sf1);
+    // same range
+    SequenceFeature sf2 = new SequenceFeature("Metal", "desc", 10, 20,
+            Float.NaN, null);
+    store.add(sf2);
+    // discontiguous range
+    SequenceFeature sf3 = new SequenceFeature("Metal", "desc", 30, 40,
+            Float.NaN, null);
+    store.add(sf3);
+    // overlapping range
+    SequenceFeature sf4 = new SequenceFeature("Metal", "desc", 15, 35,
+            Float.NaN, null);
+    store.add(sf4);
+    // enclosing range
+    SequenceFeature sf5 = new SequenceFeature("Metal", "desc", 5, 50,
+            Float.NaN, null);
+    store.add(sf5);
+    // non-positional feature
+    SequenceFeature sf6 = new SequenceFeature("Metal", "desc", 0, 0,
+            Float.NaN, null);
+    store.add(sf6);
+    // contact feature
+    SequenceFeature sf7 = new SequenceFeature("Disulphide bond", "desc",
+            18, 45, Float.NaN, null);
+    store.add(sf7);
+    // different feature type
+    SequenceFeature sf8 = new SequenceFeature("Pfam", "desc", 30, 40,
+            Float.NaN, null);
+    store.add(sf8);
+    SequenceFeature sf9 = new SequenceFeature("Pfam", "desc", 15, 35,
+            Float.NaN, null);
+    store.add(sf9);
+
+    /*
+     * get all features
+     */
+    List<SequenceFeature> features = store.getFeatures();
+    assertEquals(features.size(), 9);
+    assertTrue(features.contains(sf1));
+    assertTrue(features.contains(sf2));
+    assertTrue(features.contains(sf3));
+    assertTrue(features.contains(sf4));
+    assertTrue(features.contains(sf5));
+    assertTrue(features.contains(sf6));
+    assertTrue(features.contains(sf7));
+    assertTrue(features.contains(sf8));
+    assertTrue(features.contains(sf9));
+
+    /*
+     * get features by type
+     */
+    assertTrue(store.getFeatures(null).isEmpty());
+    assertTrue(store.getFeatures("Cath").isEmpty());
+    assertTrue(store.getFeatures("METAL").isEmpty());
+
+    features = store.getFeatures("Metal");
+    assertEquals(features.size(), 6);
+    assertTrue(features.contains(sf1));
+    assertTrue(features.contains(sf2));
+    assertTrue(features.contains(sf3));
+    assertTrue(features.contains(sf4));
+    assertTrue(features.contains(sf5));
+    assertTrue(features.contains(sf6));
+
+    features = store.getFeatures("Disulphide bond");
+    assertEquals(features.size(), 1);
+    assertTrue(features.contains(sf7));
+
+    features = store.getFeatures("Pfam");
+    assertEquals(features.size(), 2);
+    assertTrue(features.contains(sf8));
+    assertTrue(features.contains(sf9));
+  }
+
+  @Test(groups = "Functional")
+  public void testGetContactFeatures()
+  {
+    SequenceFeatures store = new SequenceFeatures();
+    // non-contact
+    SequenceFeature sf1 = new SequenceFeature("Metal", "desc", 10, 20,
+            Float.NaN, null);
+    store.add(sf1);
+    // non-positional
+    SequenceFeature sf2 = new SequenceFeature("Metal", "desc", 0, 0,
+            Float.NaN, null);
+    store.add(sf2);
+    // contact feature
+    SequenceFeature sf3 = new SequenceFeature("Disulphide bond", "desc",
+            18, 45, Float.NaN, null);
+    store.add(sf3);
+    // repeat for different feature type
+    SequenceFeature sf4 = new SequenceFeature("Pfam", "desc", 10, 20,
+            Float.NaN, null);
+    store.add(sf4);
+    SequenceFeature sf5 = new SequenceFeature("Pfam", "desc", 0, 0,
+            Float.NaN, null);
+    store.add(sf5);
+    SequenceFeature sf6 = new SequenceFeature("Disulfide bond", "desc", 18,
+            45, Float.NaN, null);
+    store.add(sf6);
+  
+    /*
+     * get all contact features
+     */
+    List<SequenceFeature> features = store.getContactFeatures();
+    assertEquals(features.size(), 2);
+    assertTrue(features.contains(sf3));
+    assertTrue(features.contains(sf6));
+  
+    /*
+     * get contact features by type
+     */
+    assertTrue(store.getContactFeatures(null).isEmpty());
+    assertTrue(store.getContactFeatures("Cath").isEmpty());
+    assertTrue(store.getContactFeatures("Pfam").isEmpty());
+    assertTrue(store.getContactFeatures("DISULPHIDE BOND").isEmpty());
+  
+    features = store.getContactFeatures("Disulphide bond");
+    assertEquals(features.size(), 1);
+    assertTrue(features.contains(sf3));
+  
+    features = store.getContactFeatures("Disulfide bond");
+    assertEquals(features.size(), 1);
+    assertTrue(features.contains(sf6));
+  }
+
+  @Test(groups = "Functional")
+  public void testGetNonPositionalFeatures()
+  {
+    SequenceFeatures store = new SequenceFeatures();
+    // positional
+    SequenceFeature sf1 = new SequenceFeature("Metal", "desc", 10, 20,
+            Float.NaN, null);
+    store.add(sf1);
+    // non-positional
+    SequenceFeature sf2 = new SequenceFeature("Metal", "desc", 0, 0,
+            Float.NaN, null);
+    store.add(sf2);
+    // contact feature
+    SequenceFeature sf3 = new SequenceFeature("Disulphide bond", "desc",
+            18, 45, Float.NaN, null);
+    store.add(sf3);
+    // repeat for different feature type
+    SequenceFeature sf4 = new SequenceFeature("Pfam", "desc", 10, 20,
+            Float.NaN, null);
+    store.add(sf4);
+    SequenceFeature sf5 = new SequenceFeature("Pfam", "desc", 0, 0,
+            Float.NaN, null);
+    store.add(sf5);
+    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,
+            Float.NaN, null);
+    store.add(sf7);
+  
+    /*
+     * get all non-positional features
+     */
+    List<SequenceFeature> features = store.getNonPositionalFeatures();
+    assertEquals(features.size(), 3);
+    assertTrue(features.contains(sf2));
+    assertTrue(features.contains(sf5));
+    assertTrue(features.contains(sf7));
+  
+    /*
+     * get non-positional features by type
+     */
+    assertTrue(store.getNonPositionalFeatures(null).isEmpty());
+    assertTrue(store.getNonPositionalFeatures("Cath").isEmpty());
+    assertTrue(store.getNonPositionalFeatures("PFAM").isEmpty());
+  
+    features = store.getNonPositionalFeatures("Metal");
+    assertEquals(features.size(), 1);
+    assertTrue(features.contains(sf2));
+  
+    features = store.getNonPositionalFeatures("Pfam");
+    assertEquals(features.size(), 2);
+    assertTrue(features.contains(sf5));
+    assertTrue(features.contains(sf7));
+  }
+
+  /**
+   * Helper method to add a feature of no particular type
+   * 
+   * @param sf
+   * @param type
+   * @param from
+   * @param to
+   * @return
+   */
+  SequenceFeature addFeature(SequenceFeatures sf, String type, int from,
+          int to)
+  {
+    SequenceFeature sf1 = new SequenceFeature(type, "", from, to,
+            Float.NaN,
+            null);
+    sf.add(sf1);
+    return sf1;
+  }
+
+  @Test(groups = "Functional")
+  public void testFindFeatures()
+  {
+    SequenceFeatures sf = new SequenceFeatures();
+    SequenceFeature sf1 = addFeature(sf, "Pfam", 10, 50);
+    SequenceFeature sf2 = addFeature(sf, "Pfam", 1, 15);
+    SequenceFeature sf3 = addFeature(sf, "Pfam", 20, 30);
+    SequenceFeature sf4 = addFeature(sf, "Pfam", 40, 100);
+    SequenceFeature sf5 = addFeature(sf, "Pfam", 60, 100);
+    SequenceFeature sf6 = addFeature(sf, "Pfam", 70, 70);
+    SequenceFeature sf7 = addFeature(sf, "Cath", 10, 50);
+    SequenceFeature sf8 = addFeature(sf, "Cath", 1, 15);
+    SequenceFeature sf9 = addFeature(sf, "Cath", 20, 30);
+    SequenceFeature sf10 = addFeature(sf, "Cath", 40, 100);
+    SequenceFeature sf11 = addFeature(sf, "Cath", 60, 100);
+    SequenceFeature sf12 = addFeature(sf, "Cath", 70, 70);
+    // null type is weird but possible:
+    SequenceFeature sf13 = addFeature(sf, null, 5, 12);
+  
+    List<SequenceFeature> overlaps = sf.findFeatures("Pfam", 200, 200);
+    assertTrue(overlaps.isEmpty());
+  
+    overlaps = sf.findFeatures("Pfam", 1, 9);
+    assertEquals(overlaps.size(), 1);
+    assertTrue(overlaps.contains(sf2));
+  
+    overlaps = sf.findFeatures("Pfam", 5, 18);
+    assertEquals(overlaps.size(), 2);
+    assertTrue(overlaps.contains(sf1));
+    assertTrue(overlaps.contains(sf2));
+  
+    overlaps = sf.findFeatures("Pfam", 30, 40);
+    assertEquals(overlaps.size(), 3);
+    assertTrue(overlaps.contains(sf1));
+    assertTrue(overlaps.contains(sf3));
+    assertTrue(overlaps.contains(sf4));
+  
+    overlaps = sf.findFeatures("Pfam", 80, 90);
+    assertEquals(overlaps.size(), 2);
+    assertTrue(overlaps.contains(sf4));
+    assertTrue(overlaps.contains(sf5));
+  
+    overlaps = sf.findFeatures("Pfam", 68, 70);
+    assertEquals(overlaps.size(), 3);
+    assertTrue(overlaps.contains(sf4));
+    assertTrue(overlaps.contains(sf5));
+    assertTrue(overlaps.contains(sf6));
+
+    overlaps = sf.findFeatures("Cath", 16, 69);
+    assertEquals(overlaps.size(), 4);
+    assertTrue(overlaps.contains(sf7));
+    assertFalse(overlaps.contains(sf8));
+    assertTrue(overlaps.contains(sf9));
+    assertTrue(overlaps.contains(sf10));
+    assertTrue(overlaps.contains(sf11));
+    assertFalse(overlaps.contains(sf12));
+
+    assertTrue(sf.findFeatures("Metal", 0, 1000).isEmpty());
+
+    overlaps = sf.findFeatures(null, 7, 7);
+    assertEquals(overlaps.size(), 1);
+    assertTrue(overlaps.contains(sf13));
+  }
+}