9cb329864400f391cb510027fca2587e7a412740
[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    *          new hidden columns collection
52    */
53   protected void resetCursor(List<int[]> hiddenCols)
54   {
55     resetCursor(hiddenCols, 0, 0);
56   }
57
58   /**
59    * Reset the cursor with a new hidden columns collection, where we know in
60    * advance the index and hidden columns count of a particular location.
61    * 
62    * @param hiddenCols
63    *          new hidden columns collection
64    * @param index
65    *          cursor index to reset to
66    * @param hiddencount
67    *          hidden columns count to reset to
68    */
69   protected void resetCursor(List<int[]> hiddenCols, int index,
70           int hiddencount)
71   {
72     hiddenColumns = hiddenCols;
73     if ((hiddenCols != null) && (!hiddenCols.isEmpty()))
74     {
75       firstColumn = hiddenColumns.get(0)[0];
76       HiddenCursorPosition oldpos = cursorPos.get();
77       HiddenCursorPosition newpos = new HiddenCursorPosition(index,
78               hiddencount);
79       cursorPos.compareAndSet(oldpos, newpos);
80     }
81   }
82
83   /**
84    * Delete the region the cursor is currently at. Avoids having to reset the
85    * cursor just because we deleted a region.
86    * 
87    * Calls to updateForDeletedRegion should be made from within a writeLock in
88    * the HiddenColumns class - since changes to the hiddenColumns collection
89    * require a writeLock the lock should already exist.
90    *
91    * @param hiddenCols
92    */
93   protected void updateForDeletedRegion(List<int[]> hiddenCols)
94   {
95
96     if ((hiddenCols != null) && (!hiddenCols.isEmpty()))
97     {
98       // if there is a region to the right of the current region,
99       // nothing changes; otherwise
100       // we deleted the last region (index=hiddenCols.size()-1)
101       // or the index was at the end of the alignment (index=hiddenCols.size())
102       HiddenCursorPosition oldpos = cursorPos.get();
103
104       int index = oldpos.getRegionIndex();
105       if (index >= hiddenColumns.size() - 1)
106       {
107         // deleted last region, index is now end of alignment
108         index = hiddenCols.size();
109
110         HiddenCursorPosition newpos = new HiddenCursorPosition(index,
111                 oldpos.getHiddenSoFar());
112         cursorPos.compareAndSet(oldpos, newpos);
113       }
114     }
115     hiddenColumns = hiddenCols;
116   }
117
118   /**
119    * Get the index of the region that column is within (if column is hidden) or
120    * which is to the right of column (if column is visible). If no hidden
121    * columns are to the right, will return size of hiddenColumns. If hidden
122    * columns is empty returns -1.
123    * 
124    * @param column
125    *          absolute position of a column in the alignment
126    * @return region index
127    */
128   protected HiddenCursorPosition findRegionForColumn(int column)
129   {
130     if (hiddenColumns == null)
131     {
132       return null;
133     }
134
135     HiddenCursorPosition oldpos = cursorPos.get();
136     int index = oldpos.getRegionIndex();
137     int hiddenCount = oldpos.getHiddenSoFar();
138
139     if (index == hiddenColumns.size())
140     {
141       // went past the end of hiddenColumns collection last time
142       index--;
143       int[] region = hiddenColumns.get(index);
144       hiddenCount -= region[1] - region[0] + 1;
145     }
146
147     if ((hiddenColumns.get(index)[0] <= column)
148             && hiddenColumns.get(index)[1] >= column)
149     {
150       // column is in the current region
151       // we hit the jackpot
152       // don't need to move index
153     }
154     else if (column < firstColumn)
155     {
156       index = 0;
157       hiddenCount = 0;
158     }
159     // column is after current region
160     else if (column > hiddenColumns.get(index)[1]) // includes if column >
161                                                    // lastColumn
162     {
163       // iterate from where we are now, if we're lucky we'll be close by
164       // (but still better than iterating from 0)
165       // stop when we find the region *before* column
166       // i.e. the next region starts after column or if not, ends after column
167       while ((index < hiddenColumns.size())
168               && (column > hiddenColumns.get(index)[1]))
169       {
170         int[] region = hiddenColumns.get(index);
171         hiddenCount += region[1] - region[0] + 1;
172         index++;
173       }
174     }
175
176     // column is before current region
177     else if (column < hiddenColumns.get(index)[0])
178     {
179       // column is before or in the previous region
180       if ((index > 0) && (hiddenColumns.get(index - 1)[1] >= column))
181       {
182         while ((index > 0) && (hiddenColumns.get(index - 1)[1] >= column))
183         {
184           index--;
185           int[] region = hiddenColumns.get(index);
186           hiddenCount -= region[1] - region[0] + 1;
187         }
188       }
189     }
190
191     if (index != oldpos.getRegionIndex()
192             || hiddenCount != oldpos.getHiddenSoFar())
193     {
194       HiddenCursorPosition newpos = new HiddenCursorPosition(index,
195               hiddenCount);
196       cursorPos.compareAndSet(oldpos, newpos);
197       return newpos;
198     }
199     return oldpos;
200   }
201
202   /**
203    * Get the number of hidden columns in regions before column i.e. excludes
204    * hidden columns in the region column is in, if any
205    * 
206    * @param column
207    *          index of column in visible alignment
208    * @return
209    */
210   protected HiddenCursorPosition getHiddenOffset(int column)
211   {
212     if (hiddenColumns == null)
213     {
214       return null;
215     }
216
217     HiddenCursorPosition oldpos = cursorPos.get();
218     int index = oldpos.getRegionIndex();
219     int hiddenCount = oldpos.getHiddenSoFar();
220
221     if (column < firstColumn)
222     {
223       index = 0;
224       hiddenCount = 0;
225     }
226     else if ((index < hiddenColumns.size())
227             && (hiddenColumns.get(index)[0] <= column + hiddenCount))
228     {
229       // iterate from where we are now, if we're lucky we'll be close by
230       // (but still better than iterating from 0)
231       while ((index < hiddenColumns.size())
232               && (hiddenColumns.get(index)[0] <= column + hiddenCount))
233       {
234         int[] region = hiddenColumns.get(index);
235         hiddenCount += region[1] - region[0] + 1;
236         index++;
237       }
238     }
239     else
240     {
241       while ((index > 0)
242               && (hiddenColumns.get(index - 1)[1] >= column + hiddenCount))
243       {
244         index--;
245         int[] region = hiddenColumns.get(index);
246         hiddenCount -= region[1] - region[0] + 1;
247       }
248
249     }
250
251     if (index != oldpos.getRegionIndex()
252             || hiddenCount != oldpos.getHiddenSoFar())
253     {
254       HiddenCursorPosition newpos = new HiddenCursorPosition(index,
255               hiddenCount);
256       cursorPos.compareAndSet(oldpos, newpos);
257       return newpos;
258     }
259     return oldpos;
260   }
261 }