+/*
+ * 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)
- {
-
- }*/
}