JAL-2674 Simplify locateVisibleBoundsOfSequence
[jalview.git] / src / jalview / datamodel / HiddenColumns.java
index b6e6ce3..09f2ec5 100644 (file)
@@ -693,48 +693,34 @@ public class HiddenColumns
    * extent of the sequence
    * 
    * @param seq
-   * @return int[] { visible start, visible end, first seqpos, last seqpos,
-   *         alignment index for seq start, alignment index for seq end }
+   * @return int[] { visible start, first seqpos, last seqpos }
    */
-  public int[] locateVisibleBoundsOfSequence(SequenceI seq)
+  public int locateVisibleBoundsOfSequence(SequenceI seq)
   {
     try
     {
       LOCK.readLock().lock();
-      int fpos = seq.getStart();
-      int lpos = seq.getEnd();
       int start = 0;
 
       if (hiddenColumns == null || hiddenColumns.size() == 0)
       {
-        int ifpos = seq.findIndex(fpos) - 1;
-        int ilpos = seq.findIndex(lpos) - 1;
-        return new int[] { ifpos, ifpos, ilpos };
+        return seq.findIndex(seq.getStart()) - 1;
       }
 
       // Simply walk along the sequence whilst watching for hidden column
       // boundaries
       Iterator<int[]> regions = iterator();
-      int spos = fpos;
+      int spos = seq.getStart();
       int hideStart = seq.getLength();
       int hideEnd = -1;
       int visPrev = 0;
       int visNext = 0;
-      int firstP = -1;
-      int lastP = -1;
+
       boolean foundStart = false;
-      for (int p = 0, pLen = seq.getLength(); spos <= seq.getEnd()
-              && p < pLen; p++)
+      for (int p = 0; spos <= seq.getEnd() && p < seq.getLength(); p++)
       {
         if (!Comparison.isGap(seq.getCharAt(p)))
         {
-          // keep track of first/last column
-          // containing sequence data regardless of visibility
-          if (firstP == -1)
-          {
-            firstP = p;
-          }
-          lastP = p;
           // update hidden region start/end
           while (hideEnd < p && regions.hasNext())
           {
@@ -749,15 +735,10 @@ public class HiddenColumns
             hideStart = seq.getLength();
           }
           // update visible boundary for sequence
-          if (p < hideStart)
+          if ((p < hideStart) && (!foundStart))
           {
-            if (!foundStart)
-            {
-              fpos = spos;
               start = p;
               foundStart = true;
-            }
-            lpos = spos;
           }
           // look for next sequence position
           spos++;
@@ -765,10 +746,10 @@ public class HiddenColumns
       }
       if (foundStart)
       {
-        return new int[] { findColumnPosition(start), firstP, lastP };
+        return findColumnPosition(start);
       }
       // otherwise, sequence was completely hidden
-      return new int[] { visPrev, firstP, lastP };
+      return visPrev;
     } finally
     {
       LOCK.readLock().unlock();
@@ -1393,6 +1374,37 @@ public class HiddenColumns
   }
 
   /**
+   * return an iterator over visible segments between the given start and end
+   * boundaries
+   * 
+   * @param start
+   *          (first column - inclusive from 0)
+   * @param end
+   *          (last column - inclusive)
+   * @param useVisibleCoords
+   *          if true, start and end are visible column positions, not absolute
+   *          positions
+   */
+  public Iterator<int[]> getVisibleBlocksIterator(int start, int end,
+          boolean useVisibleCoords)
+  {
+    if (useVisibleCoords)
+    {
+      // TODO
+      // we should really just convert start and end here with
+      // adjustForHiddenColumns
+      // and then create a VisibleBlocksIterator
+      // but without a cursor this will be horribly slow in some situations
+      // ... so until then...
+      return new VisibleBlocksVisBoundsIterator(start, end, true);
+    }
+    else
+    {
+      return new VisibleBlocksIterator(start, end, true);
+    }
+  }
+
+  /**
    * An iterator which iterates over hidden column regions in a range.
    */
   private class BoundedHiddenColsIterator implements Iterator<int[]>
@@ -1610,14 +1622,14 @@ public class HiddenColumns
 
     private List<int[]> localHidden = new ArrayList<>();
 
-    private int lasthiddenregion;
+    private int nexthiddenregion;
 
     VisibleColsIterator(int firstcol, int lastcol, boolean useCopy)
     {
       last = lastcol;
       current = firstcol;
       next = firstcol;
-      lasthiddenregion = -1;
+      nexthiddenregion = 0;
 
       try
       {
@@ -1632,7 +1644,7 @@ public class HiddenColumns
         {
           int i = 0;
           for (i = 0; i < hiddenColumns.size()
-                  && (current < hiddenColumns.get(i)[0]); ++i)
+                  && (current <= hiddenColumns.get(i)[0]); ++i)
           {
             if (current >= hiddenColumns.get(i)[0]
                     && current <= hiddenColumns.get(i)[1])
@@ -1640,13 +1652,12 @@ public class HiddenColumns
               // current is hidden, move to right
               current = hiddenColumns.get(i)[1] + 1;
               next = current;
+              nexthiddenregion = i + 1;
             }
           }
 
-          lasthiddenregion = i - 1;
-
           for (i = hiddenColumns.size() - 1; i >= 0
-                  && (last > hiddenColumns.get(i)[1]); --i)
+                  && (last >= hiddenColumns.get(i)[1]); --i)
           {
             if (last >= hiddenColumns.get(i)[0]
                     && last <= hiddenColumns.get(i)[1])
@@ -1657,7 +1668,7 @@ public class HiddenColumns
           }
 
           // make a local copy of the bit we need
-          i = lasthiddenregion + 1;
+          i = nexthiddenregion;
           while (i < hiddenColumns.size()
                   && hiddenColumns.get(i)[0] <= last)
           {
@@ -1666,7 +1677,6 @@ public class HiddenColumns
             localHidden.add(region);
             i++;
           }
-          lasthiddenregion = -1;
         }
       } finally
       {
@@ -1692,20 +1702,20 @@ public class HiddenColumns
       }
       current = next;
       if ((localHidden != null)
-              && (lasthiddenregion + 1 < localHidden.size()))
+              && (nexthiddenregion < localHidden.size()))
       {
         // still some more hidden regions
-        if (next + 1 < localHidden.get(lasthiddenregion + 1)[0])
+        if (next + 1 < localHidden.get(nexthiddenregion)[0])
         {
           // next+1 is still before the next hidden region
           next++;
         }
-        else if ((next + 1 >= localHidden.get(lasthiddenregion + 1)[0])
-                && (next + 1 <= localHidden.get(lasthiddenregion + 1)[1]))
+        else if ((next + 1 >= localHidden.get(nexthiddenregion)[0])
+                && (next + 1 <= localHidden.get(nexthiddenregion)[1]))
         {
           // next + 1 is in the next hidden region
-          next = localHidden.get(lasthiddenregion + 1)[1] + 1;
-          lasthiddenregion++;
+          next = localHidden.get(nexthiddenregion)[1] + 1;
+          nexthiddenregion++;
         }
       }
       else
