Clover icon

Coverage Report

  1. Project Clover database Mon Sep 2 2024 17:57:51 BST
  2. Package jalview.datamodel

File HiddenColumns.java

 

Coverage histogram

../../img/srcFileCovDistChart10.png
0% of files have more coverage

Code metrics

102
289
33
1
1,129
650
98
0.34
8.76
33
2.97

Classes

Class Line # Actions
HiddenColumns 65 289 98
0.997641599.8%
 

Contributing tests

This file is covered by 470 tests. .

Source view

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.Arrays;
25    import java.util.BitSet;
26    import java.util.Iterator;
27    import java.util.List;
28    import java.util.concurrent.locks.ReentrantReadWriteLock;
29   
30    /**
31    * This class manages the collection of hidden columns associated with an
32    * alignment. To iterate over the collection, or over visible columns/regions,
33    * use an iterator obtained from one of:
34    *
35    * - getBoundedIterator: iterates over the hidden regions, within some bounds,
36    * returning *absolute* positions
37    *
38    * - getBoundedStartIterator: iterates over the start positions of hidden
39    * regions, within some bounds, returning *visible* positions
40    *
41    * - getVisContigsIterator: iterates over visible regions in a range, returning
42    * *absolute* positions
43    *
44    * - getVisibleColsIterator: iterates over the visible *columns*
45    *
46    * For performance reasons, provide bounds where possible. Note that column
47    * numbering begins at 0 throughout this class.
48    *
49    * @author kmourao
50    */
51   
52    /* Implementation notes:
53    *
54    * Methods which change the hiddenColumns collection should use a writeLock to
55    * prevent other threads accessing the hiddenColumns collection while changes
56    * are being made. They should also reset the hidden columns cursor, and either
57    * update the hidden columns count, or set it to 0 (so that it will later be
58    * updated when needed).
59    *
60    *
61    * Methods which only need read access to the hidden columns collection should
62    * use a readLock to prevent other threads changing the hidden columns
63    * collection while it is in use.
64    */
 
