JAL-2759 First pass instantiate hiddenColumns collection in constructor
[jalview.git] / src / jalview / datamodel / HiddenColumnsCursor.java
index 19c2969..a875f87 100644 (file)
+/*
+ * 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;
 
+import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
 
 public class HiddenColumnsCursor
 {
   // absolute position of first hidden column
   private int firstColumn;
 
-  // absolute position of last hidden column
-  private int lastColumn;
+  private List<int[]> hiddenColumns = new ArrayList<>();
 
-  // index of last visited region
-  private int regionIndex;
+  // AtomicReference to hold the current region index and hidden column count
+  // Could be done with synchronisation but benchmarking shows this way is 2x
+  // faster
+  private final AtomicReference<HiddenCursorPosition> cursorPos = new AtomicReference<>(
+          new HiddenCursorPosition(0, 0));
 
-  // number of hidden columns before last visited region
-  private int hiddenSoFar;
-
-  // flag to indicate if current cursor settings are valid
-  private boolean isValid;
+  protected HiddenColumnsCursor()
+  {
 
-  private List<int[]> hiddenColumns;
+  }
 
-  protected HiddenColumnsCursor()
+  /**
+   * Reset the cursor with a new hidden columns collection. Calls to resetCursor
+   * should be made from within a writeLock in the HiddenColumns class - since
+   * changes to the hiddenColumns collection require a writeLock the lock should
+   * already exist.
+   * 
+   * @param hiddenCols
+   *          new hidden columns collection
+   */
+  protected void resetCursor(List<int[]> hiddenCols)
   {
-    isValid = false;
+    resetCursor(hiddenCols, 0, 0);
   }
 
   /**
-   * Set the cursor to a position
+   * Reset the cursor with a new hidden columns collection, where we know in
+   * advance the index and hidden columns count of a particular location.
    * 
-   * @param first
-   *          absolute position of first hidden column
-   * @param last
-   *          absolute position of last hidden column
+   * @param hiddenCols
+   *          new hidden columns collection
    * @param index
-   *          index of last visited region
-   * @param hiddenCount
-   *          number of hidden columns before last visited region
+   *          cursor index to reset to
+   * @param hiddencount
+   *          hidden columns count to reset to
    */
-  protected void resetCursor(// int first, int last, int index, int hiddenCount,
-          List<int[]> hiddenCols)
+  protected void resetCursor(List<int[]> hiddenCols, int index,
+          int hiddencount)
   {
-    isValid = true;
-    if ((hiddenCols != null) && (!hiddenCols.isEmpty()))
+    hiddenColumns = hiddenCols;
+    if (!hiddenCols.isEmpty())
     {
-      hiddenColumns = hiddenCols;
       firstColumn = hiddenColumns.get(0)[0];
-      lastColumn = hiddenColumns.get(hiddenColumns.size() - 1)[1];
-      regionIndex = 0;
-      hiddenSoFar = 0;
+      HiddenCursorPosition oldpos = cursorPos.get();
+      HiddenCursorPosition newpos = new HiddenCursorPosition(index,
+              hiddencount);
+      cursorPos.compareAndSet(oldpos, newpos);
     }
   }
 
