JAL-3140 NCList now via IntervalStore library
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Thu, 29 Nov 2018 09:42:43 +0000 (09:42 +0000)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Thu, 29 Nov 2018 09:42:43 +0000 (09:42 +0000)
16 files changed:
.classpath
src/jalview/datamodel/Range.java [deleted file]
src/jalview/datamodel/Sequence.java
src/jalview/datamodel/SequenceI.java
src/jalview/datamodel/features/FeatureLocationI.java
src/jalview/datamodel/features/FeatureStore.java
src/jalview/datamodel/features/NCList.java [deleted file]
src/jalview/datamodel/features/NCNode.java [deleted file]
src/jalview/datamodel/features/RangeComparator.java [deleted file]
src/jalview/datamodel/features/SequenceFeatures.java
src/jalview/renderer/seqfeatures/FeatureRenderer.java
test/jalview/datamodel/SequenceTest.java
test/jalview/datamodel/features/FeatureStoreTest.java
test/jalview/datamodel/features/NCListTest.java [deleted file]
test/jalview/datamodel/features/NCNodeTest.java [deleted file]
test/jalview/datamodel/features/RangeComparatorTest.java [deleted file]

index 0da91bb..f964086 100644 (file)
@@ -70,5 +70,6 @@
        <classpathentry kind="lib" path="lib/htsjdk-2.12.0.jar"/>
        <classpathentry kind="lib" path="lib/groovy-all-2.4.12-indy.jar"/>
        <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+       <classpathentry kind="lib" path="lib/intervalstore.jar" sourcepath="/IntervalStoreJ/binaries/intervalstore-src.jar"/>
        <classpathentry kind="output" path="classes"/>
 </classpath>
diff --git a/src/jalview/datamodel/Range.java b/src/jalview/datamodel/Range.java
deleted file mode 100644 (file)
index 8b6f617..0000000
+++ /dev/null
@@ -1,72 +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 <http://www.gnu.org/licenses/>.
- * The Jalview Authors are detailed in the 'AUTHORS' file.
- */
-package jalview.datamodel;
-
-/**
- * An immutable data bean that models a start-end range
- */
-public class Range implements ContiguousI
-{
-  public final int start;
-
-  public final 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 obj)
-  {
-    if (obj instanceof Range)
-    {
-      Range r = (Range) obj;
-      return (start == r.start && end == r.end);
-    }
-    return false;
-  }
-}
index 33de452..674fabe 100755 (executable)
@@ -40,6 +40,8 @@ import java.util.ListIterator;
 import java.util.Vector;
 
 import fr.orsay.lri.varna.models.rna.RNA;
+import intervalstore.api.IntervalI;
+import intervalstore.impl.Range;
 
 /**
  * 
@@ -1069,7 +1071,7 @@ public class Sequence extends ASequence implements SequenceI
    * {@inheritDoc}
    */
   @Override
-  public Range findPositions(int fromColumn, int toColumn)
+  public IntervalI findPositions(int fromColumn, int toColumn)
   {
     if (toColumn < fromColumn || fromColumn < 1)
     {
index 8dce31e..ed8e8e4 100755 (executable)
@@ -29,6 +29,7 @@ import java.util.List;
 import java.util.Vector;
 
 import fr.orsay.lri.varna.models.rna.RNA;
+import intervalstore.api.IntervalI;
 
 /**
  * Methods for manipulating a sequence, its metadata and related annotation in
@@ -213,7 +214,7 @@ public interface SequenceI extends ASequenceI
    * @param toColumn
    * @return
    */
-  public Range findPositions(int fromColum, int toColumn);
+  public IntervalI findPositions(int fromColum, int toColumn);
 
   /**
    * Returns an int array where indices correspond to each residue in the
index 378b8db..535346c 100644 (file)
  */
 package jalview.datamodel.features;
 
-import jalview.datamodel.ContiguousI;
+import intervalstore.api.IntervalI;
 
 /**
- * An extension of ContiguousI that allows start/end values to be interpreted
+ * An extension of IntervalI that allows start/end values to be interpreted
  * instead as two contact positions
  */
-public interface FeatureLocationI extends ContiguousI
+public interface FeatureLocationI extends IntervalI
 {
   boolean isContactFeature();
 }
index 02ce1c5..1347213 100644 (file)
  */
 package jalview.datamodel.features;
 
-import jalview.datamodel.ContiguousI;
 import jalview.datamodel.SequenceFeature;
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
+import intervalstore.api.IntervalStoreI;
+import intervalstore.impl.BinarySearcher;
+import intervalstore.impl.IntervalStore;
+
 /**
  * A data store for a set of sequence features that supports efficient lookup of
  * features overlapping a given range. Intended for (but not limited to) storage
@@ -40,80 +42,6 @@ import java.util.Set;
  */
 public class FeatureStore
 {
-  /**
-   * a class providing criteria for performing a binary search of a list
-   */
-  abstract static class SearchCriterion
-  {
-    /**
-     * Answers true if the entry passes the search criterion test
-     * 
-     * @param entry
-     * @return
-     */
-    abstract boolean compare(SequenceFeature entry);
-
-    /**
-     * serves a search condition for finding the first feature whose start
-     * position follows a given target location
-     * 
-     * @param target
-     * @return
-     */
-    static SearchCriterion byStart(final long target)
-    {
-      return new SearchCriterion() {
-
-        @Override
-        boolean compare(SequenceFeature entry)
-        {
-          return entry.getBegin() >= target;
-        }
-      };
-    }
-
-    /**
-     * serves a search condition for finding the first feature whose end
-     * position is at or follows a given target location
-     * 
-     * @param target
-     * @return
-     */
-    static SearchCriterion byEnd(final long target)
-    {
-      return new SearchCriterion()
-      {
-
-        @Override
-        boolean compare(SequenceFeature entry)
-        {
-          return entry.getEnd() >= target;
-        }
-      };
-    }
-
-    /**
-     * serves a search condition for finding the first feature which follows the
-     * given range as determined by a supplied comparator
-     * 
-     * @param target
-     * @return
-     */
-    static SearchCriterion byFeature(final ContiguousI to,
-            final Comparator<ContiguousI> rc)
-    {
-      return new SearchCriterion()
-      {
-
-        @Override
-        boolean compare(SequenceFeature entry)
-        {
-          return rc.compare(entry, to) >= 0;
-        }
-      };
-    }
-  }
-
   /*
    * Non-positional features have no (zero) start/end position.
    * Kept as a separate list in case this criterion changes in future.
@@ -121,14 +49,6 @@ public class FeatureStore
   List<SequenceFeature> nonPositionalFeatures;
 
   /*
-   * An ordered list of features, with the promise that no feature in the list 
-   * properly contains any other. This constraint allows bounded linear search
-   * of the list for features overlapping a region.
-   * Contact features are not included in this list.
-   */
-  List<SequenceFeature> nonNestedFeatures;
-
-  /*
    * contact features ordered by first contact position
    */
   List<SequenceFeature> contactFeatureStarts;
@@ -139,13 +59,10 @@ public class FeatureStore
   List<SequenceFeature> contactFeatureEnds;
 
   /*
-   * Nested Containment List is used to hold any features that are nested 
-   * within (properly contained by) any other feature. This is a recursive tree
-   * which supports depth-first scan for features overlapping a range.
-   * It is used here as a 'catch-all' fallback for features that cannot be put
-   * into a simple ordered list without invalidating the search methods.
+   * IntervalStore holds remaining features and provides efficient
+   * query for features overlapping any given interval
    */
-  NCList<SequenceFeature> nestedFeatures;
+  IntervalStoreI<SequenceFeature> features;
 
   /*
    * Feature groups represented in stored positional features 
@@ -178,16 +95,15 @@ public class FeatureStore
    */
   public FeatureStore()
   {
-    nonNestedFeatures = new ArrayList<SequenceFeature>();
-    positionalFeatureGroups = new HashSet<String>();
-    nonPositionalFeatureGroups = new HashSet<String>();
+    features = new IntervalStore<>();
+    positionalFeatureGroups = new HashSet<>();
+    nonPositionalFeatureGroups = new HashSet<>();
     positionalMinScore = Float.NaN;
     positionalMaxScore = Float.NaN;
     nonPositionalMinScore = Float.NaN;
     nonPositionalMaxScore = Float.NaN;
 
-    // we only construct nonPositionalFeatures, contactFeatures
-    // or the NCList if we need to
+    // we only construct nonPositionalFeatures, contactFeatures if we need to
   }
 
   /**
@@ -213,58 +129,46 @@ public class FeatureStore
       positionalFeatureGroups.add(feature.getFeatureGroup());
     }
 
-    boolean added = false;
-
     if (feature.isContactFeature())
     {
-      added = addContactFeature(feature);
+      addContactFeature(feature);
     }
     else if (feature.isNonPositional())
     {
-      added = addNonPositionalFeature(feature);
+      addNonPositionalFeature(feature);
     }
     else
     {
-      added = addNonNestedFeature(feature);
-      if (!added)
-      {
-        /*
-         * detected a nested feature - put it in the NCList structure
-         */
-        added = addNestedFeature(feature);
-      }
+      addNestedFeature(feature);
     }
 
-    if (added)
-    {
-      /*
-       * record the total extent of positional features, to make
-       * getTotalFeatureLength possible; we count the length of a 
-       * contact feature as 1
-       */
-      totalExtent += getFeatureLength(feature);
+    /*
+     * record the total extent of positional features, to make
+     * getTotalFeatureLength possible; we count the length of a 
+     * contact feature as 1
+     */
+    totalExtent += getFeatureLength(feature);
 
-      /*
-       * record the minimum and maximum score for positional
-       * and non-positional features
-       */
-      float score = feature.getScore();
-      if (!Float.isNaN(score))
+    /*
+     * record the minimum and maximum score for positional
+     * and non-positional features
+     */
+    float score = feature.getScore();
+    if (!Float.isNaN(score))
+    {
+      if (feature.isNonPositional())
+      {
+        nonPositionalMinScore = min(nonPositionalMinScore, score);
+        nonPositionalMaxScore = max(nonPositionalMaxScore, score);
+      }
+      else
       {
-        if (feature.isNonPositional())
-        {
-          nonPositionalMinScore = min(nonPositionalMinScore, score);
-          nonPositionalMaxScore = max(nonPositionalMaxScore, score);
-        }
-        else
-        {
-          positionalMinScore = min(positionalMinScore, score);
-          positionalMaxScore = max(positionalMaxScore, score);
-        }
+        positionalMinScore = min(positionalMinScore, score);
+        positionalMaxScore = max(positionalMaxScore, score);
       }
     }
 
-    return added;
+    return true;
   }
 
   /**
@@ -288,12 +192,7 @@ public class FeatureStore
               contactFeatureStarts, feature);
     }
 
-    if (listContains(nonNestedFeatures, feature))
-    {
-      return true;
-    }
-
-    return nestedFeatures == null ? false : nestedFeatures
+    return features == null ? false : features
             .contains(feature);
   }
 
@@ -330,7 +229,7 @@ public class FeatureStore
   {
     if (nonPositionalFeatures == null)
     {
-      nonPositionalFeatures = new ArrayList<SequenceFeature>();
+      nonPositionalFeatures = new ArrayList<>();
     }
 
     nonPositionalFeatures.add(feature);
@@ -341,91 +240,16 @@ public class FeatureStore
   }
 
   /**
-   * Adds one feature to the NCList that can manage nested features (creating
-   * the NCList if necessary), and returns true. If the feature is already
-   * stored in the NCList (by equality test), then it is not added, and this
-   * method returns false.
+   * Adds one feature to the IntervalStore that can manage nested features
+   * (creating the IntervalStore if necessary)
    */
-  protected synchronized boolean addNestedFeature(SequenceFeature feature)
+  protected synchronized void addNestedFeature(SequenceFeature feature)
   {
-    if (nestedFeatures == null)
+    if (features == null)
     {
-      nestedFeatures = new NCList<>(feature);
-      return true;
+      features = new IntervalStore<>();
     }
-    return nestedFeatures.add(feature, false);
-  }
-
-  /**
-   * Add a feature to the list of non-nested features, maintaining the ordering
-   * of the list. A check is made for whether the feature is nested in (properly
-   * contained by) an existing feature. If there is no nesting, the feature is
-   * added to the list and the method returns true. If nesting is found, the
-   * feature is not added and the method returns false.
-   * 
-   * @param feature
-   * @return
-   */
-  protected boolean addNonNestedFeature(SequenceFeature feature)
-  {
-    synchronized (nonNestedFeatures)
-    {
-      /*
-       * find the first stored feature which doesn't precede the new one
-       */
-      int insertPosition = binarySearch(nonNestedFeatures,
-              SearchCriterion.byFeature(feature, RangeComparator.BY_START_POSITION));
-
-      /*
-       * fail if we detect feature enclosure - of the new feature by
-       * the one preceding it, or of the next feature by the new one
-       */
-      if (insertPosition > 0)
-      {
-        if (encloses(nonNestedFeatures.get(insertPosition - 1), feature))
-        {
-          return false;
-        }
-      }
-      if (insertPosition < nonNestedFeatures.size())
-      {
-        if (encloses(feature, nonNestedFeatures.get(insertPosition)))
-        {
-          return false;
-        }
-      }
-
-      /*
-       * checks passed - add the feature
-       */
-      nonNestedFeatures.add(insertPosition, feature);
-
-      return true;
-    }
-  }
-
-  /**
-   * Answers true if range1 properly encloses range2, else false
-   * 
-   * @param range1
-   * @param range2
-   * @return
-   */
-  protected boolean encloses(ContiguousI range1, ContiguousI range2)
-  {
-    int begin1 = range1.getBegin();
-    int begin2 = range2.getBegin();
-    int end1 = range1.getEnd();
-    int end2 = range2.getEnd();
-    if (begin1 == begin2 && end1 > end2)
-    {
-      return true;
-    }
-    if (begin1 < begin2 && end1 >= end2)
-    {
-      return true;
-    }
-    return false;
+    features.add(feature);
   }
 
   /**
@@ -441,28 +265,29 @@ public class FeatureStore
   {
     if (contactFeatureStarts == null)
     {
-      contactFeatureStarts = new ArrayList<SequenceFeature>();
+      contactFeatureStarts = new ArrayList<>();
     }
     if (contactFeatureEnds == null)
     {
-      contactFeatureEnds = new ArrayList<SequenceFeature>();
+      contactFeatureEnds = new ArrayList<>();
     }
 
     /*
+     * insert into list sorted by start (first contact position):
      * binary search the sorted list to find the insertion point
      */
-    int insertPosition = binarySearch(contactFeatureStarts,
-            SearchCriterion.byFeature(feature,
-                    RangeComparator.BY_START_POSITION));
+    int insertPosition = BinarySearcher.findFirst(contactFeatureStarts,
+            f -> f.getBegin() >= feature.getBegin());
     contactFeatureStarts.add(insertPosition, feature);
-    // and resort to mak siccar...just in case insertion point not quite right
-    Collections.sort(contactFeatureStarts, RangeComparator.BY_START_POSITION);
 
-    insertPosition = binarySearch(contactFeatureStarts,
-            SearchCriterion.byFeature(feature,
-                    RangeComparator.BY_END_POSITION));
-    contactFeatureEnds.add(feature);
-    Collections.sort(contactFeatureEnds, RangeComparator.BY_END_POSITION);
+
+    /*
+     * insert into list sorted by end (second contact position):
+     * binary search the sorted list to find the insertion point
+     */
+    insertPosition = BinarySearcher.findFirst(contactFeatureEnds,
+            f -> f.getEnd() >= feature.getEnd());
+    contactFeatureEnds.add(insertPosition, feature);
 
     return true;
   }
