From 99b6c1ca13af92cd8867971986a05664b0cd787c Mon Sep 17 00:00:00 2001 From: hansonr Date: Sun, 4 Aug 2019 10:39:36 -0500 Subject: [PATCH] JAL-3383 JAL-3253-applet -- Dissociates IntervalStore and BinarySearch from FeatureStoreJS. -- Adds test FeatureStoreJS -- cursory timing test in TestNG suggest JS version is 2x faster in *Java* (TestNG FeatureStoreTest 120 ms vs. FeatueStoreJSTest 66 ms) for default test. -- hypothesize that overhead of lambda functions is coming into play here. Needs a more heavy-duty test. --- src/jalview/datamodel/features/FeatureStore.java | 687 ++++++++++---------- src/jalview/datamodel/features/FeatureStoreI.java | 3 + .../datamodel/features/FeatureStoreImpl.java | 67 +- src/jalview/datamodel/features/FeatureStoreJS.java | 30 +- .../datamodel/features/FeatureStoreJSTest.java | 13 +- .../datamodel/features/FeatureStoreTest.java | 13 +- 6 files changed, 448 insertions(+), 365 deletions(-) diff --git a/src/jalview/datamodel/features/FeatureStore.java b/src/jalview/datamodel/features/FeatureStore.java index 2cdbeb2..fe575e0 100644 --- a/src/jalview/datamodel/features/FeatureStore.java +++ b/src/jalview/datamodel/features/FeatureStore.java @@ -29,11 +29,108 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import intervalstore.impl.BinarySearcher; - public abstract class FeatureStore implements FeatureStoreI { + /** + * Answers the 'length' of the feature, counting 0 for non-positional features + * and 1 for contact features + * + * @param feature + * @return + */ + protected static int getFeatureLength(SequenceFeature feature) + { + if (feature.isNonPositional()) + { + return 0; + } + if (feature.isContactFeature()) + { + return 1; + } + return 1 + feature.getEnd() - feature.getBegin(); + } + + /** + * Answers true if the list contains the feature, else false. This method is + * optimised for the condition that the list is sorted on feature start + * position ascending, and will give unreliable results if this does not hold. + * + * @param features + * @param feature + * @return + */ + @Override + public boolean listContains(List features, + SequenceFeature feature) + { + if (features == null || feature == null) + { + return false; + } + + /* + * locate the first entry in the list which does not precede the feature + */ + int pos = findFirstBegin(features, feature.begin); + int len = features.size(); + while (pos < len) + { + SequenceFeature sf = features.get(pos); + if (sf.getBegin() > feature.getBegin()) + { + return false; // no match found + } + if (sf.equals(feature)) + { + return true; + } + pos++; + } + return false; + } + + /** + * A helper method to return the maximum of two floats, where a non-NaN value + * is treated as 'greater than' a NaN value (unlike Math.max which does the + * opposite) + * + * @param f1 + * @param f2 + */ + protected static float max(float f1, float f2) + { + if (Float.isNaN(f1)) + { + return Float.isNaN(f2) ? f1 : f2; + } + else + { + return Float.isNaN(f2) ? f1 : Math.max(f1, f2); + } + } + + /** + * A helper method to return the minimum of two floats, where a non-NaN value + * is treated as 'less than' a NaN value (unlike Math.min which does the + * opposite) + * + * @param f1 + * @param f2 + */ + protected static float min(float f1, float f2) + { + if (Float.isNaN(f1)) + { + return Float.isNaN(f2) ? f1 : f2; + } + else + { + return Float.isNaN(f2) ? f1 : Math.min(f1, f2); + } + } + /* * Non-positional features have no (zero) start/end position. * Kept as a separate list in case this criterion changes in future. @@ -56,12 +153,6 @@ public abstract class FeatureStore implements FeatureStoreI */ Collection features; - @Override - public Collection getFeatures() - { - return features; - } - /* * Feature groups represented in stored positional features * (possibly including null) @@ -104,6 +195,39 @@ public abstract class FeatureStore implements FeatureStoreI } /** + * 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, and returns true. This method allows duplicate features to be + * added, so test before calling to avoid this. + * + * @param feature + * @return + */ + protected synchronized boolean addContactFeature(SequenceFeature feature) + { + if (contactFeatureStarts == null) + { + contactFeatureStarts = new ArrayList<>(); + contactFeatureEnds = new ArrayList<>(); + } + + /* + * insert into list sorted by start (first contact position): + * binary search the sorted list to find the insertion point + */ + contactFeatureStarts.add( + findFirstBegin(contactFeatureStarts, feature.begin), feature); + /* + * insert into list sorted by end (second contact position): + * binary search the sorted list to find the insertion point + */ + contactFeatureEnds.add(findFirstEnd(contactFeatureEnds, feature.end), + feature); + + return true; + } + + /** * 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() @@ -170,49 +294,31 @@ public abstract class FeatureStore implements FeatureStoreI return true; } - /** - * Answers true if this store contains the given feature (testing by - * SequenceFeature.equals), else false - * - * @param feature - * @return - */ - @Override - public boolean contains(SequenceFeature feature) + private void addFeaturesForGroup(String group, + Collection sfs, List result) { - if (feature.isNonPositional()) + if (sfs == null) { - return nonPositionalFeatures == null ? false - : nonPositionalFeatures.contains(feature); + return; } - - if (feature.isContactFeature()) + for (SequenceFeature sf : sfs) { - return contactFeatureStarts != null - && listContains(contactFeatureStarts, feature); + String featureGroup = sf.getFeatureGroup(); + if (group == null && featureGroup == null + || group != null && group.equals(featureGroup)) + { + result.add(sf); + } } - - return features == null ? false : features.contains(feature); } /** - * Answers the 'length' of the feature, counting 0 for non-positional features - * and 1 for contact features - * - * @param feature - * @return + * Adds one feature to the IntervalStore that can manage nested features + * (creating the IntervalStore if necessary) */ - protected static int getFeatureLength(SequenceFeature feature) + protected synchronized void addNestedFeature(SequenceFeature feature) { - if (feature.isNonPositional()) - { - return 0; - } - if (feature.isContactFeature()) - { - return 1; - } - return 1 + feature.getEnd() - feature.getBegin(); + features.add(feature); } /** @@ -239,183 +345,54 @@ public abstract class FeatureStore implements FeatureStoreI } /** - * Adds one feature to the IntervalStore that can manage nested features - * (creating the IntervalStore if necessary) - */ - protected synchronized void addNestedFeature(SequenceFeature feature) - { - features.add(feature); - } - /** - * 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, and returns true. This method allows duplicate features to be - * added, so test before calling to avoid this. + * Answers true if this store contains the given feature (testing by + * SequenceFeature.equals), else false * * @param feature * @return */ - protected synchronized boolean addContactFeature(SequenceFeature feature) + @Override + public boolean contains(SequenceFeature feature) { - if (contactFeatureStarts == null) + if (feature.isNonPositional()) { - contactFeatureStarts = new ArrayList<>(); - contactFeatureEnds = new ArrayList<>(); + return nonPositionalFeatures == null ? false + : nonPositionalFeatures.contains(feature); } - /* - * insert into list sorted by start (first contact position): - * binary search the sorted list to find the insertion point - */ - int insertPosition = BinarySearcher.findFirst(contactFeatureStarts, - f -> f.getBegin() >= feature.getBegin()); - contactFeatureStarts.add(insertPosition, feature); - - /* - * insert into list sorted by end (second contact position): - * binary search the sorted list to find the insertion point - */ - insertPosition = BinarySearcher.findFirst(contactFeatureEnds, - f -> f.getEnd() >= feature.getEnd()); - contactFeatureEnds.add(insertPosition, feature); + if (feature.isContactFeature()) + { + return contactFeatureStarts != null + && listContains(contactFeatureStarts, feature); + } - return true; + return features == null ? false : features.contains(feature); } /** - * Answers true if the list contains the feature, else false. This method is - * optimised for the condition that the list is sorted on feature start - * position ascending, and will give unreliable results if this does not hold. + * 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 features - * @param feature - * @return + * @param sf */ - protected static boolean listContains(List features, - SequenceFeature feature) + + @Override + public synchronized boolean delete(SequenceFeature sf) { - if (features == null || feature == null) - { - return false; - } + boolean removed = false; /* - * locate the first entry in the list which does not precede the feature + * try contact positions (and if found, delete + * from both lists of contact positions) */ - // int pos = binarySearch(features, - // SearchCriterion.byFeature(feature, RangeComparator.BY_START_POSITION)); - int pos = BinarySearcher.findFirst(features, - val -> val.getBegin() >= feature.getBegin()); - int len = features.size(); - while (pos < len) + if (!removed && contactFeatureStarts != null) { - SequenceFeature sf = features.get(pos); - if (sf.getBegin() > feature.getBegin()) - { - return false; // no match found - } - if (sf.equals(feature)) + removed = contactFeatureStarts.remove(sf); + if (removed) { - return true; - } - pos++; - } - return false; - } - - abstract protected void findContactFeatures(long from, long to, - List result); - - /** - * Answers a list of all positional features stored, in no guaranteed order - * - * @return - */ - - @Override - public List getPositionalFeatures( - List result) - { - - /* - * add any contact features - from the list by start position - */ - if (contactFeatureStarts != null) - { - result.addAll(contactFeatureStarts); - } - - /* - * add any nested features - */ - if (features != null) - { - result.addAll(features); - } - - return result; - } - - /** - * Answers a list of all contact features. If there are none, returns an - * immutable empty list. - * - * @return - */ - - @Override - public List getContactFeatures( - List result) - { - if (contactFeatureStarts != null) - { - result.addAll(contactFeatureStarts); - } - return result; - } - - /** - * Answers a list of all non-positional features. If there are none, returns - * an immutable empty list. - * - * @return - */ - - @Override - public List getNonPositionalFeatures( - List result) - { - if (nonPositionalFeatures != null) - { - result.addAll(nonPositionalFeatures); - } - 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 - */ - - @Override - public synchronized boolean delete(SequenceFeature sf) - { - boolean removed = false; - - /* - * 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); + contactFeatureEnds.remove(sf); } } @@ -446,115 +423,64 @@ public abstract class FeatureStore implements FeatureStoreI return removed; } - /** - * Rescan all features to recompute any cached values after an entry has been - * deleted. This is expected to be an infrequent event, so performance here is - * not critical. - */ - protected synchronized void rescanAfterDelete() - { - positionalFeatureGroups.clear(); - nonPositionalFeatureGroups.clear(); - totalExtent = 0; - positionalMinScore = Float.NaN; - positionalMaxScore = Float.NaN; - nonPositionalMinScore = Float.NaN; - nonPositionalMaxScore = Float.NaN; - /* - * scan non-positional features for groups and scores - */ - if (nonPositionalFeatures != null) - { - for (SequenceFeature sf : nonPositionalFeatures) - { - nonPositionalFeatureGroups.add(sf.getFeatureGroup()); - float score = sf.getScore(); - nonPositionalMinScore = min(nonPositionalMinScore, score); - nonPositionalMaxScore = max(nonPositionalMaxScore, score); - } - } + abstract protected void findContactFeatures(long from, long to, + List result); - /* - * scan positional features for groups, scores and extents - */ + abstract protected int findFirstBegin(List list, + long pos); - rescanPositional(contactFeatureStarts); - rescanPositional(features); - } + abstract protected int findFirstEnd(List list, long pos); - private void rescanPositional(Collection sfs) + @Override + public List findOverlappingFeatures(long start, long end) { - if (sfs == null) - { - return; - } - for (SequenceFeature sf : sfs) - { - positionalFeatureGroups.add(sf.getFeatureGroup()); - float score = sf.getScore(); - positionalMinScore = min(positionalMinScore, score); - positionalMaxScore = max(positionalMaxScore, score); - totalExtent += getFeatureLength(sf); - } + return findOverlappingFeatures(start, end, null); } - /** - * A helper method to return the minimum of two floats, where a non-NaN value - * is treated as 'less than' a NaN value (unlike Math.min which does the - * opposite) - * - * @param f1 - * @param f2 - */ - protected static float min(float f1, float f2) + @Override + public List getContactFeatures() { - if (Float.isNaN(f1)) - { - return Float.isNaN(f2) ? f1 : f2; - } - else - { - return Float.isNaN(f2) ? f1 : Math.min(f1, f2); - } + return getContactFeatures(new ArrayList<>()); } /** - * A helper method to return the maximum of two floats, where a non-NaN value - * is treated as 'greater than' a NaN value (unlike Math.max which does the - * opposite) + * Answers a list of all contact features. If there are none, returns an + * immutable empty list. * - * @param f1 - * @param f2 + * @return */ - protected static float max(float f1, float f2) + + @Override + public List getContactFeatures( + List result) { - if (Float.isNaN(f1)) - { - return Float.isNaN(f2) ? f1 : f2; - } - else + if (contactFeatureStarts != null) { - return Float.isNaN(f2) ? f1 : Math.max(f1, f2); + result.addAll(contactFeatureStarts); } + return result; } - /** - * Answers true if this store has no features, else false + * Answers the number of positional (or non-positional) features stored. + * Contact features count as 1. * + * @param positional * @return */ @Override - public boolean isEmpty() + public int getFeatureCount(boolean positional) { - boolean hasFeatures = (contactFeatureStarts != null - && !contactFeatureStarts.isEmpty()) - || (nonPositionalFeatures != null - && !nonPositionalFeatures.isEmpty()) - || features.size() > 0; + if (!positional) + { + return nonPositionalFeatures == null ? 0 + : nonPositionalFeatures.size(); + } + + return (contactFeatureStarts == null ? 0 : contactFeatureStarts.size()) + + features.size(); - return !hasFeatures; } /** @@ -581,6 +507,12 @@ public abstract class FeatureStore implements FeatureStoreI } } + @Override + public Collection getFeatures() + { + return features; + } + /** * Answers a list of all either positional or non-positional features whose * feature group matches the given group (which may be null) @@ -618,47 +550,96 @@ public abstract class FeatureStore implements FeatureStoreI return result; } - private void addFeaturesForGroup(String group, - Collection sfs, List result) + /** + * Answers the maximum score held for positional or non-positional features. + * This may be Float.NaN if there are no features, are none has a non-NaN + * score. + * + * @param positional + * @return + */ + + @Override + public float getMaximumScore(boolean positional) { - if (sfs == null) - { - return; - } - for (SequenceFeature sf : sfs) + return positional ? positionalMaxScore : nonPositionalMaxScore; + } + + /** + * Answers the minimum score held for positional or non-positional features. + * This may be Float.NaN if there are no features, are none has a non-NaN + * score. + * + * @param positional + * @return + */ + + @Override + public float getMinimumScore(boolean positional) + { + return positional ? positionalMinScore : nonPositionalMinScore; + } + + @Override + public List getNonPositionalFeatures() + { + return getNonPositionalFeatures(new ArrayList<>()); + } + + /** + * Answers a list of all non-positional features. If there are none, returns + * an immutable empty list. + * + * @return + */ + + @Override + public List getNonPositionalFeatures( + List result) + { + if (nonPositionalFeatures != null) { - String featureGroup = sf.getFeatureGroup(); - if (group == null && featureGroup == null - || group != null && group.equals(featureGroup)) - { - result.add(sf); - } + result.addAll(nonPositionalFeatures); } + return result; + } + + @Override + public List getPositionalFeatures() + { + return getPositionalFeatures(new ArrayList<>()); } /** - * Answers the number of positional (or non-positional) features stored. - * Contact features count as 1. + * Answers a list of all positional features stored, in no guaranteed order * - * @param positional * @return */ @Override - public int getFeatureCount(boolean positional) + public List getPositionalFeatures( + List result) { - if (!positional) + + /* + * add any contact features - from the list by start position + */ + if (contactFeatureStarts != null) { - return nonPositionalFeatures == null ? 0 - : nonPositionalFeatures.size(); + result.addAll(contactFeatureStarts); } - return (contactFeatureStarts == null ? 0 : contactFeatureStarts.size()) - + features.size(); + /* + * add any nested features + */ + if (features != null) + { + result.addAll(features); + } + return result; } - /** * Answers the total length of positional features (or zero if there are * none). Contact features contribute a value of 1 to the total. @@ -673,35 +654,74 @@ public abstract class FeatureStore implements FeatureStoreI } /** - * Answers the minimum score held for positional or non-positional features. - * This may be Float.NaN if there are no features, are none has a non-NaN - * score. + * Answers true if this store has no features, else false * - * @param positional * @return */ @Override - public float getMinimumScore(boolean positional) + public boolean isEmpty() { - return positional ? positionalMinScore : nonPositionalMinScore; + boolean hasFeatures = (contactFeatureStarts != null + && !contactFeatureStarts.isEmpty()) + || (nonPositionalFeatures != null + && !nonPositionalFeatures.isEmpty()) + || features.size() > 0; + + return !hasFeatures; } /** - * Answers the maximum score held for positional or non-positional features. - * This may be Float.NaN if there are no features, are none has a non-NaN - * score. - * - * @param positional - * @return + * Rescan all features to recompute any cached values after an entry has been + * deleted. This is expected to be an infrequent event, so performance here is + * not critical. */ - - @Override - public float getMaximumScore(boolean positional) + protected synchronized void rescanAfterDelete() { - return positional ? positionalMaxScore : nonPositionalMaxScore; + positionalFeatureGroups.clear(); + nonPositionalFeatureGroups.clear(); + totalExtent = 0; + positionalMinScore = Float.NaN; + positionalMaxScore = Float.NaN; + nonPositionalMinScore = Float.NaN; + nonPositionalMaxScore = Float.NaN; + /* + * scan non-positional features for groups and scores + */ + if (nonPositionalFeatures != null) + { + for (SequenceFeature sf : nonPositionalFeatures) + { + nonPositionalFeatureGroups.add(sf.getFeatureGroup()); + float score = sf.getScore(); + nonPositionalMinScore = min(nonPositionalMinScore, score); + nonPositionalMaxScore = max(nonPositionalMaxScore, score); + } + } + + /* + * scan positional features for groups, scores and extents + */ + + rescanPositional(contactFeatureStarts); + rescanPositional(features); } + private void rescanPositional(Collection sfs) + { + if (sfs == null) + { + return; + } + for (SequenceFeature sf : sfs) + { + positionalFeatureGroups.add(sf.getFeatureGroup()); + float score = sf.getScore(); + positionalMinScore = min(positionalMinScore, score); + positionalMaxScore = max(positionalMaxScore, score); + totalExtent += getFeatureLength(sf); + } + } /** * Adds the shift amount to the start and end of all positional features whose @@ -746,29 +766,4 @@ public abstract class FeatureStore implements FeatureStoreI return modified; } - - @Override - public List findOverlappingFeatures(long start, long end) - { - return findOverlappingFeatures(start, end, null); - } - - @Override - public List getPositionalFeatures() - { - return getPositionalFeatures(new ArrayList<>()); - } - - @Override - public List getContactFeatures() - { - return getContactFeatures(new ArrayList<>()); - } - - @Override - public List getNonPositionalFeatures() - { - return getNonPositionalFeatures(new ArrayList<>()); - } - } diff --git a/src/jalview/datamodel/features/FeatureStoreI.java b/src/jalview/datamodel/features/FeatureStoreI.java index 873d53c..fb32577 100644 --- a/src/jalview/datamodel/features/FeatureStoreI.java +++ b/src/jalview/datamodel/features/FeatureStoreI.java @@ -52,4 +52,7 @@ public interface FeatureStoreI boolean shiftFeatures(int fromPosition, int shiftBy); + boolean listContains(List features, + SequenceFeature feature); + } diff --git a/src/jalview/datamodel/features/FeatureStoreImpl.java b/src/jalview/datamodel/features/FeatureStoreImpl.java index 62832d7..d1d2670 100644 --- a/src/jalview/datamodel/features/FeatureStoreImpl.java +++ b/src/jalview/datamodel/features/FeatureStoreImpl.java @@ -46,6 +46,43 @@ public class FeatureStoreImpl extends 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, and returns true. This method allows duplicate features to be + * added, so test before calling to avoid this. + * + * @param feature + * @return + */ + @Override + protected synchronized boolean addContactFeature(SequenceFeature feature) + { + if (contactFeatureStarts == null) + { + contactFeatureStarts = new ArrayList<>(); + contactFeatureEnds = new ArrayList<>(); + } + + /* + * insert into list sorted by start (first contact position): + * binary search the sorted list to find the insertion point + */ + int insertPosition = findFirstBeginStatic(contactFeatureStarts, + feature.getBegin()); + contactFeatureStarts.add(insertPosition, feature); + + /* + * insert into list sorted by end (second contact position): + * binary search the sorted list to find the insertion point + */ + insertPosition = findFirstEndStatic(contactFeatureEnds, + feature.getEnd()); + contactFeatureEnds.add(insertPosition, feature); + + return true; + } + + /** * Adds contact features to the result list where either the second or the * first contact position lies within the target range * @@ -81,8 +118,7 @@ public class FeatureStoreImpl extends FeatureStore * find the first contact feature (if any) * whose end point is not before the target range */ - int index = BinarySearcher.findFirst(contactFeatureEnds, - f -> f.getEnd() >= from); + int index = findFirstEndStatic(contactFeatureEnds, from); int n = contactFeatureEnds.size(); while (index < n) @@ -136,8 +172,7 @@ public class FeatureStoreImpl extends FeatureStore private void findContactStartOverlaps(long from, long to, List result) { - int index = BinarySearcher.findFirst(contactFeatureStarts, - f -> f.getBegin() >= from); + int index = findFirstBegin(contactFeatureStarts, from); while (index < contactFeatureStarts.size()) { @@ -195,8 +230,30 @@ public class FeatureStoreImpl extends FeatureStore { result.addAll(((IntervalStoreI) features) .findOverlaps(start, end)); - // TODO Auto-generated method stub + } + + @Override + protected int findFirstBegin(List list, long pos) + { + return findFirstBeginStatic(list, pos); + } + private static int findFirstBeginStatic(List list, + long pos) + { + return BinarySearcher.findFirst(list, f -> f.getBegin() >= pos); + } + + @Override + protected int findFirstEnd(List list, long pos) + { + return findFirstEndStatic(list, pos); + } + + private static int findFirstEndStatic(List list, + long pos) + { + return BinarySearcher.findFirst(list, f -> f.getEnd() >= pos); } } diff --git a/src/jalview/datamodel/features/FeatureStoreJS.java b/src/jalview/datamodel/features/FeatureStoreJS.java index 295d7a0..3afbfb2 100644 --- a/src/jalview/datamodel/features/FeatureStoreJS.java +++ b/src/jalview/datamodel/features/FeatureStoreJS.java @@ -48,6 +48,30 @@ public class FeatureStoreJS extends 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, and returns true. This method allows duplicate features to be + * added, so test before calling to avoid this. + * + * @param feature + * @return + */ + @Override + protected synchronized boolean addContactFeature(SequenceFeature feature) + { + if (contactFeatureStarts == null) + { + contactFeatureStarts = new ArrayList<>(); + contactFeatureEnds = new ArrayList<>(); + } + contactFeatureStarts.add( + findFirstBegin(contactFeatureStarts, feature.begin), feature); + contactFeatureEnds.add(findFirstEnd(contactFeatureEnds, feature.end), + feature); + return true; + } + + /** * Returns a (possibly empty) list of features whose extent overlaps the given * range. The returned list is not ordered. Contact features are included if * either of the contact points lies within the range. @@ -451,7 +475,8 @@ public class FeatureStoreJS extends FeatureStore } } - public static int findFirstBegin(List list, long pos) + @Override + protected int findFirstBegin(List list, long pos) { int start = 0; int end = list.size() - 1; @@ -473,7 +498,8 @@ public class FeatureStoreJS extends FeatureStore return matched; } - public static int findFirstEnd(List list, long pos) + @Override + protected int findFirstEnd(List list, long pos) { int start = 0; int end = list.size() - 1; diff --git a/test/jalview/datamodel/features/FeatureStoreJSTest.java b/test/jalview/datamodel/features/FeatureStoreJSTest.java index 221d7a4..12b6c74 100644 --- a/test/jalview/datamodel/features/FeatureStoreJSTest.java +++ b/test/jalview/datamodel/features/FeatureStoreJSTest.java @@ -645,14 +645,15 @@ public class FeatureStoreJSTest @Test(groups = "Functional") public void testListContains() { - assertFalse(FeatureStore.listContains(null, null)); + FeatureStoreI featureStore = newFeatureStore(); + assertFalse(featureStore.listContains(null, null)); List features = new ArrayList<>(); - assertFalse(FeatureStore.listContains(features, null)); + assertFalse(featureStore.listContains(features, null)); SequenceFeature sf1 = new SequenceFeature("type1", "desc1", 20, 30, 3f, "group1"); - assertFalse(FeatureStore.listContains(null, sf1)); - assertFalse(FeatureStore.listContains(features, sf1)); + assertFalse(featureStore.listContains(null, sf1)); + assertFalse(featureStore.listContains(features, sf1)); features.add(sf1); SequenceFeature sf2 = new SequenceFeature("type1", "desc1", 20, 30, 3f, @@ -661,8 +662,8 @@ public class FeatureStoreJSTest "group1"); // sf2.equals(sf1) so contains should return true - assertTrue(FeatureStore.listContains(features, sf2)); - assertFalse(FeatureStore.listContains(features, sf3)); + assertTrue(featureStore.listContains(features, sf2)); + assertFalse(featureStore.listContains(features, sf3)); } @Test(groups = "Functional") diff --git a/test/jalview/datamodel/features/FeatureStoreTest.java b/test/jalview/datamodel/features/FeatureStoreTest.java index 7fe94d0..a1593eb 100644 --- a/test/jalview/datamodel/features/FeatureStoreTest.java +++ b/test/jalview/datamodel/features/FeatureStoreTest.java @@ -645,14 +645,15 @@ public class FeatureStoreTest @Test(groups = "Functional") public void testListContains() { - assertFalse(FeatureStore.listContains(null, null)); + FeatureStoreI featureStore = newFeatureStore(); + assertFalse(featureStore.listContains(null, null)); List features = new ArrayList<>(); - assertFalse(FeatureStore.listContains(features, null)); + assertFalse(featureStore.listContains(features, null)); SequenceFeature sf1 = new SequenceFeature("type1", "desc1", 20, 30, 3f, "group1"); - assertFalse(FeatureStore.listContains(null, sf1)); - assertFalse(FeatureStore.listContains(features, sf1)); + assertFalse(featureStore.listContains(null, sf1)); + assertFalse(featureStore.listContains(features, sf1)); features.add(sf1); SequenceFeature sf2 = new SequenceFeature("type1", "desc1", 20, 30, 3f, @@ -661,8 +662,8 @@ public class FeatureStoreTest "group1"); // sf2.equals(sf1) so contains should return true - assertTrue(FeatureStore.listContains(features, sf2)); - assertFalse(FeatureStore.listContains(features, sf3)); + assertTrue(featureStore.listContains(features, sf2)); + assertFalse(featureStore.listContains(features, sf3)); } @Test(groups = "Functional") -- 1.7.10.2