JAL-192 ‘efficiently’ locate visible bounds for a sequence (needs scalability testing)
[jalview.git] / src / jalview / datamodel / ColumnSelection.java
index cfe4685..c062884 100644 (file)
@@ -20,6 +20,7 @@
  */
 package jalview.datamodel;
 
+import jalview.util.Comparison;
 import jalview.util.ShiftList;
 import jalview.viewmodel.annotationfilter.AnnotationFilterParameter;
 import jalview.viewmodel.annotationfilter.AnnotationFilterParameter.SearchableAnnotationField;
@@ -184,9 +185,30 @@ public class ColumnSelection
     {
       return selected.get(0) ? 0 : selected.nextSetBit(0);
     }
+
+    /**
+     * @return a series of selection intervals along the range
+     */
+    public List<int[]> getRanges()
+    {
+      List<int[]> rlist = new ArrayList<int[]>();
+      if (selected.isEmpty())
+      {
+        return rlist;
+      }
+      int next = selected.nextSetBit(0), clear = -1;
+      while (next != -1)
+      {
+        clear = selected.nextClearBit(next);
+        rlist.add(new int[] { next, clear - 1 });
+        next = selected.nextSetBit(clear);
+      }
+      return rlist;
+    }
   }
 
   IntList selected = new IntList();
+
   /*
    * list of hidden column [start, end] ranges; the list is maintained in
    * ascending start column order
@@ -255,15 +277,24 @@ public class ColumnSelection
   }
 
   /**
+   * @return list of int arrays containing start and end column position for
+   *         runs of selected columns ordered from right to left.
+   */
+  public List<int[]> getSelectedRanges()
+  {
+    return selected.getRanges();
+  }
+
+  /**
    * 
    * @param col
    *          index to search for in column selection
    * 
-   * @return true if Integer(col) is in selection.
+   * @return true if col is selected
    */
   public boolean contains(int col)
   {
-    return selected.isSelected(col);
+    return (col > -1) ? selected.isSelected(col) : false;
   }
 
   /**
@@ -623,6 +654,10 @@ public class ColumnSelection
 
   /**
    * Use this method to determine where the next hiddenRegion starts
+   * 
+   * @param hiddenRegion
+   *          index of hidden region (counts from 0)
+   * @return column number in visible view
    */
   public int findHiddenRegionPosition(int hiddenRegion)
   {
@@ -642,7 +677,7 @@ public class ColumnSelection
         gaps += region[1] + 1 - region[0];
         result = region[1] + 1;
         index++;
-      } while (index < hiddenRegion + 1);
+      } while (index <= hiddenRegion);
 
       result -= gaps;
     }
@@ -708,10 +743,13 @@ public class ColumnSelection
 
   public void hideSelectedColumns()
   {
-    while (!selected.isEmpty())
+    synchronized (selected)
     {
-      int column = selected.elementAt(0);
-      hideColumns(column);
+      for (int[] selregions : selected.getRanges())
+      {
+        hideColumns(selregions[0], selregions[1]);
+      }
+      selected.clear();
     }
 
   }
@@ -771,7 +809,7 @@ public class ColumnSelection
     /*
      * remaining case is that the new range follows everything else
      */
-      hiddenColumns.addElement(new int[] { start, end });
+    hiddenColumns.addElement(new int[] { start, end });
   }
 
   /**
@@ -1042,6 +1080,76 @@ public class ColumnSelection
   }
 
   /**
+   * Locate the first and last position visible for this sequence. if seq isn't
+   * visible then return the position of the left and right of the hidden
+   * boundary region
+   * 
+   * @param seq
+   * @return int[] { visible start, visible end, first seqpos, last seqpos }
+   */
+  public int[] locateVisibleBoundsOfSequence(SequenceI seq)
+  {
+    int fpos=seq.getStart(),lpos= seq.getEnd();
+    int start = 0;
+    int end = seq.getLength();
+    
+    if (hiddenColumns == null || hiddenColumns.size() == 0)
+    {
+      return new int[] { seq.findIndex(fpos), seq.findIndex(lpos), fpos,
+          lpos };
+    }
+
+    // Simply walk along the sequence whilst watching for hidden column
+    // boundaries
+    List<int[]> regions = getHiddenColumns();
+    int spos = fpos, lastvispos = -1, rcount = 0, hideStart = seq
+            .getLength(), hideEnd = -1;
+    int visPrev = 0, visNext = 0, base = 0;
+    boolean foundStart = false;
+    for (int p = 0, pLen = seq.getLength(); spos <= seq.getEnd()
+            && p < pLen; p++)
+    {
+      if (!Comparison.isGap(seq.getCharAt(p)))
+      {
+        // update hidden region start/end
+        while (hideEnd < p && rcount < regions.size())
+        {
+          int[] region = regions.get(rcount++);
+          visNext += region[1] + 1 - region[0];
+          visPrev = visNext-1;
+          hideStart = region[0];
+          hideEnd = region[1];
+        }
+        if (hideEnd < p)
+        {
+          hideStart = seq.getLength();
+        }
+        // update visible boundary for sequence
+        if (p < hideStart)
+        {
+          if (!foundStart)
+          {
+            fpos = spos;
+            start = p;
+            foundStart = true;
+          }
+          lastvispos = p;
+          lpos = spos;
+        }
+        // look for next sequence position
+        spos++;
+      }
+    }
+    if (foundStart)
+    {
+      return new int[] { findColumnPosition(start),
+          findColumnPosition(lastvispos), fpos, lpos };
+    }
+    // otherwise, sequence was completely hidden
+    return new int[] { visPrev, visNext, 0, 0 };
+  }
+
+  /**
    * delete any columns in alignmentAnnotation that are hidden (including
    * sequence associated annotation).
    * 
@@ -1493,9 +1601,4 @@ public class ColumnSelection
     } while (count < annotations.length);
     return false;
   }
-
-  public boolean isSelected(int column)
-  {
-    return selected.isSelected(column);
-  }
 }