JAL-2759 Fix unintended type change
[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.ArrayList;
24 import java.util.List;
25 import java.util.concurrent.atomic.AtomicReference;
26
27 public class HiddenColumnsCursor
28 {
29   // absolute position of first hidden column
30   private int firstColumn;
31
32   private List<int[]> hiddenColumns = new ArrayList<>();
33
34   // AtomicReference to hold the current region index and hidden column count
35   // Could be done with synchronisation but benchmarking shows this way is 2x
36   // faster
37   private final AtomicReference<HiddenCursorPosition> cursorPos = new AtomicReference<>(
38           new HiddenCursorPosition(0, 0));
39
40   protected HiddenColumnsCursor()
41   {
42
43   }
44
45   /**
46    * Reset the cursor with a new hidden columns collection. Calls to resetCursor
47    * should be made from within a writeLock in the HiddenColumns class - since
48    * changes to the hiddenColumns collection require a writeLock the lock should
49    * already exist.
50    * 
51    * @param hiddenCols
52    *          new hidden columns collection
53    */
54   protected void resetCursor(List<int[]> hiddenCols)
55   {
56     resetCursor(hiddenCols, 0, 0);
57   }
58
59   /**
60    * Reset the cursor with a new hidden columns collection, where we know in
61    * advance the index and hidden columns count of a particular location.
62    * 
63    * @param hiddenCols
64    *          new hidden columns collection
65    * @param index
66    *          cursor index to reset to
67    * @param hiddencount
68    *          hidden columns count to reset to
69    */
70   protected void resetCursor(List<int[]> hiddenCols, int index,
71           int hiddencount)
72   {
73     hiddenColumns = hiddenCols;
74     if (!hiddenCols.isEmpty())
75     {
76       firstColumn = hiddenColumns.get(0)[0];
77       HiddenCursorPosition oldpos = cursorPos.get();
78       HiddenCursorPosition newpos = new HiddenCursorPosition(index,
79               hiddencount);
80       cursorPos.compareAndSet(oldpos, newpos);
81     }
82   }
83
84   /**
85    * Delete the region the cursor is currently at. Avoids having to reset the
86    * cursor just because we deleted a region.
87    * 
88    * Calls to updateForDeletedRegion should be made from within a writeLock in
89    * the HiddenColumns class - since changes to the hiddenColumns collection
90    * require a writeLock the lock should already exist.
91    *
92    * @param hiddenCols
93    */
94   protected void updateForDeletedRegion(List<int[]> hiddenCols)
95   {
96
97     if (!hiddenCols.isEmpty())
98     {
99       // if there is a region to the right of the current region,
100       // nothing changes; otherwise
101       // we deleted the last region (index=hiddenCols.size()-1)
102       // or the index was at the end of the alignment (index=hiddenCols.size())
103       HiddenCursorPosition oldpos = cursorPos.get();
104
105       int index = oldpos.getRegionIndex();
106       if (index >= hiddenColumns.size() - 1)
107       {
108         // deleted last region, index is now end of alignment
109         index = hiddenCols.size();
110
111         HiddenCursorPosition newpos = new HiddenCursorPosition(index,
112                 oldpos.getHiddenSoFar());
113         cursorPos.compareAndSet(oldpos, newpos);
114       }
115     }
116     hiddenColumns = hiddenCols;
117   }
118
119   /**
120    * Get the cursor pointing to the hidden region that column is within (if
121    * column is hidden) or which is to the right of column (if column is
122    * visible). If no hidden columns are to the right, returns a cursor pointing
123    * to an imaginary hidden region beyond the end of the hidden columns
124    * collection (this ensures the count of previous hidden columns is correct).
125    * If hidden columns is empty returns null.
126    * 
127    * @param column
128    *          absolute position of a column in the alignment
129    * @return cursor pointing to hidden region containing the column (if hidden)
130    *         or to the right of the column (if visible)
131    */
132   protected HiddenCursorPosition findRegionForColumn(int column)
133   {
134     if (hiddenColumns.isEmpty())
135     {
136       return null;
137     }
138
139     HiddenCursorPosition oldpos = cursorPos.get();
140     int index = oldpos.getRegionIndex();
141     int hiddenCount = oldpos.getHiddenSoFar();
142
143     if (index == hiddenColumns.size())
144     {
145       // went past the end of hiddenColumns collection last time
146       index--;
147       int[] region = hiddenColumns.get(index);
148       hiddenCount -= region[1] - region[0] + 1;
149     }
150
151     // this if statement excludes case where column is in current region
152     // - no changes needed
153     if (column < firstColumn)
154     {
155       index = 0;
156       hiddenCount = 0;
157     }
158     // column is after current region
159     else if (column > hiddenColumns.get(index)[1])
160     {
161       // iterate from where we are now, if we're lucky we'll be close by
162       // (but still better than iterating from 0)
163       // stop when we find the region *before* column
164       // i.e. the next region starts after column or if not, ends after column
165       while ((index < hiddenColumns.size())
166               && (column > hiddenColumns.get(index)[1]))
167       {
168         int[] region = hiddenColumns.get(index);
169         hiddenCount += region[1] - region[0] + 1;
170         index++;
171       }
172     }
173     // column is before current region
174     else if (column < hiddenColumns.get(index)[0])
175     {
176       // column is before or in the previous region
177       while ((index > 0) && (hiddenColumns.get(index - 1)[1] >= column))
178       {
179         index--;
180         int[] region = hiddenColumns.get(index);
181         hiddenCount -= region[1] - region[0] + 1;
182       }
183     }
184
185     if (index != oldpos.getRegionIndex()
186             || hiddenCount != oldpos.getHiddenSoFar())
187     {
188       HiddenCursorPosition newpos = new HiddenCursorPosition(index,
189               hiddenCount);
190       cursorPos.compareAndSet(oldpos, newpos);
191       return newpos;
192     }
193     return oldpos;
194   }
195
196   /**
197    * Get the cursor pointing to the hidden region just after a visible column
198    * 
199    * @param column
200    *          index of column in *visible* alignment (therefore by definition
201    *          column is visible)
202    * @return cursor pointing to hidden region to the right of the column
203    */
204   protected HiddenCursorPosition findRegionForVisColumn(int column)
205   {
206     if (hiddenColumns.isEmpty())
207     {
208       return null;
209     }
210
211     HiddenCursorPosition oldpos = cursorPos.get();
212     int index = oldpos.getRegionIndex();
213     int hiddenCount = oldpos.getHiddenSoFar();
214
215     if (column < firstColumn)
216     {
217       index = 0;
218       hiddenCount = 0;
219     }
220     else if ((index < hiddenColumns.size())
221             && (hiddenColumns.get(index)[0] <= column + hiddenCount))
222     {
223       // iterate from where we are now, if we're lucky we'll be close by
224       // (but still better than iterating from 0)
225       while ((index < hiddenColumns.size())
226               && (hiddenColumns.get(index)[0] <= column + hiddenCount))
227       {
228         int[] region = hiddenColumns.get(index);
229         hiddenCount += region[1] - region[0] + 1;
230         index++;
231       }
232     }
233     else
234     {
235       while ((index > 0)
236               && (hiddenColumns.get(index - 1)[1] >= column + hiddenCount))
237       {
238         index--;
239         int[] region = hiddenColumns.get(index);
240         hiddenCount -= region[1] - region[0] + 1;
241       }
242
243     }
244
245     if (index != oldpos.getRegionIndex()
246             || hiddenCount != oldpos.getHiddenSoFar())
247     {
248       HiddenCursorPosition newpos = new HiddenCursorPosition(index,
249               hiddenCount);
250       cursorPos.compareAndSet(oldpos, newpos);
251       return newpos;
252     }
253     return oldpos;
254   }
255 }