@@ -1891,4 +1901,139 @@ public class HiddenColumns
     }
   }
 
+  /**
+   * An iterator which iterates over visible regions in a range. The range is
+   * specified in terms of visible column positions. Provides a special
+   * "endsAtHidden" indicator to allow callers to determine if the final visible
+   * column is adjacent to a hidden region.
+   */
+  public class VisibleBlocksVisBoundsIterator implements Iterator<int[]>
+  {
+    private List<int[]> vcontigs = new ArrayList<>();
+
+    private int currentPosition = 0;
+
+    private boolean endsAtHidden = false;
+
+    /**
+     * Constructor for iterator over visible regions in a range.
+     * 
+     * @param start
+     *          start position in terms of visible column position
+     * @param end
+     *          end position in terms of visible column position
+     * @param usecopy
+     *          whether to use a local copy of hidden columns
+     */
+    VisibleBlocksVisBoundsIterator(int start, int end, boolean usecopy)
+    {
+      /* actually this implementation always uses a local copy but this may change in future */
+      try
+      {
+        if (usecopy)
+        {
+          LOCK.readLock().lock();
+        }
+
+        if (hiddenColumns != null && hiddenColumns.size() > 0)
+        {
+          int blockStart = start;
+          int blockEnd = end;
+          int hiddenSoFar = 0;
+          int visSoFar = 0;
+
+          // iterate until a region begins within (start,end]
+          int i = 0;
+          while ((i < hiddenColumns.size())
+                  && (hiddenColumns.get(i)[0] <= blockStart + hiddenSoFar))
+          {
+            hiddenSoFar += hiddenColumns.get(i)[1] - hiddenColumns.get(i)[0]
+                    + 1;
+            i++;
+          }
+
+          blockStart += hiddenSoFar; // convert start to absolute position
+          blockEnd += hiddenSoFar; // convert end to absolute position
+
+          // iterate from start to end, adding each visible region. Positions
+          // are
+          // absolute, and all hidden regions which overlap [start,end] are
+          // used.
+          while (i < hiddenColumns.size()
+                  && (hiddenColumns.get(i)[0] <= blockEnd))
+          {
+            int[] region = hiddenColumns.get(i);
+
+            // end position of this visible region is either just before the
+            // start of the next hidden region, or the absolute position of
+            // 'end', whichever is lowest
+            blockEnd = Math.min(blockEnd, region[0] - 1);
+
+            vcontigs.add(new int[] { blockStart, blockEnd });
+
+            visSoFar += blockEnd - blockStart + 1;
+
+            // next visible region starts after this hidden region
+            blockStart = region[1] + 1;
+
+            hiddenSoFar += region[1] - region[0] + 1;
+
+            // reset blockEnd to absolute position of 'end', assuming we've now
+            // passed all hidden regions before end
+            blockEnd = end + hiddenSoFar;
+
+            i++;
+          }
+          if (visSoFar < end - start)
+          {
+            // the number of visible columns we've accounted for is less than
+            // the number specified by end-start; work out the end position of
+            // the last visible region
+            blockEnd = blockStart + end - start - visSoFar;
+            vcontigs.add(new int[] { blockStart, blockEnd });
+
+            // if the last visible region ends at the next hidden region, set
+            // endsAtHidden=true
+            if (i < hiddenColumns.size()
+                    && hiddenColumns.get(i)[0] - 1 == blockEnd)
+            {
+              endsAtHidden = true;
+            }
+          }
+        }
+        else
+        {
+          // there are no hidden columns, return a single visible contig
+          vcontigs.add(new int[] { start, end });
+          endsAtHidden = false;
+        }
+      } finally
+      {
+        if (usecopy)
+        {
+          LOCK.readLock().unlock();
+        }
+      }
+    }
+
+    @Override
+    public boolean hasNext()
+    {
+      return (currentPosition < vcontigs.size());
+    }
+
+    @Override
+    public int[] next()
+    {
+      int[] result = vcontigs.get(currentPosition);
+      currentPosition++;
+      return result;
+    }
+
+    public boolean endsAtHidden()
+    {
+      return endsAtHidden;
+    }
+  }
+
 }