04d7189588b56a178e0c2f0882491a863e247c5d
[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         HiddenCursorPosition newpos = new HiddenCursorPosition(index,
92                 oldpos.getHiddenSoFar());
93         cursorPos.compareAndSet(oldpos, newpos);
94       }
95     }
96     hiddenColumns = hiddenCols;
97   }
98
99   /**
100    * Get the index of the region that column is within (if column is hidden) or
101    * which is to the right of column (if column is visible). If no hidden
102    * columns are to the right, will return size of hiddenColumns. If hidden
103    * columns is empty returns -1.
104    * 
105    * @param column
106    *          absolute position of a column in the alignment
107    * @return region index
108    */
109   protected HiddenCursorPosition findRegionForColumn(int column)
110   {
111     if (hiddenColumns == null)
112     {
113       return null;
114     }
115
116     HiddenCursorPosition oldpos = cursorPos.get();
117     int index = oldpos.getRegionIndex();
118     int hiddenCount = oldpos.getHiddenSoFar();
119
120     if (index == hiddenColumns.size())
121     {
122       // went past the end of hiddenColumns collection last time
123       index--;
124       int[] region = hiddenColumns.get(index);
125       hiddenCount -= region[1] - region[0] + 1;
126     }
127
128     if ((hiddenColumns.get(index)[0] <= column)
129             && hiddenColumns.get(index)[1] >= column)
130     {
131       // column is in the current region
132       // we hit the jackpot
133       // don't need to move index
134     }
135     else if (column < firstColumn)
136     {
137       index = 0;
138       hiddenCount = 0;
139     }
140     // column is after current region
141     else if (column > hiddenColumns.get(index)[1]) // includes if column >
142                                                    // lastColumn
143     {
144       // iterate from where we are now, if we're lucky we'll be close by
145       // (but still better than iterating from 0)
146       // stop when we find the region *before* column
147       // i.e. the next region starts after column or if not, ends after column
148       while ((index < hiddenColumns.size())
149               && (column > hiddenColumns.get(index)[1]))
150       {
151         int[] region = hiddenColumns.get(index);
152         hiddenCount += region[1] - region[0] + 1;
153         index++;
154       }
155     }
156
157     // column is before current region
158     else if (column < hiddenColumns.get(index)[0])
159     {
160       // column is before or in the previous region
161       if ((index > 0) && (hiddenColumns.get(index - 1)[1] >= column))
162       {
163         while ((index > 0) && (hiddenColumns.get(index - 1)[1] >= column))
164         {
165           index--;
166           int[] region = hiddenColumns.get(index);
167           hiddenCount -= region[1] - region[0] + 1;
168         }
169       }
170     }
171
172     if (index != oldpos.getRegionIndex()
173             || hiddenCount != oldpos.getHiddenSoFar())
174     {
175       HiddenCursorPosition newpos = new HiddenCursorPosition(index,
176               hiddenCount);
177       cursorPos.compareAndSet(oldpos, newpos);
178       return newpos;
179     }
180     return oldpos;
181   }
182
183   /**
184    * Get the number of hidden columns in regions before column i.e. excludes
185    * hidden columns in the region column is in, if any
186    * 
187    * @param column
188    *          index of column in visible alignment
189    * @return
190    */
191   protected HiddenCursorPosition getHiddenOffset(int column)
192   {
193     if (hiddenColumns == null)
194     {
195       return null;
196     }
197
198     HiddenCursorPosition oldpos = cursorPos.get();
199     int index = oldpos.getRegionIndex();
200     int hiddenCount = oldpos.getHiddenSoFar();
201
202     if (column < firstColumn)
203     {
204       index = 0;
205       hiddenCount = 0;
206     }
207     else if ((index < hiddenColumns.size())
208             && (hiddenColumns.get(index)[0] <= column + hiddenCount))
209     {
210       // iterate from where we are now, if we're lucky we'll be close by
211       // (but still better than iterating from 0)
212       while ((index < hiddenColumns.size())
213               && (hiddenColumns.get(index)[0] <= column + hiddenCount))
214       {
215         int[] region = hiddenColumns.get(index);
216         hiddenCount += region[1] - region[0] + 1;
217         index++;
218       }
219     }
220     else
221     {
222       while ((index > 0)
223               && (hiddenColumns.get(index - 1)[1] >= column + hiddenCount))
224       {
225         index--;
226         int[] region = hiddenColumns.get(index);
227         hiddenCount -= region[1] - region[0] + 1;
228       }
229
230     }
231
232     if (index != oldpos.getRegionIndex()
233             || hiddenCount != oldpos.getHiddenSoFar())
234     {
235       HiddenCursorPosition newpos = new HiddenCursorPosition(index,
236               hiddenCount);
237       cursorPos.compareAndSet(oldpos, newpos);
238       return newpos;
239     }
240     return oldpos;
241   }
242 }