JAL-2759 cursor option for BoundedStartRegionIterator
[jalview.git] / src / jalview / datamodel / HiddenColumnsCursor.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.datamodel;
22
23 import java.util.List;
24 import java.util.concurrent.atomic.AtomicReference;
25
26 public class HiddenColumnsCursor
27 {
28   // absolute position of first hidden column
29   private int firstColumn;
30
31   private List<int[]> hiddenColumns;
32
33   // AtomicReference to hold the current region index and hidden column count
34   // Could be done with synchronisation but benchmarking shows this way is 2x
35   // faster
36   private final AtomicReference<HiddenCursorPosition> cursorPos = new AtomicReference<>(
37           new HiddenCursorPosition(0, 0));
38
39   protected HiddenColumnsCursor()
40   {
41
42   }
43
44   /**
45    * Reset the cursor with a new hidden columns collection. Calls to resetCursor
46    * should be made from within a writeLock in the HiddenColumns class - since
47    * changes to the hiddenColumns collection require a writeLock the lock should
48    * already exist.
49    * 
50    * @param hiddenCols
51    */
52   protected void resetCursor(List<int[]> hiddenCols)
53   {
54     hiddenColumns = hiddenCols;
55     if ((hiddenCols != null) && (!hiddenCols.isEmpty()))
56     {
57       firstColumn = hiddenColumns.get(0)[0];
58       HiddenCursorPosition oldpos = cursorPos.get();
59       HiddenCursorPosition newpos = new HiddenCursorPosition(0, 0);
60       cursorPos.compareAndSet(oldpos, newpos);
61     }
62   }
63
64   /**
65    * Delete the region the cursor is currently at. Avoids having to reset the
66    * cursor just because we deleted a region.
67    * 
68    * Calls to updateForDeletedRegion should be made from within a writeLock in
69    * the HiddenColumns class - since changes to the hiddenColumns collection
70    * require a writeLock the lock should already exist.
71    *
72    * @param hiddenCols
73    */
74   protected void updateForDeletedRegion(List<int[]> hiddenCols)
75   {
76
77     if ((hiddenCols != null) && (!hiddenCols.isEmpty()))
78     {
79       // if there is a region to the right of the current region,
80       // nothing changes; otherwise
81       // we deleted the last region (index=hiddenCols.size()-1)
82       // or the index was at the end of the alignment (index=hiddenCols.size())
83       HiddenCursorPosition oldpos = cursorPos.get();
84
85       int index = oldpos.getRegionIndex();
86       if (index >= hiddenColumns.size() - 1)
87       {
88         // deleted last region, index is now end of alignment
89         index = hiddenCols.size();
90       }
91
92       // update the cursor position
93       HiddenCursorPosition newpos = new HiddenCursorPosition(index,
94               oldpos.getHiddenSoFar());
95       cursorPos.compareAndSet(oldpos, newpos);
96     }
97     hiddenColumns = hiddenCols;
98   }
99
100   /**
101    * Get the index of the region that column is within (if column is hidden) or
102    * which is to the right of column (if column is visible). If no hidden
103    * columns are to the right, will return size of hiddenColumns. If hidden
104    * columns is empty returns -1.
105    * 
106    * @param column
107    *          absolute position of a column in the alignment
108    * @return region index
109    */
110   protected HiddenCursorPosition findRegionForColumn(int column)
111   {
112     if (hiddenColumns == null)
113     {
114       return null;
115     }
116
117     HiddenCursorPosition oldpos = cursorPos.get();
118     int index = oldpos.getRegionIndex();
119     int hiddenCount = oldpos.getHiddenSoFar();
120
121     if (index == hiddenColumns.size())
122     {
123       // went past the end of hiddenColumns collection last time
124       index--;
125       int[] region = hiddenColumns.get(index);
126       hiddenCount -= region[1] - region[0] + 1;
127     }
128
129     if ((hiddenColumns.get(index)[0] <= column)
130             && hiddenColumns.get(index)[1] >= column)
131     {
132       // column is in the current region
133       // we hit the jackpot
134       // don't need to move index
135     }
136     else if (column < firstColumn)
137     {
138       index = 0;
139       hiddenCount = 0;
140     }
141     // column is after current region
142     else if (column > hiddenColumns.get(index)[1]) // includes if column >
143                                                    // lastColumn
144     {
145       // iterate from where we are now, if we're lucky we'll be close by
146       // (but still better than iterating from 0)
147       // stop when we find the region *before* column
148       // i.e. the next region starts after column or if not, ends after column
149       while ((index < hiddenColumns.size())
150               && (column > hiddenColumns.get(index)[1]))
151       {
152         int[] region = hiddenColumns.get(index);
153         hiddenCount += region[1] - region[0] + 1;
154         index++;
155       }
156     }
157
158     // column is before current region
159     else if (column < hiddenColumns.get(index)[0])
160     {
161       // column is before or in the previous region
162       if ((index > 0) && (hiddenColumns.get(index - 1)[1] >= column))
163       {
164         while ((index > 0) && (hiddenColumns.get(index - 1)[1] >= column))
165         {
166           index--;
167           int[] region = hiddenColumns.get(index);
168           hiddenCount -= region[1] - region[0] + 1;
169         }
170       }
171     }
172     HiddenCursorPosition newpos = new HiddenCursorPosition(index,
173             hiddenCount);
174     cursorPos.compareAndSet(oldpos, newpos);
175     return newpos;
176   }
177
178   /**
179    * Get the number of hidden columns in regions before column i.e. excludes
180    * hidden columns in the region column is in, if any
181    * 
182    * @param column
183    *          index of column in visible alignment
184    * @return
185    */
186   protected HiddenCursorPosition getHiddenOffset(int column)
187   {
188     if (hiddenColumns == null)
189     {
190       return null;
191     }
192
193     HiddenCursorPosition oldpos = cursorPos.get();
194     int index = oldpos.getRegionIndex();
195     int hiddenCount = oldpos.getHiddenSoFar();
196
197     if (column < firstColumn)
198     {
199       index = 0;
200       hiddenCount = 0;
201     }
202     else if ((index < hiddenColumns.size())
203             && (hiddenColumns.get(index)[0] <= column + hiddenCount))
204     {
205       // iterate from where we are now, if we're lucky we'll be close by
206       // (but still better than iterating from 0)
207       while ((index < hiddenColumns.size())
208               && (hiddenColumns.get(index)[0] <= column + hiddenCount))
209       {
210         int[] region = hiddenColumns.get(index);
211         hiddenCount += region[1] - region[0] + 1;
212         index++;
213       }
214     }
215     else
216     {
217       while ((index > 0)
218               && (hiddenColumns.get(index - 1)[1] >= column + hiddenCount))
219       {
220         index--;
221         int[] region = hiddenColumns.get(index);
222         hiddenCount -= region[1] - region[0] + 1;
223       }
224
225     }
226
227     HiddenCursorPosition newpos = new HiddenCursorPosition(index,
228             hiddenCount);
229     cursorPos.compareAndSet(oldpos, newpos);
230     return newpos;
231   }
232 }