X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fdatamodel%2Ffeatures%2FFeatureStore.java;h=54c0d59f90b164a1f62d890b60e32be94cfd383b;hb=4f77328104498504339216829abf5ea87e2791ec;hp=1451892985f1f26d63e2c1693e41d8e0c9a66e2c;hpb=2b8c0785318a3528e1876e8e2dd48b7d831eae69;p=jalview.git diff --git a/src/jalview/datamodel/features/FeatureStore.java b/src/jalview/datamodel/features/FeatureStore.java index 1451892..54c0d59 100644 --- a/src/jalview/datamodel/features/FeatureStore.java +++ b/src/jalview/datamodel/features/FeatureStore.java @@ -23,134 +23,25 @@ package jalview.datamodel.features; import jalview.datamodel.SequenceFeature; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; -public abstract class FeatureStore implements FeatureStoreI -{ - - /** - * track last start for quick insertion of ordered features - */ - protected int lastStart = -1, lastContactStart = -1; - - /** - * 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 list - * @param feature - * @return - */ - @Override - public boolean listContains(List list, - SequenceFeature feature) - { - if (list == null || feature == null) - { - return false; - } - - return (getEquivalentFeatureIndex(list, feature) >= 0); - } - - /** - * Binary search for the index (>= 0) of a feature in a list. - * - * @param list - * @param feature - * @return index if found; -1 if not - */ - protected int getEquivalentFeatureIndex(List list, - SequenceFeature feature) - { - - /* - * locate the first entry in the list which does not precede the feature - */ - int begin = feature.begin; - int pos = findFirstBegin(list, begin); - int len = list.size(); - while (pos < len) - { - SequenceFeature sf = list.get(pos); - if (sf.begin > begin) - { - return -1; // no match found - } - if (sf.equals(feature)) - { - return pos; - } - pos++; - } - return -1; - } - - /** - * 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); - } - } +import intervalstore.api.IntervalStoreI; +import intervalstore.impl.BinarySearcher; +import intervalstore.impl.IntervalStore; +/** + * A data store for a set of sequence features that supports efficient lookup of + * features overlapping a given range. Intended for (but not limited to) storage + * of features for one sequence and feature type. + * + * @author gmcarstairs + * + */ +public class FeatureStore +{ /* * Non-positional features have no (zero) start/end position. * Kept as a separate list in case this criterion changes in future. @@ -171,7 +62,7 @@ public abstract class FeatureStore implements FeatureStoreI * IntervalStore holds remaining features and provides efficient * query for features overlapping any given interval */ - Collection features; + IntervalStoreI features; /* * Feature groups represented in stored positional features @@ -204,6 +95,7 @@ public abstract class FeatureStore implements FeatureStoreI */ public FeatureStore() { + features = new IntervalStore<>(); positionalFeatureGroups = new HashSet<>(); nonPositionalFeatureGroups = new HashSet<>(); positionalMinScore = Float.NaN; @@ -215,39 +107,6 @@ 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() @@ -255,59 +114,32 @@ public abstract class FeatureStore implements FeatureStoreI * * @param feature */ - - @Override public boolean addFeature(SequenceFeature feature) { - // if (contains(feature)) - // { - // return false; - // } - - // /* - // * keep a record of feature groups - // */ - // if (!feature.isNonPositional()) - // { - // positionalFeatureGroups.add(feature.getFeatureGroup()); - // } + if (contains(feature)) + { + return false; + } - if (feature.isContactFeature()) + /* + * keep a record of feature groups + */ + if (!feature.isNonPositional()) { - if (containsContactFeature(feature)) - { - return false; - } positionalFeatureGroups.add(feature.getFeatureGroup()); - if (feature.begin > lastContactStart) - { - lastContactStart = feature.begin; - } + } + + if (feature.isContactFeature()) + { addContactFeature(feature); } else if (feature.isNonPositional()) { - if (containsNonPositional(feature)) - { - return false; - } - addNonPositionalFeature(feature); } else { - // allow for check with - if (checkContainsPositionalFeatureForAdd(feature) - || !addPositionalFeature(feature)) - { - return false; - } - positionalFeatureGroups.add(feature.getFeatureGroup()); - // addPositionalFeature(feature); - if (feature.begin > lastStart) - { - lastStart = feature.begin; - } + addNestedFeature(feature); } /* @@ -339,31 +171,50 @@ public abstract class FeatureStore implements FeatureStoreI return true; } - private void addFeaturesForGroup(String group, - Collection sfs, List result) + /** + * Answers true if this store contains the given feature (testing by + * SequenceFeature.equals), else false + * + * @param feature + * @return + */ + public boolean contains(SequenceFeature feature) { - if (sfs == null) + if (feature.isNonPositional()) { - return; + return nonPositionalFeatures == null ? false : nonPositionalFeatures + .contains(feature); } - for (SequenceFeature sf : sfs) + + if (feature.isContactFeature()) { - String featureGroup = sf.getFeatureGroup(); - if (group == null && featureGroup == null - || group != null && group.equals(featureGroup)) - { - result.add(sf); - } + return contactFeatureStarts == null ? false : listContains( + contactFeatureStarts, feature); } + + return features == null ? false : features + .contains(feature); } /** - * Adds one feature to the IntervalStore that can manage nested features - * (creating the IntervalStore if necessary) + * Answers the 'length' of the feature, counting 0 for non-positional features + * and 1 for contact features * - * @return true if added -- allowing for late checking during addition + * @param feature + * @return */ - abstract protected boolean addPositionalFeature(SequenceFeature feature); + protected static int getFeatureLength(SequenceFeature feature) + { + if (feature.isNonPositional()) + { + return 0; + } + if (feature.isContactFeature()) + { + return 1; + } + return 1 + feature.getEnd() - feature.getBegin(); + } /** * Adds the feature to the list of non-positional features (with lazy @@ -389,64 +240,302 @@ public abstract class FeatureStore implements FeatureStoreI } /** - * Answers true if this store contains the given feature (testing by - * SequenceFeature.equals), else false + * Adds one feature to the IntervalStore that can manage nested features + * (creating the IntervalStore if necessary) + */ + protected synchronized void addNestedFeature(SequenceFeature feature) + { + if (features == null) + { + features = new IntervalStore<>(); + } + 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. * * @param feature * @return */ - @Override - public boolean contains(SequenceFeature feature) + protected synchronized boolean addContactFeature(SequenceFeature feature) { - if (feature.isNonPositional()) + if (contactFeatureStarts == null) { - return containsNonPositional(feature); - + contactFeatureStarts = new ArrayList<>(); } - - if (feature.isContactFeature()) + if (contactFeatureEnds == null) { - return containsContactFeature(feature); - + contactFeatureEnds = new ArrayList<>(); } - return containsPositionalFeature(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); + + return true; } /** - * A check that can be overridden if the check is being done during the add - * operation itself. + * 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 */ - protected boolean checkContainsPositionalFeatureForAdd( + protected static boolean listContains(List features, SequenceFeature feature) { - return containsPositionalFeature(feature); + if (features == null || feature == null) + { + return false; + } + + /* + * locate the first entry in the list which does not precede the feature + */ + // 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) + { + SequenceFeature sf = features.get(pos); + if (sf.getBegin() > feature.getBegin()) + { + return false; // no match found + } + if (sf.equals(feature)) + { + return true; + } + pos++; + } + return false; + } + + /** + * 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. + * + * @param start + * start position of overlap range (inclusive) + * @param end + * end position of overlap range (inclusive) + * @return + */ + public List findOverlappingFeatures(long start, long end) + { + List result = new ArrayList<>(); + + findContactFeatures(start, end, result); + + if (features != null) + { + result.addAll(features.findOverlaps(start, end)); + } + + return result; + } + + /** + * Adds contact features to the result list where either the second or the + * first contact position lies within the target range + * + * @param from + * @param to + * @param result + */ + protected void findContactFeatures(long from, long to, + List result) + { + if (contactFeatureStarts != null) + { + findContactStartOverlaps(from, to, result); + } + if (contactFeatureEnds != null) + { + findContactEndOverlaps(from, to, result); + } + } + + /** + * Adds to the result list any contact features whose end (second contact + * point), but not start (first contact point), lies in the query from-to + * range + * + * @param from + * @param to + * @param result + */ + protected void findContactEndOverlaps(long from, long to, + List result) + { + /* + * 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); + + while (index < contactFeatureEnds.size()) + { + SequenceFeature sf = contactFeatureEnds.get(index); + if (!sf.isContactFeature()) + { + System.err.println("Error! non-contact feature type " + + sf.getType() + " in contact features list"); + index++; + continue; + } + + int begin = sf.getBegin(); + if (begin >= from && begin <= to) + { + /* + * this feature's first contact position lies in the search range + * so we don't include it in results a second time + */ + index++; + continue; + } + + if (sf.getEnd() > to) + { + /* + * this feature (and all following) has end point after the target range + */ + break; + } + + /* + * feature has end >= from and end <= to + * i.e. contact end point lies within overlap search range + */ + result.add(sf); + index++; + } + } + + /** + * Adds contact features whose start position lies in the from-to range to the + * result list + * + * @param from + * @param to + * @param result + */ + protected void findContactStartOverlaps(long from, long to, + List result) + { + int index = BinarySearcher.findFirst(contactFeatureStarts, + f -> f.getBegin() >= from); + + while (index < contactFeatureStarts.size()) + { + SequenceFeature sf = contactFeatureStarts.get(index); + if (!sf.isContactFeature()) + { + System.err.println("Error! non-contact feature " + sf.toString() + + " in contact features list"); + index++; + continue; + } + if (sf.getBegin() > to) + { + /* + * this feature's start (and all following) follows the target range + */ + break; + } + + /* + * feature has begin >= from and begin <= to + * i.e. contact start point lies within overlap search range + */ + result.add(sf); + index++; + } } - private boolean containsPositionalFeature(SequenceFeature feature) + /** + * Answers a list of all positional features stored, in no guaranteed order + * + * @return + */ + public List getPositionalFeatures() { - return features == null || feature.begin > lastStart ? false - : containsFeature(feature); + List result = new ArrayList<>(); + + /* + * 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; } - private boolean containsContactFeature(SequenceFeature feature) + /** + * Answers a list of all contact features. If there are none, returns an + * immutable empty list. + * + * @return + */ + public List getContactFeatures() { - return contactFeatureStarts != null && feature.begin <= lastContactStart - && listContains(contactFeatureStarts, feature); + if (contactFeatureStarts == null) + { + return Collections.emptyList(); + } + return new ArrayList<>(contactFeatureStarts); } - private boolean containsNonPositional(SequenceFeature feature) + /** + * Answers a list of all non-positional features. If there are none, returns + * an immutable empty list. + * + * @return + */ + public List getNonPositionalFeatures() { - return nonPositionalFeatures == null ? false - : nonPositionalFeatures.contains(feature); + if (nonPositionalFeatures == null) + { + return Collections.emptyList(); + } + return new ArrayList<>(nonPositionalFeatures); } - abstract protected boolean containsFeature(SequenceFeature feature); - /** * 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 @@ -455,8 +544,6 @@ public abstract class FeatureStore implements FeatureStoreI * * @param sf */ - - @Override public synchronized boolean delete(SequenceFeature sf) { boolean removed = false; @@ -474,12 +561,15 @@ public abstract class FeatureStore implements FeatureStoreI } } + boolean removedNonPositional = false; + /* * if not found, try non-positional features */ if (!removed && nonPositionalFeatures != null) { - removed = nonPositionalFeatures.remove(sf); + removedNonPositional = nonPositionalFeatures.remove(sf); + removed = removedNonPositional; } /* @@ -487,7 +577,7 @@ public abstract class FeatureStore implements FeatureStoreI */ if (!removed && features != null) { - removed = findAndRemoveNonContactFeature(sf); + removed = features.remove(sf); } if (removed) @@ -498,66 +588,100 @@ public abstract class FeatureStore implements FeatureStoreI return removed; } - abstract protected boolean findAndRemoveNonContactFeature(SequenceFeature sf); - - abstract protected void findContactFeatures(long from, long to, - List result); - - abstract protected int findFirstBegin(List list, - long pos); + /** + * 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; - abstract protected int findFirstEnd(List list, long pos); + /* + * scan non-positional features for groups and scores + */ + for (SequenceFeature sf : getNonPositionalFeatures()) + { + nonPositionalFeatureGroups.add(sf.getFeatureGroup()); + float score = sf.getScore(); + nonPositionalMinScore = min(nonPositionalMinScore, score); + nonPositionalMaxScore = max(nonPositionalMaxScore, score); + } - @Override - public List findOverlappingFeatures(long start, long end) - { - return findOverlappingFeatures(start, end, null); + /* + * scan positional features for groups, scores and extents + */ + for (SequenceFeature sf : getPositionalFeatures()) + { + positionalFeatureGroups.add(sf.getFeatureGroup()); + float score = sf.getScore(); + positionalMinScore = min(positionalMinScore, score); + positionalMaxScore = max(positionalMaxScore, score); + totalExtent += getFeatureLength(sf); + } } - @Override - public List getContactFeatures() + /** + * 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) { - return getContactFeatures(new ArrayList<>()); + if (Float.isNaN(f1)) + { + return Float.isNaN(f2) ? f1 : f2; + } + else + { + return Float.isNaN(f2) ? f1 : Math.min(f1, f2); + } } /** - * Answers a list of all contact features. If there are none, returns an - * immutable empty list. + * 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) * - * @return + * @param f1 + * @param f2 */ - - @Override - public List getContactFeatures( - List result) + protected static float max(float f1, float f2) { - if (contactFeatureStarts != null) + if (Float.isNaN(f1)) { - result.addAll(contactFeatureStarts); + return Float.isNaN(f2) ? f1 : f2; + } + else + { + return Float.isNaN(f2) ? f1 : Math.max(f1, f2); } - return result; } /** - * Answers the number of positional (or non-positional) features stored. - * Contact features count as 1. + * Answers true if this store has no features, else false * - * @param positional * @return */ - - @Override - public int getFeatureCount(boolean positional) + public boolean isEmpty() { - if (!positional) - { - return nonPositionalFeatures == null ? 0 - : nonPositionalFeatures.size(); - } - - return (contactFeatureStarts == null ? 0 : contactFeatureStarts.size()) - + features.size(); + boolean hasFeatures = (contactFeatureStarts != null + && !contactFeatureStarts + .isEmpty()) + || (nonPositionalFeatures != null && !nonPositionalFeatures + .isEmpty()) + || (features != null && features.size() > 0); + return !hasFeatures; } /** @@ -568,8 +692,6 @@ public abstract class FeatureStore implements FeatureStoreI * @param positionalFeatures * @return */ - - @Override public Set getFeatureGroups(boolean positionalFeatures) { if (positionalFeatures) @@ -578,68 +700,52 @@ public abstract class FeatureStore implements FeatureStoreI } else { - return nonPositionalFeatureGroups == null - ? Collections. emptySet() - : Collections.unmodifiableSet(nonPositionalFeatureGroups); + return nonPositionalFeatureGroups == null ? Collections + . emptySet() : Collections + .unmodifiableSet(nonPositionalFeatureGroups); } } - @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) + * Answers the number of positional (or non-positional) features stored. + * Contact features count as 1. * * @param positional - * @param group * @return */ - - @Override - public List getFeaturesForGroup(boolean positional, - String group) + public int getFeatureCount(boolean positional) { - List result = new ArrayList<>(); - - /* - * if we know features don't include the target group, no need - * to inspect them for matches - */ - if (positional && !positionalFeatureGroups.contains(group) - || !positional && !nonPositionalFeatureGroups.contains(group)) + if (!positional) { - return result; + return nonPositionalFeatures == null ? 0 : nonPositionalFeatures + .size(); } - if (positional) + int size = 0; + + if (contactFeatureStarts != null) { - addFeaturesForGroup(group, contactFeatureStarts, result); - addFeaturesForGroup(group, features, result); + // note a contact feature (start/end) counts as one + size += contactFeatureStarts.size(); } - else + + if (features != null) { - addFeaturesForGroup(group, nonPositionalFeatures, result); + size += features.size(); } - return result; + + return size; } /** - * 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. + * Answers the total length of positional features (or zero if there are + * none). Contact features contribute a value of 1 to the total. * - * @param positional * @return */ - - @Override - public float getMaximumScore(boolean positional) + public int getTotalFeatureLength() { - return positional ? positionalMaxScore : nonPositionalMaxScore; + return totalExtent; } /** @@ -650,156 +756,59 @@ public abstract class FeatureStore implements FeatureStoreI * @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. + * 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 List getNonPositionalFeatures( - List result) - { - if (nonPositionalFeatures != null) - { - result.addAll(nonPositionalFeatures); - } - return result; - } - - @Override - public List getPositionalFeatures() + public float getMaximumScore(boolean positional) { - return getPositionalFeatures(new ArrayList<>()); + return positional ? positionalMaxScore : nonPositionalMaxScore; } /** - * Answers a list of all positional features stored, in no guaranteed order + * Answers a list of all either positional or non-positional features whose + * feature group matches the given group (which may be null) * + * @param positional + * @param group * @return */ - - @Override - public List getPositionalFeatures( - List result) + public List getFeaturesForGroup(boolean positional, + String group) { + List result = new ArrayList<>(); /* - * add any contact features - from the list by start position - */ - if (contactFeatureStarts != null) - { - result.addAll(contactFeatureStarts); - } - - /* - * add any nested features + * if we know features don't include the target group, no need + * to inspect them for matches */ - if (features != null) + if (positional && !positionalFeatureGroups.contains(group) + || !positional && !nonPositionalFeatureGroups.contains(group)) { - result.addAll(features); + return result; } - 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. - * - * @return - */ - - @Override - public int getTotalFeatureLength() - { - return totalExtent; - } - - /** - * Answers true if this store has no features, else false - * - * @return - */ - - @Override - public boolean isEmpty() - { - boolean hasFeatures = (contactFeatureStarts != null - && !contactFeatureStarts.isEmpty()) - || (nonPositionalFeatures != null - && !nonPositionalFeatures.isEmpty()) - || features.size() > 0; - - return !hasFeatures; - } - - /** - * 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) + List sfs = positional ? getPositionalFeatures() + : getNonPositionalFeatures(); + for (SequenceFeature sf : sfs) { - List list = nonPositionalFeatures; - for (int i = 0, n = list.size(); i < n; i++) + String featureGroup = sf.getFeatureGroup(); + if (group == null && featureGroup == null || group != null + && group.equals(featureGroup)) { - SequenceFeature sf = list.get(i); - nonPositionalFeatureGroups.add(sf.getFeatureGroup()); - float score = sf.getScore(); - nonPositionalMinScore = min(nonPositionalMinScore, score); - nonPositionalMaxScore = max(nonPositionalMaxScore, score); + result.add(sf); } } - - /* - * 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); - } + return result; } /** @@ -811,8 +820,6 @@ public abstract class FeatureStore implements FeatureStoreI * @param shiftBy * @return */ - - @Override public synchronized boolean shiftFeatures(int fromPosition, int shiftBy) { /* @@ -821,10 +828,8 @@ public abstract class FeatureStore implements FeatureStoreI * (Although a simple shift of all values would preserve data integrity!) */ boolean modified = false; - List list = getPositionalFeatures(); - for (int i = 0, n = list.size(); i < n; i++) + for (SequenceFeature sf : getPositionalFeatures()) { - SequenceFeature sf = list.get(i); if (sf.getBegin() >= fromPosition) { modified = true; @@ -846,5 +851,4 @@ public abstract class FeatureStore implements FeatureStoreI } return modified; } - }