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);
}
/**
/**
* 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>();
{
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;
}
/**
}
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;
+ }
}
*/
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
* @param ranges
* @return
*/
- protected List<SubList> findSubranges(List<T> ranges)
+ protected List<SubList> buildSubranges(List<T> ranges)
{
List<SubList> sublists = new ArrayList<SubList>();
}
/**
- * 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();
* all subranges precede this one - add it on the end
*/
subranges.add(new NCNode<T>(entry));
- return;
+ return true;
}
/*
* 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)
* push new entry inside this subrange as it encloses it
*/
subrange.add(entry);
- return;
+ return true;
}
if (start <= subrange.getStart())
* entry encloses one or more preceding subranges
*/
addEnclosingRange(entry, firstEnclosed, lastEnclosed);
- return;
+ return true;
}
else
{
* so just add it
*/
subranges.add(j, new NCNode<T>(entry));
- return;
+ return true;
}
}
}
{
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.
* @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.
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;
+ }
}
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;
+ }
}
}
/**
- * 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();
{
featureStore.put(type, new FeatureStore());
}
- featureStore.get(type).addFeature(sf);
+ return featureStore.get(type).addFeature(sf);
}
/**
}
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;
+ }
}
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));
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);
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
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));
+ }
}
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;
@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")
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));
+ }
}
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;
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)
+ }
+
}
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
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);
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());
+ }
}