65    public class HiddenColumns
66    {
67    private static final int HASH_MULTIPLIER = 31;
68   
69    private static final ReentrantReadWriteLock LOCK = new ReentrantReadWriteLock();
70   
71    /*
72    * Cursor which tracks the last used hidden columns region, and the number
73    * of hidden columns up to (but not including) that region.
74    */
75    private HiddenColumnsCursor cursor = new HiddenColumnsCursor();
76   
77    /*
78    * cache of the number of hidden columns: must be kept up to date by methods
79    * which add or remove hidden columns
80    */
81    private int numColumns = 0;
82   
83    /*
84    * list of hidden column [start, end] ranges; the list is maintained in
85    * ascending start column order
86    */
87    private List<int[]> hiddenColumns = new ArrayList<>();
88   
89    /**
90    * Constructor
91    */
 
92  1941 toggle public HiddenColumns()
93    {
94    }
95   
96    /**
97    * Copy constructor
98    *
99    * @param copy
100    * the HiddenColumns object to copy from
101    */
 
102  28 toggle public HiddenColumns(HiddenColumns copy)
103    {
104  28 this(copy, Integer.MIN_VALUE, Integer.MAX_VALUE, 0);
105    }
106   
107    /**
108    * Copy constructor within bounds and with offset. Copies hidden column
109    * regions fully contained between start and end, and offsets positions by
110    * subtracting offset.
111    *
112    * @param copy
113    * HiddenColumns instance to copy from
114    * @param start
115    * lower bound to copy from
116    * @param end
117    * upper bound to copy to
118    * @param offset
119    * offset to subtract from each region boundary position
120    *
121    */
 
122  31 toggle public HiddenColumns(HiddenColumns copy, int start, int end, int offset)
123    {
124  31 try
125    {
126  31 LOCK.writeLock().lock();
127  31 if (copy != null)
128    {
129  31 numColumns = 0;
130  31 Iterator<int[]> it = copy.getBoundedIterator(start, end);
131  52 while (it.hasNext())
132    {
133  21 int[] region = it.next();
134    // still need to check boundaries because iterator returns
135    // all overlapping regions and we need contained regions
136  21 if (region[0] >= start && region[1] <= end)
137    {
138  19 hiddenColumns
139    .add(new int[]
140    { region[0] - offset, region[1] - offset });
141  19 numColumns += region[1] - region[0] + 1;
142    }
143    }
144  31 cursor = new HiddenColumnsCursor(hiddenColumns);
145    }
146    } finally
147    {
148  31 LOCK.writeLock().unlock();
149    }
150    }
151   
152    /**
153    * Adds the specified column range to the hidden columns collection
154    *
155    * @param start
156    * start of range to add (absolute position in alignment)
157    * @param end
158    * end of range to add (absolute position in alignment)
159    */
 
160  430 toggle public void hideColumns(int start, int end)
161    {
162  430 try
163    {
164  430 LOCK.writeLock().lock();
165   
166  430 int previndex = 0;
167  430 int prevHiddenCount = 0;
168  430 int regionindex = 0;
169  430 if (!hiddenColumns.isEmpty())
170    {
171    // set up cursor reset values
172  209 HiddenCursorPosition cursorPos = cursor.findRegionForColumn(start,
173    false);
174  209 regionindex = cursorPos.getRegionIndex();
175   
176  209 if (regionindex > 0)
177    {
178    // get previous index and hidden count for updating the cursor later
179  180 previndex = regionindex - 1;
180  180 int[] prevRegion = hiddenColumns.get(previndex);
181  180 prevHiddenCount = cursorPos.getHiddenSoFar()
182    - (prevRegion[1] - prevRegion[0] + 1);
183    }
184    }
185   
186    // new range follows everything else; check first to avoid looping over
187    // whole hiddenColumns collection
188  430 if (hiddenColumns.isEmpty()
189    || start > hiddenColumns.get(hiddenColumns.size() - 1)[1])
190    {
191  393 hiddenColumns.add(new int[] { start, end });
192  393 numColumns += end - start + 1;
193    }
194    else
195    {
196    /*
197    * traverse existing hidden ranges and insert / amend / append as
198    * appropriate
199    */
200  37 boolean added = false;
201  37 if (regionindex > 0)
202    {
203  8 added = insertRangeAtRegion(regionindex - 1, start, end);
204    }
205  37 if (!added && regionindex < hiddenColumns.size())
206    {
207  33 insertRangeAtRegion(regionindex, start, end);
208    }
209    }
210   
211    // reset the cursor to just before our insertion point: this saves
212    // a lot of reprocessing in large alignments
213  430 cursor = new HiddenColumnsCursor(hiddenColumns, previndex,
214    prevHiddenCount);
215    } finally
216    {
217  430 LOCK.writeLock().unlock();
218    }
219    }
220   
221    /**
222    * Insert [start, range] at the region at index i in hiddenColumns, if
223    * feasible
224    *
225    * @param i
226    * index to insert at
227    * @param start
228    * start of range to insert
229    * @param end
230    * end of range to insert
231    * @return true if range was successfully inserted
232    */
 
233  41 toggle private boolean insertRangeAtRegion(int i, int start, int end)
234    {
235  41 boolean added = false;
236   
237  41 int[] region = hiddenColumns.get(i);
238  41 if (end < region[0] - 1)
239    {
240    /*
241    * insert discontiguous preceding range
242    */
243  17 hiddenColumns.add(i, new int[] { start, end });
244  17 numColumns += end - start + 1;
245  17 added = true;
246    }
247  24 else if (end <= region[1])
248    {
249    /*
250    * new range overlaps existing, or is contiguous preceding it - adjust
251    * start column
252    */
253  8 int oldstart = region[0];
254  8 region[0] = Math.min(region[0], start);
255  8 numColumns += oldstart - region[0]; // new columns are between old and
256    // adjusted starts
257  8 added = true;
258    }
259  16 else if (start <= region[1] + 1)
260    {
261    /*
262    * new range overlaps existing, or is contiguous following it - adjust
263    * start and end columns
264    */
265  12 insertRangeAtOverlap(i, start, end, region);
266  12 added = true;
267    }
268  41 return added;
269    }
270   
271    /**
272    * Insert a range whose start position overlaps an existing region and/or is
273    * contiguous to the right of the region
274    *
275    * @param i
276    * index to insert at
277    * @param start
278    * start of range to insert
279    * @param end
280    * end of range to insert
281    * @param region
282    * the overlapped/continued region
283    */
 
284  12 toggle private void insertRangeAtOverlap(int i, int start, int end, int[] region)
285    {
286  12 int oldstart = region[0];
287  12 int oldend = region[1];
288  12 region[0] = Math.min(region[0], start);
289  12 region[1] = Math.max(region[1], end);
290   
291  12 numColumns += oldstart - region[0];
292   
293    /*
294    * also update or remove any subsequent ranges
295    * that are overlapped
296    */
297  12 int endi = i;
298  20 while (endi < hiddenColumns.size() - 1)
299    {
300  10 int[] nextRegion = hiddenColumns.get(endi + 1);
301  10 if (nextRegion[0] > end + 1)
302    {
303    /*
304    * gap to next hidden range - no more to update
305    */
306  2 break;
307    }
308  8 numColumns -= nextRegion[1] - nextRegion[0] + 1;
309  8 region[1] = Math.max(nextRegion[1], end);
310  8 endi++;
311    }
312  12 numColumns += region[1] - oldend;
313  12 hiddenColumns.subList(i + 1, endi + 1).clear();
314    }
315   
316    /**
317    * hide a list of ranges
318    *
319    * @param ranges
320    */
 
321  3 toggle public void hideList(List<int[]> ranges)
322    {
323  3 try
324    {
325  3 LOCK.writeLock().lock();
326  3 for (int[] r : ranges)
327    {
328  9 hideColumns(r[0], r[1]);
329    }
330  3 cursor = new HiddenColumnsCursor(hiddenColumns);
331   
332    } finally
333    {
334  3 LOCK.writeLock().unlock();
335    }
336    }
337   
338    /**
339    * Unhides, and adds to the selection list, all hidden columns
340    */
 
341  141 toggle public void revealAllHiddenColumns(ColumnSelection sel)
342    {
343  141 try
344    {
345  141 LOCK.writeLock().lock();
346   
347  141 for (int[] region : hiddenColumns)
348    {
349  725 for (int j = region[0]; j < region[1] + 1; j++)
350    {
351  633 sel.addElement(j);
352    }
353    }
354  141 hiddenColumns.clear();
355  141 cursor = new HiddenColumnsCursor(hiddenColumns);
356  141 numColumns = 0;
357   
358    } finally
359    {
360  141 LOCK.writeLock().unlock();
361    }
362    }
363   
364    /**
365    * Reveals, and marks as selected, the hidden column range with the given
366    * start column
367    *
368    * @param start
369    * the start column to look for
370    * @param sel
371    * the column selection to add the hidden column range to
372    */
 
373  6 toggle public void revealHiddenColumns(int start, ColumnSelection sel)
374    {
375  6 try
376    {
377  6 LOCK.writeLock().lock();
378   
379  6 if (!hiddenColumns.isEmpty())
380    {
381  5 int regionIndex = cursor.findRegionForColumn(start, false)
382    .getRegionIndex();
383   
384  5 if (regionIndex != -1 && regionIndex != hiddenColumns.size())
385    {
386    // regionIndex is the region which either contains start
387    // or lies to the right of start
388  4 int[] region = hiddenColumns.get(regionIndex);
389  4 if (start == region[0])
390    {
391  14 for (int j = region[0]; j < region[1] + 1; j++)
392    {
393  11 sel.addElement(j);
394    }
395  3 int colsToRemove = region[1] - region[0] + 1;
396  3 hiddenColumns.remove(regionIndex);
397  3 numColumns -= colsToRemove;
398    }
399    }
400    }
401    } finally
402    {
403  6 LOCK.writeLock().unlock();
404    }
405    }
406   
407    /**
408    * Output regions data as a string. String is in the format:
409    * reg0[0]<between>reg0[1]<delimiter>reg1[0]<between>reg1[1] ... regn[1]
410    *
411    * @param delimiter
412    * string to delimit regions
413    * @param betweenstring
414    * to put between start and end region values
415    * @return regions formatted according to delimiter and between strings
416    */
 
417  8 toggle public String regionsToString(String delimiter, String between)
418    {
419  8 try
420    {
421  8 LOCK.readLock().lock();
422  8 StringBuilder regionBuilder = new StringBuilder();
423   
424  8 boolean first = true;
425  8 for (int[] range : hiddenColumns)
426    {
427  19 if (!first)
428    {
429  12 regionBuilder.append(delimiter);
430    }
431    else
432    {
433  7 first = false;
434    }
435  19 regionBuilder.append(range[0]).append(between).append(range[1]);
436   
437    }
438   
439  8 return regionBuilder.toString();
440    } finally
441    {
442  8 LOCK.readLock().unlock();
443    }
444    }
445   
446    /**
447    * Find the number of hidden columns
448    *
449    * @return number of hidden columns
450    */
 
451  8290 toggle public int getSize()
452    {
453  8290 return numColumns;
454    }
455   
456    /**
457    * Get the number of distinct hidden regions
458    *
459    * @return number of regions
460    */
 
461  46 toggle public int getNumberOfRegions()
462    {
463  46 try
464    {
465  46 LOCK.readLock().lock();
466  46 return hiddenColumns.size();
467    } finally
468    {
469  46 LOCK.readLock().unlock();
470    }
471    }
472   
473    /**
474    * Answers true if obj is an instance of HiddenColumns, and holds the same
475    * array of start-end column ranges as this, else answers false
476    */
 
477  60 toggle @Override
478    public boolean equals(Object obj)
479    {
480  60 try
481    {
482  60 LOCK.readLock().lock();
483   
484  60 if (!(obj instanceof HiddenColumns))
485    {
486  2 return false;
487    }
488  58 HiddenColumns that = (HiddenColumns) obj;
489   
490    /*
491    * check hidden columns are either both null, or match
492    */
493   
494  58 if (that.hiddenColumns.size() != this.hiddenColumns.size())
495    {
496  20 return false;
497    }
498   
499  38 Iterator<int[]> it = this.iterator();
500  38 Iterator<int[]> thatit = that.iterator();
501  63 while (it.hasNext())
502    {
503  44 if (!(Arrays.equals(it.next(), thatit.next())))
504    {
505  19 return false;
506    }
507    }
508  19 return true;
509   
510    } finally
511    {
512  60 LOCK.readLock().unlock();
513    }
514    }
515   
516    /**
517    * Return absolute column index for a visible column index
518    *
519    * @param column
520    * int column index in alignment view (count from zero)
521    * @return alignment column index for column
522    */
 
523  286287 toggle public int visibleToAbsoluteColumn(int column)
524    {
525  286287 try
526    {
527  286287 LOCK.readLock().lock();
528  286287 int result = column;
529   
530  286287 if (!hiddenColumns.isEmpty())
531    {
532  276628 result += cursor.findRegionForColumn(column, true).getHiddenSoFar();
533    }
534   
535  286287 return result;
536    } finally
537    {
538  286287 LOCK.readLock().unlock();
539    }
540    }
541   
542    /**
543    * Use this method to find out where a column will appear in the visible
544    * alignment when hidden columns exist. If the column is not visible, then the
545    * index of the next visible column on the left will be returned (or 0 if
546    * there is no visible column on the left)
547    *
548    * @param hiddenColumn
549    * the column index in the full alignment including hidden columns
550    * @return the position of the column in the visible alignment
551    */
 
552  349 toggle public int absoluteToVisibleColumn(int hiddenColumn)
553    {
554  349 try
555    {
556  349 LOCK.readLock().lock();
557  349 int result = hiddenColumn;
558   
559  349 if (!hiddenColumns.isEmpty())
560    {
561  102 HiddenCursorPosition cursorPos = cursor
562    .findRegionForColumn(hiddenColumn, false);
563  102 int index = cursorPos.getRegionIndex();
564  102 int hiddenBeforeCol = cursorPos.getHiddenSoFar();
565   
566    // just subtract hidden cols count - this works fine if column is
567    // visible
568  102 result = hiddenColumn - hiddenBeforeCol;
569   
570    // now check in case column is hidden - it will be in the returned
571    // hidden region
572  102 if (index < hiddenColumns.size())
573    {
574  60 int[] region = hiddenColumns.get(index);
575  60 if (hiddenColumn >= region[0] && hiddenColumn <= region[1])
576    {
577    // actually col is hidden, return region[0]-1
578    // unless region[0]==0 in which case return 0
579  14 if (region[0] == 0)
580    {
581  3 result = 0;
582    }
583    else
584    {
585  11 result = region[0] - 1 - hiddenBeforeCol;
586    }
587    }
588    }
589    }
590   
591  349 return result; // return the shifted position after removing hidden
592    // columns.
593    } finally
594    {
595  349 LOCK.readLock().unlock();
596    }
597    }
598   
599    /**
600    * Find the visible column which is a given visible number of columns to the
601    * left (negative visibleDistance) or right (positive visibleDistance) of
602    * startColumn. If startColumn is not visible, we use the visible column at
603    * the left boundary of the hidden region containing startColumn.
604    *
605    * @param visibleDistance
606    * the number of visible columns to offset by (left offset = negative
607    * value; right offset = positive value)
608    * @param startColumn
609    * the position of the column to start from (absolute position)
610    * @return the position of the column which is <visibleDistance> away
611    * (absolute position)
612    */
 
613  81 toggle public int offsetByVisibleColumns(int visibleDistance, int startColumn)
614    {
615  81 try
616    {
617  81 LOCK.readLock().lock();
618  81 int start = absoluteToVisibleColumn(startColumn);
619  81 return visibleToAbsoluteColumn(start + visibleDistance);
620   
621    } finally
622    {
623  81 LOCK.readLock().unlock();
624    }
625    }
626   
627    /**
628    * This method returns the rightmost limit of a region of an alignment with
629    * hidden columns. In otherwords, the next hidden column.
630    *
631    * @param alPos
632    * the absolute (visible) alignmentPosition to find the next hidden
633    * column for
634    * @return the index of the next hidden column, or alPos if there is no next
635    * hidden column
636    */
 
637  13 toggle public int getNextHiddenBoundary(boolean left, int alPos)
638    {
639  13 try
640    {
641  13 LOCK.readLock().lock();
642  13 if (!hiddenColumns.isEmpty())
643    {
644  11 int index = cursor.findRegionForColumn(alPos, false)
645    .getRegionIndex();
646   
647  11 if (left && index > 0)
648    {
649  3 int[] region = hiddenColumns.get(index - 1);
650  3 return region[1];
651    }
652  8 else if (!left && index < hiddenColumns.size())
653    {
654  4 int[] region = hiddenColumns.get(index);
655  4 if (alPos < region[0])
656    {
657  2 return region[0];
658    }
659  2 else if ((alPos <= region[1])
660    && (index + 1 < hiddenColumns.size()))
661    {
662    // alPos is within a hidden region, return the next one
663    // if there is one
664  1 region = hiddenColumns.get(index + 1);
665  1 return region[0];
666    }
667    }
668    }
669  7 return alPos;
670    } finally
671    {
672  13 LOCK.readLock().unlock();
673    }
674    }
675   
676    /**
677    * Answers if a column in the alignment is visible
678    *
679    * @param column
680    * absolute position of column in the alignment
681    * @return true if column is visible
682    */
 
683  37875 toggle public boolean isVisible(int column)
684    {
685  37875 try
686    {
687  37875 LOCK.readLock().lock();
688   
689  37876 if (!hiddenColumns.isEmpty())
690    {
691  88 int regionindex = cursor.findRegionForColumn(column, false)
692    .getRegionIndex();
693  88 if (regionindex > -1 && regionindex < hiddenColumns.size())
694    {
695  59 int[] region = hiddenColumns.get(regionindex);
696    // already know that column <= region[1] as cursor returns containing
697    // region or region to right
698  59 if (column >= region[0])
699    {
700  26 return false;
701    }
702    }
703    }
704  37850 return true;
705   
706    } finally
707    {
708  37876 LOCK.readLock().unlock();
709    }
710    }
711   
712    /**
713    *
714    * @return true if there are columns hidden
715    */
 
716  8464 toggle public boolean hasHiddenColumns()
717    {
718  8464 try
719    {
720  8464 LOCK.readLock().lock();
721   
722    // we don't use getSize()>0 here because it has to iterate over
723    // the full hiddenColumns collection and so will be much slower
724  8464 return (!hiddenColumns.isEmpty());
725    } finally
726    {
727  8464 LOCK.readLock().unlock();
728    }
729    }
730   
731    /**
732    *
733    * @return true if there is more than one hidden column region
734    */
 
735  10 toggle public boolean hasMultiHiddenColumnRegions()
736    {
737  10 try
738    {
739  10 LOCK.readLock().lock();
740  10 return !hiddenColumns.isEmpty() && hiddenColumns.size() > 1;
741    } finally
742    {
743  10 LOCK.readLock().unlock();
744    }
745    }
746   
747    /**
748    * Returns a hashCode built from hidden column ranges
749    */
 
750  3 toggle @Override
751    public int hashCode()
752    {
753  3 try
754    {
755  3 LOCK.readLock().lock();
756  3 int hashCode = 1;
757   
758  3 for (int[] hidden : hiddenColumns)
759    {
760  5 hashCode = HASH_MULTIPLIER * hashCode + hidden[0];
761  5 hashCode = HASH_MULTIPLIER * hashCode + hidden[1];
762    }
763  3 return hashCode;
764    } finally
765    {
766  3 LOCK.readLock().unlock();
767    }
768    }
769   
770    /**
771    * Hide columns corresponding to the marked bits
772    *
773    * @param inserts
774    * - columns mapped to bits starting from zero
775    */
 
776  10 toggle public void hideColumns(BitSet inserts)
777    {
778  10 hideColumns(inserts, 0, inserts.length() - 1);
779    }
780   
781    /**
782    * Hide columns corresponding to the marked bits, within the range
783    * [start,end]. Entries in tohide which are outside [start,end] are ignored.
784    *
785    * @param tohide
786    * columns mapped to bits starting from zero
787    * @param start
788    * start of range to hide columns within
789    * @param end
790    * end of range to hide columns within
791    */
 
792  18 toggle private void hideColumns(BitSet tohide, int start, int end)
793    {
794  18 try
795    {
796  18 LOCK.writeLock().lock();
797  18 for (int firstSet = tohide
798  39 .nextSetBit(start), lastSet = start; firstSet >= start
799    && lastSet <= end; firstSet = tohide
800    .nextSetBit(lastSet))
801    {
802  21 lastSet = tohide.nextClearBit(firstSet);
803  21 if (lastSet <= end)
804    {
805  12 hideColumns(firstSet, lastSet - 1);
806    }
807  9 else if (firstSet <= end)
808    {
809  8 hideColumns(firstSet, end);
810    }
811    }
812  18 cursor = new HiddenColumnsCursor(hiddenColumns);
813    } finally
814    {
815  18 LOCK.writeLock().unlock();
816    }
817    }
818   
819    /**
820    * Hide columns corresponding to the marked bits, within the range
821    * [start,end]. Entries in tohide which are outside [start,end] are ignored.
822    * NB Existing entries in [start,end] are cleared.
823    *
824    * @param tohide
825    * columns mapped to bits starting from zero
826    * @param start
827    * start of range to hide columns within
828    * @param end
829    * end of range to hide columns within
830    */
 
831  8 toggle public void clearAndHideColumns(BitSet tohide, int start, int end)
832    {
833  8 clearHiddenColumnsInRange(start, end);
834  8 hideColumns(tohide, start, end);
835    }
836   
837    /**
838    * Make all columns in the range [start,end] visible
839    *
840    * @param start
841    * start of range to show columns
842    * @param end
843    * end of range to show columns
844    */
 
845  8 toggle private void clearHiddenColumnsInRange(int start, int end)
846    {
847  8 try
848    {
849  8 LOCK.writeLock().lock();
850   
851  8 if (!hiddenColumns.isEmpty())
852    {
853  7 HiddenCursorPosition pos = cursor.findRegionForColumn(start, false);
854  7 int index = pos.getRegionIndex();
855   
856  7 if (index != -1 && index != hiddenColumns.size())
857    {
858    // regionIndex is the region which either contains start
859    // or lies to the right of start
860  5 int[] region = hiddenColumns.get(index);
861  5 if (region[0] < start && region[1] >= start)
862    {
863    // region contains start, truncate so that it ends just before start
864  2 numColumns -= region[1] - start + 1;
865  2 region[1] = start - 1;
866  2 index++;
867    }
868   
869  5 int endi = index;
870  11 while (endi < hiddenColumns.size())
871    {
872  8 region = hiddenColumns.get(endi);
873   
874  8 if (region[1] > end)
875    {
876  2 if (region[0] <= end)
877    {
878    // region contains end, truncate so it starts just after end
879  1 numColumns -= end - region[0] + 1;
880  1 region[0] = end + 1;
881    }
882  2 break;
883    }
884   
885  6 numColumns -= region[1] - region[0] + 1;
886  6 endi++;
887    }
888  5 hiddenColumns.subList(index, endi).clear();
889   
890    }
891   
892  7 cursor = new HiddenColumnsCursor(hiddenColumns);
893    }
894    } finally
895    {
896  8 LOCK.writeLock().unlock();
897    }
898    }
899   
900    /**
901    *
902    * @param updates
903    * BitSet where hidden columns will be marked
904    */
 
905  2 toggle protected void andNot(BitSet updates)
906    {
907  2 try
908    {
909  2 LOCK.writeLock().lock();
910   
911  2 BitSet hiddenBitSet = new BitSet();
912  2 for (int[] range : hiddenColumns)
913    {
914  2 hiddenBitSet.set(range[0], range[1] + 1);
915    }
916  2 hiddenBitSet.andNot(updates);
917  2 hiddenColumns.clear();
918  2 hideColumns(hiddenBitSet);
919    } finally
920    {
921  2 LOCK.writeLock().unlock();
922    }
923    }
924   
925    /**
926    * Calculate the visible start and end index of an alignment.
927    *
928    * @param width
929    * full alignment width
930    * @return integer array where: int[0] = startIndex, and int[1] = endIndex
931    */
 
932  126 toggle public int[] getVisibleStartAndEndIndex(int width)
933    {
934  126 try
935    {
936  126 LOCK.readLock().lock();
937   
938  126 int firstVisible = 0;
939  126 int lastVisible = width - 1;
940   
941  126 if (!hiddenColumns.isEmpty())
942    {
943    // first visible col with index 0, convert to absolute index
944  4 firstVisible = visibleToAbsoluteColumn(0);
945   
946    // last visible column is either immediately to left of
947    // last hidden region, or is just the last column in the alignment
948  4 int[] lastregion = hiddenColumns.get(hiddenColumns.size() - 1);
949  4 if (lastregion[1] == width - 1)
950    {
951    // last region is at very end of alignment
952    // last visible column immediately precedes it
953  1 lastVisible = lastregion[0] - 1;
954    }
955    }
956  126 return new int[] { firstVisible, lastVisible };
957   
958    } finally
959    {
960  126 LOCK.readLock().unlock();
961    }
962    }
963   
964    /**
965    * Finds the hidden region (if any) which starts or ends at res
966    *
967    * @param res
968    * visible residue position, unadjusted for hidden columns
969    * @return region as [start,end] or null if no matching region is found. If
970    * res is adjacent to two regions, returns the left region.
971    */
 
972  17 toggle public int[] getRegionWithEdgeAtRes(int res)
973    {
974  17 try
975    {
976  17 LOCK.readLock().lock();
977  17 int adjres = visibleToAbsoluteColumn(res);
978   
979  17 int[] reveal = null;
980   
981  17 if (!hiddenColumns.isEmpty())
982    {
983    // look for a region ending just before adjres
984  16 int regionindex = cursor.findRegionForColumn(adjres - 1, false)
985    .getRegionIndex();
986  16 if (regionindex < hiddenColumns.size()
987    && hiddenColumns.get(regionindex)[1] == adjres - 1)
988    {
989  5 reveal = hiddenColumns.get(regionindex);
990    }
991    // check if the region ends just after adjres
992  11 else if (regionindex < hiddenColumns.size()
993    && hiddenColumns.get(regionindex)[0] == adjres + 1)
994    {
995  5 reveal = hiddenColumns.get(regionindex);
996    }
997    }
998  17 return reveal;
999   
1000    } finally
1001    {
1002  17 LOCK.readLock().unlock();
1003    }
1004    }
1005   
1006    /**
1007    * Return an iterator over the hidden regions
1008    */
 
1009  285 toggle public Iterator<int[]> iterator()
1010    {
1011  285 try
1012    {
1013  285 LOCK.readLock().lock();
1014  285 return new RangeIterator(hiddenColumns);
1015    } finally
1016    {
1017  285 LOCK.readLock().unlock();
1018    }
1019    }
1020   
1021    /**
1022    * Return a bounded iterator over the hidden regions
1023    *
1024    * @param start
1025    * position to start from (inclusive, absolute column position)
1026    * @param end
1027    * position to end at (inclusive, absolute column position)
1028    * @return
1029    */
 
1030  57 toggle public Iterator<int[]> getBoundedIterator(int start, int end)
1031    {
1032  57 try
1033    {
1034  57 LOCK.readLock().lock();
1035  57 return new RangeIterator(start, end, hiddenColumns);
1036    } finally
1037    {
1038  57 LOCK.readLock().unlock();
1039    }
1040    }
1041   
1042    /**
1043    * Return a bounded iterator over the *visible* start positions of hidden
1044    * regions
1045    *
1046    * @param start
1047    * position to start from (inclusive, visible column position)
1048    * @param end
1049    * position to end at (inclusive, visible column position)
1050    */
 
1051  491 toggle public Iterator<Integer> getStartRegionIterator(int start, int end)
1052    {
1053  491 try
1054    {
1055  491 LOCK.readLock().lock();
1056   
1057    // get absolute position of column in alignment
1058  491 int absoluteStart = visibleToAbsoluteColumn(start);
1059   
1060    // Get cursor position and supply it to the iterator:
1061    // Since we want visible region start, we look for a cursor for the
1062    // (absoluteStart-1), then if absoluteStart is the start of a visible
1063    // region we'll get the cursor pointing to the region before, which is
1064    // what we want
1065  491 HiddenCursorPosition pos = cursor
1066    .findRegionForColumn(absoluteStart - 1, false);
1067   
1068  491 return new StartRegionIterator(pos, start, end, hiddenColumns);
1069    } finally
1070    {
1071  491 LOCK.readLock().unlock();
1072    }
1073    }
1074   
1075    /**
1076    * Return an iterator over visible *columns* (not regions) between the given
1077    * start and end boundaries
1078    *
1079    * @param start
1080    * first column (inclusive)
1081    * @param end
1082    * last column (inclusive)
1083    */
 
1084  2457 toggle public Iterator<Integer> getVisibleColsIterator(int start, int end)
1085    {
1086  2457 try
1087    {
1088  2457 LOCK.readLock().lock();
1089  2457 return new RangeElementsIterator(
1090    new VisibleContigsIterator(start, end + 1, hiddenColumns));
1091    } finally
1092    {
1093  2457 LOCK.readLock().unlock();
1094    }
1095    }
1096   
1097    /**
1098    * return an iterator over visible segments between the given start and end
1099    * boundaries
1100    *
1101    * @param start
1102    * first column, inclusive from 0
1103    * @param end
1104    * last column - not inclusive
1105    * @param useVisibleCoords
1106    * if true, start and end are visible column positions, not absolute
1107    * positions*
1108    */
 
1109  760 toggle public VisibleContigsIterator getVisContigsIterator(int start, int end,
1110    boolean useVisibleCoords)
1111    {
1112  760 int adjstart = start;
1113  760 int adjend = end;
1114  760 if (useVisibleCoords)
1115    {
1116  501 adjstart = visibleToAbsoluteColumn(start);
1117  501 adjend = visibleToAbsoluteColumn(end);
1118    }
1119   
1120  760 try
1121    {
1122  760 LOCK.readLock().lock();
1123  760 return new VisibleContigsIterator(adjstart, adjend, hiddenColumns);
1124    } finally
1125    {
1126  760 LOCK.readLock().unlock();
1127    }
1128    }
1129    }