X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fdatamodel%2FHiddenColumnsCursor.java;h=0fef622df7916d49fbb1492a3a30a6ae27c7bdaf;hb=2a66892874abea357a5e5d9ef2f69919453c31d0;hp=44ceb2a8d2eed2936cbb2c21be130e6d28bf66b6;hpb=73da6e56faf48a5640f99fb2c38f401d2d84e1bc;p=jalview.git diff --git a/src/jalview/datamodel/HiddenColumnsCursor.java b/src/jalview/datamodel/HiddenColumnsCursor.java index 44ceb2a..0fef622 100644 --- a/src/jalview/datamodel/HiddenColumnsCursor.java +++ b/src/jalview/datamodel/HiddenColumnsCursor.java @@ -1,94 +1,143 @@ +/* + * 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 . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ package jalview.datamodel; 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; - - // index of last visited region - private int regionIndex; - - // number of hidden columns before last visited region - private int hiddenSoFar; - private List hiddenColumns; + // 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 cursorPos = new AtomicReference<>( + new HiddenCursorPosition(0, 0)); + protected HiddenColumnsCursor() { } /** - * Set the cursor to a position + * 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 first - * absolute position of first hidden column - * @param last - * absolute position of last hidden column - * @param index - * index of last visited region - * @param hiddenCount - * number of hidden columns before last visited region + * @param hiddenCols + * new hidden columns collection */ protected void resetCursor(List hiddenCols) { - synchronized (this) - { - if ((hiddenCols != null) && (!hiddenCols.isEmpty())) - { - hiddenColumns = hiddenCols; - firstColumn = hiddenColumns.get(0)[0]; - lastColumn = hiddenColumns.get(hiddenColumns.size() - 1)[1]; - regionIndex = 0; - hiddenSoFar = 0; - } - } + resetCursor(hiddenCols, 0, 0); } - protected void updateCursor(int index, int hiddenCount) + /** + * 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 hiddenCols + * new hidden columns collection + * @param index + * cursor index to reset to + * @param hiddencount + * hidden columns count to reset to + */ + protected void resetCursor(List hiddenCols, int index, + int hiddencount) { - synchronized (this) + hiddenColumns = hiddenCols; + if ((hiddenCols != null) && (!hiddenCols.isEmpty())) { - regionIndex = index; - hiddenSoFar = hiddenCount; + firstColumn = hiddenColumns.get(0)[0]; + HiddenCursorPosition oldpos = cursorPos.get(); + HiddenCursorPosition newpos = new HiddenCursorPosition(index, + hiddencount); + cursorPos.compareAndSet(oldpos, newpos); } } - private synchronized int getIndex() + /** + * 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 hiddenCols) { - return regionIndex; - } - private synchronized int getHiddenSoFar() - { - return hiddenSoFar; - } + if ((hiddenCols != null) && (!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) { - return -1; + return null; } - int index = getIndex(); - int hiddenCount = getHiddenSoFar(); + HiddenCursorPosition oldpos = cursorPos.get(); + int index = oldpos.getRegionIndex(); + int hiddenCount = oldpos.getHiddenSoFar(); if (index == hiddenColumns.size()) { @@ -98,57 +147,69 @@ public class HiddenColumnsCursor hiddenCount -= region[1] - region[0] + 1; } - if ((hiddenColumns.get(index)[0] <= column) - && (hiddenColumns.get(index)[1] >= column)) - { - // we hit the jackpot - // don't need to move index - } - else if (column < firstColumn) + // this if statement excludes case where column is in current region + // - no changes needed + if (column < firstColumn) { index = 0; hiddenCount = 0; } - else if (column > lastColumn) - { - index = hiddenColumns.size(); - // TODO resolve here - need full hidden count - } + // 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) + // 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()) - && (hiddenColumns.get(index)[0] <= column)) + && (column > hiddenColumns.get(index)[1])) { 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]) { - while ((index > 0) && (hiddenColumns.get(index)[1] > column)) + // column is before or in the previous region + while ((index > 0) && (hiddenColumns.get(index - 1)[1] >= column)) { index--; int[] region = hiddenColumns.get(index); hiddenCount -= region[1] - region[0] + 1; } } - updateCursor(index, hiddenCount); - return index; + + 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) { - return -1; + return null; } - int index = getIndex(); - int hiddenCount = getHiddenSoFar(); + HiddenCursorPosition oldpos = cursorPos.get(); + int index = oldpos.getRegionIndex(); + int hiddenCount = oldpos.getHiddenSoFar(); if (column < firstColumn) { @@ -168,10 +229,10 @@ public class HiddenColumnsCursor index++; } } - else if (index < hiddenColumns.size()) + else { while ((index > 0) - && (hiddenColumns.get(index)[1] > column + hiddenCount)) + && (hiddenColumns.get(index - 1)[1] >= column + hiddenCount)) { index--; int[] region = hiddenColumns.get(index); @@ -179,7 +240,15 @@ public class HiddenColumnsCursor } } - updateCursor(index, hiddenCount); - return hiddenCount; + + if (index != oldpos.getRegionIndex() + || hiddenCount != oldpos.getHiddenSoFar()) + { + HiddenCursorPosition newpos = new HiddenCursorPosition(index, + hiddenCount); + cursorPos.compareAndSet(oldpos, newpos); + return newpos; + } + return oldpos; } }