/**
* 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
*
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.
{
addContactFeature(feature);
}
+ else if (feature.isNonPositional())
+ {
+ addNonPositionalFeature(feature);
+ }
else
{
boolean added = addNonNestedFeature(feature);
}
/**
+ * 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)
*/
}
int start = sf.getBegin();
int end = sf.getEnd();
- if (sf.isContactFeature())
- {
- end = start;
- }
if (start <= to && end >= from)
{
result.add(sf);
}
}
}
+
+ /**
+ * 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);
+ }
}
* 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>
*/
break;
}
- candidate.addOverlaps(from, to, result);
+ candidate.findOverlaps(from, to, result);
}
}
{
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);
+ }
+ }
}
private V region;
+ /*
+ * null, or an object holding contained subregions of this nodes region
+ */
private NCList<V> subregions;
/**
{
region = entry;
subregions = newNCList;
- // size = 1 + newNCList.size();
+ size = 1 + newNCList.getSize();
}
/**
* @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)
{
}
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);
+ }
+ }
}
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;
public void add(SequenceFeature sf)
{
String type = sf.getType();
+
if (featureStore.get(type) == null)
{
featureStore.put(type, new FeatureStore());
}
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;
+ }
}
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));
+ }
}
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
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());
+ }
}
--- /dev/null
+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));
+ }
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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));
+ }
+}