-  protected void updateCursor(int index, int hiddenCount)
+  /**
+   * Delete the region the cursor is currently at. Avoids having to reset the
+   * cursor just because we deleted a region.
+   * 
+   * Calls to updateForDeletedRegion should be made from within a writeLock in
+   * the HiddenColumns class - since changes to the hiddenColumns collection
+   * require a writeLock the lock should already exist.
+   *
+   * @param hiddenCols
+   */
+  protected void updateForDeletedRegion(List<int[]> hiddenCols)
   {
-    regionIndex = index;
-    hiddenSoFar = hiddenCount;
+
+    if (!hiddenCols.isEmpty())
+    {
+      // if there is a region to the right of the current region,
+      // nothing changes; otherwise
+      // we deleted the last region (index=hiddenCols.size()-1)
+      // or the index was at the end of the alignment (index=hiddenCols.size())
+      HiddenCursorPosition oldpos = cursorPos.get();
+
+      int index = oldpos.getRegionIndex();
+      if (index >= hiddenColumns.size() - 1)
+      {
+        // deleted last region, index is now end of alignment
+        index = hiddenCols.size();
+
+        HiddenCursorPosition newpos = new HiddenCursorPosition(index,
+                oldpos.getHiddenSoFar());
+        cursorPos.compareAndSet(oldpos, newpos);
+      }
+    }
+    hiddenColumns = hiddenCols;
   }
 
   /**
-   * Get the index of the region that column is within (if column is hidden) or
-   * which is to the right of column (if column is visible). If no hidden
-   * columns are to the right, will return size of hiddenColumns. If hidden
-   * columns is empty returns -1.
+   * Get the cursor pointing to the hidden region that column is within (if
+   * column is hidden) or which is to the right of column (if column is
+   * visible). If no hidden columns are to the right, returns a cursor pointing
+   * to an imaginary hidden region beyond the end of the hidden columns
+   * collection (this ensures the count of previous hidden columns is correct).
+   * If hidden columns is empty returns null.
    * 
    * @param column
    *          absolute position of a column in the alignment
-   * @return region index
+   * @return cursor pointing to hidden region containing the column (if hidden)
+   *         or to the right of the column (if visible)
    */
-  protected int findRegionForColumn(int column)
+  protected HiddenCursorPosition findRegionForColumn(int column)
   {
-    if (hiddenColumns == null)
+    if (hiddenColumns.isEmpty())
     {
-      return -1;
+      return null;
     }
 
-    if ((hiddenColumns.get(regionIndex)[0] <= column)
-            && (hiddenColumns.get(regionIndex)[1] >= column))
-    {
-      // we hit the jackpot
-      return regionIndex;
-    }
-    else if (column < firstColumn)
+    HiddenCursorPosition oldpos = cursorPos.get();
+    int index = oldpos.getRegionIndex();
+    int hiddenCount = oldpos.getHiddenSoFar();
+
+    if (index == hiddenColumns.size())
     {
-      return 0;
+      // went past the end of hiddenColumns collection last time
+      index--;
+      int[] region = hiddenColumns.get(index);
+      hiddenCount -= region[1] - region[0] + 1;
     }
-    else if (column > lastColumn)
+
+    // this if statement excludes case where column is in current region
+    // - no changes needed
+    if (column < firstColumn)
     {
-      return hiddenColumns.size();
+      index = 0;
+      hiddenCount = 0;
     }
-    else if (column > hiddenColumns.get(regionIndex)[1])
+    // column is after current region
+    else if (column > hiddenColumns.get(index)[1])
     {
       // iterate from where we are now, if we're lucky we'll be close by
       // (but still better than iterating from 0)
-      while ((regionIndex < hiddenColumns.size())
-              && (hiddenColumns.get(regionIndex)[0] <= column))
+      // stop when we find the region *before* column
+      // i.e. the next region starts after column or if not, ends after column
+      while ((index < hiddenColumns.size())
+              && (column > hiddenColumns.get(index)[1]))
       {
-        int[] region = hiddenColumns.get(regionIndex);
-        hiddenSoFar += region[1] - region[0] + 1;
-        regionIndex++;
+        int[] region = hiddenColumns.get(index);
+        hiddenCount += region[1] - region[0] + 1;
+        index++;
       }
-
     }
-    else // (column < hiddenColumns.get(regionIndex)[0])
+    // column is before current region
+    else if (column < hiddenColumns.get(index)[0])
     {
-      int lastHidden = 0;
-      while ((regionIndex >= 0)
-              && (hiddenColumns.get(regionIndex)[0] > column))
+      // column is before or in the previous region
+      while ((index > 0) && (hiddenColumns.get(index - 1)[1] >= column))
       {
-        int[] region = hiddenColumns.get(regionIndex);
-        hiddenSoFar -= lastHidden;
-        lastHidden = region[1] - region[0] + 1;
-        regionIndex--;
+        index--;
+        int[] region = hiddenColumns.get(index);
+        hiddenCount -= region[1] - region[0] + 1;
       }
-      hiddenSoFar -= lastHidden;
     }
-    return regionIndex;
+
+    if (index != oldpos.getRegionIndex()
+            || hiddenCount != oldpos.getHiddenSoFar())
+    {
+      HiddenCursorPosition newpos = new HiddenCursorPosition(index,
+              hiddenCount);
+      cursorPos.compareAndSet(oldpos, newpos);
+      return newpos;
+    }
+    return oldpos;
   }
 
