JAL-2446 pseudo-random generator unit test, and fixes arising
[jalview.git] / src / jalview / datamodel / features / NCList.java
index 7046670..40b062a 100644 (file)
@@ -14,6 +14,10 @@ import java.util.List;
  */
 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 
@@ -38,6 +42,7 @@ public class NCList<T extends ContiguousI>
    */
   public NCList(List<T> ranges)
   {
+    this();
     build(ranges);
   }
 
@@ -54,8 +59,6 @@ public class NCList<T extends ContiguousI>
 
     List<SubList> sublists = findSubranges(ranges);
 
-    subranges = new ArrayList<NCNode<T>>();
-
     /*
      * convert each subrange to an NCNode consisting of a range and
      * (possibly) its contained NCList
@@ -65,16 +68,23 @@ public class NCList<T extends ContiguousI>
       subranges.add(new NCNode<T>(ranges.subList(sublist.startIndex,
               sublist.endIndex + 1)));
     }
-    sublists.clear();
+
+    size = ranges.size();
   }
 
   public NCList(T entry)
   {
+    this();
     List<T> ranges = new ArrayList<T>();
     ranges.add(entry);
     build(ranges);
   }
 
+  public NCList()
+  {
+    subranges = new ArrayList<NCNode<T>>();
+  }
+
   /**
    * Traverses the sorted ranges to identify sublists, within which each
    * interval contains the one that follows it
@@ -117,6 +127,7 @@ public class NCList<T extends ContiguousI>
    */
   public synchronized void add(T entry)
   {
+    size++;
     long start = entry.getBegin();
     long end = entry.getEnd();
 
@@ -146,15 +157,16 @@ public class NCList<T extends ContiguousI>
      * search for maximal span of subranges i-k that the new entry
      * encloses; or a subrange that encloses the new entry
      */
-    int i = candidateIndex;
     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.getStart() && !overlapping)
+      if (end < subrange.getStart() && !overlapping && !enclosing)
       {
         /*
          * new entry lies between subranges j-1 j
@@ -180,6 +192,11 @@ public class NCList<T extends ContiguousI>
            * 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;
         }
@@ -193,7 +210,7 @@ public class NCList<T extends ContiguousI>
             /*
              * entry encloses one or more preceding subranges
              */
-            addEnclosingRange(entry, i, j - 1);
+            addEnclosingRange(entry, firstEnclosed, lastEnclosed);
             return;
           }
           else
@@ -207,22 +224,41 @@ public class NCList<T extends ContiguousI>
           }
         }
       }
+      else
+      {
+        overlapping = true;
+      }
+    }
+
+    /*
+     * drops through to here if new range encloses all others
+     */
+    if (enclosing)
+    {
+      addEnclosingRange(entry, firstEnclosed, lastEnclosed);
     }
   }
   
   /**
    * Update the tree so that the range of the new entry encloses subranges i to
-   * j (inclusive). That is, replace subranges i-j with a new subrange that
-   * contains them.
+   * 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, int i, int j)
+  protected synchronized void addEnclosingRange(T entry, final int i,
+          final int j)
   {
-    // TODO how to rebuild the subranges as an NCList?
-
+    NCList<T> newNCList = new NCList<T>();
+    newNCList.subranges.addAll(subranges.subList(i, j + 1));
+    NCNode<T> newNode = new NCNode<T>(entry, newNCList);
+    for (int k = j; k >= i; k--)
+    {
+      subranges.remove(k);
+    }
+    subranges.add(i, newNode);
   }
 
   /**
@@ -321,4 +357,108 @@ public class NCList<T extends ContiguousI>
   {
     return subranges.toString();
   }
+
+  /**
+   * Returns a string representation of the data where containment is shown bgy
+   * indentation on new lines
+   * 
+   * @return
+   */
+  public String prettyPrint()
+  {
+    StringBuilder sb = new StringBuilder(512);
+    int offset = 0;
+    int indent = 2;
+    prettyPrint(sb, offset, indent);
+    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.getStart() < 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.getStart();
+
+      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).getStart();
+  }
+
+  /**
+   * Returns the number of ranges held (deep count)
+   * 
+   * @return
+   */
+  public int getSize()
+  {
+    return size;
+  }
 }