@@ -487,8 +312,10 @@ public class FeatureStore
     /*
      * locate the first entry in the list which does not precede the feature
      */
-    int pos = binarySearch(features,
-            SearchCriterion.byFeature(feature, RangeComparator.BY_START_POSITION));
+    // int pos = binarySearch(features,
+    // SearchCriterion.byFeature(feature, RangeComparator.BY_START_POSITION));
+    int pos = BinarySearcher.findFirst(features,
+            val -> val.getBegin() >= feature.getBegin());
     int len = features.size();
     while (pos < len)
     {
@@ -521,13 +348,11 @@ public class FeatureStore
   {
     List<SequenceFeature> result = new ArrayList<>();
 
-    findNonNestedFeatures(start, end, result);
-
     findContactFeatures(start, end, result);
 
-    if (nestedFeatures != null)
+    if (features != null)
     {
-      result.addAll(nestedFeatures.findOverlaps(start, end));
+      result.addAll(features.findOverlaps(start, end));
     }
 
     return result;
@@ -546,11 +371,11 @@ public class FeatureStore
   {
     if (contactFeatureStarts != null)
     {
-      findContactStartFeatures(from, to, result);
+      findContactStartOverlaps(from, to, result);
     }
     if (contactFeatureEnds != null)
     {
-      findContactEndFeatures(from, to, result);
+      findContactEndOverlaps(from, to, result);
     }
   }
 
@@ -563,22 +388,24 @@ public class FeatureStore
    * @param to
    * @param result
    */
-  protected void findContactEndFeatures(long from, long to,
+  protected void findContactEndOverlaps(long from, long to,
           List<SequenceFeature> result)
   {
     /*
-     * find the first contact feature (if any) that does not lie 
-     * entirely before the target range
+     * find the first contact feature (if any) 
+     * whose end point is not before the target range
      */
-    int startPosition = binarySearch(contactFeatureEnds,
-            SearchCriterion.byEnd(from));
-    for (; startPosition < contactFeatureEnds.size(); startPosition++)
+    int index = BinarySearcher.findFirst(contactFeatureEnds,
+            f -> f.getEnd() >= from);
+
+    while (index < contactFeatureEnds.size())
     {
-      SequenceFeature sf = contactFeatureEnds.get(startPosition);
+      SequenceFeature sf = contactFeatureEnds.get(index);
       if (!sf.isContactFeature())
       {
         System.err.println("Error! non-contact feature type "
                 + sf.getType() + " in contact features list");
+        index++;
         continue;
       }
 
@@ -589,54 +416,24 @@ public class FeatureStore
          * this feature's first contact position lies in the search range
          * so we don't include it in results a second time
          */
+        index++;
         continue;
       }
 
-      int end = sf.getEnd();
-      if (end >= from && end <= to)
-      {
-        result.add(sf);
-      }
-      if (end > to)
+      if (sf.getEnd() > to)
       {
+        /*
+         * this feature (and all following) has end point after the target range
+         */
         break;
       }
-    }
-  }
-
-  /**
-   * Adds non-nested features to the result list that lie within the target
-   * range. Non-positional features (start=end=0), contact features and nested
-   * features are excluded.
-   * 
-   * @param from
-   * @param to
-   * @param result
-   */
-  protected void findNonNestedFeatures(long from, long to,
-          List<SequenceFeature> result)
-  {
-    /*
-     * find the first feature whose end position is
-     * after the target range start
-     */
-    int startIndex = binarySearch(nonNestedFeatures,
-            SearchCriterion.byEnd(from));
 
-    final int startIndex1 = startIndex;
-    int i = startIndex1;
-    while (i < nonNestedFeatures.size())
-    {
-      SequenceFeature sf = nonNestedFeatures.get(i);
-      if (sf.getBegin() > to)
-      {
-        break;
-      }
-      if (sf.getBegin() <= to && sf.getEnd() >= from)
-      {
-        result.add(sf);
-      }
-      i++;
+      /*
+       * feature has end >= from and end <= to
+       * i.e. contact end point lies within overlap search range
+       */
+      result.add(sf);
+      index++;
     }
   }
 
@@ -648,26 +445,36 @@ public class FeatureStore
    * @param to
    * @param result
    */
-  protected void findContactStartFeatures(long from, long to,
+  protected void findContactStartOverlaps(long from, long to,
           List<SequenceFeature> result)
   {
-    int startPosition = binarySearch(contactFeatureStarts,
-            SearchCriterion.byStart(from));
+    int index = BinarySearcher.findFirst(contactFeatureStarts,
+            f -> f.getBegin() >= from);
 
-    for (; startPosition < contactFeatureStarts.size(); startPosition++)
+    while (index < contactFeatureStarts.size())
     {
-      SequenceFeature sf = contactFeatureStarts.get(startPosition);
+      SequenceFeature sf = contactFeatureStarts.get(index);
       if (!sf.isContactFeature())
       {
-        System.err.println("Error! non-contact feature type "
-                + sf.getType() + " in contact features list");
+        System.err.println("Error! non-contact feature " + sf.toString()
+                + " in contact features list");
+        index++;
         continue;
       }
-      int begin = sf.getBegin();
-      if (begin >= from && begin <= to)
+      if (sf.getBegin() > to)
       {
-        result.add(sf);
+        /*
+         * 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++;
     }
   }
 
@@ -678,11 +485,7 @@ public class FeatureStore
    */
   public List<SequenceFeature> getPositionalFeatures()
   {
-    /*
-     * add non-nested features (may be all features for many cases)
-     */
     List<SequenceFeature> result = new ArrayList<>();
-    result.addAll(nonNestedFeatures);
 
     /*
      * add any contact features - from the list by start position
@@ -695,9 +498,9 @@ public class FeatureStore
     /*
      * add any nested features
      */
-    if (nestedFeatures != null)
+    if (features != null)
     {
-      result.addAll(nestedFeatures.getEntries());
+      result.addAll(features);
     }
 
     return result;
@@ -743,13 +546,10 @@ public class FeatureStore
    */
   public synchronized boolean delete(SequenceFeature sf)
   {
-    /*
-     * try the non-nested positional features first
-     */
-    boolean removed = nonNestedFeatures.remove(sf);
+    boolean removed = false;
 
     /*
-     * if not found, try contact positions (and if found, delete
+     * try contact positions (and if found, delete
      * from both lists of contact positions)
      */
     if (!removed && contactFeatureStarts != null)
@@ -775,9 +575,9 @@ public class FeatureStore
     /*
      * if not found, try nested features
      */
-    if (!removed && nestedFeatures != null)
+    if (!removed && features != null)
     {
-      removed = nestedFeatures.delete(sf);
+      removed = features.remove(sf);
     }
 
     if (removed)
@@ -874,12 +674,12 @@ public class FeatureStore
    */
   public boolean isEmpty()
   {
-    boolean hasFeatures = !nonNestedFeatures.isEmpty()
-            || (contactFeatureStarts != null && !contactFeatureStarts
+    boolean hasFeatures = (contactFeatureStarts != null
+            && !contactFeatureStarts
                     .isEmpty())
             || (nonPositionalFeatures != null && !nonPositionalFeatures
                     .isEmpty())
-            || (nestedFeatures != null && nestedFeatures.size() > 0);
+            || (features != null && features.size() > 0);
 
     return !hasFeatures;
   }
@@ -907,41 +707,6 @@ public class FeatureStore
   }
 
   /**
-   * Performs a binary search of the (sorted) list to find the index of the
-   * first entry which returns true for the given comparator function. Returns
-   * the length of the list if there is no such entry.
-   * 
-   * @param features
-   * @param sc
-   * @return
-   */
-  protected static int binarySearch(List<SequenceFeature> features,
-          SearchCriterion sc)
-  {
-    int start = 0;
-    int end = features.size() - 1;
-    int matched = features.size();
-
-    while (start <= end)
-    {
-      int mid = (start + end) / 2;
-      SequenceFeature entry = features.get(mid);
-      boolean compare = sc.compare(entry);
-      if (compare)
-      {
-        matched = mid;
-        end = mid - 1;
-      }
-      else
-      {
-        start = mid + 1;
-      }
-    }
-
-    return matched;
-  }
-
-  /**
    * Answers the number of positional (or non-positional) features stored.
    * Contact features count as 1.
    * 
@@ -956,7 +721,7 @@ public class FeatureStore
               .size();
     }
 
-    int size = nonNestedFeatures.size();
+    int size = 0;
 
     if (contactFeatureStarts != null)
     {
@@ -964,9 +729,9 @@ public class FeatureStore
       size += contactFeatureStarts.size();
     }
 
-    if (nestedFeatures != null)
+    if (features != null)
     {
-      size += nestedFeatures.size();
+      size += features.size();
     }
 
     return size;
diff --git a/src/jalview/datamodel/features/NCList.java b/src/jalview/datamodel/features/NCList.java
deleted file mode 100644 (file)
index ae58a69..0000000
+++ /dev/null
@@ -1,646 +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 <http://www.gnu.org/licenses/>.
- * The Jalview Authors are detailed in the 'AUTHORS' file.
- */
-package jalview.datamodel.features;
-
-import jalview.datamodel.ContiguousI;
-import jalview.datamodel.Range;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * An adapted implementation of NCList as described in the paper
- * 
- * <pre>
- * Nested Containment List (NCList): a new algorithm for accelerating
- * interval query of genome alignment and interval databases
- * - Alexander V. Alekseyenko, Christopher J. Lee
- * https://doi.org/10.1093/bioinformatics/btl647
- * </pre>
- */
-public class NCList<T extends ContiguousI>
-{
-  /*
-   * the number of ranges 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<NCNode<T>> subranges;
-
-  /**
-   * Constructor given a list of things that are each located on a contiguous
-   * interval. Note that the constructor may reorder the list.
-   * <p>
-   * We assume here that for each range, start &lt;= end. Behaviour for reverse
-   * ordered ranges is undefined.
-   * 
-   * @param ranges
-   */
-  public NCList(List<T> ranges)
-  {
-    this();
-    build(ranges);
-  }
-
-  /**
-   * Sort and group ranges into sublists where each sublist represents a region
-   * and its contained subregions
-   * 
-   * @param ranges
-   */
-  protected void build(List<T> ranges)
-  {
-    /*
-     * sort by start ascending so that contained intervals 
-     * follow their containing interval
-     */
-    Collections.sort(ranges, RangeComparator.BY_START_POSITION);
-
-    List<Range> sublists = buildSubranges(ranges);
-
-    /*
-     * convert each subrange to an NCNode consisting of a range and
-     * (possibly) its contained NCList
-     */
-    for (Range sublist : sublists)
-    {
-      subranges.add(new NCNode<T>(ranges.subList(sublist.start,
-              sublist.end + 1)));
-    }
-
-    size = ranges.size();
-  }
-
-  public NCList(T entry)
-  {
-    this();
-    subranges.add(new NCNode<>(entry));
-    size = 1;
-  }
-
-  public NCList()
-  {
-    subranges = new ArrayList<NCNode<T>>();
-  }
-
-  /**
-   * Traverses the sorted ranges to identify sublists, within which each
-   * interval contains the one that follows it
-   * 
-   * @param ranges
-   * @return
-   */
-  protected List<Range> buildSubranges(List<T> ranges)
-  {
-    List<Range> sublists = new ArrayList<>();
-    
-    if (ranges.isEmpty())
-    {
-      return sublists;
-    }
-
-    int listStartIndex = 0;
-    long lastEndPos = Long.MAX_VALUE;
-
-    for (int i = 0; i < ranges.size(); i++)
-    {
-      ContiguousI nextInterval = ranges.get(i);
-      long nextStart = nextInterval.getBegin();
-      long nextEnd = nextInterval.getEnd();
-      if (nextStart > lastEndPos || nextEnd > lastEndPos)
-      {
-        /*
-         * this interval is not contained in the preceding one 
-         * close off the last sublist
-         */
-        sublists.add(new Range(listStartIndex, i - 1));
-        listStartIndex = i;
-      }
-      lastEndPos = nextEnd;
-    }
-
-    sublists.add(new Range(listStartIndex, ranges.size() - 1));
-    return sublists;
-  }
-
-  /**
-   * Adds one entry to the stored set (with duplicates allowed)
-   * 
-   * @param entry
-   */
-  public void add(T entry)
-  {
-    add(entry, true);
-  }
-
-  /**
-   * Adds one entry to the stored set, and returns true, unless allowDuplicates
-   * is set to false and it is already contained (by object equality test), in
-   * which case it is not added and this method returns false.
-   * 
-   * @param entry
-   * @param allowDuplicates
-   * @return
-   */
-  public synchronized boolean add(T entry, boolean allowDuplicates)
-  {
-    if (!allowDuplicates && contains(entry))
-    {
-      return false;
-    }
-
-    size++;
-    long start = entry.getBegin();
-    long end = entry.getEnd();
-
-    /*
-     * cases:
-     * - precedes all subranges: add as NCNode on front of list
-     * - follows all subranges: add as NCNode on end of list
-     * - enclosed by a subrange - add recursively to subrange
-     * - encloses one or more subranges - push them inside it
-     * - none of the above - add as a new node and resort nodes list (?)
-     */
-
-    /*
-     * find the first subrange whose end does not precede entry's start
-     */
-    int candidateIndex = findFirstOverlap(start);
-    if (candidateIndex == -1)
-    {
-      /*
-       * all subranges precede this one - add it on the end
-       */
-      subranges.add(new NCNode<>(entry));
-      return true;
-    }
-
-    /*
-     * 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;
-    boolean overlapping = false;
-
-    for (int j = candidateIndex; j < subranges.size(); j++)
-    {
-      NCNode<T> subrange = subranges.get(j);
-
-      if (end < subrange.getBegin() && !overlapping && !enclosing)
-      {
-        /*
-         * new entry lies between subranges j-1 j
-         */
-        subranges.add(j, new NCNode<>(entry));
-        return true;
-      }
-
-      if (subrange.getBegin() <= start && subrange.getEnd() >= end)
-      {
-        /*
-         * push new entry inside this subrange as it encloses it
-         */
-        subrange.add(entry);
-        return true;
-      }
-      
-      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
-             */
-            addEnclosingRange(entry, firstEnclosed, lastEnclosed);
-            return true;
-          }
-          else
-          {
-            /*
-             * entry spans two subranges but doesn't enclose any
-             * so just add it 
-             */
-            subranges.add(j, new NCNode<>(entry));
-            return true;
-          }
-        }
-      }
-      else
-      {
-        overlapping = true;
-      }
-    }
-
-    /*
-     * drops through to here if new range encloses all others
-     * or overlaps the last one
-     */
-    if (enclosing)
-    {
-      addEnclosingRange(entry, firstEnclosed, lastEnclosed);
-    }
-    else
-    {
-      subranges.add(new NCNode<>(entry));
-    }
-
-    return true;
-  }
-  
-  /**
-   * Answers true if this NCList contains the given entry (by object equality
-   * test), else false
-   * 
-   * @param entry
-   * @return
-   */
-  public boolean contains(T entry)
-  {
-    /*
-     * find the first sublist that might overlap, i.e. 
-     * the first whose end position is >= from
-     */
-    int candidateIndex = findFirstOverlap(entry.getBegin());
-
-    if (candidateIndex == -1)
-    {
-      return false;
-    }
-
-    int to = entry.getEnd();
-
-    for (int i = candidateIndex; i < subranges.size(); i++)
-    {
-      NCNode<T> candidate = subranges.get(i);
-      if (candidate.getBegin() > to)
-      {
-        /*
-         * we are past the end of our target range
-         */
-        break;
-      }
-      if (candidate.contains(entry))
-      {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  /**
-   * Update the tree so that the range of the new entry encloses subranges i to
-   * j (inclusive). That is, replace subranges i-j (inclusive) with a new
-   * subrange that contains them.
-   * 
-   * @param entry
-   * @param i
-   * @param j
-   */
-  protected synchronized void addEnclosingRange(T entry, final int i,
-          final int j)
-  {
-    NCList<T> newNCList = new NCList<>();
-    newNCList.addNodes(subranges.subList(i, j + 1));
-    NCNode<T> newNode = new NCNode<>(entry, newNCList);
-    for (int k = j; k >= i; k--)
-    {
-      subranges.remove(k);
-    }
-    subranges.add(i, newNode);
-  }
-
-  protected void addNodes(List<NCNode<T>> nodes)
-  {
-    for (NCNode<T> node : nodes)
-    {
-      subranges.add(node);
-      size += node.size();
-    }
-  }
-
-  /**
-   * 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
-   */
-  public List<T> findOverlaps(long from, long to)
-  {
-    List<T> 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 void findOverlaps(long from, long to, List<T> result)
-  {
-    /*
-     * find the first sublist that might overlap, i.e. 
-     * the first whose end position is >= from
-     */
-    int candidateIndex = findFirstOverlap(from);
-
-    if (candidateIndex == -1)
-    {
-      return;
-    }
-
-    for (int i = candidateIndex; i < subranges.size(); i++)
-    {
-      NCNode<T> candidate = subranges.get(i);
-      if (candidate.getBegin() > to)
-      {
-        /*
-         * we are past the end of our target range
-         */
-        break;
-      }
-      candidate.findOverlaps(from, to, result);
-    }
-
-  }
-
-  /**
-   * 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 -1 if none found.
-   * 
-   * @param from
-   * @return
-   */
-  protected int findFirstOverlap(long from)
-  {
-    /*
-     * The NCList paper describes binary search for this step,
-     * but this not implemented here as (a) I haven't understood it yet
-     * and (b) it seems to imply complications for adding to an NCList
-     */
-
-    int i = 0;
-    if (subranges != null)
-    {
-      for (NCNode<T> subrange : subranges)
-      {
-        if (subrange.getEnd() >= from)
-        {
-          return i;
-        }
-        i++;
-      }
-    }
-    return -1;
-  }
-
-  /**
-   * Formats the tree as a bracketed list e.g.
-   * 
-   * <pre>
-   * [1-100 [10-30 [10-20]], 15-30 [20-20]]
-   * </pre>
-   */
-  @Override
-  public String toString()
-  {
-    return subranges.toString();
-  }
-
-  /**
-   * Returns a string representation of the data where containment is shown by
-   * indentation on new lines
-   * 
-   * @return
-   */
-  public String prettyPrint()
-  {
-    StringBuilder sb = new StringBuilder(512);
-    int offset = 0;
-    int indent = 2;
-    prettyPrint(sb, offset, indent);
-    sb.append(System.lineSeparator());
-    return sb.toString();
-  }
-
-  /**
-   * @param sb
-   * @param offset
-   * @param indent
-   */
-  void prettyPrint(StringBuilder sb, int offset, int indent)
-  {
-    boolean first = true;
-    for (NCNode<T> subrange : subranges)
-    {
-      if (!first)
-      {
-        sb.append(System.lineSeparator());
-      }
-      first = false;
-      subrange.prettyPrint(sb, offset, indent);
-    }
-  }
-
-  /**
-   * Answers true if the data held satisfy the rules of construction of an
-   * NCList, else false.
-   * 
-   * @return
-   */
-  public boolean isValid()
-  {
-    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.
-   * <p>
-   * Each subrange must lie within start-end (inclusive). Subranges must be
-   * ordered by start position ascending.
-   * <p>
-   * 
-   * @param start
-   * @param end
-   * @return
-   */
-  boolean isValid(final int start, final int end)
-  {
-    int lastStart = start;
-    for (NCNode<T> subrange : subranges)
-    {
-      if (subrange.getBegin() < lastStart)
-      {
-        System.err.println("error in NCList: range " + subrange.toString()
-                + " starts before " + lastStart);
-        return false;
-      }
-      if (subrange.getEnd() > end)
-      {
-        System.err.println("error in NCList: range " + subrange.toString()
-                + " ends after " + end);
-        return false;
-      }
-      lastStart = subrange.getBegin();
-
-      if (!subrange.isValid())
-      {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  /**
-   * Answers the lowest start position enclosed by the ranges
-   * 
-   * @return
-   */
-  public int getStart()
-  {
-    return subranges.isEmpty() ? 0 : subranges.get(0).getBegin();
-  }
-
-  /**
-   * Returns the number of ranges held (deep count)
-   * 
-   * @return
-   */
-  public int size()
-  {
-    return size;
-  }
-
-  /**
-   * Returns a list of all entries stored
-   * 
-   * @return
-   */
-  public List<T> getEntries()
-  {
-    List<T> result = new ArrayList<>();
-    getEntries(result);
-    return result;
-  }
-
-  /**
-   * Adds all contained entries to the given list
-   * 
-   * @param result
-   */
-  void getEntries(List<T> result)
-  {
-    for (NCNode<T> subrange : subranges)
-    {
-      subrange.getEntries(result);
-    }
-  }
-
-  /**
-   * Deletes the given entry from the store, returning true if it was found (and
-   * deleted), else false. This method makes no assumption that the entry is in
-   * the 'expected' place in the store, in case it has been modified since it
-   * was added. Only the first 'same object' match is deleted, not 'equal' or
-   * multiple objects.
-   * 
-   * @param entry
-   */
-  public synchronized boolean delete(T entry)
-  {
-    if (entry == null)
-    {
-      return false;
-    }
-    for (int i = 0; i < subranges.size(); i++)
-    {
-      NCNode<T> subrange = subranges.get(i);
-      NCList<T> subRegions = subrange.getSubRegions();
-
-      if (subrange.getRegion() == entry)
-      {
-        /*
-         * if the subrange is rooted on this entry, promote its
-         * subregions (if any) to replace the subrange here;
-         * NB have to resort subranges after doing this since e.g.
-         * [10-30 [12-20 [16-18], 13-19]]
-         * after deleting 12-20, 16-18 is promoted to sibling of 13-19
-         * but should follow it in the list of subranges of 10-30 
-         */
-        subranges.remove(i);
-        if (subRegions != null)
-        {
-          subranges.addAll(subRegions.subranges);
-          Collections.sort(subranges, RangeComparator.BY_START_POSITION);
-        }
-        size--;
-        return true;
-      }
-      else
-      {
-        if (subRegions != null && subRegions.delete(entry))
-        {
-          size--;
-          subrange.deleteSubRegionsIfEmpty();
-          return true;
-        }
-      }
-    }
-    return false;
-  }
-}
diff --git a/src/jalview/datamodel/features/NCNode.java b/src/jalview/datamodel/features/NCNode.java
deleted file mode 100644 (file)
index b991750..0000000
+++ /dev/null
@@ -1,275 +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 <http://www.gnu.org/licenses/>.
- * The Jalview Authors are detailed in the 'AUTHORS' file.
- */
-package jalview.datamodel.features;
-
-import jalview.datamodel.ContiguousI;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Each node of the NCList tree consists of a range, and (optionally) the NCList
- * of ranges it encloses
- *
- * @param <V>
- */
-class NCNode<V extends ContiguousI> implements ContiguousI
-{
-  /*
-   * deep size (number of ranges included)
-   */
-  private int size;
-
-  private V region;
-
-  /*
-   * null, or an object holding contained subregions of this nodes region
-   */
-  private NCList<V> subregions;
-
-  /**
-   * Constructor given a list of ranges
-   * 
-   * @param ranges
-   */
-  NCNode(List<V> ranges)
-  {
-    build(ranges);
-  }
-
-  /**
-   * Constructor given a single range
-   * 
-   * @param range
-   */
-  NCNode(V range)
-  {
-    List<V> ranges = new ArrayList<>();
-    ranges.add(range);
-    build(ranges);
-  }
-
-  NCNode(V entry, NCList<V> newNCList)
-  {
-    region = entry;
-    subregions = newNCList;
-    size = 1 + newNCList.size();
-  }
-
-  /**
-   * @param ranges
-   */
-  protected void build(List<V> ranges)
-  {
-    size = ranges.size();
-
-    if (!ranges.isEmpty())
-    {
-      region = ranges.get(0);
-    }
-    if (ranges.size() > 1)
-    {
-      subregions = new NCList<V>(ranges.subList(1, ranges.size()));
-    }
-  }
-
-  @Override
-  public int getBegin()
-  {
-    return region.getBegin();
-  }
-
-  @Override
-  public int getEnd()
-  {
-    return region.getEnd();
-  }
-
-  /**
-   * Formats the node as a bracketed list e.g.
-   * 
-   * <pre>
-   * [1-100 [10-30 [10-20]], 15-30 [20-20]]
-   * </pre>
-   */
-  @Override
-  public String toString() {
-    StringBuilder sb = new StringBuilder(10 * size);
-    sb.append(region.getBegin()).append("-").append(region.getEnd());
-    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.getBegin()).append("-").append(region.getEnd());
-    if (subregions != null)
-    {
-      sb.append(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<V> result)
-  {
-    if (region.getBegin() <= to && region.getEnd() >= from)
-    {
-      result.add(region);
-    }
-    if (subregions != null)
-    {
-      subregions.findOverlaps(from, to, result);
-    }
-  }
-
-  /**
-   * Add one range to this subrange
-   * 
-   * @param entry
-   */
-  synchronized void add(V entry)
-  {
-    if (entry.getBegin() < region.getBegin() || entry.getEnd() > region.getEnd()) {
-      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<V>(entry);
-    }
-    else
-    {
-      subregions.add(entry);
-    }
-    size++;
-  }
-
-  /**
-   * 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;
-    }
-    return subregions.isValid(getBegin(), getEnd());
-  }
-
-  /**
-   * Adds all contained entries to the given list
-   * 
-   * @param entries
-   */
-  void getEntries(List<V> entries)
-  {
-    entries.add(region);
-    if (subregions != null)
-    {
-      subregions.getEntries(entries);
-    }
-  }
-
-  /**
-   * Answers true if this object contains the given entry (by object equals
-   * test), else false
-   * 
-   * @param entry
-   * @return
-   */
-  boolean contains(V entry)
-  {
-    if (entry == null)
-    {
-      return false;
-    }
-    if (entry.equals(region))
-    {
-      return true;
-    }
-    return subregions == null ? false : subregions.contains(entry);
-  }
-
-  /**
-   * Answers the 'root' region modelled by this object
-   * 
-   * @return
-   */
-  V getRegion()
-  {
-    return region;
-  }
-
-  /**
-   * Answers the (possibly null) contained regions within this object
-   * 
-   * @return
-   */
-  NCList<V> getSubRegions()
-  {
-    return subregions;
-  }
-
-  /**
-   * Nulls the subregion reference if it is empty (after a delete entry
-   * operation)
-   */
-  void deleteSubRegionsIfEmpty()
-  {
-    if (subregions != null && subregions.size() == 0)
-    {
-      subregions = null;
-    }
-  }
-
-  /**
-   * Answers the (deep) size of this node i.e. the number of ranges it models
-   * 
-   * @return
-   */
-  int size()
-  {
-    return size;
-  }
-}
diff --git a/src/jalview/datamodel/features/RangeComparator.java b/src/jalview/datamodel/features/RangeComparator.java
deleted file mode 100644 (file)
index b7d702d..0000000
+++ /dev/null
@@ -1,98 +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 <http://www.gnu.org/licenses/>.
- * The Jalview Authors are detailed in the 'AUTHORS' file.
- */
-package jalview.datamodel.features;
-
-import jalview.datamodel.ContiguousI;
-
-import java.util.Comparator;
-
-/**
- * A comparator that orders ranges by either start position or end position
- * ascending. If the position matches, ordering is resolved by end position (or
- * start position).
- * 
- * @author gmcarstairs
- *
- */
-public class RangeComparator implements Comparator<ContiguousI>
-{
-  public static final Comparator<ContiguousI> BY_START_POSITION = new RangeComparator(
-          true);
-
-  public static final Comparator<ContiguousI> BY_END_POSITION = new RangeComparator(
-          false);
-
-  boolean byStart;
-
-  /**
-   * Constructor
-   * 
-   * @param byStartPosition
-   *          if true, order based on start position, if false by end position
-   */
-  RangeComparator(boolean byStartPosition)
-  {
-    byStart = byStartPosition;
-  }
-
-  @Override
-  public int compare(ContiguousI o1, ContiguousI o2)
-  {
-    int len1 = o1.getEnd() - o1.getBegin();
-    int len2 = o2.getEnd() - o2.getBegin();
-
-    if (byStart)
-    {
-      return compare(o1.getBegin(), o2.getBegin(), len1, len2);
-    }
-    else
-    {
-      return compare(o1.getEnd(), o2.getEnd(), len1, len2);
-    }
-  }
-
-  /**
-   * Compares two ranges for ordering
-   * 
-   * @param pos1
-   *          first range positional ordering criterion
-   * @param pos2
-   *          second range positional ordering criterion
-   * @param len1
-   *          first range length ordering criterion
-   * @param len2
-   *          second range length ordering criterion
-   * @return
-   */
-  public int compare(long pos1, long pos2, int len1, int len2)
-  {
-    int order = Long.compare(pos1, pos2);
-    if (order == 0)
-    {
-      /*
-       * if tied on position order, longer length sorts to left
-       * i.e. the negation of normal ordering by length
-       */
-      order = -Integer.compare(len1, len2);
-    }
-    return order;
-  }
-}
index 727d3ef..bd102f6 100644 (file)
@@ -20,7 +20,6 @@
  */
 package jalview.datamodel.features;
 
-import jalview.datamodel.ContiguousI;
 import jalview.datamodel.SequenceFeature;
 import jalview.io.gff.SequenceOntologyFactory;
 import jalview.io.gff.SequenceOntologyI;
@@ -36,6 +35,8 @@ import java.util.Map.Entry;
 import java.util.Set;
 import java.util.TreeMap;
 
+import intervalstore.api.IntervalI;
+
 /**
  * A class that stores sequence features in a way that supports efficient
  * querying by type and location (overlap). Intended for (but not limited to)
@@ -49,10 +50,10 @@ public class SequenceFeatures implements SequenceFeaturesI
   /**
    * a comparator for sorting features by start position ascending
    */
-  private static Comparator<ContiguousI> FORWARD_STRAND = new Comparator<ContiguousI>()
+  private static Comparator<IntervalI> FORWARD_STRAND = new Comparator<IntervalI>()
   {
     @Override
-    public int compare(ContiguousI o1, ContiguousI o2)
+    public int compare(IntervalI o1, IntervalI o2)
     {
       return Integer.compare(o1.getBegin(), o2.getBegin());
     }
@@ -61,10 +62,10 @@ public class SequenceFeatures implements SequenceFeaturesI
   /**
    * a comparator for sorting features by end position descending
    */
-  private static Comparator<ContiguousI> REVERSE_STRAND = new Comparator<ContiguousI>()
+  private static Comparator<IntervalI> REVERSE_STRAND = new Comparator<IntervalI>()
   {
     @Override
-    public int compare(ContiguousI o1, ContiguousI o2)
+    public int compare(IntervalI o1, IntervalI o2)
     {
       return Integer.compare(o2.getEnd(), o1.getEnd());
     }
index 795cd36..9db55d3 100644 (file)
@@ -22,7 +22,6 @@ package jalview.renderer.seqfeatures;
 
 import jalview.api.AlignViewportI;
 import jalview.api.FeatureColourI;
-import jalview.datamodel.Range;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
 import jalview.util.Comparison;
@@ -35,6 +34,8 @@ import java.awt.Graphics;
 import java.awt.Graphics2D;
 import java.util.List;
 
+import intervalstore.api.IntervalI;
+
 public class FeatureRenderer extends FeatureRendererModel
 {
   private static final AlphaComposite NO_TRANSPARENCY = AlphaComposite
@@ -272,7 +273,7 @@ public class FeatureRenderer extends FeatureRendererModel
     /*
      * if columns are all gapped, or sequence has no features, nothing to do
      */
-    Range visiblePositions = seq.findPositions(start+1, end+1);
+    IntervalI visiblePositions = seq.findPositions(start + 1, end + 1);
     if (visiblePositions == null || !seq.getFeatures().hasFeatures())
     {
       return null;
index 79bb2bb..787179b 100644 (file)
@@ -48,11 +48,11 @@ import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
+import intervalstore.impl.Range;
 import junit.extensions.PA;
 
 public class SequenceTest
 {
-
   @BeforeClass(alwaysRun = true)
   public void setUpJvOptionPane()
   {
index db21c2f..2972192 100644 (file)
@@ -184,58 +184,6 @@ public class FeatureStoreTest
     assertTrue(overlaps.contains(sf));
   }
 
-  /**
-   * Tests for the method that returns false for an attempt to add a feature
-   * that would enclose, or be enclosed by, another feature
-   */
-  @Test(groups = "Functional")
-  public void testAddNonNestedFeature()
-  {
-    FeatureStore fs = new FeatureStore();
-
-    String type = "Domain";
-    SequenceFeature sf1 = new SequenceFeature(type, type, 10, 20,
-            Float.NaN, null);
-    assertTrue(fs.addNonNestedFeature(sf1));
-
-    // co-located feature is ok
-    SequenceFeature sf2 = new SequenceFeature(type, type, 10, 20,
-            Float.NaN, null);
-    assertTrue(fs.addNonNestedFeature(sf2));
-
-    // overlap left is ok
-    SequenceFeature sf3 = new SequenceFeature(type, type, 5, 15, Float.NaN,
-            null);
-    assertTrue(fs.addNonNestedFeature(sf3));
-
-    // overlap right is ok
-    SequenceFeature sf4 = new SequenceFeature(type, type, 15, 25,
-            Float.NaN, null);
-    assertTrue(fs.addNonNestedFeature(sf4));
-
-    // add enclosing feature is not ok
-    SequenceFeature sf5 = new SequenceFeature(type, type, 10, 21,
-            Float.NaN, null);
-    assertFalse(fs.addNonNestedFeature(sf5));
-    SequenceFeature sf6 = new SequenceFeature(type, type, 4, 15, Float.NaN,
-            null);
-    assertFalse(fs.addNonNestedFeature(sf6));
-    SequenceFeature sf7 = new SequenceFeature(type, type, 1, 50, Float.NaN,
-            null);
-    assertFalse(fs.addNonNestedFeature(sf7));
-
-    // add enclosed feature is not ok
-    SequenceFeature sf8 = new SequenceFeature(type, type, 10, 19,
-            Float.NaN, null);
-    assertFalse(fs.addNonNestedFeature(sf8));
-    SequenceFeature sf9 = new SequenceFeature(type, type, 16, 25,
-            Float.NaN, null);
-    assertFalse(fs.addNonNestedFeature(sf9));
-    SequenceFeature sf10 = new SequenceFeature(type, type, 7, 7, Float.NaN,
-            null);
-    assertFalse(fs.addNonNestedFeature(sf10));
-  }
-
   @Test(groups = "Functional")
   public void testGetPositionalFeatures()
   {
@@ -462,9 +410,7 @@ public class FeatureStoreTest
     assertEquals(fs.getFeatureCount(true), 3);
     assertTrue(fs.delete(sf1));
     assertEquals(fs.getFeatureCount(true), 2);
-    // FeatureStore should now only contain features in the NCList
-    assertTrue(fs.nonNestedFeatures.isEmpty());
-    assertEquals(fs.nestedFeatures.size(), 2);
+    assertEquals(fs.features.size(), 2);
     assertFalse(fs.isEmpty());
     assertTrue(fs.delete(sf2));
     assertEquals(fs.getFeatureCount(true), 1);
@@ -693,7 +639,7 @@ public class FeatureStoreTest
   public void testListContains()
   {
     assertFalse(FeatureStore.listContains(null, null));
-    List<SequenceFeature> features = new ArrayList<SequenceFeature>();
+    List<SequenceFeature> features = new ArrayList<>();
     assertFalse(FeatureStore.listContains(features, null));
 
     SequenceFeature sf1 = new SequenceFeature("type1", "desc1", 20, 30, 3f,
@@ -842,8 +788,8 @@ public class FeatureStoreTest
     assertEquals(features.size(), 2);
     assertTrue(features.contains(sf1));
     assertTrue(features.contains(sf2));
-    assertTrue(store.nonNestedFeatures.contains(sf1));
-    assertTrue(store.nestedFeatures.contains(sf2));
+    assertTrue(store.features.contains(sf1));
+    assertTrue(store.features.contains(sf2));
   
     /*
      * delete the first feature
diff --git a/test/jalview/datamodel/features/NCListTest.java b/test/jalview/datamodel/features/NCListTest.java
deleted file mode 100644 (file)
index 2c7f752..0000000
+++ /dev/null
@@ -1,682 +0,0 @@
-package jalview.datamodel.features;
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertSame;
-import static org.testng.Assert.assertTrue;
-
-import jalview.datamodel.ContiguousI;
-import jalview.datamodel.Range;
-import jalview.datamodel.SequenceFeature;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Random;
-
-import junit.extensions.PA;
-
-import org.testng.annotations.DataProvider;
-import org.testng.annotations.Test;
-
-public class NCListTest
-{
-
-  private Random random = new Random(107);
-
-  private Comparator<ContiguousI> sorter = new RangeComparator(true);
-
-  /**
-   * A basic sanity test of the constructor
-   */
-  @Test(groups = "Functional")
-  public void testConstructor()
-  {
-    List<Range> ranges = new ArrayList<Range>();
-    ranges.add(new Range(20, 20));
-    ranges.add(new Range(10, 20));
-    ranges.add(new Range(15, 30));
-    ranges.add(new Range(10, 30));
-    ranges.add(new Range(11, 19));
-    ranges.add(new Range(10, 20));
-    ranges.add(new Range(1, 100));
-
-    NCList<Range> ncl = new NCList<Range>(ranges);
-    String expected = "[1-100 [10-30 [10-20 [10-20 [11-19]]]], 15-30 [20-20]]";
-    assertEquals(ncl.toString(), expected);
-    assertTrue(ncl.isValid());
-
-    Collections.reverse(ranges);
-    ncl = new NCList<Range>(ranges);
-    assertEquals(ncl.toString(), expected);
-    assertTrue(ncl.isValid());
-  }
-
-  @Test(groups = "Functional")
-  public void testFindOverlaps()
-  {
-    List<Range> ranges = new ArrayList<Range>();
-    ranges.add(new Range(20, 50));
-    ranges.add(new Range(30, 70));
-    ranges.add(new Range(1, 100));
-    ranges.add(new Range(70, 120));
-  
-    NCList<Range> ncl = new NCList<Range>(ranges);
-
-    List<Range> overlaps = ncl.findOverlaps(121, 122);
-    assertEquals(overlaps.size(), 0);
-
-    overlaps = ncl.findOverlaps(21, 22);
-    assertEquals(overlaps.size(), 2);
-    assertEquals(((ContiguousI) overlaps.get(0)).getBegin(), 1);
-    assertEquals(((ContiguousI) overlaps.get(0)).getEnd(), 100);
-    assertEquals(((ContiguousI) overlaps.get(1)).getBegin(), 20);
-    assertEquals(((ContiguousI) overlaps.get(1)).getEnd(), 50);
-
-    overlaps = ncl.findOverlaps(110, 110);
-    assertEquals(overlaps.size(), 1);
-    assertEquals(((ContiguousI) overlaps.get(0)).getBegin(), 70);
-    assertEquals(((ContiguousI) overlaps.get(0)).getEnd(), 120);
-  }
-
-  @Test(groups = "Functional")
-  public void testAdd_onTheEnd()
-  {
-    List<Range> ranges = new ArrayList<Range>();
-    ranges.add(new Range(20, 50));
-    NCList<Range> ncl = new NCList<Range>(ranges);
-    assertEquals(ncl.toString(), "[20-50]");
-    assertTrue(ncl.isValid());
-
-    ncl.add(new Range(60, 70));
-    assertEquals(ncl.toString(), "[20-50, 60-70]");
-    assertTrue(ncl.isValid());
-  }
-
-  @Test(groups = "Functional")
-  public void testAdd_inside()
-  {
-    List<Range> ranges = new ArrayList<Range>();
-    ranges.add(new Range(20, 50));
-    NCList<Range> ncl = new NCList<Range>(ranges);
-    assertEquals(ncl.toString(), "[20-50]");
-    assertTrue(ncl.isValid());
-
-    ncl.add(new Range(30, 40));
-    assertEquals(ncl.toString(), "[20-50 [30-40]]");
-  }
-
-  @Test(groups = "Functional")
-  public void testAdd_onTheFront()
-  {
-    List<Range> ranges = new ArrayList<Range>();
-    ranges.add(new Range(20, 50));
-    NCList<Range> ncl = new NCList<Range>(ranges);
-    assertEquals(ncl.toString(), "[20-50]");
-    assertTrue(ncl.isValid());
-
-    ncl.add(new Range(5, 15));
-    assertEquals(ncl.toString(), "[5-15, 20-50]");
-    assertTrue(ncl.isValid());
-  }
-
-  @Test(groups = "Functional")
-  public void testAdd_enclosing()
-  {
-    List<Range> ranges = new ArrayList<Range>();
-    ranges.add(new Range(20, 50));
-    ranges.add(new Range(30, 60));
-    NCList<Range> ncl = new NCList<Range>(ranges);
-    assertEquals(ncl.toString(), "[20-50, 30-60]");
-    assertTrue(ncl.isValid());
-    assertEquals(ncl.getStart(), 20);
-
-    ncl.add(new Range(10, 70));
-    assertEquals(ncl.toString(), "[10-70 [20-50, 30-60]]");
-    assertTrue(ncl.isValid());
-  }
-
-  @Test(groups = "Functional")
-  public void testAdd_spanning()
-  {
-    List<Range> ranges = new ArrayList<Range>();
-    ranges.add(new Range(20, 40));
-    ranges.add(new Range(60, 70));
-    NCList<Range> ncl = new NCList<Range>(ranges);
-    assertEquals(ncl.toString(), "[20-40, 60-70]");
-    assertTrue(ncl.isValid());
-
-    ncl.add(new Range(30, 50));
-    assertEquals(ncl.toString(), "[20-40, 30-50, 60-70]");
-    assertTrue(ncl.isValid());
-
-    ncl.add(new Range(40, 65));
-    assertEquals(ncl.toString(), "[20-40, 30-50, 40-65, 60-70]");
-    assertTrue(ncl.isValid());
-  }
-
-  /**
-   * Provides the scales for pseudo-random NCLists i.e. the range of the maximal
-   * [0-scale] interval to be stored
-   * 
-   * @return
-   */
-  @DataProvider(name = "scalesOfLife")
-  public Object[][] getScales()
-  {
-    return new Object[][] { new Integer[] { 10 }, new Integer[] { 100 } };
-  }
-
-  /**
-   * Do a number of pseudo-random (reproducible) builds of an NCList, to
-   * exercise as many methods of the class as possible while generating the
-   * range of possible structure topologies
-   * <ul>
-   * <li>verify that add adds an entry and increments size</li>
-   * <li>...except where the entry is already contained (by equals test)</li>
-   * <li>verify that the structure is valid at all stages of construction</li>
-   * <li>generate, run and verify a range of overlap queries</li>
-   * <li>tear down the structure by deleting entries, verifying correctness at
-   * each stage</li>
-   * </ul>
-   */
-  @Test(groups = "Functional", dataProvider = "scalesOfLife")
-  public void test_pseudoRandom(Integer scale)
-  {
-    NCList<SequenceFeature> ncl = new NCList<SequenceFeature>();
-    List<SequenceFeature> features = new ArrayList<SequenceFeature>(scale);
-    
-    testAdd_pseudoRandom(scale, ncl, features);
-
-    /*
-     * sort the list of added ranges - this doesn't affect the test,
-     * just makes it easier to inspect the data in the debugger
-     */
-    Collections.sort(features, sorter);
-
-    testFindOverlaps_pseudoRandom(ncl, scale, features);
-
-    testDelete_pseudoRandom(ncl, features);
-  }
-
-  /**
-   * Pick randomly selected entries to delete in turn, checking the NCList size
-   * and validity at each stage, until it is empty
-   * 
-   * @param ncl
-   * @param features
-   */
-  protected void testDelete_pseudoRandom(NCList<SequenceFeature> ncl,
-          List<SequenceFeature> features)
-  {
-    int deleted = 0;
-
-    while (!features.isEmpty())
-    {
-      assertEquals(ncl.size(), features.size());
-      int toDelete = random.nextInt(features.size());
-      SequenceFeature entry = features.get(toDelete);
-      assertTrue(ncl.contains(entry), String.format(
-              "NCList doesn't contain entry [%d] '%s'!", deleted,
-              entry.toString()));
-
-      ncl.delete(entry);
-      assertFalse(ncl.contains(entry), String.format(
-              "NCList still contains deleted entry [%d] '%s'!", deleted,
-              entry.toString()));
-      features.remove(toDelete);
-      deleted++;
-
-      assertTrue(ncl.isValid(), String.format(
-              "NCList invalid after %d deletions, last deleted was '%s'",
-              deleted, entry.toString()));
-
-      /*
-       * brute force check that deleting one entry didn't delete any others
-       */
-      for (int i = 0; i < features.size(); i++)
-      {
-        SequenceFeature sf = features.get(i);
-        assertTrue(ncl.contains(sf), String.format(
-                        "NCList doesn't contain entry [%d] %s after deleting '%s'!",
-                        i, sf.toString(), entry.toString()));
-      }
-    }
-    assertEquals(ncl.size(), 0); // all gone
-  }
-
-  /**
-   * Randomly generate entries and add them to the NCList, checking its validity
-   * and size at each stage. A few entries should be duplicates (by equals test)
-   * so not get added.
-   * 
-   * @param scale
-   * @param ncl
-   * @param features
-   */
-  protected void testAdd_pseudoRandom(Integer scale,
-          NCList<SequenceFeature> ncl,
-          List<SequenceFeature> features)
-  {
-    int count = 0;
-    final int size = 50;
-
-    for (int i = 0; i < size; i++)
-    {
-      int r1 = random.nextInt(scale + 1);
-      int r2 = random.nextInt(scale + 1);
-      int from = Math.min(r1, r2);
-      int to = Math.max(r1, r2);
-
-      /*
-       * choice of two feature values means that occasionally an identical
-       * feature may be generated, in which case it should not be added 
-       */
-      float value = (float) i % 2;
-      SequenceFeature feature = new SequenceFeature("Pfam", "", from, to,
-              value, "group");
-
-      /*
-       * add to NCList - with duplicate entries (by equals) disallowed
-       */
-      ncl.add(feature, false);
-      if (features.contains(feature))
-      {
-        System.out.println("Duplicate feature generated "
-                + feature.toString());
-      }
-      else
-      {
-        features.add(feature);
-        count++;
-      }
-    
-      /*
-       * check list format is valid at each stage of its construction
-       */
-      assertTrue(ncl.isValid(),
-              String.format("Failed for scale = %d, i=%d", scale, i));
-      assertEquals(ncl.size(), count);
-    }
-    // System.out.println(ncl.prettyPrint());
-  }
-
-  /**
-   * A helper method that generates pseudo-random range queries and veries that
-   * findOverlaps returns the correct matches
-   * 
-   * @param ncl
-   *          the NCList to query
-   * @param scale
-   *          ncl maximal range is [0, scale]
-   * @param features
-   *          a list of the ranges stored in ncl
-   */
-  protected void testFindOverlaps_pseudoRandom(NCList<SequenceFeature> ncl,
-          int scale,
-          List<SequenceFeature> features)
-  {
-    int halfScale = scale / 2;
-    int minIterations = 20;
-
-    /*
-     * generates ranges in [-halfScale, scale+halfScale]
-     * - some should be internal to [0, scale] P = 1/4
-     * - some should lie before 0 P = 1/16
-     * - some should lie after scale P = 1/16
-     * - some should overlap left P = 1/4
-     * - some should overlap right P = 1/4
-     * - some should enclose P = 1/8
-     * 
-     * 50 iterations give a 96% probability of including the
-     * unlikeliest case; keep going until we have done all!
-     */
-    boolean inside = false;
-    boolean enclosing = false;
-    boolean before = false;
-    boolean after = false;
-    boolean overlapLeft = false;
-    boolean overlapRight = false;
-    boolean allCasesCovered = false;
-
-    int i = 0;
-    while (i < minIterations || !allCasesCovered)
-    {
-      i++;
-      int r1 = random.nextInt((scale + 1) * 2);
-      int r2 = random.nextInt((scale + 1) * 2);
-      int from = Math.min(r1, r2) - halfScale;
-      int to = Math.max(r1, r2) - halfScale;
-
-      /*
-       * ensure all cases of interest get covered
-       */
-      inside |= from >= 0 && to <= scale;
-      enclosing |= from <= 0 && to >= scale;
-      before |= to < 0;
-      after |= from > scale;
-      overlapLeft |= from < 0 && to >= 0 && to <= scale;
-      overlapRight |= from >= 0 && from <= scale && to > scale;
-      if (!allCasesCovered)
-      {
-        allCasesCovered |= inside && enclosing && before && after
-              && overlapLeft && overlapRight;
-        if (allCasesCovered)
-        {
-          System.out
-                  .println(String
-                          .format("Covered all findOverlaps cases after %d iterations for scale %d",
-                                  i, scale));
-        }
-      }
-
-      verifyFindOverlaps(ncl, from, to, features);
-    }
-  }
-
-  /**
-   * A helper method that verifies that overlaps found by interrogating an
-   * NCList correctly match those found by brute force search
-   * 
-   * @param ncl
-   * @param from
-   * @param to
-   * @param features
-   */
-  protected void verifyFindOverlaps(NCList<SequenceFeature> ncl, int from,
-          int to, List<SequenceFeature> features)
-  {
-    List<SequenceFeature> overlaps = ncl.findOverlaps(from, to);
-
-    /*
-     * check returned entries do indeed overlap from-to range
-     */
-    for (ContiguousI sf : overlaps)
-    {
-      int begin = sf.getBegin();
-      int end = sf.getEnd();
-      assertTrue(begin <= to && end >= from, String.format(
-              "[%d, %d] does not overlap query range [%d, %d]", begin, end,
-              from, to));
-    }
-
-    /*
-     * check overlapping ranges are included in the results
-     * (the test above already shows non-overlapping ranges are not)
-     */
-    for (ContiguousI sf : features)
-    {
-      int begin = sf.getBegin();
-      int end = sf.getEnd();
-      if (begin <= to && end >= from)
-      {
-        boolean found = overlaps.contains(sf);
-        assertTrue(found, String.format(
-                "[%d, %d] missing in query range [%d, %d]", begin, end,
-                from, to));
-      }
-    }
-  }
-
-  @Test(groups = "Functional")
-  public void testGetEntries()
-  {
-    List<Range> ranges = new ArrayList<Range>();
-    Range r1 = new Range(20, 20);
-    Range r2 = new Range(10, 20);
-    Range r3 = new Range(15, 30);
-    Range r4 = new Range(10, 30);
-    Range r5 = new Range(11, 19);
-    Range r6 = new Range(10, 20);
-    ranges.add(r1);
-    ranges.add(r2);
-    ranges.add(r3);
-    ranges.add(r4);
-    ranges.add(r5);
-    ranges.add(r6);
-  
-    NCList<Range> ncl = new NCList<Range>(ranges);
-    Range r7 = new Range(1, 100);
-    ncl.add(r7);
-
-    List<Range> contents = ncl.getEntries();
-    assertEquals(contents.size(), 7);
-    assertTrue(contents.contains(r1));
-    assertTrue(contents.contains(r2));
-    assertTrue(contents.contains(r3));
-    assertTrue(contents.contains(r4));
-    assertTrue(contents.contains(r5));
-    assertTrue(contents.contains(r6));
-    assertTrue(contents.contains(r7));
-
-    ncl = new NCList<Range>();
-    assertTrue(ncl.getEntries().isEmpty());
-  }
-
-  @Test(groups = "Functional")
-  public void testDelete()
-  {
-    List<Range> ranges = new ArrayList<Range>();
-    Range r1 = new Range(20, 30);
-    ranges.add(r1);
-    NCList<Range> ncl = new NCList<Range>(ranges);
-    assertTrue(ncl.getEntries().contains(r1));
-
-    Range r2 = new Range(20, 30);
-    assertFalse(ncl.delete(null)); // null argument
-    assertFalse(ncl.delete(r2)); // never added
-    assertTrue(ncl.delete(r1)); // success
-    assertTrue(ncl.getEntries().isEmpty());
-
-    /*
-     * tests where object.equals() == true
-     */
-    NCList<SequenceFeature> features = new NCList<SequenceFeature>();
-    SequenceFeature sf1 = new SequenceFeature("type", "desc", 1, 10, 2f,
-            "group");
-    SequenceFeature sf2 = new SequenceFeature("type", "desc", 1, 10, 2f,
-            "group");
-    features.add(sf1);
-    assertEquals(sf1, sf2); // sf1.equals(sf2)
-    assertFalse(features.delete(sf2)); // equality is not enough for deletion
-    assertTrue(features.getEntries().contains(sf1)); // still there!
-    assertTrue(features.delete(sf1));
-    assertTrue(features.getEntries().isEmpty()); // gone now
-
-    /*
-     * test with duplicate objects in NCList
-     */
-    features.add(sf1);
-    features.add(sf1);
-    assertEquals(features.getEntries().size(), 2);
-    assertSame(features.getEntries().get(0), sf1);
-    assertSame(features.getEntries().get(1), sf1);
-    assertTrue(features.delete(sf1)); // first match only is deleted
-    assertTrue(features.contains(sf1));
-    assertEquals(features.size(), 1);
-    assertTrue(features.delete(sf1));
-    assertTrue(features.getEntries().isEmpty());
-  }
-
-  @Test(groups = "Functional")
-  public void testAdd_overlapping()
-  {
-    List<Range> ranges = new ArrayList<Range>();
-    ranges.add(new Range(40, 50));
-    ranges.add(new Range(20, 30));
-    NCList<Range> ncl = new NCList<Range>(ranges);
-    assertEquals(ncl.toString(), "[20-30, 40-50]");
-    assertTrue(ncl.isValid());
-  
-    /*
-     * add range overlapping internally
-     */
-    ncl.add(new Range(25, 35));
-    assertEquals(ncl.toString(), "[20-30, 25-35, 40-50]");
-    assertTrue(ncl.isValid());
-
-    /*
-     * add range overlapping last range
-     */
-    ncl.add(new Range(45, 55));
-    assertEquals(ncl.toString(), "[20-30, 25-35, 40-50, 45-55]");
-    assertTrue(ncl.isValid());
-
-    /*
-     * add range overlapping first range
-     */
-    ncl.add(new Range(15, 25));
-    assertEquals(ncl.toString(), "[15-25, 20-30, 25-35, 40-50, 45-55]");
-    assertTrue(ncl.isValid());
-  }
-
-  /**
-   * Test the contains method (which uses object equals test)
-   */
-  @Test(groups = "Functional")
-  public void testContains()
-  {
-    NCList<SequenceFeature> ncl = new NCList<SequenceFeature>();
-    SequenceFeature sf1 = new SequenceFeature("type", "desc", 1, 10, 2f,
-            "group");
-    SequenceFeature sf2 = new SequenceFeature("type", "desc", 1, 10, 2f,
-            "group");
-    SequenceFeature sf3 = new SequenceFeature("type", "desc", 1, 10, 2f,
-            "anothergroup");
-    ncl.add(sf1);
-
-    assertTrue(ncl.contains(sf1));
-    assertTrue(ncl.contains(sf2)); // sf1.equals(sf2)
-    assertFalse(ncl.contains(sf3)); // !sf1.equals(sf3)
-
-    /*
-     * make some deeper structure in the NCList
-     */
-    SequenceFeature sf4 = new SequenceFeature("type", "desc", 2, 9, 2f,
-            "group");
-    ncl.add(sf4);
-    assertTrue(ncl.contains(sf4));
-    SequenceFeature sf5 = new SequenceFeature("type", "desc", 4, 5, 2f,
-            "group");
-    SequenceFeature sf6 = new SequenceFeature("type", "desc", 6, 8, 2f,
-            "group");
-    ncl.add(sf5);
-    ncl.add(sf6);
-    assertTrue(ncl.contains(sf5));
-    assertTrue(ncl.contains(sf6));
-  }
-
-  @Test(groups = "Functional")
-  public void testIsValid()
-  {
-    List<Range> ranges = new ArrayList<Range>();
-    Range r1 = new Range(40, 50);
-    ranges.add(r1);
-    NCList<Range> ncl = new NCList<Range>(ranges);
-    assertTrue(ncl.isValid());
-
-    Range r2 = new Range(42, 44);
-    ncl.add(r2);
-    assertTrue(ncl.isValid());
-    Range r3 = new Range(46, 48);
-    ncl.add(r3);
-    assertTrue(ncl.isValid());
-    Range r4 = new Range(43, 43);
-    ncl.add(r4);
-    assertTrue(ncl.isValid());
-
-    assertEquals(ncl.toString(), "[40-50 [42-44 [43-43], 46-48]]");
-    assertTrue(ncl.isValid());
-
-    PA.setValue(r1, "start", 43);
-    assertFalse(ncl.isValid()); // r2 not inside r1
-    PA.setValue(r1, "start", 40);
-    assertTrue(ncl.isValid());
-
-    PA.setValue(r3, "start", 41);
-    assertFalse(ncl.isValid()); // r3 should precede r2
-    PA.setValue(r3, "start", 46);
-    assertTrue(ncl.isValid());
-
-    PA.setValue(r4, "start", 41);
-    assertFalse(ncl.isValid()); // r4 not inside r2
-    PA.setValue(r4, "start", 43);
-    assertTrue(ncl.isValid());
-
-    PA.setValue(r4, "start", 44);
-    assertFalse(ncl.isValid()); // r4 has reverse range
-  }
-
-  @Test(groups = "Functional")
-  public void testPrettyPrint()
-  {
-    /*
-     * construct NCList from a list of ranges
-     * they are sorted then assembled into NCList subregions
-     * notice that 42-42 end up inside 41-46
-     */
-    List<Range> ranges = new ArrayList<Range>();
-    ranges.add(new Range(40, 50));
-    ranges.add(new Range(45, 55));
-    ranges.add(new Range(40, 45));
-    ranges.add(new Range(41, 46));
-    ranges.add(new Range(42, 42));
-    ranges.add(new Range(42, 42));
-    NCList<Range> ncl = new NCList<Range>(ranges);
-    assertTrue(ncl.isValid());
-    assertEquals(ncl.toString(),
-            "[40-50 [40-45], 41-46 [42-42 [42-42]], 45-55]");
-    String expected = "40-50\n  40-45\n41-46\n  42-42\n    42-42\n45-55\n";
-    assertEquals(ncl.prettyPrint(), expected);
-
-    /*
-     * repeat but now add ranges one at a time
-     * notice that 42-42 end up inside 40-50 so we get
-     * a different but equal valid NCList structure
-     */
-    ranges.clear();
-    ncl = new NCList<Range>(ranges);
-    ncl.add(new Range(40, 50));
-    ncl.add(new Range(45, 55));
-    ncl.add(new Range(40, 45));
-    ncl.add(new Range(41, 46));
-    ncl.add(new Range(42, 42));
-    ncl.add(new Range(42, 42));
-    assertTrue(ncl.isValid());
-    assertEquals(ncl.toString(),
-            "[40-50 [40-45 [42-42 [42-42]], 41-46], 45-55]");
-    expected = "40-50\n  40-45\n    42-42\n      42-42\n  41-46\n45-55\n";
-    assertEquals(ncl.prettyPrint(), expected);
-  }
-
-  /**
-   * A test that shows different valid trees can be constructed from the same
-   * set of ranges, depending on the order of construction
-   */
-  @Test(groups = "Functional")
-  public void testConstructor_alternativeTrees()
-  {
-    List<Range> ranges = new ArrayList<Range>();
-    ranges.add(new Range(10, 60));
-    ranges.add(new Range(20, 30));
-    ranges.add(new Range(40, 50));
-  
-    /*
-     * constructor with greedy traversal of sorted ranges to build nested
-     * containment lists results in 20-30 inside 10-60, 40-50 a sibling
-     */
-    NCList<Range> ncl = new NCList<Range>(ranges);
-    assertEquals(ncl.toString(), "[10-60 [20-30], 40-50]");
-    assertTrue(ncl.isValid());
-
-    /*
-     * adding ranges one at a time results in 40-50 
-     * a sibling of 20-30 inside 10-60
-     */
-    ncl = new NCList<Range>(new Range(10, 60));
-    ncl.add(new Range(20, 30));
-    ncl.add(new Range(40, 50));
-    assertEquals(ncl.toString(), "[10-60 [20-30, 40-50]]");
-    assertTrue(ncl.isValid());
-  }
-}
diff --git a/test/jalview/datamodel/features/NCNodeTest.java b/test/jalview/datamodel/features/NCNodeTest.java
deleted file mode 100644 (file)
index 4713084..0000000
+++ /dev/null
@@ -1,136 +0,0 @@
-package jalview.datamodel.features;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertTrue;
-
-import jalview.datamodel.Range;
-import jalview.datamodel.SequenceFeature;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import junit.extensions.PA;
-
-import org.testng.annotations.Test;
-
-public class NCNodeTest
-{
-  @Test(groups = "Functional")
-  public void testAdd()
-  {
-    Range r1 = new Range(10, 20);
-    NCNode<Range> node = new NCNode<Range>(r1);
-    assertEquals(node.getBegin(), 10);
-    Range r2 = new Range(10, 15);
-    node.add(r2);
-
-    List<Range> contents = new ArrayList<Range>();
-    node.getEntries(contents);
-    assertEquals(contents.size(), 2);
-    assertTrue(contents.contains(r1));
-    assertTrue(contents.contains(r2));
-  }
-
-  @Test(
-    groups = "Functional",
-    expectedExceptions = { IllegalArgumentException.class })
-  public void testAdd_invalidRangeStart()
-  {
-    Range r1 = new Range(10, 20);
-    NCNode<Range> node = new NCNode<Range>(r1);
-    assertEquals(node.getBegin(), 10);
-    Range r2 = new Range(9, 15);
-    node.add(r2);
-  }
-
-  @Test(
-    groups = "Functional",
-    expectedExceptions = { IllegalArgumentException.class })
-  public void testAdd_invalidRangeEnd()
-  {
-    Range r1 = new Range(10, 20);
-    NCNode<Range> node = new NCNode<Range>(r1);
-    assertEquals(node.getBegin(), 10);
-    Range r2 = new Range(12, 21);
-    node.add(r2);
-  }
-
-  @Test(groups = "Functional")
-  public void testGetEntries()
-  {
-    Range r1 = new Range(10, 20);
-    NCNode<Range> node = new NCNode<Range>(r1);
-    List<Range> entries = new ArrayList<Range>();
-
-    node.getEntries(entries);
-    assertEquals(entries.size(), 1);
-    assertTrue(entries.contains(r1));
-
-    // clearing the returned list does not affect the NCNode
-    entries.clear();
-    node.getEntries(entries);
-    assertEquals(entries.size(), 1);
-    assertTrue(entries.contains(r1));
-
-    Range r2 = new Range(15, 18);
-    node.add(r2);
-    entries.clear();
-    node.getEntries(entries);
-    assertEquals(entries.size(), 2);
-    assertTrue(entries.contains(r1));
-    assertTrue(entries.contains(r2));
-  }
-
-  /**
-   * Tests for the contains method (uses entry.equals() test)
-   */
-  @Test(groups = "Functional")
-  public void testContains()
-  {
-    SequenceFeature sf1 = new SequenceFeature("type", "desc", 1, 10, 2f,
-            "group");
-    SequenceFeature sf2 = new SequenceFeature("type", "desc", 1, 10, 2f,
-            "group");
-    SequenceFeature sf3 = new SequenceFeature("type", "desc", 1, 10, 2f,
-            "anothergroup");
-    NCNode<SequenceFeature> node = new NCNode<SequenceFeature>(sf1);
-
-    assertFalse(node.contains(null));
-    assertTrue(node.contains(sf1));
-    assertTrue(node.contains(sf2)); // sf1.equals(sf2)
-    assertFalse(node.contains(sf3)); // !sf1.equals(sf3)
-  }
-
-  /**
-   * Test method that checks for valid structure. Valid means that all
-   * subregions (if any) lie within the root range, and that all subregions have
-   * valid structure.
-   */
-  @Test(groups = "Functional")
-  public void testIsValid()
-  {
-    Range r1 = new Range(10, 20);
-    Range r2 = new Range(14, 15);
-    Range r3 = new Range(16, 17);
-    NCNode<Range> node = new NCNode<Range>(r1);
-    node.add(r2);
-    node.add(r3);
-
-    /*
-     * node has root range [10-20] and contains an
-     * NCList of [14-15, 16-17]
-     */
-    assertTrue(node.isValid());
-    PA.setValue(r1, "start", 15);
-    assertFalse(node.isValid()); // r2 not within r1
-    PA.setValue(r1, "start", 10);
-    assertTrue(node.isValid());
-    PA.setValue(r1, "end", 16);
-    assertFalse(node.isValid()); // r3 not within r1
-    PA.setValue(r1, "end", 20);
-    assertTrue(node.isValid());
-    PA.setValue(r3, "start", 12);
-    assertFalse(node.isValid()); // r3 should precede r2
-  }
-}
diff --git a/test/jalview/datamodel/features/RangeComparatorTest.java b/test/jalview/datamodel/features/RangeComparatorTest.java
deleted file mode 100644 (file)
index 4849b38..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-package jalview.datamodel.features;
-
-import static org.testng.Assert.assertEquals;
-
-import jalview.datamodel.ContiguousI;
-import jalview.datamodel.Range;
-
-import java.util.Comparator;
-
-import org.testng.annotations.Test;
-
-public class RangeComparatorTest
-{
-
-  @Test(groups = "Functional")
-  public void testCompare()
-  {
-    RangeComparator comp = new RangeComparator(true);
-
-    // same position, same length
-    assertEquals(comp.compare(10, 10, 20, 20), 0);
-    // same position, len1 > len2
-    assertEquals(comp.compare(10, 10, 20, 19), -1);
-    // same position, len1 < len2
-    assertEquals(comp.compare(10, 10, 20, 21), 1);
-    // pos1 > pos2
-    assertEquals(comp.compare(11, 10, 20, 20), 1);
-    // pos1 < pos2
-    assertEquals(comp.compare(10, 11, 20, 10), -1);
-  }
-
-  @Test(groups = "Functional")
-  public void testCompare_byStart()
-  {
-    Comparator<ContiguousI> comp = RangeComparator.BY_START_POSITION;
-
-    // same start position, same length
-    assertEquals(comp.compare(new Range(10, 20), new Range(10, 20)), 0);
-    // same start position, len1 > len2
-    assertEquals(comp.compare(new Range(10, 20), new Range(10, 19)), -1);
-    // same start position, len1 < len2
-    assertEquals(comp.compare(new Range(10, 18), new Range(10, 20)), 1);
-    // pos1 > pos2
-    assertEquals(comp.compare(new Range(11, 20), new Range(10, 20)), 1);
-    // pos1 < pos2
-    assertEquals(comp.compare(new Range(10, 20), new Range(11, 20)), -1);
-  }
-
-  @Test(groups = "Functional")
-  public void testCompare_byEnd()
-  {
-    Comparator<ContiguousI> comp = RangeComparator.BY_END_POSITION;
-
-    // same end position, same length
-    assertEquals(comp.compare(new Range(10, 20), new Range(10, 20)), 0);
-    // same end position, len1 > len2
-    assertEquals(comp.compare(new Range(10, 20), new Range(11, 20)), -1);
-    // same end position, len1 < len2
-    assertEquals(comp.compare(new Range(11, 20), new Range(10, 20)), 1);
-    // end1 > end2
-    assertEquals(comp.compare(new Range(10, 21), new Range(10, 20)), 1);
-    // end1 < end2
-    assertEquals(comp.compare(new Range(10, 20), new Range(10, 21)), -1);
-  }
-}