-  protected int getHiddenOffset(int column)
+  /**
+   * Get the cursor pointing to the hidden region just after a visible column
+   * 
+   * @param column
+   *          index of column in *visible* alignment (therefore by definition
+   *          column is visible)
+   * @return cursor pointing to hidden region to the right of the column
+   */
+  protected HiddenCursorPosition findRegionForVisColumn(int column)
   {
-    if (hiddenColumns == null)
+    if (hiddenColumns.isEmpty())
     {
-      return -1;
+      return null;
     }
 
+    HiddenCursorPosition oldpos = cursorPos.get();
+    int index = oldpos.getRegionIndex();
+    int hiddenCount = oldpos.getHiddenSoFar();
+
     if (column < firstColumn)
     {
-      return 0;
+      index = 0;
+      hiddenCount = 0;
     }
-    else if ((regionIndex < hiddenColumns.size())
-      && (hiddenColumns.get(regionIndex)[0] <= column
-      + hiddenSoFar))
+    else if ((index < hiddenColumns.size())
+            && (hiddenColumns.get(index)[0] <= column + hiddenCount))
     {
-
       // iterate from where we are now, if we're lucky we'll be close by
       // (but still better than iterating from 0)
-      while ((regionIndex < hiddenColumns.size())
-              && (hiddenColumns.get(regionIndex)[0] <= column
-                      + hiddenSoFar))
+      while ((index < hiddenColumns.size())
+              && (hiddenColumns.get(index)[0] <= column + hiddenCount))
       {
-        int[] region = hiddenColumns.get(regionIndex);
-        hiddenSoFar += region[1] - region[0] + 1;
-        regionIndex++;
+        int[] region = hiddenColumns.get(index);
+        hiddenCount += region[1] - region[0] + 1;
+        index++;
       }
     }
-    else if (regionIndex < hiddenColumns.size())
+    else
     {
-      int lastHidden = hiddenColumns.get(regionIndex)[1]
-              - hiddenColumns.get(regionIndex)[0] + 1;
-
-      while ((regionIndex >= 0)
-              && (hiddenColumns.get(regionIndex)[0] <= column + hiddenSoFar
-                      - lastHidden))
+      while ((index > 0)
+              && (hiddenColumns.get(index - 1)[1] >= column + hiddenCount))
       {
-        int[] region = hiddenColumns.get(regionIndex);
-        hiddenSoFar -= lastHidden;
-        lastHidden = region[1] - region[0] + 1;
-        regionIndex--;
+        index--;
+        int[] region = hiddenColumns.get(index);
+        hiddenCount -= region[1] - region[0] + 1;
       }
 
     }
-  
-    int result = hiddenSoFar;
-    if ((regionIndex >= 0) && (regionIndex < hiddenColumns.size())
-            && (hiddenColumns.get(regionIndex)[0] <= column + hiddenSoFar)
-            && (hiddenColumns.get(regionIndex)[1] >= column + hiddenSoFar))
+
+    if (index != oldpos.getRegionIndex()
+            || hiddenCount != oldpos.getHiddenSoFar())
     {
-      int[] region = hiddenColumns.get(regionIndex);
-      result += region[1] - region[0] + 1;
+      HiddenCursorPosition newpos = new HiddenCursorPosition(index,
+              hiddenCount);
+      cursorPos.compareAndSet(oldpos, newpos);
+      return newpos;
     }
-
-    return result;
+    return oldpos;
   }
-
-  /* public int findVisiblePositionFromAbsolute(int column)
-  {
-  
-  }
-  
-  public int findAbsolutePositionFromVisible(int column)
-  {
-  
-  }*/
 }