From 08b87509ada06ac8614424247346daef4054b41a Mon Sep 17 00:00:00 2001 From: gmungoc Date: Sat, 7 Sep 2019 15:44:52 +0100 Subject: [PATCH] JAL-3397 impl.IntervalStore and nonc.IntervalStore unified api --- .classpath | 11 +- lib/intervalstore-v1.0.jar | Bin 28303 -> 0 bytes lib/intervalstore-v1.1.jar | Bin 0 -> 30374 bytes src/intervalstore/api/IntervalI.java | 200 ----- src/intervalstore/api/IntervalStoreI.java | 120 --- src/intervalstore/impl/BinarySearcher.java | 116 --- src/intervalstore/impl/IntervalStore.java | 555 ------------- src/intervalstore/impl/NCList.java | 830 -------------------- src/intervalstore/impl/NCListBuilder.java | 143 ---- src/intervalstore/impl/NCNode.java | 382 --------- src/intervalstore/impl/Range.java | 107 --- src/intervalstore/nonc/IntervalEndSorter.java | 686 ---------------- src/intervalstore/nonc/IntervalStore.java | 83 +- src/intervalstore/nonc/IntervalStore0.java | 112 +-- src/jalview/api/AlignmentColsCollectionI.java | 5 +- src/jalview/datamodel/Alignment.java | 24 +- src/jalview/datamodel/AllColsCollection.java | 4 +- src/jalview/datamodel/Sequence.java | 35 +- src/jalview/datamodel/SequenceFeature.java | 13 +- src/jalview/datamodel/SequenceI.java | 21 +- src/jalview/datamodel/VisibleColsCollection.java | 2 +- src/jalview/datamodel/features/FeatureStore.java | 315 +++++--- src/jalview/datamodel/features/FeatureStoreI.java | 58 -- .../datamodel/features/FeatureStoreImpl.java | 274 ------- src/jalview/datamodel/features/FeatureStoreJS.java | 412 ---------- .../datamodel/features/SequenceFeatures.java | 105 +-- .../datamodel/features/SequenceFeaturesI.java | 13 +- src/jalview/ext/ensembl/EnsemblFeatures.java | 5 - src/jalview/renderer/OverviewRenderer.java | 2 +- src/jalview/renderer/OverviewResColourFinder.java | 20 +- .../renderer/seqfeatures/FeatureColourFinder.java | 5 - .../renderer/seqfeatures/FeatureRenderer.java | 35 +- test/intervalstore/nonc/IntervalStoreTest.java | 583 ++++++++++++++ .../intervalstore/nonc}/SimpleFeature.java | 60 +- .../datamodel/features/FeatureStoreJSTest.java | 40 +- .../datamodel/features/FeatureStoreJavaTest.java | 41 +- .../datamodel/features/FeatureStoreLinkedTest.java | 64 +- .../features/FeatureStoreNCListBufferTest.java | 38 +- .../datamodel/features/SequenceFeaturesTest.java | 6 +- 39 files changed, 1066 insertions(+), 4459 deletions(-) delete mode 100644 lib/intervalstore-v1.0.jar create mode 100644 lib/intervalstore-v1.1.jar delete mode 100644 src/intervalstore/api/IntervalI.java delete mode 100644 src/intervalstore/api/IntervalStoreI.java delete mode 100644 src/intervalstore/impl/BinarySearcher.java delete mode 100644 src/intervalstore/impl/IntervalStore.java delete mode 100644 src/intervalstore/impl/NCList.java delete mode 100644 src/intervalstore/impl/NCListBuilder.java delete mode 100644 src/intervalstore/impl/NCNode.java delete mode 100644 src/intervalstore/impl/Range.java delete mode 100644 src/intervalstore/nonc/IntervalEndSorter.java delete mode 100644 src/jalview/datamodel/features/FeatureStoreI.java delete mode 100644 src/jalview/datamodel/features/FeatureStoreImpl.java delete mode 100644 src/jalview/datamodel/features/FeatureStoreJS.java create mode 100644 test/intervalstore/nonc/IntervalStoreTest.java rename {src/intervalstore/impl => test/intervalstore/nonc}/SimpleFeature.java (74%) diff --git a/.classpath b/.classpath index 004d432..d3aa906 100644 --- a/.classpath +++ b/.classpath @@ -61,7 +61,6 @@ - @@ -69,6 +68,14 @@ - + + + + + + + + + diff --git a/lib/intervalstore-v1.0.jar b/lib/intervalstore-v1.0.jar deleted file mode 100644 index 4f6101ca65c250fb97cabb6929d979e3714079a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28303 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 literal 0 HcmV?d00001 diff --git a/src/intervalstore/api/IntervalI.java b/src/intervalstore/api/IntervalI.java deleted file mode 100644 index d2594b8..0000000 --- a/src/intervalstore/api/IntervalI.java +++ /dev/null @@ -1,200 +0,0 @@ -/* -BSD 3-Clause License - -Copyright (c) 2018, Mungo Carstairs -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -package intervalstore.api; - -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -public interface IntervalI -{ - - /** - * Compare intervals by start position ascending and end position descending. - * - * BIGENDIAN sorts 10-100 ahead of 10-80 (original IntervalStoreJ method - * - */ - static Comparator COMPARATOR_BIGENDIAN = new Comparator() - { - @Override - public int compare(IntervalI o1, IntervalI o2) - { - int ret = Integer.signum(o1.getBegin() - o2.getBegin()); - return (ret == 0 ? Integer.signum(o2.getEnd() - o1.getEnd()) : ret); - } - }; - - /** - * Compare intervals by start position ascending and end position descending. - * - * LITTLEENDIAN sorts 10-100 after 10-80 - * - */ - static Comparator COMPARATOR_LITTLEENDIAN = new Comparator() - { - @Override - public int compare(IntervalI o1, IntervalI o2) - { - int ret = Integer.signum(o1.getBegin() - o2.getBegin()); - return (ret == 0 ? Integer.signum(o1.getEnd() - o2.getEnd()) : ret); - } - }; - - /** - * a comparator for sorting intervals by start position ascending - */ - static Comparator FORWARD_STRAND = new Comparator() - { - @Override - public int compare(IntervalI o1, IntervalI o2) - { - return Integer.signum(o1.getBegin() - o2.getBegin()); - } - }; - - /** - * a comparator for sorting intervals by end position descending - */ - static Comparator REVERSE_STRAND = new Comparator() - { - @Override - public int compare(IntervalI o1, IntervalI o2) - { - return Integer.signum(o2.getEnd() - o1.getEnd()); - } - }; - - static int NOT_CONTAINED = Integer.MIN_VALUE; - static int CONTAINMENT_UNKNOWN = 0; - - int getBegin(); - int getEnd(); - - /** - * Answers true if this interval contains (or matches) the given interval - * based solely on start and end. - * - * @param i - * @return - */ - default boolean containsInterval(IntervalI i) - { - return i != null && i.getBegin() >= getBegin() - && i.getEnd() <= getEnd(); - } - - - /** - * Answers true if this interval properly contains the given interval, that - * is, it contains it and is larger than it - * - * @param i - * @return - */ - default boolean properlyContainsInterval(IntervalI i) - { - return containsInterval(i) - && (i.getBegin() > getBegin() || i.getEnd() < getEnd()); - } - - /** - * Slower than equalsInterval; also includes type. - * - * Ensure that subclasses override equals(Object). For example: - * - * public boolean equals(Object o) { return o != null && o instanceof XXX && - * equalsInterval((XXX) i); } - * - * - * equalsInterval also must be overridden. - * - * public boolean equalsInterval(IntervalI i) {return ((SimpleFeature)i).start - * == start && ((SimpleFeature)i).end == end && ((SimpleFeature)i).description - * == this.description; } - * - * - * @param o - * @return true if equal, including a type check - */ - @Override - abstract boolean equals(Object o); - - - - /** - * Check that two intervals are equal, in terms of end points, descriptions, - * or any other distinguishing features. - * - * Used in IntervalStore in searches, since it is faster than equals(), as at - * that point we know that we have the correct type. - * - * @param i - * may be null - * @return true if equal; null value must return false, not throw - * NullPointerException - */ - abstract boolean equalsInterval(IntervalI i); - - default boolean overlapsInterval(IntervalI i) - { - if (i == null) - { - return false; - } - if (i.getBegin() < getBegin()) - { - return i.getEnd() >= getBegin(); - } - if (i.getEnd() > getEnd()) - { - return i.getBegin() <= getEnd(); - } - return true; // i internal to this - } - - /** - * Sorts the list by start position ascending (if forwardString==true), or by - * end position descending - * - * @param intervals - * @param forwardStrand - */ - static void sortIntervals(List intervals, - final boolean forwardStrand) - { - Collections.sort(intervals, - forwardStrand ? FORWARD_STRAND : REVERSE_STRAND); - } - - -} diff --git a/src/intervalstore/api/IntervalStoreI.java b/src/intervalstore/api/IntervalStoreI.java deleted file mode 100644 index 43aea2b..0000000 --- a/src/intervalstore/api/IntervalStoreI.java +++ /dev/null @@ -1,120 +0,0 @@ -/* -BSD 3-Clause License - -Copyright (c) 2018, Mungo Carstairs -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -package intervalstore.api; - -import java.util.Collection; -import java.util.List; - -import intervalstore.impl.NCList; - -public interface IntervalStoreI extends Collection -{ - - /** - * Returns a (possibly empty) list of items whose extent overlaps the given - * range - * - * @param from - * start of overlap range (inclusive) - * @param to - * end of overlap range (inclusive) - * @return - */ - List findOverlaps(long from, long to); - - /** - * Ensures that the IntervalStore is ready for findOverlap. - * - * @return true iff the data held satisfy the rules of construction of an - * IntervalStore. - * - */ - boolean isValid(); - - /** - * Answers the level of nesting of intervals in the store, as - *
    - *
  • 0 if the store is empty
  • - *
  • 1 if all intervals are 'top level' (non nested)
  • - *
  • else 1 plus the depth of the enclosed NCList
  • - *
- * - * @return - * @see NCList#getDepth() - */ - int getDepth(); - - /** - * Return the number of top-level (not-contained) intervals. - * - * @return - */ - int getWidth(); - - List findOverlaps(long start, long end, List result); - - String prettyPrint(); - - /** - * Resort and rebuild links. - * - * @return - */ - boolean revalidate(); - - /** - * Get the i-th interval, whatever that means to this store. - * - * @param i - * @return - */ - IntervalI get(int i); - - /** - * Check to see if this store can check for duplicates while adding. - * - * @return - */ - boolean canCheckForDuplicates(); - - /** - * Add with a check for duplicates, if possible. - * - * @param interval - * @param checkForDuplicate - * @return false only if addition was unsuccessful because there was an - * identical interval already in the store or because the store cannot - * check for duplicates - */ - boolean add(T interval, boolean checkForDuplicate); - -} \ No newline at end of file diff --git a/src/intervalstore/impl/BinarySearcher.java b/src/intervalstore/impl/BinarySearcher.java deleted file mode 100644 index 6c598ce..0000000 --- a/src/intervalstore/impl/BinarySearcher.java +++ /dev/null @@ -1,116 +0,0 @@ -/* -BSD 3-Clause License - -Copyright (c) 2018, Mungo Carstairs -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -package intervalstore.impl; - -import java.util.List; -import java.util.function.ToIntFunction; - -import intervalstore.api.IntervalI; - -/** - * Provides a method to perform binary search of an ordered list for the first - * entry that satisfies a supplied condition - * - * @author gmcarstairs - */ -public final class BinarySearcher -{ - - public static ToIntFunction fbegin = new ToIntFunction() - { - - @Override - public int applyAsInt(IntervalI value) - { - return value.getBegin(); - } - - }; - - public static ToIntFunction fend = new ToIntFunction() - { - - @Override - public int applyAsInt(IntervalI value) - { - return value.getEnd(); - } - - }; - - private BinarySearcher() - { - } - - /** - * Performs a binary search of the list to find the index of the first entry - * for which the test returns true. Answers the length of the list if there is - * no such entry. - *

- * For correct behaviour, the provided list must be ordered consistent with - * the test, that is, any entries returning false must precede any entries - * returning true. Note that this means that this method is not - * usable to search for equality (to find a specific entry), as all unequal - * entries will answer false to the test. To do that, use - * Collections.binarySearch instead. - * - * @param list - * @param test - * @return - * @see java.util.Collections#binarySearch(List, Object) - */ - public static int findFirst(List list, int pos, - ToIntFunction test) - { - int start = 0; - int end = list.size() - 1; - int matched = list.size(); - - while (start <= end) - { - int mid = (start + end) / 2; - T entry = list.get(mid); - boolean itsTrue = test.applyAsInt(entry) >= pos; - if (itsTrue) - { - matched = mid; - end = mid - 1; - } - else - { - start = mid + 1; - } - } - - return matched; - } -} diff --git a/src/intervalstore/impl/IntervalStore.java b/src/intervalstore/impl/IntervalStore.java deleted file mode 100644 index 9faae7f..0000000 --- a/src/intervalstore/impl/IntervalStore.java +++ /dev/null @@ -1,555 +0,0 @@ -/* -BSD 3-Clause License - -Copyright (c) 2018, Mungo Carstairs -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -package intervalstore.impl; - -import java.util.AbstractCollection; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.NoSuchElementException; - -import intervalstore.api.IntervalI; -import intervalstore.api.IntervalStoreI; - -/** - * A collection class to store interval-associated data, with O(log N) - * performance for overlap queries, insertion and deletion (where N is the size - * of the store). Accepts duplicate entries but not null values. - * - * @author gmcarstairs - * - * @param - * any type providing getBegin() and getEnd() - */ -public class IntervalStore - extends AbstractCollection implements IntervalStoreI -{ - /** - * An iterator over the intervals held in this store, with no particular - * ordering guaranteed. The iterator does not support the optional - * remove operation (throws - * UnsupportedOperationException if attempted). - * - * @author gmcarstairs - * - * @param - */ - private class IntervalIterator implements Iterator - { - /* - * iterator over top level non-nested intervals - */ - Iterator topLevelIterator; - - /* - * iterator over NCList (if any) - */ - Iterator nestedIterator; - - /** - * Constructor initialises iterators over the top level list and any nested - * NCList - * - * @param intervalStore - */ - public IntervalIterator( - IntervalStore intervalStore) - { - topLevelIterator = nonNested.iterator(); - if (nested != null) - { - nestedIterator = nested.iterator(); - } - } - - @Override - public boolean hasNext() - { - return topLevelIterator.hasNext() ? true - : (nestedIterator != null && nestedIterator.hasNext()); - } - - @SuppressWarnings("unchecked") - @Override - public V next() - { - if (topLevelIterator.hasNext()) - { - return (V) topLevelIterator.next(); - } - if (nestedIterator != null) - { - return (V) nestedIterator.next(); - } - throw new NoSuchElementException(); - } - - } - - private List nonNested; - - private NCList nested; - - /** - * Constructor - */ - public IntervalStore() - { - nonNested = new ArrayList<>(); - } - - /** - * Constructor given a list of intervals. Note that the list may get sorted as - * a side-effect of calling this constructor. - */ - public IntervalStore(List intervals) - { - this(); - - /* - * partition into subranges whose root intervals - * have no mutual containment (if no intervals are nested, - * each subrange is of length 1 i.e. a single interval) - */ - List sublists = new NCListBuilder() - .partitionNestedSublists(intervals); - - /* - * add all 'subrange root intervals' (and any co-located intervals) - * to our top level list of 'non-nested' intervals; - * put aside any left over for our NCList - */ - List nested = new ArrayList<>(); - - for (IntervalI subrange : sublists) - { - int listIndex = subrange.getBegin(); - IntervalI root = intervals.get(listIndex); - while (listIndex <= subrange.getEnd()) - { - T t = intervals.get(listIndex); - if (root.equalsInterval(t)) - { - nonNested.add(t); - } - else - { - nested.add(t); - } - listIndex++; - } - } - - if (!nested.isEmpty()) - { - this.nested = new NCList<>(nested); - } - } - - /** - * Adds one interval to the store. - * - * @param interval - */ - @Override - public boolean add(T interval) - { - if (interval == null) - { - return false; - } - if (!addNonNestedInterval(interval)) - { - /* - * detected a nested interval - put it in the NCList structure - */ - addNestedInterval(interval); - } - return true; - } - - @Override - public boolean contains(Object entry) - { - if (listContains(nonNested, entry)) - { - return true; - } - - return nested == null ? false : nested.contains(entry); - } - - protected boolean addNonNestedInterval(T entry) - { - synchronized (nonNested) - { - /* - * find the first stored interval which doesn't precede the new one - */ - int insertPosition = BinarySearcher.findFirst(nonNested, - entry.getBegin(), - BinarySearcher.fbegin); - /* - * fail if we detect interval enclosure - * - of the new interval by the one before or after it - * - of the next interval by the new one - */ - if (insertPosition > 0) - { - if (nonNested.get(insertPosition - 1) - .properlyContainsInterval(entry)) - { - return false; - } - } - if (insertPosition < nonNested.size()) - { - T following = nonNested.get(insertPosition); - if (entry.properlyContainsInterval(following) - || following.properlyContainsInterval(entry)) - { - return false; - } - } - - /* - * checks passed - add the interval - */ - nonNested.add(insertPosition, entry); - - return true; - } - } - - @Override - public List findOverlaps(long from, long to) - { - List result = new ArrayList<>(); - - findNonNestedOverlaps(from, to, result); - - if (nested != null) - { - nested.findOverlaps(from, to, result); - } - - return result; - } - - @Override - public String prettyPrint() - { - String pp = nonNested.toString(); - if (nested != null) - { - pp += '\n' + nested.prettyPrint(); - } - return pp; - } - - @Override - public boolean isValid() - { - for (int i = 0; i < nonNested.size() - 1; i++) - { - IntervalI i1 = nonNested.get(i); - IntervalI i2 = nonNested.get(i + 1); - - if (i2.getBegin() < i1.getBegin()) - { - System.err.println("nonNested wrong start order : " + i1.toString() - + ", " + i2.toString()); - return false; - } - if (i1.properlyContainsInterval(i2) - || i2.properlyContainsInterval(i1)) - { - System.err.println("nonNested invalid containment!: " - + i1.toString() - + ", " + i2.toString()); - return false; - } - } - return nested == null ? true : nested.isValid(); - } - - @Override - public int size() - { - int i = nonNested.size(); - if (nested != null) - { - i += nested.size(); - } - return i; - } - - @Override - public synchronized boolean remove(Object o) - { - if (o == null) - { - return false; - } - try - { - @SuppressWarnings("unchecked") - T entry = (T) o; - - /* - * try the non-nested positional intervals first - */ - boolean removed = removeNonNested(entry); - - /* - * if not found, try nested intervals - */ - if (!removed && nested != null) - { - removed = nested.remove(entry); - } - - return removed; - } catch (ClassCastException e) - { - return false; - } - } - - /** - * Removes the given entry from the list of non-nested entries, returning true - * if found and removed, or false if not found. Specifically, removes the - * first item in the list for which item.equals(entry). - * - * @param entry - * @return - */ - protected boolean removeNonNested(T entry) - { - /* - * find the first interval that might match, i.e. whose - * start position is not less than the target range start - * (NB inequality test ensures the first match if any is found) - */ - int from = entry.getBegin(); - int startIndex = BinarySearcher.findFirst(nonNested, from, - BinarySearcher.fbegin); - - /* - * traverse intervals to look for a match - */ - - int i = startIndex; - int size = nonNested.size(); - while (i < size) - { - T sf = nonNested.get(i); - if (sf.getBegin() > from) - { - break; - } - if (sf.equals(entry)) - { - nonNested.remove(i); - return true; - } - i++; - } - return false; - } - - @Override - public int getDepth() - { - if (size() == 0) - { - return 0; - } - return (nonNested.isEmpty() ? 0 : 1) - + (nested == null ? 0 : nested.getDepth()); - } - - /** - * Adds one interval to the NCList that can manage nested intervals (creating - * the NCList if necessary) - */ - protected synchronized void addNestedInterval(T interval) - { - if (nested == null) - { - nested = new NCList<>(); - } - nested.add(interval); - } - - /** - * Answers true if the list contains the interval, else false. This method is - * optimised for the condition that the list is sorted on interval start - * position ascending, and will give unreliable results if this does not hold. - * - * @param intervals - * @param entry - * @return - */ - protected boolean listContains(List intervals, Object entry) - { - if (intervals == null || entry == null || !(entry instanceof IntervalI)) - { - return false; - } - - IntervalI interval = (IntervalI) entry; - - /* - * locate the first entry in the list which does not precede the interval - */ - int from = interval.getBegin(); - int pos = BinarySearcher.findFirst(intervals, from, - BinarySearcher.fbegin); - int len = intervals.size(); - while (pos < len) - { - T sf = intervals.get(pos); - if (sf.getBegin() > from) - { - return false; // no match found - } - if (sf.equals(interval)) - { - return true; - } - pos++; - } - return false; - } - - /** - * Answers an iterator over the intervals in the store, with no particular - * ordering guaranteed. The iterator does not support the optional - * remove operation (throws - * UnsupportedOperationException if attempted). - */ - @Override - public Iterator iterator() - { - return new IntervalIterator<>(this); - } - - @Override - public void clear() - { - this.nonNested.clear(); - this.nested = new NCList<>(); - } - - /** - * Adds non-nested intervals to the result list that lie within the target - * range - * - * @param from - * @param to - * @param result - */ - protected void findNonNestedOverlaps(long from, long to, - List result) - { - /* - * find the first interval whose end position is - * after the target range start - */ - int startIndex = BinarySearcher.findFirst(nonNested, (int) from, - BinarySearcher.fend); - for (int i = startIndex, n = nonNested.size(); i < n; i++) - { - T sf = nonNested.get(i); - if (sf.getBegin() > to) - { - break; - } - if (sf.getEnd() >= from) - { - result.add(sf); - } - } - } - - @Override - public String toString() - { - String s = nonNested.toString(); - if (nested != null) - { - s = s + '\n'// + System.lineSeparator() - + nested.toString(); - } - return s; - } - - @Override - public int getWidth() - { - return (nonNested == null ? 0 : nonNested.size()) - + (nested == null ? 0 : nested.size()); - } - - @Override - public List findOverlaps(long start, long end, List result) - { - return findOverlaps(start, end); - } - - @Override - public boolean revalidate() - { - // not applicable - return true; - } - - @Override - public IntervalI get(int i) - { - // not supported (but could be) - return null; - } - - @Override - public boolean canCheckForDuplicates() - { - return false; - } - - @Override - public boolean add(T interval, boolean checkForDuplicate) - { - return add(interval); - } -} diff --git a/src/intervalstore/impl/NCList.java b/src/intervalstore/impl/NCList.java deleted file mode 100644 index 243192d..0000000 --- a/src/intervalstore/impl/NCList.java +++ /dev/null @@ -1,830 +0,0 @@ -/* -BSD 3-Clause License - -Copyright (c) 2018, Mungo Carstairs -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -package intervalstore.impl; - -import java.util.AbstractCollection; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.NoSuchElementException; - -import intervalstore.api.IntervalI; - -/** - * An adapted implementation of NCList as described in the paper - * - *

- * 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
- * 
- */ -public class NCList extends AbstractCollection -{ - - // private static final boolean OPTION_FIND_ANY = false; - - /** - * A depth-first iterator over the elements stored in the NCList - */ - private class NCListIterator implements Iterator - { - int subrangeIndex; - - Iterator nodeIterator; - - /** - * Constructor bootstraps a pointer to an iterator over the first subrange - * (if any) - */ - NCListIterator() - { - subrangeIndex = nextSubrange(-1); - } - - /** - * Moves the subrange iterator to the next subrange, and answers its index - * in the list of subranges. If there are no more, sets the iterator to null - * and answers -1. - * - * @return - */ - private int nextSubrange(int after) - { - int nextIndex = after + 1; - if (nextIndex < subranges.size()) - { - nodeIterator = subranges.get(nextIndex).iterator(); - return nextIndex; - } - nodeIterator = null; - return -1; - } - - @Override - public boolean hasNext() - { - return nodeIterator != null && nodeIterator.hasNext(); - } - - /** - * Answers the next element returned by the current NCNode's iterator, and - * advances the iterator (to the next NCNode if necessary) - */ - @Override - public T next() - { - if (nodeIterator == null || !nodeIterator.hasNext()) - { - throw new NoSuchElementException(); - } - T result = nodeIterator.next(); - - if (!nodeIterator.hasNext()) - { - subrangeIndex = nextSubrange(subrangeIndex); - } - return result; - } - - } - - /* - * the number of interval instances represented - */ - private int size; - - /* - * a list, in start position order, of sublists of ranges ordered so - * that each contains (or is the same as) the one that follows it - */ - private List> subranges; - - /** - * Constructor given a list of things that are each located on a contiguous - * interval. Note that the constructor may reorder the list. - *

- * We assume here that for each range, start <= end. Behaviour for reverse - * ordered ranges is undefined. - * - * @param ranges - */ - public NCList(List ranges) - { - this(); - build(ranges); - } - - /** - * Sorts and groups ranges into sublists where each sublist represents an - * interval and its contained subintervals - * - * @param ranges - */ - protected void build(List ranges) - { - /* - * sort and partition into subranges - * which have no mutual containment - */ - List sublists = partitionNestedSublists(ranges); - - /* - * convert each subrange to an NCNode consisting of a range and - * (possibly) its contained NCList - */ - for (IntervalI sublist : sublists) - { - subranges.add(new NCNode<>( - ranges.subList(sublist.getBegin(), sublist.getEnd() + 1))); - } - - size = ranges.size(); - } - - /** - * Default constructor - */ - public NCList() - { - subranges = new ArrayList<>(); - } - - /** - * Sorts and traverses the ranges to identify sublists, whose start intervals - * are overlapping or disjoint but not mutually contained. Answers a list of - * start-end indices of the sorted list of ranges. - * - * @param ranges - * @return - */ - protected List partitionNestedSublists(List ranges) - { - List sublists = new ArrayList<>(); - - if (ranges.isEmpty()) - { - return sublists; - } - - /* - * sort by start ascending, length descending, so that - * contained intervals follow their containing interval - */ - Collections.sort(ranges, IntervalI.COMPARATOR_BIGENDIAN); - - int listStartIndex = 0; - - IntervalI lastParent = ranges.get(0); - boolean first = true; - - for (int i = 0; i < ranges.size(); i++) - { - IntervalI nextInterval = ranges.get(i); - if (!first && !lastParent.properlyContainsInterval(nextInterval)) - { - /* - * this interval is not properly contained in the parent; - * close off the last sublist - */ - sublists.add(new Range(listStartIndex, i - 1)); - listStartIndex = i; - lastParent = nextInterval; - } - first = false; - } - - sublists.add(new Range(listStartIndex, ranges.size() - 1)); - return sublists; - } - - /** - * Adds one entry to the stored set - * - * @param entry - */ - @Override - public synchronized boolean add(final T entry) - { - final NCNode newNode = new NCNode<>(entry); - addNode(newNode); - return true; - } - - /** - * Adds one NCNode to this NCList - *

- * This method does not update the size (interval count) of this - * NCList, as it may be used to rearrange nodes without changing their count. - * Callers should increment the count if needed. - * - * @param newNode - */ - protected void addNode(final NCNode newNode) - { - final long start = newNode.getBegin(); - final long end = newNode.getEnd(); - size += newNode.size(); - - /* - * cases: - * 1) precedes all subranges - add as NCNode on front of list - * 2) follows all subranges - add as NCNode on end of list - * 3) matches a subrange - add as a sibling in the list - * 4) properly enclosed by a subrange - add recursively to subrange - * 5) properly encloses one or more subranges - push them inside it - * 6) spans two subranges - insert between them - */ - - /* - * find the first subrange whose end does not precede entry's start - */ - int candidateIndex = findFirstOverlap(start); - - /* - * search for maximal span of subranges i-k that the new entry - * encloses; or a subrange that encloses the new entry - */ - boolean enclosing = false; - int firstEnclosed = 0; - int lastEnclosed = 0; - - for (int j = candidateIndex; j < subranges.size(); j++) - { - NCNode subrange = subranges.get(j); - - if (subrange.equalsInterval(newNode)) - { - /* - * matching interval - insert adjacent - */ - subranges.add(j, newNode); - return; - } - - if (end < subrange.getBegin() && !enclosing) - { - /* - * new entry lies between subranges j-1 j - */ - subranges.add(j, newNode); - return; - } - - if (subrange.properlyContainsInterval(newNode)) - { - /* - * push new entry inside this subrange as it encloses it - */ - subrange.addNode(newNode); - return; - } - - if (start <= subrange.getBegin()) - { - if (end >= subrange.getEnd()) - { - /* - * new entry encloses this subrange (and possibly preceding ones); - * continue to find the maximal list it encloses - */ - if (!enclosing) - { - firstEnclosed = j; - } - lastEnclosed = j; - enclosing = true; - continue; - } - else - { - /* - * entry spans from before this subrange to inside it - */ - if (enclosing) - { - /* - * entry encloses one or more preceding subranges - */ - push(newNode, firstEnclosed, lastEnclosed); - } - else - { - /* - * entry overlaps two subranges but doesn't enclose either - * so just add it - */ - subranges.add(j, newNode); - } - return; - } - } - } - - /* - * drops through to here if new range encloses all others - * or overlaps the last one - */ - if (enclosing) - { - push(newNode, firstEnclosed, lastEnclosed); - } - else - { - subranges.add(newNode); - } - } - - @Override - public boolean contains(Object entry) - { - if (!(entry instanceof IntervalI)) - { - return false; - } - IntervalI interval = (IntervalI) entry; - - /* - * find the first sublist that might overlap, i.e. - * the first whose end position is >= from - */ - int candidateIndex = findFirstOverlap(interval.getBegin()); - - int to = interval.getEnd(); - - for (int i = candidateIndex; i < subranges.size(); i++) - { - NCNode candidate = subranges.get(i); - if (candidate.getBegin() > to) - { - /* - * we are past the end of our target range - */ - break; - } - if (candidate.contains(interval)) - { - return true; - } - } - return false; - } - - /** - * Update the tree so that the new node encloses current subranges i to j - * (inclusive). That is, replace subranges i-j (inclusive) with a new subrange - * that contains them. - * - * @param node - * @param i - * @param j - * @throws IllegalArgumentException - * if any of the subranges is not contained by the node's start-end - * range - */ - protected synchronized void push(NCNode node, final int i, - final int j) - { - for (int k = i; k <= j; k++) - { - NCNode n = subranges.get(k); - if (!node.containsInterval(n)) { - throw new IllegalArgumentException("Can't push " + n.toString() - + " inside " + node.toString()); - } - node.addNode(n); - } - - for (int k = j; k >= i; k--) - { - subranges.remove(k); - } - subranges.add(i, node); - } - - /** - * Answers a list of contained intervals that overlap the given range - * - * @param from - * @param to - * @return - */ - public List findOverlaps(long from, long to) - { - List result = new ArrayList<>(); - - findOverlaps(from, to, result); - - return result; - } - - /** - * Recursively searches the NCList adding any items that overlap the from-to - * range to the result list - * - * @param from - * @param to - * @param result - */ - protected List findOverlaps(long from, long to, List result) - { - - // if (OPTION_FIND_ANY) - // { - // return findAnyOverlaps(from, to, result); - // } - /* - * find the first sublist that might overlap, i.e. - * the first whose end position is >= from - */ - int candidateIndex = findFirstOverlap(from); - - for (int i = candidateIndex; i < subranges.size(); i++) - { - NCNode candidate = subranges.get(i); - if (candidate.getBegin() > to) - { - /* - * we are past the end of our target range - */ - break; - } - candidate.findOverlaps(from, to, result); - } - return result; - } - - // /** - // * Recursively searches the NCList adding any items that overlap the from-to - // * range to the result list - // * - // * @param from - // * @param to - // * @param result - // */ - // protected List findAnyOverlaps(long from, long to, List result) - // { - // - // // BH find ANY overlap - // - // int candidateIndex = findAnyOverlap(subranges, from, to); - // - // if (candidateIndex < 0) - // return result; - // for (int i = candidateIndex, n = subranges.size(); i < n; i++) - // { - // NCNode candidate = subranges.get(i); - // if (candidate.getBegin() > to) - // { - // /* - // * we are past the end of our target range - // */ - // break; - // } - // candidate.findOverlaps(from, to, result); - // } - // - // // BH adds dual-direction check - // - // for (int i = candidateIndex; --i >= 0;) - // { - // NCNode candidate = subranges.get(i); - // if (candidate.getEnd() < from) - // { - // break; - // } - // candidate.findOverlaps(from, to, result); - // } - // return result; - // } - // - // private int findAnyOverlap(List> ranges, long from, long to) - // { - // int start = 0; - // int end = ranges.size() - 1; - // while (start <= end) - // { - // int mid = (start + end) >>> 1; - // NCNode r = ranges.get(mid); - // if (r.getEnd() >= from) - // { - // if (r.getBegin() <= to) - // return mid; - // end = mid - 1; - // } - // else - // { - // start = mid + 1; - // } - // } - // return -1; - // } - - /** - * Search subranges for the first one whose end position is not before the - * target range's start position, i.e. the first one that may overlap the - * target range. Returns the index in the list of the first such range found, - * or the length of the list if none found. - * - * @param from - * @return - */ - protected int findFirstOverlap(final long from) - { - return BinarySearcher.findFirst(subranges, (int) from, - BinarySearcher.fend); - } - - /** - * Formats the tree as a bracketed list e.g. - * - *

-   * [1-100 [10-30 [10-20]], 15-30 [20-20]]
-   * 
- */ - @Override - public String toString() - { - return subranges.toString(); - } - - /** - * Answers the NCList as an indented list - * - * @return - */ - public String prettyPrint() - { - StringBuilder sb = new StringBuilder(512); - int offset = 0; - int indent = 2; - prettyPrint(sb, offset, indent); - sb.append('\n');// System.lineSeparator()); - return sb.toString(); - } - - /** - * @param sb - * @param offset - * @param indent - */ - void prettyPrint(StringBuilder sb, int offset, int indent) - { - boolean first = true; - for (NCNode subrange : subranges) - { - if (!first) - { - sb.append('\n');// System.lineSeparator()); - } - first = false; - subrange.prettyPrint(sb, offset, indent); - } - } - - /** - * Answers true if the store's structure is valid (nesting containment rules - * are obeyed), else false. For use in testing and debugging. - * - * @return - */ - public boolean isValid() - { - int count = 0; - for (NCNode subrange : subranges) - { - count += subrange.size(); - } - if (count != size) - { - return false; - } - return isValid(Integer.MIN_VALUE, Integer.MAX_VALUE); - } - - /** - * Answers true if the data held satisfy the rules of construction of an - * NCList bounded within the given start-end range, else false. - *

- * Each subrange must lie within start-end (inclusive). Subranges must be - * ordered by start position ascending, and within that by end position - * descending. - *

- * - * @param start - * @param end - * @return - */ - boolean isValid(final int start, final int end) - { - NCNode lastRange = null; - - for (NCNode subrange : subranges) - { - if (subrange.getBegin() < start) - { - System.err.println("error in NCList: range " + subrange.toString() - + " starts before " + end); - return false; - } - if (subrange.getEnd() > end) - { - System.err.println("error in NCList: range " + subrange.toString() - + " ends after " + end); - return false; - } - - if (lastRange != null) - { - if (subrange.getBegin() < lastRange.getBegin()) - { - System.err.println("error in NCList: range " + subrange.toString() - + " starts before " + lastRange.toString()); - return false; - } - if (subrange.properlyContainsInterval(lastRange)) - { - System.err.println("error in NCList: range " + subrange.toString() - + " encloses preceding: " + lastRange.toString()); - return false; - } - if (lastRange.properlyContainsInterval(subrange)) - { - System.err.println("error in NCList: range " + subrange.toString() - + " enclosed by preceding: " + lastRange.toString()); - return false; - } - } - lastRange = subrange; - - if (!subrange.isValid()) - { - return false; - } - } - return true; - } - - /** - * Answers the number of intervals stored - * - * @return - */ - @Override - public int size() - { - return size; - } - - /** - * Answers a list of all entries stored, in no guaranteed order. This method - * is not synchronized, so is not thread-safe. - */ - public List getEntries() - { - List result = new ArrayList<>(); - getEntries(result); - return result; - } - - /** - * Adds all contained entries to the given list - * - * @param result - */ - void getEntries(List result) - { - for (NCNode subrange : subranges) - { - subrange.getEntries(result); - } - } - - /** - * Removes the first interval Ifound that is equal to T - * (I.equals(T)). Answers true if an interval is removed, false - * if no match is found. This method is synchronized so thread-safe. - * - * @param entry - * @return - */ - public synchronized boolean remove(T entry) - { - if (entry == null) - { - return false; - } - int i = findFirstOverlap(entry.getBegin()); - - for (; i < subranges.size(); i++) - { - NCNode subrange = subranges.get(i); - if (subrange.getBegin() > entry.getBegin()) - { - /* - * not found - */ - return false; - } - NCList subRegions = subrange.getSubRegions(); - - if (subrange.getRegion().equals(entry)) - { - /* - * if the subrange is rooted on this entry, remove it, - * and remove and promote its subregions (if any) - */ - subranges.remove(i); - size -= subrange.size(); - if (subRegions != null) - { - for (NCNode r : subRegions.subranges) - { - addNode(r); - } - } - return true; - } - else - { - if (subrange.remove(entry)) - { - size--; - return true; - } - } - } - return false; - } - - /** - * Answers the depth of interval nesting of this object, where 1 means there - * are no nested sub-intervals - * - * @return - */ - public int getDepth() - { - int subDepth = 0; - for (NCNode subrange : subranges) - { - subDepth = Math.max(subDepth, subrange.getDepth()); - } - - return subDepth; - } - - /** - * Answers an iterator over the contained intervals, with no particular order - * guaranteed. The iterator does not support the optional remove - * operation (throws UnsupportedOperationException if attempted). - */ - @Override - public Iterator iterator() - { - return new NCListIterator(); - } - - @Override - public synchronized void clear() - { - subranges.clear(); - size = 0; - } - - public int getWidth() - { - return subranges.size(); - } -} diff --git a/src/intervalstore/impl/NCListBuilder.java b/src/intervalstore/impl/NCListBuilder.java deleted file mode 100644 index d640589..0000000 --- a/src/intervalstore/impl/NCListBuilder.java +++ /dev/null @@ -1,143 +0,0 @@ -/* -BSD 3-Clause License - -Copyright (c) 2018, Mungo Carstairs -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -package intervalstore.impl; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -import intervalstore.api.IntervalI; - -/** - * A comparator that orders ranges by either start position ascending. If - * position matches, ordering is by length descending. This provides the - * canonical ordering of intervals into subranges in order to build a nested - * containment list. - * - * @author gmcarstairs - */ -public class NCListBuilder -{ - // class NCListComparator implements Comparator - // { - // /** - // * Compares two intervals in a way that will sort a list by start position - // * ascending, then by length descending. Answers - // *

    - // *
  • a negative value if o1.begin < o2.begin
  • - // *
  • else a positive value if o1.begin > o2.begin
  • - // *
  • else a negative value if o1.end > o2.end
  • - // *
  • else a positive value of o1.end < o2.end
  • - // *
  • else zero
  • - // */ - // @Override - // public int compare(V o1, V o2) - // { - // int order = Integer.compare(o1.getBegin(), o2.getBegin()); - // if (order == 0) - // { - // /* - // * if tied on start position, longer length sorts to left - // * i.e. the negation of normal ordering by length - // */ - // order = Integer.compare(o2.getEnd(), o1.getEnd()); - // } - // return order; - // } - // } - - private Comparator comparator = IntervalI.COMPARATOR_BIGENDIAN;// new - // NCListComparator<>(); - - /** - * Default constructor - */ - public NCListBuilder() - { - } - - /** - * Answers a comparator which sorts items of type T by start position - * ascending, and within that by end position (length) descending) - * - * @return - */ - Comparator getComparator() - { - return comparator; - } - - /** - * Sorts and traverses the ranges to identify sublists, whose start intervals - * are overlapping or disjoint but not mutually contained. Answers a list of - * start-end indices of the sorted list of ranges. - * - * @param ranges - * @return - */ - List partitionNestedSublists(List ranges) - { - List sublists = new ArrayList<>(); - - /* - * sort by start ascending, length descending, so that - * contained intervals follow their containing interval - */ - Collections.sort(ranges, comparator); - - int listStartIndex = 0; - - IntervalI lastParent = ranges.get(0); - boolean first = true; - - for (int i = 0, n = ranges.size(); i < n; i++) - { - IntervalI nextInterval = ranges.get(i); - if (!first && !lastParent.properlyContainsInterval(nextInterval)) - { - /* - * this interval is not properly contained in the parent; - * close off the last sublist - */ - sublists.add(new Range(listStartIndex, i - 1)); - listStartIndex = i; - lastParent = nextInterval; - } - first = false; - } - - sublists.add(new Range(listStartIndex, ranges.size() - 1)); - return sublists; - } -} diff --git a/src/intervalstore/impl/NCNode.java b/src/intervalstore/impl/NCNode.java deleted file mode 100644 index a3702f5..0000000 --- a/src/intervalstore/impl/NCNode.java +++ /dev/null @@ -1,382 +0,0 @@ -/* -BSD 3-Clause License - -Copyright (c) 2018, Mungo Carstairs -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -package intervalstore.impl; - -import java.util.Iterator; -import java.util.List; -import java.util.NoSuchElementException; - -import intervalstore.api.IntervalI; - -/** - * Each node of the NCList tree consists of a range, and (optionally) the NCList - * of ranges it encloses - * - * @param - */ -class NCNode implements IntervalI -{ - /** - * A depth-first iterator over the intervals stored in the NCNode. The - * optional remove operation is not supported. - * - * @author gmcarstairs - * - */ - private class NCNodeIterator implements Iterator - { - boolean first = true; - Iterator subregionIterator; - - @Override - public boolean hasNext() - { - return first - || (subregionIterator != null && subregionIterator.hasNext()); - } - - /** - * Answers the next interval - initially the top level interval for this - * node, thereafter the intervals returned by the NCList's iterator - */ - @Override - public T next() - { - if (first) - { - subregionIterator = subregions == null ? null - : subregions.iterator(); - first = false; - return region; - } - if (subregionIterator == null || !subregionIterator.hasNext()) - { - throw new NoSuchElementException(); - } - return subregionIterator.next(); - } - } - - private T region; - - /* - * null, or an object holding contained subregions of this nodes region - */ - private NCList subregions; - - /** - * Constructor given a list of ranges. The list not be empty, and should be - * ordered so that the first range contains all the others. If not, behaviour - * will likely be invalid. - * - * @param ranges - * @throws IllegalArgumentException - * if the list is empty - */ - NCNode(List ranges) - { - if (ranges.isEmpty()) - { - throw new IllegalArgumentException("List may not be empty"); - } - region = ranges.get(0); - - if (ranges.size() > 1) - { - subregions = new NCList<>(ranges.subList(1, ranges.size())); - } - } - - /** - * Constructor given a single range - * - * @param range - */ - NCNode(T range) - { - region = range; - } - - @Override - public int getBegin() - { - return region.getBegin(); - } - - @Override - public int getEnd() - { - return region.getEnd(); - } - - /** - * Formats the node as a bracketed list e.g. - * - *
    -   * [1-100 [10-30 [10-20]], 15-30 [20-20]]
    -   * 
    - * - * where the format for each interval is as given by T.toString() - */ - @Override - public String toString() - { - StringBuilder sb = new StringBuilder(10 * size()); - sb.append(region.toString()); - if (subregions != null) - { - sb.append(" ").append(subregions.toString()); - } - return sb.toString(); - } - - void prettyPrint(StringBuilder sb, int offset, int indent) - { - for (int i = 0; i < offset; i++) - { - sb.append(" "); - } - sb.append(region.toString()); - if (subregions != null) - { - sb.append('\n');// System.lineSeparator()); - subregions.prettyPrint(sb, offset + 2, indent); - } - } - - /** - * Add any ranges that overlap the from-to range to the result list - * - * @param from - * @param to - * @param result - */ - void findOverlaps(long from, long to, List result) - { - if (region.getBegin() <= to && region.getEnd() >= from) - { - result.add(region); - if (subregions != null) - { - subregions.findOverlaps(from, to, result); - } - } - } - - /** - * Add one node to this node's subregions. - * - * @param entry - * @throws IllegalArgumentException - * if the added node is not contained by the node's start-end range - */ - synchronized void addNode(NCNode entry) - { - if (!region.containsInterval(entry)) - { - throw new IllegalArgumentException( - String.format("adding improper subrange %d-%d to range %d-%d", - entry.getBegin(), entry.getEnd(), region.getBegin(), - region.getEnd())); - } - if (subregions == null) - { - subregions = new NCList<>(); - } - - subregions.addNode(entry); - } - - /** - * Answers true if the data held satisfy the rules of construction of an - * NCList, else false - * - * @return - */ - boolean isValid() - { - /* - * we don't handle reverse ranges - */ - if (region != null && region.getBegin() > region.getEnd()) - { - return false; - } - if (subregions == null) - { - return true; - } - if (subregions.isEmpty()) - { - /* - * we expect empty subregions to be nulled - */ - return false; - } - return subregions.isValid(getBegin(), getEnd()); - } - - /** - * Adds all contained entries to the given list - * - * @param entries - */ - void getEntries(List entries) - { - entries.add(region); - if (subregions != null) - { - subregions.getEntries(entries); - } - } - - /** - * Answers true if this object contains the given entry (by object equals - * test), else false - * - * @param entry - * @return - */ - boolean contains(IntervalI 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 - */ - T getRegion() - { - return region; - } - - /** - * Answers the (possibly null) contained regions within this object - * - * @return - */ - NCList getSubRegions() - { - return subregions; - } - - /** - * Answers the (deep) size of this node i.e. the number of intervals it models - * - * @return - */ - int size() - { - return subregions == null ? 1 : 1 + subregions.size(); - } - - /** - * Answers the depth of NCNode / NCList nesting in the data tree - * - * @return - */ - int getDepth() - { - return subregions == null ? 1 : 1 + subregions.getDepth(); - } - - /** - * Answers an iterator over the intervals stored in this node. The iterator - * does not support the optional remove operation (throws - * UnsupportedOperationException if attempted). - * - * @return - */ - public Iterator iterator() - { - return new NCNodeIterator(); - } - - /** - * Removes the first interval found equal to the given entry. Answers true if - * a matching interval is found and removed, else false. - * - * @param entry - * @return - */ - boolean remove(T entry) - { - if (region.equals(entry)) - { - /* - * this case must be handled by NCList, to allow any - * children of a deleted interval to be promoted - */ - throw new IllegalArgumentException("NCNode can't remove self"); - } - if (subregions == null) - { - return false; - } - if (subregions.remove(entry)) - { - if (subregions.isEmpty()) - { - subregions = null; - } - return true; - } - return false; - } - - @SuppressWarnings("unchecked") - @Override - public boolean equals(Object o) - { - return o != null && o instanceof NCNode - && equalsInterval((NCNode) o); - } - - @Override - public boolean equalsInterval(IntervalI i) - { - return i != null && getBegin() == i.getBegin() - && getEnd() == i.getEnd(); - - } - -} diff --git a/src/intervalstore/impl/Range.java b/src/intervalstore/impl/Range.java deleted file mode 100644 index c07e793..0000000 --- a/src/intervalstore/impl/Range.java +++ /dev/null @@ -1,107 +0,0 @@ -/* -BSD 3-Clause License - -Copyright (c) 2018, Mungo Carstairs -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -package intervalstore.impl; - -import intervalstore.api.IntervalI; - -/** - * An immutable data bean that models a start-end range - */ -public class Range implements IntervalI -{ - - // no need for final here; these can be fully mutable as long as - // store.revalidate() is run afterwords - - public int start; - - public int end; - - - @Override - public int getBegin() - { - return start; - } - - @Override - public int getEnd() - { - return end; - } - - public Range(int i, int j) - { - start = i; - end = j; - } - - @Override - public String toString() - { - return String.valueOf(start) + "-" + String.valueOf(end); - } - - @Override - public int hashCode() - { - return start * 31 + end; - } - - @Override - public boolean equals(Object o) - { - return (o != null && o instanceof Range && equalsInterval((Range) o)); - } - - @Override - public boolean equalsInterval(IntervalI obj) - { - - // override equalsInterval, not equals - return (obj != null && start == ((Range) obj).start - && end == ((Range) obj).end); - - } - - public void setStart(int pos) - { - start = pos; - } - - public void setEnd(int pos) - { - end = pos; - } - - -} diff --git a/src/intervalstore/nonc/IntervalEndSorter.java b/src/intervalstore/nonc/IntervalEndSorter.java deleted file mode 100644 index 282c880..0000000 --- a/src/intervalstore/nonc/IntervalEndSorter.java +++ /dev/null @@ -1,686 +0,0 @@ -/* - * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package intervalstore.nonc; - -import intervalstore.api.IntervalI; - -/** - * A dual pivot quicksort for int[] where the int is a pointer to something for - * which the value needs to be checked. This class is not used; it was just an - * idea I was trying. But it is sort of cool, so I am keeping it in the package - * for possible future use. - * - * Adapted from Java 7 java.util.DualPivotQuicksort -- int[] only. The only - * difference is that wherever an a[] value is compared, we use val(a[i]) - * instead of a[i] itself. Pretty straightforward. Could be adapted for general - * use. Why didn't they do this in Java? - * - * val(i) is just a hack here, of course. A more general implementation might - * use a Function call. - * - * Just thought it was cool that you can do this. - * - * @author Bob Hanson 2019.09.02 - * - */ - -class IntervalEndSorter -{ - - private IntervalI[] intervals; - - private int val(int i) - { - return intervals[i].getEnd(); - } - - /* - * Tuning parameters. - */ - - /** - * The maximum number of runs in merge sort. - */ - private static final int MAX_RUN_COUNT = 67; - - /** - * The maximum length of run in merge sort. - */ - private static final int MAX_RUN_LENGTH = 33; - - /** - * If the length of an array to be sorted is less than this constant, - * Quicksort is used in preference to merge sort. - */ - private static final int QUICKSORT_THRESHOLD = 286; - - /** - * If the length of an array to be sorted is less than this constant, - * insertion sort is used in preference to Quicksort. - */ - private static final int INSERTION_SORT_THRESHOLD = 47; - - /* - * Sorting methods for seven primitive types. - */ - - /** - * Sorts the specified range of the array using the given workspace array - * slice if possible for merging - * - * @param a - * the array to be sorted - * @param left - * the index of the first element, inclusive, to be sorted - * @param right - * the index of the last element, inclusive, to be sorted - * @param work - * a workspace array (slice) - * @param workBase - * origin of usable space in work array - * @param workLen - * usable size of work array - */ - void sort(int[] a, IntervalI[] intervals, int len) - { - this.intervals = intervals; - - int left = 0, right = len - 1; - // Use Quicksort on small arrays - if (right - left < QUICKSORT_THRESHOLD) - { - sort(a, left, right, true); - return; - } - - /* - * Index run[i] is the start of i-th run - * (ascending or descending sequence). - */ - int[] run = new int[MAX_RUN_COUNT + 1]; - int count = 0; - run[0] = left; - - // Check if the array is nearly sorted - for (int k = left; k < right; run[count] = k) - { - switch (Integer.signum(val(a[k + 1]) - val(a[k]))) - { - case 1: - // ascending - while (++k <= right && val(a[k - 1]) <= val(a[k])) - ; - break; - case -1: - // descending - while (++k <= right && val(a[k - 1]) >= val(a[k])) - ; - for (int lo = run[count] - 1, hi = k; ++lo < --hi;) - { - int t = a[lo]; - a[lo] = a[hi]; - a[hi] = t; - } - break; - default: - // equal - for (int m = MAX_RUN_LENGTH; ++k <= right - && val(a[k - 1]) == val(a[k]);) - { - if (--m == 0) - { - sort(a, left, right, true); - return; - } - } - } - - /* - * The array is not highly structured, - * use Quicksort instead of merge sort. - */ - if (++count == MAX_RUN_COUNT) - { - sort(a, left, right, true); - return; - } - } - - // Check special cases - // Implementation note: variable "right" is increased by 1. - if (run[count] == right++) - { // The last run contains one element - run[++count] = right; - } - else if (count == 1) - { // The array is already sorted - return; - } - - // Determine alternation base for merge - byte odd = 0; - for (int n = 1; (n <<= 1) < count; odd ^= 1) - ; - - // Use or create temporary array b for merging - int[] b; // temp array; alternates with a - int ao, bo; // array offsets from 'left' - int blen = right - left; // space needed for b - int[] work = new int[blen]; - int workBase = 0; - if (odd == 0) - { - System.arraycopy(a, left, work, workBase, blen); - b = a; - bo = 0; - a = work; - ao = workBase - left; - } - else - { - b = work; - ao = 0; - bo = workBase - left; - } - - // Merging - for (int last; count > 1; count = last) - { - for (int k = (last = 0) + 2; k <= count; k += 2) - { - int hi = run[k], mi = run[k - 1]; - for (int i = run[k - 2], p = i, q = mi; i < hi; ++i) - { - if (q >= hi || p < mi && val(a[p + ao]) <= val(a[q + ao])) - { - b[i + bo] = a[p++ + ao]; - } - else - { - b[i + bo] = a[q++ + ao]; - } - } - run[++last] = hi; - } - if ((count & 1) != 0) - { - for (int i = right, lo = run[count - 1]; --i >= lo; b[i + bo] = a[i - + ao]) - ; - run[++last] = right; - } - int[] t = a; - a = b; - b = t; - int o = ao; - ao = bo; - bo = o; - } - } - - /** - * Sorts the specified range of the array by Dual-Pivot Quicksort. - * - * @param a - * the array to be sorted - * @param left - * the index of the first element, inclusive, to be sorted - * @param right - * the index of the last element, inclusive, to be sorted - * @param leftmost - * indicates if this part is the leftmost in the range - */ - private void sort(int[] a, int left, int right, boolean leftmost) - { - int length = right - left + 1; - - // Use insertion sort on tiny arrays - if (length < INSERTION_SORT_THRESHOLD) - { - if (leftmost) - { - /* - * Traditional (without sentinel) insertion sort, - * optimized for server VM, is used in case of - * the leftmost part. - */ - for (int i = left, j = i; i < right; j = ++i) - { - int ai = a[i + 1]; - while (val(ai) < val(a[j])) - { - a[j + 1] = a[j]; - if (j-- == left) - { - break; - } - } - a[j + 1] = ai; - } - } - else - { - /* - * Skip the longest ascending sequence. - */ - do - { - if (left >= right) - { - return; - } - } while (val(a[++left]) >= val(a[left - 1])); - - /* - * Every element from adjoining part plays the role - * of sentinel, therefore this allows us to avoid the - * left range check on each iteration. Moreover, we use - * the more optimized algorithm, so called pair insertion - * sort, which is faster (in the context of Quicksort) - * than traditional implementation of insertion sort. - */ - for (int k = left; ++left <= right; k = ++left) - { - int a1 = a[k], a2 = a[left]; - - if (val(a1) < val(a2)) - { - a2 = a1; - a1 = a[left]; - } - while (val(a1) < val(a[--k])) - { - a[k + 2] = a[k]; - } - a[++k + 1] = a1; - - while (val(a2) < val(a[--k])) - { - a[k + 1] = a[k]; - } - a[k + 1] = a2; - } - int last = a[right]; - - while (val(last) < val(a[--right])) - { - a[right + 1] = a[right]; - } - a[right + 1] = last; - } - return; - } - - // Inexpensive approximation of length / 7 - int seventh = (length >> 3) + (length >> 6) + 1; - - /* - * Sort five evenly spaced elements around (and including) the - * center element in the range. These elements will be used for - * pivot selection as described below. The choice for spacing - * these elements was empirically determined to work well on - * a wide variety of inputs. - */ - int e3 = (left + right) >>> 1; // The midpoint - int e2 = e3 - seventh; - int e1 = e2 - seventh; - int e4 = e3 + seventh; - int e5 = e4 + seventh; - - // Sort these elements using insertion sort - if (val(a[e2]) < val(a[e1])) - { - int t = a[e2]; - a[e2] = a[e1]; - a[e1] = t; - } - - if (val(a[e3]) < val(a[e2])) - { - int t = a[e3]; - a[e3] = a[e2]; - a[e2] = t; - if (val(t) < val(a[e1])) - { - a[e2] = a[e1]; - a[e1] = t; - } - } - if (val(a[e4]) < val(a[e3])) - { - int t = a[e4]; - a[e4] = a[e3]; - a[e3] = t; - int vt = val(t); - if (vt < val(a[e2])) - { - a[e3] = a[e2]; - a[e2] = t; - if (vt < val(a[e1])) - { - a[e2] = a[e1]; - a[e1] = t; - } - } - } - if (val(a[e5]) < val(a[e4])) - { - int t = a[e5]; - a[e5] = a[e4]; - a[e4] = t; - int vt = val(t); - if (vt < val(a[e3])) - { - a[e4] = a[e3]; - a[e3] = t; - if (vt < val(a[e2])) - { - a[e3] = a[e2]; - a[e2] = t; - if (vt < val(a[e1])) - { - a[e2] = a[e1]; - a[e1] = t; - } - } - } - } - - // Pointers - int less = left; // The index of the first element of center part - int great = right; // The index before the first element of right part - - if (val(a[e1]) != val(a[e2]) && val(a[e2]) != val(a[e3]) - && val(a[e3]) != val(a[e4]) && val(a[e4]) != val(a[e5])) - { - /* - * Use the second and fourth of the five sorted elements as pivots. - * These values are inexpensive approximations of the first and - * second terciles of the array. Note that pivot1 <= pivot2. - */ - int pivot1 = val(a[e2]); - int pivot2 = val(a[e4]); - int pivot1k = a[e2]; - int pivot2k = a[e4]; - - /* - * The first and the last elements to be sorted are moved to the - * locations formerly occupied by the pivots. When partitioning - * is complete, the pivots are swapped back into their final - * positions, and excluded from subsequent sorting. - */ - a[e2] = a[left]; - a[e4] = a[right]; - - /* - * Skip elements, which are less or greater than pivot values. - */ - while (val(a[++less]) < pivot1) - ; - while (val(a[--great]) > pivot2) - ; - - /* - * Partitioning: - * - * left part center part right part - * +--------------------------------------------------------------+ - * | < pivot1 | pivot1 <= && <= pivot2 | ? | > pivot2 | - * +--------------------------------------------------------------+ - * ^ ^ ^ - * | | | - * less k great - * - * Invariants: - * - * all in (left, less) < pivot1 - * pivot1 <= all in [less, k) <= pivot2 - * all in (great, right) > pivot2 - * - * Pointer k is the first index of ?-part. - */ - outer: for (int k = less - 1; ++k <= great;) - { - int ak = a[k]; - if (val(ak) < pivot1) - { // Move a[k] to left part - a[k] = a[less]; - /* - * Here and below we use "a[i] = b; i++;" instead - * of "a[i++] = b;" due to performance issue. - */ - a[less] = ak; - ++less; - } - else if (val(ak) > pivot2) - { // Move a[k] to right part - while (val(a[great]) > pivot2) - { - if (great-- == k) - { - break outer; - } - } - if (val(a[great]) < pivot1) - { // a[great] <= pivot2 - a[k] = a[less]; - a[less] = a[great]; - ++less; - } - else - { // pivot1 <= a[great] <= pivot2 - a[k] = a[great]; - } - /* - * Here and below we use "a[i] = b; i--;" instead - * of "a[i--] = b;" due to performance issue. - */ - a[great] = ak; - --great; - } - } - - // Swap pivots into their final positions - a[left] = a[less - 1]; - a[less - 1] = pivot1k; - a[right] = a[great + 1]; - a[great + 1] = pivot2k; - - // Sort left and right parts recursively, excluding known pivots - sort(a, left, less - 2, leftmost); - sort(a, great + 2, right, false); - - /* - * If center part is too large (comprises > 4/7 of the array), - * swap internal pivot values to ends. - */ - if (less < e1 && e5 < great) - { - /* - * Skip elements, which are equal to pivot values. - */ - while (val(a[less]) == pivot1) - { - ++less; - } - - while (val(a[great]) == pivot2) - { - --great; - } - - /* - * Partitioning: - * - * left part center part right part - * +----------------------------------------------------------+ - * | == pivot1 | pivot1 < && < pivot2 | ? | == pivot2 | - * +----------------------------------------------------------+ - * ^ ^ ^ - * | | | - * less k great - * - * Invariants: - * - * all in (*, less) == pivot1 - * pivot1 < all in [less, k) < pivot2 - * all in (great, *) == pivot2 - * - * Pointer k is the first index of ?-part. - */ - outer: for (int k = less - 1; ++k <= great;) - { - int ak = a[k]; - if (val(ak) == pivot1) - { // Move a[k] to left part - a[k] = a[less]; - a[less] = ak; - ++less; - } - else if (val(ak) == pivot2) - { // Move a[k] to right part - while (val(a[great]) == pivot2) - { - if (great-- == k) - { - break outer; - } - } - if (val(a[great]) == pivot1) - { // a[great] < pivot2 - a[k] = a[less]; - /* - * Even though a[great] equals to pivot1, the - * assignment a[less] = pivot1 may be incorrect, - * if a[great] and pivot1 are floating-point zeros - * of different signs. Therefore in float and - * double sorting methods we have to use more - * accurate assignment a[less] = a[great]. - */ - a[less] = pivot1k; - ++less; - } - else - { // pivot1 < a[great] < pivot2 - a[k] = a[great]; - } - a[great] = ak; - --great; - } - } - } - - // Sort center part recursively - sort(a, less, great, false); - - } - else - { // Partitioning with one pivot - /* - * Use the third of the five sorted elements as pivot. - * This value is inexpensive approximation of the median. - */ - int pivot = val(a[e3]); - - /* - * Partitioning degenerates to the traditional 3-way - * (or "Dutch National Flag") schema: - * - * left part center part right part - * +-------------------------------------------------+ - * | < pivot | == pivot | ? | > pivot | - * +-------------------------------------------------+ - * ^ ^ ^ - * | | | - * less k great - * - * Invariants: - * - * all in (left, less) < pivot - * all in [less, k) == pivot - * all in (great, right) > pivot - * - * Pointer k is the first index of ?-part. - */ - for (int k = less; k <= great; ++k) - { - if (val(a[k]) == pivot) - { - continue; - } - int ak = a[k]; - if (val(ak) < pivot) - { // Move a[k] to left part - a[k] = a[less]; - a[less] = ak; - ++less; - } - else - { // a[k] > pivot - Move a[k] to right part - while (val(a[great]) > pivot) - { - --great; - } - if (val(a[great]) < pivot) - { // a[great] <= pivot - a[k] = a[less]; - a[less] = a[great]; - ++less; - } - else - { // a[great] == pivot - /* - * Even though a[great] equals to pivot, the - * assignment a[k] = pivot may be incorrect, - * if a[great] and pivot are floating-point - * zeros of different signs. Therefore in float - * and double sorting methods we have to use - * more accurate assignment a[k] = a[great]. - */ - // So, guess what? - // - // Actually, we do need a[great] for IntervalStore, - // because here, two, the numbers are not necessarily the same item - // - // a[k] = pivot; - a[k] = a[great]; - } - a[great] = ak; - --great; - } - } - - /* - * Sort left and right parts recursively. - * All elements from center part are equal - * and, therefore, already sorted. - */ - sort(a, left, less - 1, leftmost); - sort(a, great + 1, right, false); - } - } - -} diff --git a/src/intervalstore/nonc/IntervalStore.java b/src/intervalstore/nonc/IntervalStore.java index bc9ca83..5c013ad 100644 --- a/src/intervalstore/nonc/IntervalStore.java +++ b/src/intervalstore/nonc/IntervalStore.java @@ -224,8 +224,8 @@ public class IntervalStore Comparator comparator, boolean bigendian) { icompare = (comparator != null ? comparator - : bigendian ? IntervalI.COMPARATOR_BIGENDIAN - : IntervalI.COMPARATOR_LITTLEENDIAN); + : bigendian ? IntervalI.COMPARE_BEGIN_ASC_END_DESC + : IntervalI.COMPARE_BEGIN_ASC_END_ASC); this.bigendian = bigendian; if (intervals != null) @@ -259,7 +259,7 @@ public class IntervalStore } /** - * Adds one interval to the store, allowing duplicates. + * Adds one interval to the store, allowing duplicates * * @param interval */ @@ -320,9 +320,6 @@ public class IntervalStore else { index = findInterval(interval); - // System.out.println("index = " + index + " for " + interval + "\n" - // + Arrays.toString(intervals) + "\n" - // + Arrays.toString(offsets)); if (!allowDuplicates && index >= 0) { return false; @@ -405,7 +402,7 @@ public class IntervalStore int pt0 = pt; while (--pt >= 0 && offsets[pt] == 0) { - ; + } if (pt < 0) { @@ -484,7 +481,7 @@ public class IntervalStore case 0: IntervalI iv = intervals[mid]; if ((bsIgnore == null || !bsIgnore.get(mid)) - && iv.equalsInterval(interval)) + && sameInterval(interval, iv)) { return mid; // found one; just scan up and down now, first checking the range, but @@ -498,7 +495,7 @@ public class IntervalStore break; } if ((bsIgnore == null || !bsIgnore.get(i)) - && iv.equalsInterval(interval)) + && sameInterval(interval, iv)) { return i; } @@ -511,7 +508,7 @@ public class IntervalStore return -1 - ++i; } if ((bsIgnore == null || !bsIgnore.get(i)) - && iv.equalsInterval(interval)) + && sameInterval(interval, iv)) { return i; } @@ -522,10 +519,21 @@ public class IntervalStore return -1 - start; } - @Override - public boolean canCheckForDuplicates() + /** + * Answers true if the two intervals are equal (as determined by + * {@code i1.equals(i2)}, else false + * + * @param i1 + * @param i2 + * @return + */ + static boolean sameInterval(IntervalI i1, IntervalI i2) { - return true; + /* + * for speed, do the fast check for begin/end equality before + * the equals check which includes type checking + */ + return i1.equalsInterval(i2) && i1.equals(i2); } /** @@ -766,21 +774,6 @@ public class IntervalStore } /** - * return the i-th interval in the designated order (bigendian or - * littleendian) - */ - @Override - public IntervalI get(int i) - { - if (i < 0 || i >= intervalCount + added) - { - return null; - } - ensureFinalized(); - return intervals[i]; - } - - /** * Return the deepest level of nesting. * */ @@ -821,26 +814,6 @@ public class IntervalStore } /** - * Get the number of root-level nests. - * - */ - @Override - public int getWidth() - { - ensureFinalized(); - // System.out.println( - // "ISList w[0]=" + nestCounts[0] + " w[1]=" + nestCounts[1]); - return nestCounts[0] + (createUnnested ? nestCounts[1] : 0); - } - - @Override - public boolean isValid() - { - ensureFinalized(); - return true; - } - - /** * Answers an iterator over the intervals in the store, with no particular * ordering guaranteed. The iterator does not support the optional * remove operation (throws @@ -915,17 +888,15 @@ public class IntervalStore { sb.append(sep).append(nests[pt + i].toString()); if (nestCounts[pt + i] > 0) + { dump(pt + i, sb, sep + " "); + } } } @Override public synchronized boolean remove(Object o) { - // if (o == null) - // { - // throw new NullPointerException(); - // } return (o != null && intervalCount > 0 && removeInterval((IntervalI) o)); } @@ -964,7 +935,7 @@ public class IntervalStore case -1: break; case 0: - if (iv.equalsInterval(interval)) + if (sameInterval(interval, iv)) { return pt; } @@ -979,9 +950,9 @@ public class IntervalStore else { int i = intervalCount; - while (--i >= 0 && !intervals[i].equalsInterval(interval)) + while (--i >= 0 && !sameInterval(intervals[i], interval)) { - ; + } return i; } @@ -1067,7 +1038,6 @@ public class IntervalStore * Recreate the key nest arrays. * */ - @Override public boolean revalidate() { isTainted = true; @@ -1186,7 +1156,6 @@ public class IntervalStore int beginLast2 = beginLast; // Phase One: Get the temporary container array myContainer. - for (int i = 1; i < intervalCount; i++) { int pt = i - 1; diff --git a/src/intervalstore/nonc/IntervalStore0.java b/src/intervalstore/nonc/IntervalStore0.java index 389439f..1050ee0 100644 --- a/src/intervalstore/nonc/IntervalStore0.java +++ b/src/intervalstore/nonc/IntervalStore0.java @@ -67,6 +67,7 @@ import intervalstore.api.IntervalStoreI; public class IntervalStore0 extends AbstractCollection implements IntervalStoreI { + private static int NOT_CONTAINED = Integer.MIN_VALUE; /** * Search for the last interval that starts before or at the specified from/to @@ -230,8 +231,8 @@ public class IntervalStore0 } DO_PRESORT = presort; icompare = (comparator != null ? comparator - : bigendian ? IntervalI.COMPARATOR_BIGENDIAN - : IntervalI.COMPARATOR_LITTLEENDIAN); + : bigendian ? IntervalI.COMPARE_BEGIN_ASC_END_DESC + : IntervalI.COMPARE_BEGIN_ASC_END_ASC); this.bigendian = bigendian; if (DO_PRESORT && intervalCount > 1) @@ -250,7 +251,7 @@ public class IntervalStore0 @Override public boolean add(T interval) { - return add(interval, true); + return add(interval, false); } /** @@ -373,7 +374,7 @@ public class IntervalStore0 int pt0 = pt; while (--pt >= 0 && offsets[pt] == 0) { - ; + } if (pt < 0) { @@ -445,7 +446,7 @@ public class IntervalStore0 case 0: IntervalI iv = intervals[mid]; if ((bsIgnore == null || !bsIgnore.get(mid)) - && iv.equalsInterval(interval)) + && sameInterval(interval, iv)) { return mid; // found one; just scan up and down now, first checking the range, but @@ -459,7 +460,7 @@ public class IntervalStore0 break; } if ((bsIgnore == null || !bsIgnore.get(i)) - && iv.equalsInterval(interval)) + && sameInterval(interval, iv)) { return i; } @@ -471,7 +472,7 @@ public class IntervalStore0 return -1 - ++i; } if ((bsIgnore == null || !bsIgnore.get(i)) - && iv.equalsInterval(interval)) + && sameInterval(interval, iv)) { return i; } @@ -482,33 +483,13 @@ public class IntervalStore0 return -1 - start; } - // private int binaryInsertionSearch(long from, long to) - // { - // int matched = intervalCount; - // int end = matched - 1; - // int start = matched; - // if (end < 0 || from > intervals[end].getEnd() - // || from < intervals[start = 0].getBegin()) - // return start; - // while (start <= end) - // { - // int mid = (start + end) >>> 1; - // switch (compareRange(intervals[mid], from, to)) - // { - // case 0: - // return mid; - // case 1: - // matched = mid; - // end = mid - 1; - // continue; - // case -1: - // start = mid + 1; - // continue; - // } - // - // } - // return matched; - // } + boolean sameInterval(IntervalI i1, IntervalI i2) + { + /* + * do the quick test of begin/end first for speed + */ + return i1.equalsInterval(i2) && i1.equals(i2); + } @Override public void clear() @@ -668,17 +649,6 @@ public class IntervalStore0 return result; } - @Override - public IntervalI get(int i) - { - if (i < 0 || i >= intervalCount + added) - { - return null; - } - ensureFinalized(); - return intervals[i]; - } - private int getContainedBy(int index, int begin) { while (index >= 0) @@ -692,7 +662,7 @@ public class IntervalStore0 } index -= Math.abs(offsets[index]); } - return IntervalI.NOT_CONTAINED; + return NOT_CONTAINED; } @Override @@ -708,7 +678,7 @@ public class IntervalStore0 for (int i = 0; i < intervalCount; i++) { IntervalI element = intervals[i]; - if (offsets[i] == IntervalI.NOT_CONTAINED) + if (offsets[i] == NOT_CONTAINED) { root = element; } @@ -728,28 +698,6 @@ public class IntervalStore0 return maxDepth; } - @Override - public int getWidth() - { - ensureFinalized(); - int w = 0; - for (int i = offsets.length; --i >= 0;) - { - if (offsets[i] > 0) - { - w++; - } - } - return w; - } - - @Override - public boolean isValid() - { - ensureFinalized(); - return true; - } - /** * Answers an iterator over the intervals in the store, with no particular * ordering guaranteed. The iterator does not support the optional @@ -792,7 +740,7 @@ public class IntervalStore0 return; } maxEnd = intervals[0].getEnd(); - offsets[0] = IntervalI.NOT_CONTAINED; + offsets[0] = NOT_CONTAINED; if (intervalCount == 1) { return; @@ -806,7 +754,7 @@ public class IntervalStore0 // System.out.println(sf + " is contained by " // + (index < 0 ? null : starts[index])); - offsets[i] = (index < 0 ? IntervalI.NOT_CONTAINED + offsets[i] = (index < 0 ? NOT_CONTAINED : isMonotonic ? index - i : i - index); isMonotonic = (sf.getEnd() > maxEnd); if (isMonotonic) @@ -888,7 +836,7 @@ public class IntervalStore0 case -1: break; case 0: - if (iv.equalsInterval(interval)) + if (sameInterval(interval, iv)) { return pt; } @@ -903,9 +851,9 @@ public class IntervalStore0 else { int i = intervalCount; - while (--i >= 0 && !intervals[i].equalsInterval(interval)) + while (--i >= 0 && !sameInterval(intervals[i], interval)) { - ; + } return i; } @@ -984,15 +932,6 @@ public class IntervalStore0 } @Override - public boolean revalidate() - { - isTainted = true; - isSorted = false; - ensureFinalized(); - return true; - } - - @Override public int size() { return intervalCount + added - deleted; @@ -1045,11 +984,4 @@ public class IntervalStore0 { return prettyPrint(); } - - @Override - public boolean canCheckForDuplicates() - { - return true; - } - } diff --git a/src/jalview/api/AlignmentColsCollectionI.java b/src/jalview/api/AlignmentColsCollectionI.java index 70dda87..5fe0567 100644 --- a/src/jalview/api/AlignmentColsCollectionI.java +++ b/src/jalview/api/AlignmentColsCollectionI.java @@ -41,12 +41,11 @@ public interface AlignmentColsCollectionI extends Iterable public boolean hasHidden(); /** - * Get the visible-column bitset, possibly containing hidden columns (which - * may or may not be hidden in the overview). + * Get the bitset where a set bit indicates a column to be shown * * @return a BitSet */ - public BitSet getOverviewBitSet(); + public BitSet getShownBitSet(); /** * Get the hidden-column bitset, (which may or may not be hidden in the diff --git a/src/jalview/datamodel/Alignment.java b/src/jalview/datamodel/Alignment.java index 98510e3..22d23bc 100755 --- a/src/jalview/datamodel/Alignment.java +++ b/src/jalview/datamodel/Alignment.java @@ -59,6 +59,15 @@ public class Alignment implements AlignmentI private boolean nucleotide = true; + private List codonFrameList; + + private static final SequenceGroup[] noGroups = new SequenceGroup[0]; + + /* + * persistent object to hold result of findAllGroups(SequenceI) + */ + private List groupsForSequence = new ArrayList<>(); + public boolean hasRNAStructure = false; public AlignmentAnnotation[] annotations; @@ -69,8 +78,6 @@ public class Alignment implements AlignmentI public Hashtable alignmentProperties; - private List codonFrameList; - private void initAlignment(SequenceI[] seqs) { groups = Collections.synchronizedList(new ArrayList()); @@ -398,10 +405,6 @@ public class Alignment implements AlignmentI return null; } - private static final SequenceGroup[] noGroups = new SequenceGroup[0]; - - private ArrayList temp = new ArrayList<>(); - /* * (non-Javadoc) * @@ -411,7 +414,6 @@ public class Alignment implements AlignmentI @Override public SequenceGroup[] findAllGroups(SequenceI s) { - synchronized (groups) { int gSize = groups.size(); @@ -419,7 +421,7 @@ public class Alignment implements AlignmentI { return noGroups; } - temp.clear(); + groupsForSequence.clear(); for (int i = 0; i < gSize; i++) { SequenceGroup sg = groups.get(i); @@ -432,12 +434,12 @@ public class Alignment implements AlignmentI if (sg.getSequences().contains(s)) { - temp.add(sg); + groupsForSequence.add(sg); } } } - SequenceGroup[] ret = new SequenceGroup[temp.size()]; - return temp.toArray(ret); + SequenceGroup[] ret = new SequenceGroup[groupsForSequence.size()]; + return groupsForSequence.toArray(ret); } /** */ diff --git a/src/jalview/datamodel/AllColsCollection.java b/src/jalview/datamodel/AllColsCollection.java index f3077fa..c65c381 100644 --- a/src/jalview/datamodel/AllColsCollection.java +++ b/src/jalview/datamodel/AllColsCollection.java @@ -67,10 +67,10 @@ public class AllColsCollection implements AlignmentColsCollectionI } /** - * return ALL columns, not just the truly visible ones + * Returns all columns, including any hidden in the alignment */ @Override - public BitSet getOverviewBitSet() + public BitSet getShownBitSet() { if (bsVisible == null) { diff --git a/src/jalview/datamodel/Sequence.java b/src/jalview/datamodel/Sequence.java index bad33d1..47a7ead 100755 --- a/src/jalview/datamodel/Sequence.java +++ b/src/jalview/datamodel/Sequence.java @@ -118,6 +118,11 @@ public class Sequence extends ASequence implements SequenceI */ private int changeCount; + /* + * cached rgb colours for each position of the aligned sequence (column) + */ + private int[] argb; + /** * Creates a new Sequence object. * @@ -392,10 +397,6 @@ public class Sequence extends ASequence implements SequenceI return sequenceFeatureStore.add(sf); } - /** - * @param sf - * A known feature of this featureStore - */ @Override public void deleteFeature(SequenceFeature sf) { @@ -1616,7 +1617,7 @@ public class Sequence extends ASequence implements SequenceI _isNa = Comparison.isNucleotide(this); } return !_isNa; - }; + } /* * (non-Javadoc) @@ -1971,15 +1972,6 @@ public class Sequence extends ASequence implements SequenceI List result = getFeatures().findFeatures(startPos, endPos, types); - // if (datasetSequence != null) - // { - // result = datasetSequence.getFeatures().findFeatures(startPos, endPos, - // types); - // } - // else - // { - // result = sequenceFeatureStore.findFeatures(startPos, endPos, types); - // } /* * if end column is gapped, endPos may be to the right, @@ -2133,8 +2125,6 @@ public class Sequence extends ASequence implements SequenceI return 0; } - private int[] argb; - @Override public int getColor(int i) { @@ -2158,12 +2148,10 @@ public class Sequence extends ASequence implements SequenceI } /** - * @author Bob Hanson 2019.07.30 - * - * allows passing the result ArrayList as a parameter to avoid - * unnecessary construction - * @return result (JavaScript) or new ArrayList (Java -- see FeatureRender) - * + * Answers a (possibly empty) list of features of the specified type that + * overlap the specified column position. If parameter {@code result} is not + * null, features are appended to it and the (possibly extended) list is + * returned. */ @Override public List findFeatures(int column, String type, @@ -2173,9 +2161,6 @@ public class Sequence extends ASequence implements SequenceI result); } - /** - * allows early intervention for renderer if this returns false - */ @Override public boolean hasFeatures(String type) { diff --git a/src/jalview/datamodel/SequenceFeature.java b/src/jalview/datamodel/SequenceFeature.java index 30e0929..bf0b996 100755 --- a/src/jalview/datamodel/SequenceFeature.java +++ b/src/jalview/datamodel/SequenceFeature.java @@ -35,8 +35,6 @@ import java.util.SortedMap; import java.util.TreeMap; import java.util.Vector; -import intervalstore.api.IntervalI; - /** * A class that models a single contiguous feature on a sequence. If flag * 'contactFeature' is true, the start and end positions are interpreted instead @@ -220,19 +218,10 @@ public class SequenceFeature implements FeatureLocationI public boolean equals(Object o) { return (o != null && (o instanceof SequenceFeature) - && equalsInterval((SequenceFeature) o)); + && equals(((SequenceFeature) o), false)); } /** - * Having determined that this is in fact a SequenceFeature, now check it for - * equivalence. Overridden in CrossRef; used by IntervalStore (possibly). - */ - @Override - public boolean equalsInterval(IntervalI sf) - { - return sf != null && equals((SequenceFeature) sf, false); - } - /** * Overloaded method allows the equality test to optionally ignore the * 'Parent' attribute of a feature. This supports avoiding adding many * superficially duplicate 'exon' or CDS features to genomic or protein diff --git a/src/jalview/datamodel/SequenceI.java b/src/jalview/datamodel/SequenceI.java index 0b26564..ca12c83 100755 --- a/src/jalview/datamodel/SequenceI.java +++ b/src/jalview/datamodel/SequenceI.java @@ -386,6 +386,12 @@ public interface SequenceI extends ASequenceI */ public boolean addSequenceFeature(SequenceFeature sf); + /** + * Deletes the feature from the sequence (if found). To be precise, deletes + * the first feature {@code f} found where {@code f.equals(sf)}. + * + * @param sf + */ public void deleteFeature(SequenceFeature sf); public void setDatasetSequence(SequenceI seq); @@ -609,21 +615,18 @@ public interface SequenceI extends ASequenceI public void resetColors(); /** - * allows passing the result ArrayList as a parameter to avoid unnecessary - * construction - * - * @author Bob Hanson 2019.07.30 - * - * + * Answers a (possibly empty) list of features of the specified type that + * overlap the specified column position. If parameter {@code result} is not + * null, features are appended to it and the (possibly extended) list is + * returned. */ List findFeatures(int column, String type, List result); /** - * allows early intervention for renderer if false - * - * @author Bob Hanson 2019.07.30 + * Answers true if this store contains at least one feature, else false * + * @return */ public boolean hasFeatures(String type); diff --git a/src/jalview/datamodel/VisibleColsCollection.java b/src/jalview/datamodel/VisibleColsCollection.java index cd812a1..13709a8 100644 --- a/src/jalview/datamodel/VisibleColsCollection.java +++ b/src/jalview/datamodel/VisibleColsCollection.java @@ -64,7 +64,7 @@ public class VisibleColsCollection implements AlignmentColsCollectionI * Only the visible columns. */ @Override - public BitSet getOverviewBitSet() + public BitSet getShownBitSet() { if (bsVisible == null) { diff --git a/src/jalview/datamodel/features/FeatureStore.java b/src/jalview/datamodel/features/FeatureStore.java index 75ec45a..4073dd6 100644 --- a/src/jalview/datamodel/features/FeatureStore.java +++ b/src/jalview/datamodel/features/FeatureStore.java @@ -31,11 +31,12 @@ import java.util.List; import java.util.Set; import intervalstore.api.IntervalStoreI; +import intervalstore.impl.BinarySearcher; +import intervalstore.impl.BinarySearcher.Compare; -public abstract class FeatureStore implements FeatureStoreI +public class FeatureStore { - - /** + /* * track last start for quick insertion of ordered features */ protected int lastStart = -1; @@ -150,7 +151,6 @@ public abstract class FeatureStore implements FeatureStoreI * @param feature * @return */ - @Override public boolean listContains(List list, SequenceFeature feature) { @@ -159,40 +159,26 @@ public abstract class FeatureStore implements FeatureStoreI 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 pos = BinarySearcher.findFirst(list, true, Compare.GE, begin); int len = list.size(); while (pos < len) { SequenceFeature sf = list.get(pos); if (sf.begin > begin) { - return -1; // no match found + return false; // no match found } if (sf.equals(feature)) { - return pos; + return true; } pos++; } - return -1; + return false; } /** @@ -248,7 +234,11 @@ public abstract class FeatureStore implements FeatureStoreI */ public FeatureStore(int intervalStoreType) { - features = getIntervalStore(intervalStoreType); + features = + // Platform.isJS() + // ? new intervalstore.nonc.IntervalStore<>(true) + // : new intervalstore.impl.IntervalStore<>(); + getIntervalStore(intervalStoreType); positionalFeatureGroups = new HashSet<>(); nonPositionalFeatureGroups = new HashSet<>(); positionalMinScore = Float.NaN; @@ -262,9 +252,9 @@ public abstract class FeatureStore implements FeatureStoreI private IntervalStoreI getIntervalStore(int type) { switch (type != INTERVAL_STORE_DEFAULT ? type : // - Platform.isJS() // - ? intervalStoreJSOption - : intervalStoreJavaOption) + Platform.isJS() // + ? intervalStoreJSOption + : intervalStoreJavaOption) { default: case INTERVAL_STORE_NCLIST_OBJECT: @@ -301,8 +291,9 @@ public abstract class FeatureStore implements FeatureStoreI * 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); + int insertAt = BinarySearcher.findFirst(contactFeatureStarts, true, + Compare.GE, feature.begin); + contactFeatureStarts.add(insertAt, feature); /* * insert into list sorted by end (second contact position): * binary search the sorted list to find the insertion point @@ -321,23 +312,8 @@ 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 (feature.isContactFeature()) { if (containsContactFeature(feature)) @@ -353,7 +329,7 @@ public abstract class FeatureStore implements FeatureStoreI } else if (feature.isNonPositional()) { - if (containsNonPositional(feature)) + if (containsNonPositionalFeature(feature)) { return false; } @@ -362,9 +338,7 @@ public abstract class FeatureStore implements FeatureStoreI } else { - // allow for check with - if (checkContainsPositionalFeatureForAdd(feature) - || !addPositionalFeature(feature)) + if (!features.add(feature, false)) { return false; } @@ -423,14 +397,6 @@ public abstract class FeatureStore implements FeatureStoreI } /** - * Adds one feature to the IntervalStore that can manage nested features - * (creating the IntervalStore if necessary) - * - * @return true if added -- allowing for late checking during addition - */ - abstract protected boolean addPositionalFeature(SequenceFeature feature); - - /** * Adds the feature to the list of non-positional features (with lazy * instantiation of the list if it is null), and returns true. The feature * group is added to the set of distinct feature groups for non-positional @@ -460,58 +426,54 @@ public abstract class FeatureStore implements FeatureStoreI * @param feature * @return */ - @Override public boolean contains(SequenceFeature feature) { if (feature.isNonPositional()) { - return containsNonPositional(feature); - + return containsNonPositionalFeature(feature); } if (feature.isContactFeature()) { return containsContactFeature(feature); - } return containsPositionalFeature(feature); } - /** - * A check that can be overridden if the check is being done during the add - * operation itself. - * - * @param feature - * @return - */ - protected boolean checkContainsPositionalFeatureForAdd( - SequenceFeature feature) - { - return containsPositionalFeature(feature); - } - private boolean containsPositionalFeature(SequenceFeature feature) { return features == null || feature.begin > lastStart ? false - : containsFeature(feature); + : features.contains(feature); } + /** + * Answers true if this store already contains a contact feature equal to the + * given feature (by {@code SequenceFeature.equals()} test), else false + * + * @param feature + * @return + */ private boolean containsContactFeature(SequenceFeature feature) { return contactFeatureStarts != null && feature.begin <= lastContactStart && listContains(contactFeatureStarts, feature); } - private boolean containsNonPositional(SequenceFeature feature) + /** + * Answers true if this store already contains a non-positional feature equal + * to the given feature (by {@code SequenceFeature.equals()} test), else false + * + * @param feature + * @return + */ + private boolean containsNonPositionalFeature(SequenceFeature feature) { return nonPositionalFeatures == null ? false : nonPositionalFeatures.contains(feature); } - 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 @@ -520,8 +482,6 @@ public abstract class FeatureStore implements FeatureStoreI * * @param sf */ - - @Override public synchronized boolean delete(SequenceFeature sf) { boolean removed = false; @@ -552,7 +512,7 @@ public abstract class FeatureStore implements FeatureStoreI */ if (!removed && features != null) { - removed = findAndRemoveNonContactFeature(sf); + removed = features.remove(sf); } if (removed) @@ -563,23 +523,11 @@ 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); - - abstract protected int findFirstEnd(List list, long pos); - - @Override public List findOverlappingFeatures(long start, long end) { return findOverlappingFeatures(start, end, null); } - @Override public List getContactFeatures() { return getContactFeatures(new ArrayList<>()); @@ -591,8 +539,6 @@ public abstract class FeatureStore implements FeatureStoreI * * @return */ - - @Override public List getContactFeatures( List result) { @@ -610,8 +556,6 @@ public abstract class FeatureStore implements FeatureStoreI * @param positional * @return */ - - @Override public int getFeatureCount(boolean positional) { if (!positional) @@ -622,7 +566,6 @@ public abstract class FeatureStore implements FeatureStoreI return (contactFeatureStarts == null ? 0 : contactFeatureStarts.size()) + features.size(); - } /** @@ -633,8 +576,6 @@ public abstract class FeatureStore implements FeatureStoreI * @param positionalFeatures * @return */ - - @Override public Set getFeatureGroups(boolean positionalFeatures) { if (positionalFeatures) @@ -649,7 +590,6 @@ public abstract class FeatureStore implements FeatureStoreI } } - @Override public Collection getFeatures() { return features; @@ -663,8 +603,6 @@ public abstract class FeatureStore implements FeatureStoreI * @param group * @return */ - - @Override public List getFeaturesForGroup(boolean positional, String group) { @@ -700,8 +638,6 @@ public abstract class FeatureStore implements FeatureStoreI * @param positional * @return */ - - @Override public float getMaximumScore(boolean positional) { return positional ? positionalMaxScore : nonPositionalMaxScore; @@ -715,14 +651,11 @@ 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<>()); @@ -734,8 +667,6 @@ public abstract class FeatureStore implements FeatureStoreI * * @return */ - - @Override public List getNonPositionalFeatures( List result) { @@ -746,7 +677,6 @@ public abstract class FeatureStore implements FeatureStoreI return result; } - @Override public List getPositionalFeatures() { return getPositionalFeatures(new ArrayList<>()); @@ -757,8 +687,6 @@ public abstract class FeatureStore implements FeatureStoreI * * @return */ - - @Override public List getPositionalFeatures( List result) { @@ -788,8 +716,6 @@ public abstract class FeatureStore implements FeatureStoreI * * @return */ - - @Override public int getTotalFeatureLength() { return totalExtent; @@ -800,8 +726,6 @@ public abstract class FeatureStore implements FeatureStoreI * * @return */ - - @Override public boolean isEmpty() { boolean hasFeatures = (contactFeatureStarts != null @@ -876,8 +800,6 @@ public abstract class FeatureStore implements FeatureStoreI * @param shiftBy * @return */ - - @Override public synchronized boolean shiftFeatures(int fromPosition, int shiftBy) { /* @@ -912,4 +834,163 @@ public abstract class FeatureStore implements FeatureStoreI return modified; } + /** + * Answers the position (0, 1...) in the list of the first entry whose end + * position is not less than {@ pos}. If no such entry is found, answers the + * length of the list. + * + * @param list + * @param pos + * @return + */ + protected int findFirstEnd(List list, long pos) + { + return BinarySearcher.findFirst(list, false, Compare.GE, (int) pos); + } + + /** + * 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); + 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 + */ + private 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 = findFirstEnd(contactFeatureEnds, from); + + int n = contactFeatureEnds.size(); + while (index < n) + { + 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 + */ + private void findContactStartOverlaps(long from, long to, + List result) + { + int index = BinarySearcher.findFirst(contactFeatureStarts, true, + Compare.GE, (int) 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++; + } + } + + /** + * 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. If the {@code result} + * parameter is not null, new entries are added to this list and the (possibly + * extended) list returned. + * + * @param start + * start position of overlap range (inclusive) + * @param end + * end position of overlap range (inclusive) + * @param result + * @return + */ + public List findOverlappingFeatures(long start, long end, + List result) + { + if (result == null) + { + result = new ArrayList<>(); + } + + findContactFeatures(start, end, result); + features.findOverlaps(start, end, result); + + return result; + } + } diff --git a/src/jalview/datamodel/features/FeatureStoreI.java b/src/jalview/datamodel/features/FeatureStoreI.java deleted file mode 100644 index fb32577..0000000 --- a/src/jalview/datamodel/features/FeatureStoreI.java +++ /dev/null @@ -1,58 +0,0 @@ -package jalview.datamodel.features; - -import jalview.datamodel.SequenceFeature; - -import java.util.Collection; -import java.util.List; -import java.util.Set; - -public interface FeatureStoreI -{ - - boolean addFeature(SequenceFeature feature); - - boolean contains(SequenceFeature feature); - - boolean delete(SequenceFeature sf); - - List findOverlappingFeatures(long start, long end); - - List findOverlappingFeatures(long start, long end, - List result); - - List getContactFeatures(); - - List getContactFeatures(List result); - - int getFeatureCount(boolean positional); - - Set getFeatureGroups(boolean positionalFeatures); - - Collection getFeatures(); - - List getFeaturesForGroup(boolean positional, - String group); - - float getMaximumScore(boolean positional); - - float getMinimumScore(boolean positional); - - List getNonPositionalFeatures(); - - List getNonPositionalFeatures( - List result); - - List getPositionalFeatures(); - - List getPositionalFeatures(List result); - - int getTotalFeatureLength(); - - boolean isEmpty(); - - 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 deleted file mode 100644 index 63ee678..0000000 --- a/src/jalview/datamodel/features/FeatureStoreImpl.java +++ /dev/null @@ -1,274 +0,0 @@ -/* - * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) - * Copyright (C) $$Year-Rel$$ The Jalview Authors - * - * This file is part of Jalview. - * - * Jalview is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, either version 3 - * of the License, or (at your option) any later version. - * - * Jalview is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Jalview. If not, see . - * The Jalview Authors are detailed in the 'AUTHORS' file. - */ -package jalview.datamodel.features; - -import jalview.datamodel.SequenceFeature; - -import java.util.ArrayList; -import java.util.List; - -import intervalstore.impl.BinarySearcher; - -/** - * 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 FeatureStoreImpl extends FeatureStore -{ - - public FeatureStoreImpl() - { - super(); - } - - public FeatureStoreImpl(int option) - { - super(option); - } - - /** - * 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 = findFirstBegin(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 = findFirstEnd(contactFeatureEnds, - feature.getEnd()); - contactFeatureEnds.add(insertPosition, feature); - - return true; - } - - /** - * Adds one feature to the IntervalStore that can manage nested features - * (creating the IntervalStore if necessary) - */ - @Override - protected synchronized boolean addPositionalFeature( - SequenceFeature feature) - { - return features.add(feature); - } - - /** - * 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 - */ - @Override - protected void findContactFeatures(long from, long to, - List result) - { - if (contactFeatureStarts != null) - { - findContactStartOverlaps(from, to, result); - findContactEndOverlaps(from, to, result); - } - } - - @Override - protected boolean containsFeature(SequenceFeature feature) - { - return features.contains(feature); - } - - /** - * 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 - */ - - private 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 = findFirstEnd(contactFeatureEnds, from); - - int n = contactFeatureEnds.size(); - while (index < n) - { - 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 - */ - - private void findContactStartOverlaps(long from, long to, - List result) - { - int index = findFirstBegin(contactFeatureStarts, 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++; - } - } - - /** - * 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) - * @param result - * ignored - * @return - */ - - @Override - public List findOverlappingFeatures(long start, long end, - List result) - { - result = new ArrayList<>(); - findContactFeatures(start, end, result); - findOverlaps(start, end, result); - return result; - } - - private void findOverlaps(long start, long end, - List result) - { - result.addAll(features - .findOverlaps(start, end)); - } - - @Override - protected int findFirstBegin(List list, long pos) - { - return BinarySearcher.findFirst(list, (int) pos, - BinarySearcher.fbegin); - } - - @Override - protected int findFirstEnd(List list, long pos) - { - return BinarySearcher.findFirst(list, (int) pos, BinarySearcher.fend); - } - - @Override - protected boolean findAndRemoveNonContactFeature(SequenceFeature sf) - { - return features.remove(sf); - } - -} diff --git a/src/jalview/datamodel/features/FeatureStoreJS.java b/src/jalview/datamodel/features/FeatureStoreJS.java deleted file mode 100644 index 05adeb1..0000000 --- a/src/jalview/datamodel/features/FeatureStoreJS.java +++ /dev/null @@ -1,412 +0,0 @@ -/* - * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) - * Copyright (C) $$Year-Rel$$ The Jalview Authors - * - * This file is part of Jalview. - * - * Jalview is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, either version 3 - * of the License, or (at your option) any later version. - * - * Jalview is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR - * PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Jalview. If not, see . - * The Jalview Authors are detailed in the 'AUTHORS' file. - */ -package jalview.datamodel.features; - -import jalview.datamodel.SequenceFeature; - -import java.util.ArrayList; -import java.util.List; - -/** - * An adaption of FeatureStore that is efficient and lightweight, accelerating - * processing speed in JavaScript. - * - * It could be used in Java as well, with significant acceleration, but all this - * is so fast anyway that it probably will not be noticed in Java to speed it up - * by a factor of two or three. So for now, at least, this implementation is - * just in JavaScript. The flag for this is in SequenceFeatures. - * - * This implementation uses the IntervalStore developed by Bob Hanson, found at - * https://github.com/BobHanson/IntervalStoreJ, forked from the one developed by - * Mungo Carstairs at https://github.com/bartongroup/IntervalStoreJ. - * - * See the discussion folder at https://github.com/BobHanson/IntervalStoreJ for - * details. - * - * @author gmcarstairs - * @author Bob Hanson 2019.08.03-2019.08.16 - * - */ -public class FeatureStoreJS extends FeatureStore -{ - - - public FeatureStoreJS() - { - super(); - } - - public FeatureStoreJS(int option) - { - super(option); - } - - /** - * 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. This method allows duplicate features to be added, so test before - * calling to avoid this. - * - * @param feature - * @return true - */ - @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; - } - - /** - * Add a feature to the IntervalStore, not allowing for duplicates. - * - * - * @return false if could not add it (late check for duplicate) - */ - @Override - protected synchronized boolean addPositionalFeature( - SequenceFeature feature) - { - return features.add(feature, false); - } - - /** - * Initial check in FeatureStore.add(feature) that in other implementations - * does a containment check, but in this implementation just returns false to - * indicate that we should continue. This implementation will do this check as - * part of the add() method for greater efficiency (one binary search instead - * of two). - * - * @return false -- meaning "maybe not contained; continue adding" - */ - @Override - protected boolean checkContainsPositionalFeatureForAdd( - SequenceFeature feature) - { - return false; - } - - /** - * Check to see if a feature (or its equivalent based on - * IntervalI.equalsInterval) is already in this store. This method should be - * avoided except when necessary, as it involves a binary search with identity - * check. - * - * @return true if this feature or its equivalent (based on equalsInterval) is - * present already in the collection. - */ - @Override - protected boolean containsFeature(SequenceFeature feature) - { - return features.contains(feature); - } - - @Override - protected boolean findAndRemoveNonContactFeature(SequenceFeature sf) - { - return features.remove(sf); - } - - /** - * Add contact features to the result list where either the second or the - * first contact position lies within the target range, inclusively. - * - * @param from - * @param to - * @param result - */ - @Override - protected void findContactFeatures(long from, long to, - List result) - { - getContactStartOverlaps(from, to, result); - getContactEndOverlaps(from, to, result); - } - - /** - * Locate the first feature start in a standard ArrayList that is at or after - * this position. - * - */ - - @Override - protected int findFirstBegin(List list, long pos) - { - int matched = list.size(); - int end = matched - 1; - int start = (end < 0 || list.get(end).begin < pos ? matched : 0); - while (start <= end) - { - int mid = (start + end) / 2; - if (list.get(mid).begin >= pos) - { - matched = mid; - end = mid - 1; - } - else - { - start = mid + 1; - } - } - return matched; - } - - /** - * Locate the feature end in a standard ArrayList that is after or at this - * position. - * - */ - - @Override - protected int findFirstEnd(List list, long pos) - { - int matched = list.size(); - int end = matched - 1; - int start = 0; - while (start <= end) - { - int mid = (start + end) / 2; - if (list.get(mid).end >= pos) - { - matched = mid; - end = mid - 1; - } - else - { - start = mid + 1; - } - } - return matched; - } - - /** - * Returns a (possibly empty) list of features whose extent overlaps the given - * range. The returned list is ordered as follows: - * - * (1) ContactFeature starts - * - * (2) ContactFeature ends (that are not also starts) - * - * (3) noncontact SequenceFeatures, in reverse start order - * - * (This last reverse order is for efficiency in processing only.) - * - * - * - * @param start - * start position of overlap range (inclusive) - * @param end - * end position of overlap range (inclusive) - * - * @param result - * optional result list; for highest efficiency, provide this - * parameter - * @return result same as result parameter, or a new ArrayList if that is null - */ - - @Override - public List findOverlappingFeatures(long start, long end, - List result) - { - if (result == null) - { - result = new ArrayList<>(); - } - if (contactFeatureStarts != null) - { - if (start == end) - { - getContactPointStarts(contactFeatureStarts, start, result); - getContactPointEnds(contactFeatureEnds, end, result); - } - else - { - findContactFeatures(start, end, result); - } - } - if (features.size() > 0) - { - features.findOverlaps(start, end, result); - } - return result; - } - - /** - * Adds to the result list any contact features having end (second contact - * point), but not start (first contact point), in the query from-to range - * - * @param from - * @param to - * @param result - */ - - private void getContactEndOverlaps(long from, long to, - List result) - { - // find the first contact feature (if any) - // with end point not before the target range - - for (int i = findFirstEnd(contactFeatureEnds, - from), n = contactFeatureEnds.size(); i < n; i++) - { - SequenceFeature sf = contactFeatureEnds.get(i); - if (sf.begin >= from && sf.begin <= to) - { - // this feature's first contact position lies in the search range - // so we don't include it in results a second time - continue; - } - - if (sf.end > 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); - } - } - - /** - * Binary search for contact start or end matching a specific position. This - * efficient search was designed specifically for rapid return for the - * OverviewPanel. It's implementation sped processing of that panel by 2300%. - * - * @param l - * @param pos - * @param result - * @param isStart - * - * @author Bob Hanson 2019.07.30 - */ - private void getContactPointStarts(List l, long pos, - List result) - { - int low = 0; - int high = l.size() - 1; - while (low <= high) - { - int mid = (low + high) >>> 1; - SequenceFeature f = l.get(mid); - switch (Long.signum(f.begin - pos)) - { - case -1: - low = mid + 1; - continue; - case 1: - high = mid - 1; - continue; - case 0: - int m = mid; - result.add(f); - // could be "5" in 12345556788 ? - while (++mid <= high && (f = l.get(mid)) != null && f.begin == pos) - { - result.add(f); - } - while (--m >= low && (f = l.get(m)) != null && f.begin == pos) - { - result.add(f); - } - return; - } - } - } - - private void getContactPointEnds(List l, long pos, - List result) - { - int low = 0; - int high = l.size() - 1; - while (low <= high) - { - int mid = (low + high) >>> 1; - SequenceFeature f = l.get(mid); - switch (Long.signum(f.end - pos)) - { - case -1: - low = mid + 1; - continue; - case 1: - high = mid - 1; - continue; - case 0: - int m = mid; - if (f.begin != f.end) - { - result.add(f); - } - // could be "5" in 12345556788 ? - while (++mid <= high && (f = l.get(mid)) != null - && f.end == pos) - { - if (f.begin != f.end) - { - result.add(f); - } - } - while (--m >= low && (f = l.get(m)) != null - && f.end == pos) - { - if (f.begin != f.end) - { - result.add(f); - } - } - return; - } - } - } - - /** - * Adds contact features whose start position lies in the from-to range to the - * result list - * - * @param from - * @param to - * @param result - */ - - private void getContactStartOverlaps(long from, long to, - List result) - { - for (int i = findFirstBegin(contactFeatureStarts, - from), n = contactFeatureStarts.size(); i < n; i++) - { - SequenceFeature sf = contactFeatureStarts.get(i); - if (sf.begin > to) - { - break; - } - result.add(sf); - } - } -} diff --git a/src/jalview/datamodel/features/SequenceFeatures.java b/src/jalview/datamodel/features/SequenceFeatures.java index 6c83013..e747d5f 100644 --- a/src/jalview/datamodel/features/SequenceFeatures.java +++ b/src/jalview/datamodel/features/SequenceFeatures.java @@ -23,10 +23,10 @@ package jalview.datamodel.features; import jalview.datamodel.SequenceFeature; import jalview.io.gff.SequenceOntologyFactory; import jalview.io.gff.SequenceOntologyI; -import jalview.util.Platform; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -46,12 +46,11 @@ import intervalstore.api.IntervalI; */ public class SequenceFeatures implements SequenceFeaturesI { - /* * map from feature type to structured store of features for that type * null types are permitted (but not a good idea!) */ - private Map featureStore; + private Map featureStore; /** * Constructor @@ -63,7 +62,7 @@ public class SequenceFeatures implements SequenceFeaturesI * ? wrap as a synchronized map for add and delete operations */ // featureStore = Collections - // .synchronizedSortedMap(new TreeMap()); + // .synchronizedSortedMap(new TreeMap()); featureStore = new TreeMap<>(); } @@ -97,19 +96,11 @@ public class SequenceFeatures implements SequenceFeaturesI if (featureStore.get(type) == null) { - featureStore.put(type, newFeatureStore()); + featureStore.put(type, new FeatureStore()); } return featureStore.get(type).addFeature(sf); } - private FeatureStoreI newFeatureStore() - { - return (// - Platform.isJS()// - ? new FeatureStoreJS() - : new FeatureStoreImpl()); - } - /** * {@inheritDoc} */ @@ -118,13 +109,9 @@ public class SequenceFeatures implements SequenceFeaturesI String... type) { List result = new ArrayList<>(); - for (FeatureStoreI featureSet : varargToTypes(type)) + for (FeatureStore featureSet : varargToTypes(type)) { - // System.err.println("SF findFeature " + System.currentTimeMillis() - // + " " + from + " " + to + " " - // + featureSet.getPositionalFeatures().get(0).type); - // - result.addAll(featureSet.findOverlappingFeatures(from, to, null)); + featureSet.findOverlappingFeatures(from, to, result); } return result; } @@ -176,7 +163,7 @@ public class SequenceFeatures implements SequenceFeaturesI { int result = 0; - for (FeatureStoreI featureSet : varargToTypes(type)) + for (FeatureStore featureSet : varargToTypes(type)) { result += featureSet.getFeatureCount(positional); } @@ -191,7 +178,7 @@ public class SequenceFeatures implements SequenceFeaturesI { int result = 0; - for (FeatureStoreI featureSet : varargToTypes(type)) + for (FeatureStore featureSet : varargToTypes(type)) { result += featureSet.getTotalFeatureLength(); } @@ -206,7 +193,7 @@ public class SequenceFeatures implements SequenceFeaturesI { List result = new ArrayList<>(); - for (FeatureStoreI featureSet : varargToTypes(type)) + for (FeatureStore featureSet : varargToTypes(type)) { featureSet.getPositionalFeatures(result); } @@ -220,7 +207,7 @@ public class SequenceFeatures implements SequenceFeaturesI * @param type * @return */ - protected Iterable varargToTypes(String... type) + protected Iterable varargToTypes(String... type) { if (type == null || type.length == 0) { @@ -230,9 +217,9 @@ public class SequenceFeatures implements SequenceFeaturesI return featureStore.values(); } - List types = new ArrayList<>(); + List types = new ArrayList<>(); List args = Arrays.asList(type); - for (Entry featureType : featureStore.entrySet()) + for (Entry featureType : featureStore.entrySet()) { if (args.contains(featureType.getKey())) { @@ -250,7 +237,7 @@ public class SequenceFeatures implements SequenceFeaturesI { List result = new ArrayList<>(); - for (FeatureStoreI featureSet : varargToTypes(type)) + for (FeatureStore featureSet : varargToTypes(type)) { featureSet.getContactFeatures(result); } @@ -265,7 +252,7 @@ public class SequenceFeatures implements SequenceFeaturesI { List result = new ArrayList<>(); - for (FeatureStoreI featureSet : varargToTypes(type)) + for (FeatureStore featureSet : varargToTypes(type)) { featureSet.getNonPositionalFeatures(result); } @@ -278,7 +265,7 @@ public class SequenceFeatures implements SequenceFeaturesI @Override public boolean delete(SequenceFeature sf) { - for (FeatureStoreI featureSet : featureStore.values()) + for (FeatureStore featureSet : featureStore.values()) { if (featureSet.delete(sf)) { @@ -294,7 +281,7 @@ public class SequenceFeatures implements SequenceFeaturesI @Override public boolean hasFeatures() { - for (FeatureStoreI featureSet : featureStore.values()) + for (FeatureStore featureSet : featureStore.values()) { if (!featureSet.isEmpty()) { @@ -313,7 +300,7 @@ public class SequenceFeatures implements SequenceFeaturesI { Set groups = new HashSet<>(); - for (FeatureStoreI featureSet : varargToTypes(type)) + for (FeatureStore featureSet : varargToTypes(type)) { groups.addAll(featureSet.getFeatureGroups(positionalFeatures)); } @@ -330,7 +317,7 @@ public class SequenceFeatures implements SequenceFeaturesI { Set result = new HashSet<>(); - for (Entry featureType : featureStore.entrySet()) + for (Entry featureType : featureStore.entrySet()) { Set featureGroups = featureType.getValue() .getFeatureGroups(positionalFeatures); @@ -357,7 +344,7 @@ public class SequenceFeatures implements SequenceFeaturesI public Set getFeatureTypes(String... soTerm) { Set types = new HashSet<>(); - for (Entry entry : featureStore.entrySet()) + for (Entry entry : featureStore.entrySet()) { String type = entry.getKey(); if (!entry.getValue().isEmpty() && isOntologyTerm(type, soTerm)) @@ -427,7 +414,9 @@ public class SequenceFeatures implements SequenceFeaturesI public static void sortFeatures(List features, final boolean forwardStrand) { - IntervalI.sortIntervals(features, forwardStrand); + Collections.sort(features, + forwardStrand ? IntervalI.COMPARE_BEGIN_ASC + : IntervalI.COMPARE_END_DESC); } /** @@ -446,7 +435,7 @@ public class SequenceFeatures implements SequenceFeaturesI String group, String... type) { List result = new ArrayList<>(); - for (FeatureStoreI featureSet : varargToTypes(type)) + for (FeatureStore featureSet : varargToTypes(type)) { if (featureSet.getFeatureGroups(positional).contains(group)) { @@ -463,7 +452,7 @@ public class SequenceFeatures implements SequenceFeaturesI public boolean shiftFeatures(int fromPosition, int shiftBy) { boolean modified = false; - for (FeatureStoreI fs : featureStore.values()) + for (FeatureStore fs : featureStore.values()) { modified |= fs.shiftFeatures(fromPosition, shiftBy); } @@ -479,58 +468,14 @@ public class SequenceFeatures implements SequenceFeaturesI featureStore.clear(); } - /** - * Simplified find for features associated with a given position. - * - * JavaScript set to not use IntervalI, but easily testable by setting false - * to true in javadoc - * - * FeatureRenderer has checked already that featureStore does contain type. - * - * @author Bob Hanson 2019.07.30 - */ @Override public List findFeatures(int pos, String type, List list) { - FeatureStoreI fs = featureStore.get(type); + FeatureStore fs = featureStore.get(type); return fs.findOverlappingFeatures(pos, pos, list); } - // Chrome; developer console closed - - // BH 2019.08.01 useIntervalStore true, redraw false: - // Platform: timer mark 13.848 0.367 overviewrender 16000 pixels row:14 - // Platform: timer mark 15.391 0.39 overviewrender 16000 pixels row:14 - // Platform: timer mark 16.498 0.39 overviewrender 16000 pixels row:14 - // Platform: timer mark 17.596 0.401 overviewrender 16000 pixels row:14 - // Platform: timer mark 18.738 0.363 overviewrender 16000 pixels row:14 - // Platform: timer mark 19.659 0.358 overviewrender 16000 pixels row:14 - // Platform: timer mark 20.737 0.359 overviewrender 16000 pixels row:14 - // Platform: timer mark 21.797 0.391 overviewrender 16000 pixels row:14 - // Platform: timer mark 22.851 0.361 overviewrender 16000 pixels row:14 - // Platform: timer mark 24.019 0.395 overviewrender 16000 pixels row:14 - - // BH 2019.08.01 useIntervalStore false, redraw false: - // Platform: timer mark 19.011 0.181 overviewrender 16000 pixels row:14 - // Platform: timer mark 20.311 0.183 overviewrender 16000 pixels row:14 - // Platform: timer mark 21.368 0.175 overviewrender 16000 pixels row:14 - // Platform: timer mark 22.347 0.178 overviewrender 16000 pixels row:14 - // Platform: timer mark 23.605 0.216 overviewrender 16000 pixels row:14 - // Platform: timer mark 24.836 0.191 overviewrender 16000 pixels row:14 - // Platform: timer mark 26.016 0.181 overviewrender 16000 pixels row:14 - // Platform: timer mark 27.278 0.178 overviewrender 16000 pixels row:14 - // Platform: timer mark 28.158 0.181 overviewrender 16000 pixels row:14 - // Platform: timer mark 29.227 0.196 overviewrender 16000 pixels row:14 - // Platform: timer mark 30.1 0.171 overviewrender 16000 pixels row:14 - // Platform: timer mark 31.684 0.196 overviewrender 16000 pixels row:14 - // Platform: timer mark 32.779 0.18 overviewrender 16000 pixels row:14 - // Platform: timer mark 52.355 0.185 overviewrender 16000 pixels row:14 - // Platform: timer mark 53.829 0.186 overviewrender 16000 pixels row:14 - - /** - * @author Bob Hanson 2019.08.01 - */ @Override public boolean hasFeatures(String type) { diff --git a/src/jalview/datamodel/features/SequenceFeaturesI.java b/src/jalview/datamodel/features/SequenceFeaturesI.java index deed751..fe5e927 100644 --- a/src/jalview/datamodel/features/SequenceFeaturesI.java +++ b/src/jalview/datamodel/features/SequenceFeaturesI.java @@ -230,21 +230,20 @@ public interface SequenceFeaturesI void deleteAll(); /** - * Point-specific parameter return for JavaScript + * Answers a (possibly empty) list of features of the specified type that + * overlap the specified column position. If parameter {@code result} is not + * null, features are appended to it and the (possibly extended) list is + * returned. * * @param pos * @param type * @param result - * @return result (JavaScript) or new ArrayList (Java -- see FeatureRender) - * @author Bob Hanson 2019.07.30 + * @return */ List findFeatures(int pos, String type, List result); /** - * @author Bob Hanson 2019.08.01 - * - * @param type - * @return true if this type is in featureStore + * Answers true if there are any features of the given type, else false */ boolean hasFeatures(String type); } diff --git a/src/jalview/ext/ensembl/EnsemblFeatures.java b/src/jalview/ext/ensembl/EnsemblFeatures.java index 2dd6ebb..e4c4365 100644 --- a/src/jalview/ext/ensembl/EnsemblFeatures.java +++ b/src/jalview/ext/ensembl/EnsemblFeatures.java @@ -27,9 +27,7 @@ import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; import jalview.io.gff.SequenceOntologyI; import jalview.util.JSONUtils; -import jalview.util.Platform; -import java.io.BufferedReader; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; @@ -95,10 +93,7 @@ class EnsemblFeatures extends EnsemblRestClient List queries = new ArrayList<>(); queries.add(query); SequenceI seq = parseFeaturesJson(queries); - if (seq == null) - return null; return new Alignment(new SequenceI[] { seq }); - } /** diff --git a/src/jalview/renderer/OverviewRenderer.java b/src/jalview/renderer/OverviewRenderer.java index c3350a5..873fdac 100644 --- a/src/jalview/renderer/OverviewRenderer.java +++ b/src/jalview/renderer/OverviewRenderer.java @@ -254,7 +254,7 @@ public class OverviewRenderer WritableRaster raster = miniMe.getRaster(); DataBufferInt db = (DataBufferInt) raster.getDataBuffer(); pixels = db.getBankData()[0]; - bscol = cols.getOverviewBitSet(); + bscol = cols.getShownBitSet(); if (skippingColumns) { columnsToShow = calcColumnsToShow(); diff --git a/src/jalview/renderer/OverviewResColourFinder.java b/src/jalview/renderer/OverviewResColourFinder.java index a98f3b3..e0d6696 100644 --- a/src/jalview/renderer/OverviewResColourFinder.java +++ b/src/jalview/renderer/OverviewResColourFinder.java @@ -29,6 +29,13 @@ import java.awt.Color; public class OverviewResColourFinder extends ResidueColourFinder { + public static final Color OVERVIEW_DEFAULT_GAP = Color.lightGray; + + public static final Color OVERVIEW_DEFAULT_LEGACY_GAP = Color.white; + + public static final Color OVERVIEW_DEFAULT_HIDDEN = Color.darkGray + .darker(); + final int GAP_COLOUR; // default colour to use at gaps final int RESIDUE_COLOUR; // default colour to use at residues @@ -37,13 +44,6 @@ public class OverviewResColourFinder extends ResidueColourFinder boolean useLegacy = false; - public static final Color OVERVIEW_DEFAULT_GAP = Color.lightGray; - - public static final Color OVERVIEW_DEFAULT_LEGACY_GAP = Color.white; - - public static final Color OVERVIEW_DEFAULT_HIDDEN = Color.darkGray - .darker(); - /** * Constructor without colour settings (used by applet) */ @@ -130,9 +130,9 @@ public class OverviewResColourFinder extends ResidueColourFinder // if there's a FeatureColourFinder we might override the residue colour // here with feature colouring - return seq.setColor(i, - finder == null || finder.noFeaturesDisplayed() ? col - : finder.findFeatureColourInt(col, seq, i)); + col = finder == null || finder.noFeaturesDisplayed() ? col + : finder.findFeatureColourInt(col, seq, i); + return seq.setColor(i, col); } /** diff --git a/src/jalview/renderer/seqfeatures/FeatureColourFinder.java b/src/jalview/renderer/seqfeatures/FeatureColourFinder.java index 8da880a..a2ce35b 100644 --- a/src/jalview/renderer/seqfeatures/FeatureColourFinder.java +++ b/src/jalview/renderer/seqfeatures/FeatureColourFinder.java @@ -120,11 +120,6 @@ public class FeatureColourFinder public int findFeatureColourInt(int defaultColour, SequenceI seq, int column) { - // if (noFeaturesDisplayed()) - // { - // return defaultColour; - // } - Graphics g = null; /* diff --git a/src/jalview/renderer/seqfeatures/FeatureRenderer.java b/src/jalview/renderer/seqfeatures/FeatureRenderer.java index 9988076..c1bccb8 100644 --- a/src/jalview/renderer/seqfeatures/FeatureRenderer.java +++ b/src/jalview/renderer/seqfeatures/FeatureRenderer.java @@ -42,6 +42,13 @@ public class FeatureRenderer extends FeatureRendererModel private static final AlphaComposite NO_TRANSPARENCY = AlphaComposite .getInstance(AlphaComposite.SRC_OVER, 1.0f); + /* + * persistent list used by JalviewJS; not threadsafe for Java + */ + private List overlaps = (Platform.isJS() + ? new ArrayList<>() + : null); + /** * Constructor given a viewport * @@ -223,13 +230,6 @@ public class FeatureRenderer extends FeatureRendererModel @Override public Color findFeatureColour(SequenceI seq, int column, Graphics g) { - // BH 2019.08.01 - // this is already checked in FeatureColorFinder - // if (!av.isShowSequenceFeatures()) - // { - // return null; - // } - // column is 'base 1' but getCharAt is an array index (ie from 0) if (Comparison.isGap(seq.getCharAt(column - 1))) { @@ -266,8 +266,7 @@ public class FeatureRenderer extends FeatureRendererModel * applies), or null if no feature is drawn in the range given. * * @param g - * the graphics context to draw on (may be null only if t == 1 from - * colourOnly==true) + * the graphics context to draw on (null if no transparency applies) * @param seq * @param start * start column @@ -284,7 +283,6 @@ public class FeatureRenderer extends FeatureRendererModel final SequenceI seq, int start, int end, int y1, boolean colourOnly) { - // from SeqCanvas and OverviewRender /* * if columns are all gapped, or sequence has no features, nothing to do */ @@ -300,8 +298,7 @@ public class FeatureRenderer extends FeatureRendererModel updateFeatures(); - if (transparency != 1f) // g cannot be null here if trans == 1f - BH // && g - // != null) + if (transparency != 1f) { ((Graphics2D) g).setComposite( AlphaComposite.getInstance(AlphaComposite.SRC_OVER, @@ -439,10 +436,6 @@ public class FeatureRenderer extends FeatureRendererModel findAllFeatures(); } - private List overlaps = (Platform.isJS() - ? new ArrayList<>() - : null); - /** * Returns the sequence feature colour rendered at the given column position, * or null if none found. The feature of highest render order (i.e. on top) is @@ -454,12 +447,6 @@ public class FeatureRenderer extends FeatureRendererModel * colour for features enclosing a gapped column. Check for gap before calling * if different behaviour is wanted. * - * BH 2019.07.30 - * - * Adds a result ArrayList to parameters in order to avoid an unnecessary - * construction of that for every pixel checked. - * - * * @param seq * @param column * (1..) @@ -484,6 +471,10 @@ public class FeatureRenderer extends FeatureRendererModel continue; } + /* + * field overlaps is used by JalviewJS to avoid object creation; + * not thread-safe for Java (Javascript is single-threaded) + */ if (overlaps != null) { overlaps.clear(); diff --git a/test/intervalstore/nonc/IntervalStoreTest.java b/test/intervalstore/nonc/IntervalStoreTest.java new file mode 100644 index 0000000..7b96e39 --- /dev/null +++ b/test/intervalstore/nonc/IntervalStoreTest.java @@ -0,0 +1,583 @@ +/* +BSD 3-Clause License + +Copyright (c) 2018, Mungo Carstairs +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +package intervalstore.nonc; + +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 java.util.ArrayList; +import java.util.List; + +import org.testng.annotations.Test; + +public class IntervalStoreTest +{ + @Test(groups = "Functional") + public void testFindOverlaps_nonNested() + { + IntervalStore store = new IntervalStore<>(); + SimpleFeature sf1 = add(store, 10, 20); + // same range different description + SimpleFeature sf2 = new SimpleFeature(10, 20, "desc"); + store.add(sf2); + SimpleFeature sf3 = add(store, 15, 25); + SimpleFeature sf4 = add(store, 20, 35); + + assertEquals(store.size(), 4); + List overlaps = store.findOverlaps(1, 9); + assertTrue(overlaps.isEmpty()); + + overlaps = store.findOverlaps(8, 10); + assertEquals(overlaps.size(), 2); + assertTrue(overlaps.contains(sf1)); + assertTrue(overlaps.contains(sf2)); + + overlaps = store.findOverlaps(12, 16); + assertEquals(overlaps.size(), 3); + assertTrue(overlaps.contains(sf1)); + assertTrue(overlaps.contains(sf2)); + assertTrue(overlaps.contains(sf3)); + + overlaps = store.findOverlaps(33, 33); + assertEquals(overlaps.size(), 1); + assertTrue(overlaps.contains(sf4)); + + /* + * ensure edge cases are covered + */ + overlaps = store.findOverlaps(35, 40); + assertEquals(overlaps.size(), 1); + assertTrue(overlaps.contains(sf4)); + assertTrue(store.findOverlaps(36, 100).isEmpty()); + assertTrue(store.findOverlaps(1, 9).isEmpty()); + } + + @Test(groups = "Functional") + public void testFindOverlaps_nested() + { + IntervalStore store = new IntervalStore<>(); + SimpleFeature sf1 = add(store, 10, 50); + SimpleFeature sf2 = add(store, 10, 40); + SimpleFeature sf3 = add(store, 20, 30); + // feature at same location but different description + SimpleFeature sf4 = new SimpleFeature(20, 30, "different desc"); + store.add(sf4); + SimpleFeature sf5 = add(store, 35, 36); + + List overlaps = store.findOverlaps(1, 9); + assertTrue(overlaps.isEmpty()); + + overlaps = store.findOverlaps(10, 15); + assertEquals(overlaps.size(), 2); + assertTrue(overlaps.contains(sf1)); + assertTrue(overlaps.contains(sf2)); + + overlaps = store.findOverlaps(45, 60); + assertEquals(overlaps.size(), 1); + assertTrue(overlaps.contains(sf1)); + + overlaps = store.findOverlaps(32, 38); + assertEquals(overlaps.size(), 3); + assertTrue(overlaps.contains(sf1)); + assertTrue(overlaps.contains(sf2)); + assertTrue(overlaps.contains(sf5)); + + overlaps = store.findOverlaps(15, 25); + assertEquals(overlaps.size(), 4); + assertTrue(overlaps.contains(sf1)); + assertTrue(overlaps.contains(sf2)); + assertTrue(overlaps.contains(sf3)); + assertTrue(overlaps.contains(sf4)); + } + + @Test(groups = "Functional") + public void testFindOverlaps_mixed() + { + IntervalStore store = new IntervalStore<>(); + SimpleFeature sf1 = add(store, 10, 50); + SimpleFeature sf2 = add(store, 1, 15); + SimpleFeature sf3 = add(store, 20, 30); + SimpleFeature sf4 = add(store, 40, 100); + SimpleFeature sf5 = add(store, 60, 100); + SimpleFeature sf6 = add(store, 70, 70); + + List overlaps = store.findOverlaps(200, 200); + assertTrue(overlaps.isEmpty()); + + overlaps = store.findOverlaps(1, 9); + assertEquals(overlaps.size(), 1); + assertTrue(overlaps.contains(sf2)); + + overlaps = store.findOverlaps(5, 18); + assertEquals(overlaps.size(), 2); + assertTrue(overlaps.contains(sf1)); + assertTrue(overlaps.contains(sf2)); + + overlaps = store.findOverlaps(30, 40); + assertEquals(overlaps.size(), 3); + assertTrue(overlaps.contains(sf1)); + assertTrue(overlaps.contains(sf3)); + assertTrue(overlaps.contains(sf4)); + + overlaps = store.findOverlaps(80, 90); + assertEquals(overlaps.size(), 2); + assertTrue(overlaps.contains(sf4)); + assertTrue(overlaps.contains(sf5)); + + overlaps = store.findOverlaps(68, 70); + assertEquals(overlaps.size(), 3); + assertTrue(overlaps.contains(sf4)); + assertTrue(overlaps.contains(sf5)); + assertTrue(overlaps.contains(sf6)); + } + + /** + * Helper method to add a feature with type "desc" + * + * @param store + * @param from + * @param to + * @return + */ + SimpleFeature add(IntervalStore store, int from, + int to) + { + SimpleFeature sf1 = new SimpleFeature(from, to, "desc"); + store.add(sf1); + return sf1; + } + + @Test(groups = "Functional") + public void testRemove() + { + IntervalStore store = new IntervalStore<>(); + SimpleFeature sf1 = add(store, 10, 20); + assertTrue(store.contains(sf1)); + + try + { + store.remove("what is this?"); + } catch (ClassCastException e) + { + // expected; + } + assertFalse(store.remove(null)); + + /* + * simple deletion + */ + assertTrue(store.remove(sf1)); + assertTrue(store.isEmpty()); + + SimpleFeature sf2 = add(store, 0, 0); + SimpleFeature sf2a = add(store, 30, 40); + assertTrue(store.contains(sf2)); + assertFalse(store.remove(sf1)); + assertTrue(store.remove(sf2)); + assertTrue(store.remove(sf2a)); + assertTrue(store.isEmpty()); + + /* + * nested feature deletion + */ + SimpleFeature sf4 = add(store, 20, 30); + SimpleFeature sf5 = add(store, 22, 26); // to NCList + SimpleFeature sf6 = add(store, 23, 24); // child of sf5 + SimpleFeature sf7 = add(store, 25, 25); // sibling of sf6 + SimpleFeature sf8 = add(store, 24, 24); // child of sf6 + SimpleFeature sf9 = add(store, 23, 23); // child of sf6 + assertEquals(store.size(), 6); + + // delete a node with children - they take its place + assertTrue(store.remove(sf6)); // sf8, sf9 should become children of sf5 + assertEquals(store.size(), 5); + assertFalse(store.contains(sf6)); + + // delete a node with no children + assertTrue(store.remove(sf7)); + assertEquals(store.size(), 4); + assertFalse(store.contains(sf7)); + + // delete root of NCList + assertTrue(store.remove(sf5)); + assertEquals(store.size(), 3); + assertFalse(store.contains(sf5)); + + // continue the killing fields + assertTrue(store.remove(sf4)); + assertEquals(store.size(), 2); + assertFalse(store.contains(sf4)); + + assertTrue(store.remove(sf9)); + assertEquals(store.size(), 1); + assertFalse(store.contains(sf9)); + + assertTrue(store.remove(sf8)); + assertTrue(store.isEmpty()); + } + + /** + * A helper method to test whether a list contains a specific object (by + * object identity, not equality test as used by List.contains()) + * + * @param list + * @param o + * @return + */ + private static boolean containsObject(List list, + Object o) + { + for (Object i : list) + { + if (i == o) + { + return true; + } + } + return false; + } + + @Test(groups = "Functional") + public void testAdd() + { + IntervalStore store = new IntervalStore<>(); + + assertFalse(store.add(null)); + + SimpleFeature sf1 = new SimpleFeature(10, 20, "Cath"); + SimpleFeature sf2 = new SimpleFeature(10, 20, "Cath"); + + assertTrue(store.add(sf1)); + assertEquals(store.size(), 1); + + /* + * contains should return true for the same or an identical feature + */ + assertTrue(store.contains(sf1)); + assertTrue(store.contains(sf2)); + + /* + * duplicates are accepted + */ + assertTrue(store.add(sf2)); + assertEquals(store.size(), 2); + + SimpleFeature sf3 = new SimpleFeature(0, 0, "Cath"); + assertTrue(store.add(sf3)); + assertEquals(store.size(), 3); + } + + @Test(groups = "Functional") + public void testAdd_noDuplicates() + { + IntervalStore store = new IntervalStore<>(); + + SimpleFeature sf1 = new SimpleFeature(10, 20, "Cath"); + SimpleFeature sf2 = new SimpleFeature(10, 20, "Cath"); + assertTrue(sf1.equals(sf2)); + assertTrue(store.add(sf1)); + assertEquals(store.size(), 1); + assertFalse(store.add(sf2, false)); + assertEquals(store.size(), 1); + assertTrue(store.contains(sf1)); + assertTrue(store.contains(sf2)); // because sf1.equals(sf2) ! + } + + @Test(groups = "Functional") + public void testIsEmpty() + { + IntervalStore store = new IntervalStore<>(); + assertTrue(store.isEmpty()); + assertEquals(store.size(), 0); + + /* + * non-nested feature + */ + SimpleFeature sf1 = new SimpleFeature(10, 20, "Cath"); + store.add(sf1); + assertFalse(store.isEmpty()); + assertEquals(store.size(), 1); + store.remove(sf1); + assertTrue(store.isEmpty()); + assertEquals(store.size(), 0); + + sf1 = new SimpleFeature(0, 0, "Cath"); + store.add(sf1); + assertFalse(store.isEmpty()); + assertEquals(store.size(), 1); + store.remove(sf1); + assertTrue(store.isEmpty()); + assertEquals(store.size(), 0); + + /* + * sf2, sf3 added as nested features + */ + sf1 = new SimpleFeature(19, 49, "Cath"); + SimpleFeature sf2 = new SimpleFeature(20, 40, "Cath"); + SimpleFeature sf3 = new SimpleFeature(25, 35, "Cath"); + store.add(sf1); + assertEquals(store.size(), 1); + store.add(sf2); + assertEquals(store.size(), 2); + store.add(sf3); + assertEquals(store.size(), 3); + assertTrue(store.remove(sf1)); + assertEquals(store.size(), 2); + + assertFalse(store.isEmpty()); + assertTrue(store.remove(sf2)); + assertEquals(store.size(), 1); + assertFalse(store.isEmpty()); + assertTrue(store.remove(sf3)); + assertEquals(store.size(), 0); + assertTrue(store.isEmpty()); // all gone + } + + @Test(groups = "Functional") + public void testRemove_readd() + { + /* + * add a feature and a nested feature + */ + IntervalStore store = new IntervalStore<>(); + SimpleFeature sf1 = add(store, 10, 20); + // sf2 is nested in sf1 so will be stored in nestedFeatures + SimpleFeature sf2 = add(store, 12, 14); + assertEquals(store.size(), 2); + assertTrue(store.contains(sf1)); + assertTrue(store.contains(sf2)); + + /* + * delete the first feature + */ + assertTrue(store.remove(sf1)); + assertFalse(store.contains(sf1)); + assertTrue(store.contains(sf2)); + + /* + * re-add the 'nested' feature; it is now duplicated + */ + store.add(sf2); + assertEquals(store.size(), 2); + assertTrue(store.contains(sf2)); + } + + @Test(groups = "Functional") + public void testContains() + { + IntervalStore store = new IntervalStore<>(); + SimpleFeature sf1 = new SimpleFeature(10, 20, "Cath"); + SimpleFeature sf2 = new SimpleFeature(10, 20, "Pfam"); + + store.add(sf1); + assertTrue(store.contains(sf1)); + assertTrue(store.contains(new SimpleFeature(sf1))); // identical feature + assertFalse(store.contains(sf2)); // different description + + /* + * add a nested feature + */ + SimpleFeature sf3 = new SimpleFeature(12, 16, "Cath"); + store.add(sf3); + assertTrue(store.contains(sf3)); + assertTrue(store.contains(new SimpleFeature(sf3))); + + /* + * delete the outer (enclosing, non-nested) feature + */ + store.remove(sf1); + assertFalse(store.contains(sf1)); + assertTrue(store.contains(sf3)); + + assertFalse(store.contains(null)); + try + { + assertFalse(store.contains("junk")); + } catch (ClassCastException e) + { + // expected; + } + } + + @Test(groups = "Functional") + public void testFindOverlaps_resultsArg_mixed() + { + IntervalStore store = new IntervalStore<>(); + SimpleFeature sf1 = add(store, 10, 50); + SimpleFeature sf2 = add(store, 1, 15); + SimpleFeature sf3 = add(store, 20, 30); + SimpleFeature sf4 = add(store, 40, 100); + SimpleFeature sf5 = add(store, 60, 100); + SimpleFeature sf6 = add(store, 70, 70); + + List overlaps = new ArrayList<>(); + List overlaps2 = store.findOverlaps(200, 200, overlaps); + assertSame(overlaps, overlaps2); + assertTrue(overlaps.isEmpty()); + + overlaps.clear(); + store.findOverlaps(1, 9, overlaps); + assertEquals(overlaps.size(), 1); + assertTrue(overlaps.contains(sf2)); + + overlaps.clear(); + store.findOverlaps(5, 18, overlaps); + assertEquals(overlaps.size(), 2); + assertTrue(overlaps.contains(sf1)); + assertTrue(overlaps.contains(sf2)); + + overlaps.clear(); + store.findOverlaps(30, 40, overlaps); + assertEquals(overlaps.size(), 3); + assertTrue(overlaps.contains(sf1)); + assertTrue(overlaps.contains(sf3)); + assertTrue(overlaps.contains(sf4)); + + overlaps.clear(); + store.findOverlaps(80, 90, overlaps); + assertEquals(overlaps.size(), 2); + assertTrue(overlaps.contains(sf4)); + assertTrue(overlaps.contains(sf5)); + + overlaps.clear(); + store.findOverlaps(68, 70, overlaps); + assertEquals(overlaps.size(), 3); + assertTrue(overlaps.contains(sf4)); + assertTrue(overlaps.contains(sf5)); + assertTrue(overlaps.contains(sf6)); + + /* + * and without clearing the list first + * note that sf4 is included twice, as an + * overlap of 68-70 and also of 30-40 + */ + store.findOverlaps(30, 40, overlaps); + assertEquals(overlaps.size(), 6); + assertTrue(overlaps.contains(sf1)); + assertTrue(overlaps.contains(sf3)); + assertTrue(overlaps.contains(sf4)); + assertTrue(overlaps.contains(sf5)); + assertTrue(overlaps.contains(sf6)); + assertSame(sf4, overlaps.get(0)); + assertSame(sf4, overlaps.get(4)); + } + + @Test(groups = "Functional") + public void testFindOverlaps_resultsArg_nested() + { + IntervalStore store = new IntervalStore<>(); + SimpleFeature sf1 = add(store, 10, 50); + SimpleFeature sf2 = add(store, 10, 40); + SimpleFeature sf3 = add(store, 20, 30); + // feature at same location but different description + SimpleFeature sf4 = new SimpleFeature(20, 30, "different desc"); + store.add(sf4); + SimpleFeature sf5 = add(store, 35, 36); + + List overlaps = new ArrayList<>(); + store.findOverlaps(1, 9, overlaps); + assertTrue(overlaps.isEmpty()); + + store.findOverlaps(10, 15, overlaps); + assertEquals(overlaps.size(), 2); + assertTrue(overlaps.contains(sf1)); + assertTrue(overlaps.contains(sf2)); + + overlaps.clear(); + store.findOverlaps(45, 60, overlaps); + assertEquals(overlaps.size(), 1); + assertTrue(overlaps.contains(sf1)); + + overlaps.clear(); + store.findOverlaps(32, 38, overlaps); + assertEquals(overlaps.size(), 3); + assertTrue(overlaps.contains(sf1)); + assertTrue(overlaps.contains(sf2)); + assertTrue(overlaps.contains(sf5)); + + overlaps.clear(); + store.findOverlaps(15, 25, overlaps); + assertEquals(overlaps.size(), 4); + assertTrue(overlaps.contains(sf1)); + assertTrue(overlaps.contains(sf2)); + assertTrue(overlaps.contains(sf3)); + assertTrue(overlaps.contains(sf4)); + } + + @Test(groups = "Functional") + public void testFindOverlaps_resultsArg_nonNested() + { + IntervalStore store = new IntervalStore<>(); + SimpleFeature sf1 = add(store, 10, 20); + // same range different description + SimpleFeature sf2 = new SimpleFeature(10, 20, "desc"); + store.add(sf2); + SimpleFeature sf3 = add(store, 15, 25); + SimpleFeature sf4 = add(store, 20, 35); + + assertEquals(store.size(), 4); + List overlaps = new ArrayList<>(); + store.findOverlaps(1, 9, overlaps); + assertTrue(overlaps.isEmpty()); + + store.findOverlaps(8, 10, overlaps); + assertEquals(overlaps.size(), 2); + assertTrue(overlaps.contains(sf1)); + assertTrue(overlaps.contains(sf2)); + + overlaps.clear(); + store.findOverlaps(12, 16, overlaps); + assertEquals(overlaps.size(), 3); + assertTrue(overlaps.contains(sf1)); + assertTrue(overlaps.contains(sf2)); + assertTrue(overlaps.contains(sf3)); + + overlaps.clear(); + store.findOverlaps(33, 33, overlaps); + assertEquals(overlaps.size(), 1); + assertTrue(overlaps.contains(sf4)); + + /* + * ensure edge cases are covered + */ + overlaps.clear(); + store.findOverlaps(35, 40, overlaps); + assertEquals(overlaps.size(), 1); + assertTrue(overlaps.contains(sf4)); + + overlaps.clear(); + assertTrue(store.findOverlaps(36, 100, overlaps).isEmpty()); + assertTrue(store.findOverlaps(1, 9, overlaps).isEmpty()); + } +} diff --git a/src/intervalstore/impl/SimpleFeature.java b/test/intervalstore/nonc/SimpleFeature.java similarity index 74% rename from src/intervalstore/impl/SimpleFeature.java rename to test/intervalstore/nonc/SimpleFeature.java index be1db97..d10d90a 100644 --- a/src/intervalstore/impl/SimpleFeature.java +++ b/test/intervalstore/nonc/SimpleFeature.java @@ -29,18 +29,20 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package intervalstore.impl; +package intervalstore.nonc; import intervalstore.api.IntervalI; /** * A simplified feature instance sufficient for unit test purposes */ -public class SimpleFeature extends Range +public class SimpleFeature implements IntervalI { + final private int begin; - private String description; + final private int end; + private String description; /** * Constructor @@ -51,7 +53,8 @@ public class SimpleFeature extends Range */ public SimpleFeature(int from, int to, String desc) { - super(from, to); + begin = from; + end = to; description = desc; } @@ -62,48 +65,59 @@ public class SimpleFeature extends Range */ public SimpleFeature(SimpleFeature sf1) { - this(sf1.start, sf1.end, sf1.description); + this(sf1.begin, sf1.end, sf1.description); } - public String getDescription() + @Override + public int getBegin() { - return description; + return begin; } @Override - public int hashCode() + public int getEnd() { - return start + 37 * end - + (description == null ? 0 : description.hashCode()); + return end; + } + + public String getDescription() + { + return description; } @Override - public boolean equals(Object o) + public int hashCode() { - return (o != null && o instanceof SimpleFeature - && equalsInterval((SimpleFeature) o)); + return begin + 37 * end + + (description == null ? 0 : description.hashCode()); } /** * Equals method that requires two instances to have the same description, as - * well as start and end position. Does not do a test for null + * well as start and end position. */ @Override - public boolean equalsInterval(IntervalI o) + public boolean equals(Object obj) { - // must override equalsInterval, not equals - return (o != null && start == ((SimpleFeature) o).start - && end == ((SimpleFeature) o).end) - && (description == null - ? ((SimpleFeature) o).description == null - : description.equals(((SimpleFeature) o).description)); + if (obj != null && obj instanceof SimpleFeature) + { + SimpleFeature o = (SimpleFeature) obj; + if (this.begin == o.begin && this.end == o.end) + { + if (this.description == null) + { + return o.description == null; + } + return this.description.equals(o.description); + } + } + return false; } @Override public String toString() { - return start + ":" + end + ":" + description; + return begin + ":" + end + ":" + description; } - } diff --git a/test/jalview/datamodel/features/FeatureStoreJSTest.java b/test/jalview/datamodel/features/FeatureStoreJSTest.java index ac80298..489ac38 100644 --- a/test/jalview/datamodel/features/FeatureStoreJSTest.java +++ b/test/jalview/datamodel/features/FeatureStoreJSTest.java @@ -21,7 +21,7 @@ public class FeatureStoreJSTest @Test(groups = "Functional") public void testFindFeatures_nonNested() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); fs.addFeature(new SequenceFeature("", "", 10, 20, Float.NaN, null)); // same range different description @@ -49,15 +49,15 @@ public class FeatureStoreJSTest assertEquals(overlaps.get(0).getEnd(), 35); } - private FeatureStoreI newFeatureStore() + private FeatureStore newFeatureStore() { - return new FeatureStoreJS(intervalStoreOption); + return new FeatureStore(intervalStoreOption); } @Test(groups = "Functional") public void testFindFeatures_nested() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); SequenceFeature sf1 = addFeature(fs, 10, 50); SequenceFeature sf2 = addFeature(fs, 10, 40); SequenceFeature sf3 = addFeature(fs, 20, 30); @@ -95,7 +95,7 @@ public class FeatureStoreJSTest private void testFind() { - FeatureStoreI fs1 = newFeatureStore(); + FeatureStore fs1 = newFeatureStore(); SequenceFeature sf = addFeature(fs1, 1, 3000); @@ -128,7 +128,7 @@ public class FeatureStoreJSTest { testFind(); - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); SequenceFeature sf1 = addFeature(fs, 10, 50); SequenceFeature sf2 = addFeature(fs, 1, 15); SequenceFeature sf3 = addFeature(fs, 20, 30); @@ -174,7 +174,7 @@ public class FeatureStoreJSTest * @param to * @return */ - SequenceFeature addFeature(FeatureStoreI fs, int from, int to) + SequenceFeature addFeature(FeatureStore fs, int from, int to) { SequenceFeature sf1 = new SequenceFeature("", "", from, to, Float.NaN, null); @@ -185,7 +185,7 @@ public class FeatureStoreJSTest @Test(groups = "Functional") public void testFindFeatures_contactFeatures() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); SequenceFeature sf = new SequenceFeature("disulphide bond", "bond", 10, 20, Float.NaN, null); @@ -228,7 +228,7 @@ public class FeatureStoreJSTest @Test(groups = "Functional") public void testGetPositionalFeatures() { - FeatureStoreI store = newFeatureStore(); + FeatureStore store = newFeatureStore(); SequenceFeature sf1 = new SequenceFeature("Metal", "desc", 10, 20, Float.NaN, null); store.addFeature(sf1); @@ -275,7 +275,7 @@ public class FeatureStoreJSTest @Test(groups = "Functional") public void testDelete() { - FeatureStoreI store = newFeatureStore(); + FeatureStore store = newFeatureStore(); SequenceFeature sf1 = addFeature(store, 10, 20); assertTrue(store.getPositionalFeatures().contains(sf1)); @@ -347,7 +347,7 @@ public class FeatureStoreJSTest @Test(groups = "Functional") public void testAddFeature() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); SequenceFeature sf1 = new SequenceFeature("Cath", "", 10, 20, Float.NaN, null); @@ -398,7 +398,7 @@ public class FeatureStoreJSTest @Test(groups = "Functional") public void testIsEmpty() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); assertTrue(fs.isEmpty()); assertEquals(fs.getFeatureCount(true), 0); @@ -464,7 +464,7 @@ public class FeatureStoreJSTest @Test(groups = "Functional") public void testGetFeatureGroups() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); assertTrue(fs.getFeatureGroups(true).isEmpty()); assertTrue(fs.getFeatureGroups(false).isEmpty()); @@ -531,7 +531,7 @@ public class FeatureStoreJSTest @Test(groups = "Functional") public void testGetTotalFeatureLength() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); assertEquals(fs.getTotalFeatureLength(), 0); addFeature(fs, 10, 20); // 11 @@ -607,7 +607,7 @@ public class FeatureStoreJSTest @Test(groups = "Functional") public void testGetMinimumScore_getMaximumScore() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); assertEquals(fs.getMinimumScore(true), Float.NaN); // positional assertEquals(fs.getMaximumScore(true), Float.NaN); assertEquals(fs.getMinimumScore(false), Float.NaN); // non-positional @@ -679,7 +679,7 @@ public class FeatureStoreJSTest @Test(groups = "Functional") public void testListContains() { - FeatureStoreI featureStore = newFeatureStore(); + FeatureStore featureStore = newFeatureStore(); assertFalse(featureStore.listContains(null, null)); List features = new ArrayList<>(); assertFalse(featureStore.listContains(features, null)); @@ -703,7 +703,7 @@ public class FeatureStoreJSTest @Test(groups = "Functional") public void testGetFeaturesForGroup() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); /* * with no features @@ -761,7 +761,7 @@ public class FeatureStoreJSTest @Test(groups = "Functional") public void testShiftFeatures() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); assertFalse(fs.shiftFeatures(0, 1)); // nothing to do SequenceFeature sf1 = new SequenceFeature("Cath", "", 2, 5, 0f, null); @@ -848,7 +848,7 @@ public class FeatureStoreJSTest /* * add a feature and a nested feature */ - FeatureStoreI store = newFeatureStore(); + FeatureStore store = newFeatureStore(); SequenceFeature sf1 = addFeature(store, 10, 20); // sf2 is nested in sf1 so will be stored in nestedFeatures SequenceFeature sf2 = addFeature(store, 12, 14); @@ -879,7 +879,7 @@ public class FeatureStoreJSTest @Test(groups = "Functional") public void testContains() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); SequenceFeature sf1 = new SequenceFeature("Cath", "", 10, 20, Float.NaN, "group1"); SequenceFeature sf2 = new SequenceFeature("Cath", "", 10, 20, diff --git a/test/jalview/datamodel/features/FeatureStoreJavaTest.java b/test/jalview/datamodel/features/FeatureStoreJavaTest.java index 5d2ca3d..efcef68 100644 --- a/test/jalview/datamodel/features/FeatureStoreJavaTest.java +++ b/test/jalview/datamodel/features/FeatureStoreJavaTest.java @@ -15,18 +15,15 @@ import org.testng.annotations.Test; public class FeatureStoreJavaTest { - - private int intervalStoreOption = FeatureStore.intervalStoreJavaOption; - - private FeatureStoreI newFeatureStore() + private FeatureStore newFeatureStore() { - return new FeatureStoreImpl(intervalStoreOption); + return new FeatureStore(); } @Test(groups = "Functional") public void testFindFeatures_nonNested() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); fs.addFeature(new SequenceFeature("", "", 10, 20, Float.NaN, null)); // same range different description @@ -56,7 +53,7 @@ public class FeatureStoreJavaTest @Test(groups = "Functional") public void testFindFeatures_nested() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); SequenceFeature sf1 = addFeature(fs, 10, 50); SequenceFeature sf2 = addFeature(fs, 10, 40); SequenceFeature sf3 = addFeature(fs, 20, 30); @@ -95,7 +92,7 @@ public class FeatureStoreJavaTest @Test(groups = "Functional") public void testFindFeatures_mixed() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); SequenceFeature sf1 = addFeature(fs, 10, 50); SequenceFeature sf2 = addFeature(fs, 1, 15); SequenceFeature sf3 = addFeature(fs, 20, 30); @@ -141,7 +138,7 @@ public class FeatureStoreJavaTest * @param to * @return */ - SequenceFeature addFeature(FeatureStoreI fs, int from, int to) + SequenceFeature addFeature(FeatureStore fs, int from, int to) { SequenceFeature sf1 = new SequenceFeature("", "", from, to, Float.NaN, null); @@ -152,7 +149,7 @@ public class FeatureStoreJavaTest @Test(groups = "Functional") public void testFindFeatures_contactFeatures() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); SequenceFeature sf = new SequenceFeature("disulphide bond", "bond", 10, 20, Float.NaN, null); @@ -195,7 +192,7 @@ public class FeatureStoreJavaTest @Test(groups = "Functional") public void testGetPositionalFeatures() { - FeatureStoreI store = newFeatureStore(); + FeatureStore store = newFeatureStore(); SequenceFeature sf1 = new SequenceFeature("Metal", "desc", 10, 20, Float.NaN, null); store.addFeature(sf1); @@ -242,7 +239,7 @@ public class FeatureStoreJavaTest @Test(groups = "Functional") public void testDelete() { - FeatureStoreI store = newFeatureStore(); + FeatureStore store = newFeatureStore(); SequenceFeature sf1 = addFeature(store, 10, 20); assertTrue(store.getPositionalFeatures().contains(sf1)); @@ -314,7 +311,7 @@ public class FeatureStoreJavaTest @Test(groups = "Functional") public void testAddFeature() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); SequenceFeature sf1 = new SequenceFeature("Cath", "", 10, 20, Float.NaN, null); @@ -365,7 +362,7 @@ public class FeatureStoreJavaTest @Test(groups = "Functional") public void testIsEmpty() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); assertTrue(fs.isEmpty()); assertEquals(fs.getFeatureCount(true), 0); @@ -431,7 +428,7 @@ public class FeatureStoreJavaTest @Test(groups = "Functional") public void testGetFeatureGroups() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); assertTrue(fs.getFeatureGroups(true).isEmpty()); assertTrue(fs.getFeatureGroups(false).isEmpty()); @@ -498,7 +495,7 @@ public class FeatureStoreJavaTest @Test(groups = "Functional") public void testGetTotalFeatureLength() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); assertEquals(fs.getTotalFeatureLength(), 0); addFeature(fs, 10, 20); // 11 @@ -574,7 +571,7 @@ public class FeatureStoreJavaTest @Test(groups = "Functional") public void testGetMinimumScore_getMaximumScore() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); assertEquals(fs.getMinimumScore(true), Float.NaN); // positional assertEquals(fs.getMaximumScore(true), Float.NaN); assertEquals(fs.getMinimumScore(false), Float.NaN); // non-positional @@ -646,7 +643,7 @@ public class FeatureStoreJavaTest @Test(groups = "Functional") public void testListContains() { - FeatureStoreI featureStore = newFeatureStore(); + FeatureStore featureStore = newFeatureStore(); assertFalse(featureStore.listContains(null, null)); List features = new ArrayList<>(); assertFalse(featureStore.listContains(features, null)); @@ -670,7 +667,7 @@ public class FeatureStoreJavaTest @Test(groups = "Functional") public void testGetFeaturesForGroup() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); /* * with no features @@ -728,7 +725,7 @@ public class FeatureStoreJavaTest @Test(groups = "Functional") public void testShiftFeatures() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); assertFalse(fs.shiftFeatures(0, 1)); // nothing to do SequenceFeature sf1 = new SequenceFeature("Cath", "", 2, 5, 0f, null); @@ -815,7 +812,7 @@ public class FeatureStoreJavaTest /* * add a feature and a nested feature */ - FeatureStoreI store = newFeatureStore(); + FeatureStore store = newFeatureStore(); SequenceFeature sf1 = addFeature(store, 10, 20); // sf2 is nested in sf1 so will be stored in nestedFeatures SequenceFeature sf2 = addFeature(store, 12, 14); @@ -846,7 +843,7 @@ public class FeatureStoreJavaTest @Test(groups = "Functional") public void testContains() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); SequenceFeature sf1 = new SequenceFeature("Cath", "", 10, 20, Float.NaN, "group1"); SequenceFeature sf2 = new SequenceFeature("Cath", "", 10, 20, diff --git a/test/jalview/datamodel/features/FeatureStoreLinkedTest.java b/test/jalview/datamodel/features/FeatureStoreLinkedTest.java index 25fdbb2..c1cd6d7 100644 --- a/test/jalview/datamodel/features/FeatureStoreLinkedTest.java +++ b/test/jalview/datamodel/features/FeatureStoreLinkedTest.java @@ -16,46 +16,50 @@ import org.testng.annotations.Test; public class FeatureStoreLinkedTest { - private FeatureStoreI newFeatureStore() + private FeatureStore newFeatureStore() { - return new FeatureStoreImpl( + return new FeatureStore( FeatureStore.INTERVAL_STORE_LINKED_LIST_PRESORT); } @Test(groups = "Functional") public void testFindFeatures_nonNested() { - FeatureStoreI fs = newFeatureStore(); - fs.addFeature(new SequenceFeature("", "", 10, 20, Float.NaN, - null)); + FeatureStore fs = newFeatureStore(); + SequenceFeature sf0 = new SequenceFeature("", "", 10, 20, Float.NaN, + null); + fs.addFeature(sf0); // 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 = new SequenceFeature("", "desc", 10, 20, Float.NaN, null); + fs.addFeature(sf1); + SequenceFeature sf2 = new SequenceFeature("", "", 15, 25, Float.NaN, null); + fs.addFeature(sf2); + SequenceFeature sf3 = new SequenceFeature("", "", 20, 35, Float.NaN, null); + fs.addFeature(sf3); List overlaps = fs.findOverlappingFeatures(1, 9); assertTrue(overlaps.isEmpty()); overlaps = fs.findOverlappingFeatures(8, 10); assertEquals(overlaps.size(), 2); - assertEquals(overlaps.get(0).getEnd(), 20); - assertEquals(overlaps.get(1).getEnd(), 20); + assertTrue(overlaps.contains(sf0)); + assertTrue(overlaps.contains(sf1)); overlaps = fs.findOverlappingFeatures(12, 16); assertEquals(overlaps.size(), 3); - assertEquals(overlaps.get(0).getEnd(), 20); - assertEquals(overlaps.get(1).getEnd(), 20); - assertEquals(overlaps.get(2).getEnd(), 25); + assertTrue(overlaps.contains(sf0)); + assertTrue(overlaps.contains(sf1)); + assertTrue(overlaps.contains(sf2)); overlaps = fs.findOverlappingFeatures(33, 33); assertEquals(overlaps.size(), 1); - assertEquals(overlaps.get(0).getEnd(), 35); + assertTrue(overlaps.contains(sf3)); } @Test(groups = "Functional") public void testFindFeatures_nested() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); SequenceFeature sf1 = addFeature(fs, 10, 50); SequenceFeature sf2 = addFeature(fs, 10, 40); SequenceFeature sf3 = addFeature(fs, 20, 30); @@ -94,7 +98,7 @@ public class FeatureStoreLinkedTest @Test(groups = "Functional") public void testFindFeatures_mixed() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); SequenceFeature sf1 = addFeature(fs, 10, 50); SequenceFeature sf2 = addFeature(fs, 1, 15); SequenceFeature sf3 = addFeature(fs, 20, 30); @@ -140,7 +144,7 @@ public class FeatureStoreLinkedTest * @param to * @return */ - SequenceFeature addFeature(FeatureStoreI fs, int from, int to) + SequenceFeature addFeature(FeatureStore fs, int from, int to) { SequenceFeature sf1 = new SequenceFeature("", "", from, to, Float.NaN, null); @@ -151,7 +155,7 @@ public class FeatureStoreLinkedTest @Test(groups = "Functional") public void testFindFeatures_contactFeatures() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); SequenceFeature sf = new SequenceFeature("disulphide bond", "bond", 10, 20, Float.NaN, null); @@ -194,7 +198,7 @@ public class FeatureStoreLinkedTest @Test(groups = "Functional") public void testGetPositionalFeatures() { - FeatureStoreI store = newFeatureStore(); + FeatureStore store = newFeatureStore(); SequenceFeature sf1 = new SequenceFeature("Metal", "desc", 10, 20, Float.NaN, null); store.addFeature(sf1); @@ -241,7 +245,7 @@ public class FeatureStoreLinkedTest @Test(groups = "Functional") public void testDelete() { - FeatureStoreI store = newFeatureStore(); + FeatureStore store = newFeatureStore(); SequenceFeature sf1 = addFeature(store, 10, 20); assertTrue(store.getPositionalFeatures().contains(sf1)); @@ -321,7 +325,7 @@ public class FeatureStoreLinkedTest @Test(groups = "Functional") public void testAddFeature() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); SequenceFeature sf1 = new SequenceFeature("Cath", "", 10, 20, Float.NaN, null); @@ -372,7 +376,7 @@ public class FeatureStoreLinkedTest @Test(groups = "Functional") public void testIsEmpty() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); assertTrue(fs.isEmpty()); assertEquals(fs.getFeatureCount(true), 0); @@ -438,7 +442,7 @@ public class FeatureStoreLinkedTest @Test(groups = "Functional") public void testGetFeatureGroups() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); assertTrue(fs.getFeatureGroups(true).isEmpty()); assertTrue(fs.getFeatureGroups(false).isEmpty()); @@ -505,7 +509,7 @@ public class FeatureStoreLinkedTest @Test(groups = "Functional") public void testGetTotalFeatureLength() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); assertEquals(fs.getTotalFeatureLength(), 0); addFeature(fs, 10, 20); // 11 @@ -581,7 +585,7 @@ public class FeatureStoreLinkedTest @Test(groups = "Functional") public void testGetMinimumScore_getMaximumScore() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); assertEquals(fs.getMinimumScore(true), Float.NaN); // positional assertEquals(fs.getMaximumScore(true), Float.NaN); assertEquals(fs.getMinimumScore(false), Float.NaN); // non-positional @@ -653,7 +657,7 @@ public class FeatureStoreLinkedTest @Test(groups = "Functional") public void testListContains() { - FeatureStoreI featureStore = newFeatureStore(); + FeatureStore featureStore = newFeatureStore(); assertFalse(featureStore.listContains(null, null)); List features = new ArrayList<>(); assertFalse(featureStore.listContains(features, null)); @@ -677,7 +681,7 @@ public class FeatureStoreLinkedTest @Test(groups = "Functional") public void testGetFeaturesForGroup() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); /* * with no features @@ -735,7 +739,7 @@ public class FeatureStoreLinkedTest @Test(groups = "Functional") public void testShiftFeatures() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); assertFalse(fs.shiftFeatures(0, 1)); // nothing to do SequenceFeature sf1 = new SequenceFeature("Cath", "", 2, 5, 0f, null); @@ -822,7 +826,7 @@ public class FeatureStoreLinkedTest /* * add a feature and a nested feature */ - FeatureStoreI store = newFeatureStore(); + FeatureStore store = newFeatureStore(); SequenceFeature sf1 = addFeature(store, 10, 20); // sf2 is nested in sf1 so will be stored in nestedFeatures SequenceFeature sf2 = addFeature(store, 12, 14); @@ -853,7 +857,7 @@ public class FeatureStoreLinkedTest @Test(groups = "Functional") public void testContains() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); SequenceFeature sf1 = new SequenceFeature("Cath", "", 10, 20, Float.NaN, "group1"); SequenceFeature sf2 = new SequenceFeature("Cath", "", 10, 20, diff --git a/test/jalview/datamodel/features/FeatureStoreNCListBufferTest.java b/test/jalview/datamodel/features/FeatureStoreNCListBufferTest.java index 2b9198a..c7598d4 100644 --- a/test/jalview/datamodel/features/FeatureStoreNCListBufferTest.java +++ b/test/jalview/datamodel/features/FeatureStoreNCListBufferTest.java @@ -16,16 +16,16 @@ import org.testng.annotations.Test; public class FeatureStoreNCListBufferTest { - private FeatureStoreI newFeatureStore() + private FeatureStore newFeatureStore() { - return new FeatureStoreImpl( + return new FeatureStore( FeatureStore.INTERVAL_STORE_NCLIST_BUFFER_PRESORT); } @Test(groups = "Functional") public void testFindFeatures_nonNested() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); fs.addFeature(new SequenceFeature("", "", 10, 20, Float.NaN, null)); // same range different description @@ -55,7 +55,7 @@ public class FeatureStoreNCListBufferTest @Test(groups = "Functional") public void testFindFeatures_nested() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); SequenceFeature sf1 = addFeature(fs, 10, 50); SequenceFeature sf2 = addFeature(fs, 10, 40); SequenceFeature sf3 = addFeature(fs, 20, 30); @@ -94,7 +94,7 @@ public class FeatureStoreNCListBufferTest @Test(groups = "Functional") public void testFindFeatures_mixed() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); SequenceFeature sf1 = addFeature(fs, 10, 50); SequenceFeature sf2 = addFeature(fs, 1, 15); SequenceFeature sf3 = addFeature(fs, 20, 30); @@ -140,7 +140,7 @@ public class FeatureStoreNCListBufferTest * @param to * @return */ - SequenceFeature addFeature(FeatureStoreI fs, int from, int to) + SequenceFeature addFeature(FeatureStore fs, int from, int to) { SequenceFeature sf1 = new SequenceFeature("", "", from, to, Float.NaN, null); @@ -151,7 +151,7 @@ public class FeatureStoreNCListBufferTest @Test(groups = "Functional") public void testFindFeatures_contactFeatures() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); SequenceFeature sf = new SequenceFeature("disulphide bond", "bond", 10, 20, Float.NaN, null); @@ -194,7 +194,7 @@ public class FeatureStoreNCListBufferTest @Test(groups = "Functional") public void testGetPositionalFeatures() { - FeatureStoreI store = newFeatureStore(); + FeatureStore store = newFeatureStore(); SequenceFeature sf1 = new SequenceFeature("Metal", "desc", 10, 20, Float.NaN, null); store.addFeature(sf1); @@ -241,7 +241,7 @@ public class FeatureStoreNCListBufferTest @Test(groups = "Functional") public void testDelete() { - FeatureStoreI store = newFeatureStore(); + FeatureStore store = newFeatureStore(); SequenceFeature sf1 = addFeature(store, 10, 20); assertTrue(store.getPositionalFeatures().contains(sf1)); @@ -321,7 +321,7 @@ public class FeatureStoreNCListBufferTest @Test(groups = "Functional") public void testAddFeature() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); SequenceFeature sf1 = new SequenceFeature("Cath", "", 10, 20, Float.NaN, null); @@ -372,7 +372,7 @@ public class FeatureStoreNCListBufferTest @Test(groups = "Functional") public void testIsEmpty() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); assertTrue(fs.isEmpty()); assertEquals(fs.getFeatureCount(true), 0); @@ -438,7 +438,7 @@ public class FeatureStoreNCListBufferTest @Test(groups = "Functional") public void testGetFeatureGroups() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); assertTrue(fs.getFeatureGroups(true).isEmpty()); assertTrue(fs.getFeatureGroups(false).isEmpty()); @@ -505,7 +505,7 @@ public class FeatureStoreNCListBufferTest @Test(groups = "Functional") public void testGetTotalFeatureLength() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); assertEquals(fs.getTotalFeatureLength(), 0); addFeature(fs, 10, 20); // 11 @@ -581,7 +581,7 @@ public class FeatureStoreNCListBufferTest @Test(groups = "Functional") public void testGetMinimumScore_getMaximumScore() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); assertEquals(fs.getMinimumScore(true), Float.NaN); // positional assertEquals(fs.getMaximumScore(true), Float.NaN); assertEquals(fs.getMinimumScore(false), Float.NaN); // non-positional @@ -653,7 +653,7 @@ public class FeatureStoreNCListBufferTest @Test(groups = "Functional") public void testListContains() { - FeatureStoreI featureStore = newFeatureStore(); + FeatureStore featureStore = newFeatureStore(); assertFalse(featureStore.listContains(null, null)); List features = new ArrayList<>(); assertFalse(featureStore.listContains(features, null)); @@ -677,7 +677,7 @@ public class FeatureStoreNCListBufferTest @Test(groups = "Functional") public void testGetFeaturesForGroup() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); /* * with no features @@ -735,7 +735,7 @@ public class FeatureStoreNCListBufferTest @Test(groups = "Functional") public void testShiftFeatures() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); assertFalse(fs.shiftFeatures(0, 1)); // nothing to do SequenceFeature sf1 = new SequenceFeature("Cath", "", 2, 5, 0f, null); @@ -822,7 +822,7 @@ public class FeatureStoreNCListBufferTest /* * add a feature and a nested feature */ - FeatureStoreI store = newFeatureStore(); + FeatureStore store = newFeatureStore(); SequenceFeature sf1 = addFeature(store, 10, 20); // sf2 is nested in sf1 so will be stored in nestedFeatures SequenceFeature sf2 = addFeature(store, 12, 14); @@ -853,7 +853,7 @@ public class FeatureStoreNCListBufferTest @Test(groups = "Functional") public void testContains() { - FeatureStoreI fs = newFeatureStore(); + FeatureStore fs = newFeatureStore(); SequenceFeature sf1 = new SequenceFeature("Cath", "", 10, 20, Float.NaN, "group1"); SequenceFeature sf2 = new SequenceFeature("Cath", "", 10, 20, diff --git a/test/jalview/datamodel/features/SequenceFeaturesTest.java b/test/jalview/datamodel/features/SequenceFeaturesTest.java index 30b1402..29e76bb 100644 --- a/test/jalview/datamodel/features/SequenceFeaturesTest.java +++ b/test/jalview/datamodel/features/SequenceFeaturesTest.java @@ -889,11 +889,11 @@ public class SequenceFeaturesTest * no type specified - get all types stored * they are returned in keyset (alphabetical) order */ - Map featureStores = (Map) PA + Map featureStores = (Map) PA .getValue(sf, "featureStore"); - Iterable types = sf.varargToTypes(); - Iterator iterator = types.iterator(); + Iterable types = sf.varargToTypes(); + Iterator iterator = types.iterator(); assertTrue(iterator.hasNext()); assertSame(iterator.next(), featureStores.get("Cath")); assertTrue(iterator.hasNext()); -- 1.7.10.2