JAL-2759 First pass instantiate hiddenColumns collection in constructor
[jalview.git] / src / jalview / datamodel / HiddenColumns.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.BitSet;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.concurrent.locks.ReentrantReadWriteLock;
28
29 /**
30  * This class manages the collection of hidden columns associated with an
31  * alignment. To iterate over the collection, or over visible columns/regions,
32  * use an iterator obtained from one of:
33  * 
34  * - getBoundedIterator: iterates over the hidden regions, within some bounds,
35  * returning absolute positions
36  * 
37  * - getBoundedStartIterator: iterates over the start positions of hidden
38  * regions, within some bounds, returning visible positions
39  * 
40  * - getVisContigsIterator: iterates over visible regions in a range, returning
41  * absolute positions
42  * 
43  * - getVisibleColsIterator: iterates over the visible *columns*
44  * 
45  * For performance reasons, provide bounds where possible. Note that column
46  * numbering begins at 0 throughout this class.
47  * 
48  * @author kmourao
49  *
50  */
51 public class HiddenColumns
52 {
53   private static final int HASH_MULTIPLIER = 31;
54
55   private static final ReentrantReadWriteLock LOCK = new ReentrantReadWriteLock();
56
57   /*
58    * Cursor which tracks the last used hidden columns region, and the number 
59    * of hidden columns up to (but not including) that region.
60    */
61   private HiddenColumnsCursor cursor = new HiddenColumnsCursor();
62
63   /*
64    * cache of the number of hidden columns
65    */
66   private int numColumns = 0;
67
68   /*
69    * list of hidden column [start, end] ranges; the list is maintained in
70    * ascending start column order
71    */
72   private List<int[]> hiddenColumns = new ArrayList<>();
73
74   /**
75    * Constructor
76    */
77   public HiddenColumns()
78   {
79   }
80
81   /* 
82    * Methods which change the hiddenColumns collection. These methods should 
83    * use a writeLock to prevent other threads accessing the hiddenColumns
84    * collection while changes are being made. They should also reset the hidden
85    * columns cursor, and either update the hidden columns count, or set it to 0 
86    * (so that it will later be updated when needed).
87    */
88
89   /**
90    * Copy constructor
91    * 
92    * @param copy
93    *          the HiddenColumns object to copy from
94    */
95   public HiddenColumns(HiddenColumns copy)
96   {
97     this(copy, Integer.MIN_VALUE, Integer.MAX_VALUE, 0);
98   }
99
100   /**
101    * Copy constructor within bounds and with offset. Copies hidden column
102    * regions fully contained between start and end, and offsets positions by
103    * subtracting offset.
104    * 
105    * @param copy
106    *          HiddenColumns instance to copy from
107    * @param start
108    *          lower bound to copy from
109    * @param end
110    *          upper bound to copy to
111    * @param offset
112    *          offset to subtract from each region boundary position
113    * 
114    */
115   public HiddenColumns(HiddenColumns copy, int start, int end, int offset)
116   {
117     try
118     {
119       LOCK.writeLock().lock();
120       if (copy != null)
121       {
122         numColumns = 0;
123         Iterator<int[]> it = copy.getBoundedIterator(start, end);
124         while (it.hasNext())
125         {
126           int[] region = it.next();
127           // still need to check boundaries because iterator returns
128           // all overlapping regions and we need contained regions
129           if (region[0] >= start && region[1] <= end)
130           {
131             hiddenColumns.add(
132                     new int[]
133             { region[0] - offset, region[1] - offset });
134             numColumns += region[1] - region[0] + 1;
135           }
136         }
137         cursor.resetCursor(hiddenColumns);
138       }
139     } finally
140     {
141       LOCK.writeLock().unlock();
142     }
143   }
144
145   /**
146    * Adds the specified column range to the hidden columns collection
147    * 
148    * @param start
149    *          start of range to add (absolute position in alignment)
150    * @param end
151    *          end of range to add (absolute position in alignment)
152    */
153   public void hideColumns(int start, int end)
154   {
155     boolean wasAlreadyLocked = false;
156     try
157     {
158       // check if the write lock was already locked by this thread,
159       // as this method can be called internally in loops within HiddenColumns
160       if (!LOCK.isWriteLockedByCurrentThread())
161       {
162         LOCK.writeLock().lock();
163       }
164       else
165       {
166         wasAlreadyLocked = true;
167       }
168
169       int previndex = 0;
170       int prevHiddenCount = 0;
171       int regionindex = 0;
172       if (!hiddenColumns.isEmpty())
173       {
174         // set up cursor reset values
175         HiddenCursorPosition cursorPos = cursor.findRegionForColumn(start);
176         regionindex = cursorPos.getRegionIndex();
177
178         if (regionindex > 0)
179         {
180           // get previous index and hidden count for updating the cursor later
181           previndex = regionindex - 1;
182           int[] prevRegion = hiddenColumns.get(previndex);
183           prevHiddenCount = cursorPos.getHiddenSoFar()
184                   - (prevRegion[1] - prevRegion[0] + 1);
185         }
186       }
187
188       /*
189        * new range follows everything else; check first to avoid looping over whole hiddenColumns collection
190        */
191       if (hiddenColumns.isEmpty()
192               || start > hiddenColumns.get(hiddenColumns.size() - 1)[1])
193       {
194         hiddenColumns.add(new int[] { start, end });
195       }
196       else
197       {
198         /*
199          * traverse existing hidden ranges and insert / amend / append as
200          * appropriate
201          */
202         boolean added = false;
203         if (regionindex > 0)
204         {
205           added = insertRangeAtRegion(regionindex - 1, start, end);
206         }
207         if (!added && regionindex < hiddenColumns.size())
208         {
209           insertRangeAtRegion(regionindex, start, end);
210         }
211       }
212
213       // reset the cursor to just before our insertion point: this saves
214       // a lot of reprocessing in large alignments
215       cursor.resetCursor(hiddenColumns, previndex, prevHiddenCount);
216
217       // reset the number of columns so they will be recounted
218       numColumns = 0;
219
220     } finally
221     {
222       if (!wasAlreadyLocked)
223       {
224         LOCK.writeLock().unlock();
225       }
226     }
227   }
228
229   /**
230    * Insert [start, range] at the region at index i in hiddenColumns, if
231    * feasible
232    * 
233    * @param i
234    *          index to insert at
235    * @param start
236    *          start of range to insert
237    * @param end
238    *          end of range to insert
239    * @return true if range was successfully inserted
240    */
241   private boolean insertRangeAtRegion(int i, int start, int end)
242   {
243     boolean added = false;
244
245     int[] region = hiddenColumns.get(i);
246     if (end < region[0] - 1)
247     {
248       /*
249        * insert discontiguous preceding range
250        */
251       hiddenColumns.add(i, new int[] { start, end });
252       added = true;
253     }
254     else if (end <= region[1])
255     {
256       /*
257        * new range overlaps existing, or is contiguous preceding it - adjust
258        * start column
259        */
260       region[0] = Math.min(region[0], start);
261       added = true;
262     }
263     else if (start <= region[1] + 1)
264     {
265       /*
266        * new range overlaps existing, or is contiguous following it - adjust
267        * start and end columns
268        */
269       region[0] = Math.min(region[0], start);
270       region[1] = Math.max(region[1], end);
271
272       /*
273        * also update or remove any subsequent ranges 
274        * that are overlapped
275        */
276       while (i < hiddenColumns.size() - 1)
277       {
278         int[] nextRegion = hiddenColumns.get(i + 1);
279         if (nextRegion[0] > end + 1)
280         {
281           /*
282            * gap to next hidden range - no more to update
283            */
284           break;
285         }
286         region[1] = Math.max(nextRegion[1], end);
287
288         // in theory this is faster than hiddenColumns.remove(i+1)
289         // benchmarking results a bit ambivalent
290         hiddenColumns.subList(i + 1, i + 2).clear();
291       }
292       added = true;
293     }
294     return added;
295   }
296
297   /**
298    * hide a list of ranges
299    * 
300    * @param ranges
301    */
302   public void hideList(List<int[]> ranges)
303   {
304     try
305     {
306       LOCK.writeLock().lock();
307       for (int[] r : ranges)
308       {
309         hideColumns(r[0], r[1]);
310       }
311       cursor.resetCursor(hiddenColumns);
312       numColumns = 0;
313     } finally
314     {
315       LOCK.writeLock().unlock();
316     }
317   }
318
319   /**
320    * Unhides, and adds to the selection list, all hidden columns
321    */
322   public void revealAllHiddenColumns(ColumnSelection sel)
323   {
324     try
325     {
326       LOCK.writeLock().lock();
327
328       Iterator<int[]> it = hiddenColumns.iterator();
329       while (it.hasNext())
330       {
331         int[] region = it.next();
332         for (int j = region[0]; j < region[1] + 1; j++)
333         {
334           sel.addElement(j);
335         }
336       }
337       hiddenColumns.clear();
338       cursor.resetCursor(hiddenColumns);
339       numColumns = 0;
340
341     } finally
342     {
343       LOCK.writeLock().unlock();
344     }
345   }
346
347   /**
348    * Reveals, and marks as selected, the hidden column range with the given
349    * start column
350    * 
351    * @param start
352    *          the start column to look for
353    * @param sel
354    *          the column selection to add the hidden column range to
355    */
356   public void revealHiddenColumns(int start, ColumnSelection sel)
357   {
358     try
359     {
360       LOCK.writeLock().lock();
361
362       if (!hiddenColumns.isEmpty())
363       {
364         int regionIndex = cursor.findRegionForColumn(start)
365                 .getRegionIndex();
366
367         if (regionIndex != -1 && regionIndex != hiddenColumns.size())
368         {
369           // regionIndex is the region which either contains start
370           // or lies to the right of start
371           int[] region = hiddenColumns.get(regionIndex);
372           if (start == region[0])
373           {
374             for (int j = region[0]; j < region[1] + 1; j++)
375             {
376               sel.addElement(j);
377             }
378             int colsToRemove = region[1] - region[0] + 1;
379             hiddenColumns.remove(regionIndex);
380
381             if (hiddenColumns.isEmpty())
382             {
383               hiddenColumns.clear();
384               numColumns = 0;
385             }
386             else
387             {
388               numColumns -= colsToRemove;
389             }
390             cursor.updateForDeletedRegion(hiddenColumns);
391           }
392         }
393       }
394     } finally
395     {
396       LOCK.writeLock().unlock();
397     }
398   }
399
400   /**
401    * Add gaps into the sequences aligned to profileseq under the given
402    * AlignmentView
403    * 
404    * @param profileseq
405    *          sequence in al which sequences are aligned to
406    * @param al
407    *          alignment to have gaps inserted into it
408    * @param input
409    *          alignment view where sequence corresponding to profileseq is first
410    *          entry
411    * @return new HiddenColumns for new alignment view, with insertions into
412    *         profileseq marked as hidden.
413    */
414   public static HiddenColumns propagateInsertions(SequenceI profileseq,
415           AlignmentI al, AlignmentView input)
416   {
417     int profsqpos = 0;
418
419     char gc = al.getGapCharacter();
420     Object[] alandhidden = input.getAlignmentAndHiddenColumns(gc);
421     HiddenColumns nview = (HiddenColumns) alandhidden[1];
422     SequenceI origseq = ((SequenceI[]) alandhidden[0])[profsqpos];
423     nview.propagateInsertions(profileseq, al, origseq);
424     return nview;
425   }
426
427   /**
428    * 
429    * @param profileseq
430    *          sequence in al which corresponds to origseq
431    * @param al
432    *          alignment which is to have gaps inserted into it
433    * @param origseq
434    *          sequence corresponding to profileseq which defines gap map for
435    *          modifying al
436    */
437   private void propagateInsertions(SequenceI profileseq, AlignmentI al,
438           SequenceI origseq)
439   {
440     try
441     {
442       LOCK.writeLock().lock();
443
444       char gc = al.getGapCharacter();
445
446       // take the set of hidden columns, and the set of gaps in origseq,
447       // and remove all the hidden gaps from hiddenColumns
448
449       // first get the gaps as a Bitset
450       BitSet gaps = origseq.gapBitset();
451
452       // now calculate hidden ^ not(gap)
453       BitSet hidden = new BitSet();
454       markHiddenRegions(hidden);
455       hidden.andNot(gaps);
456       hiddenColumns.clear();
457       this.hideColumns(hidden);
458
459       // for each sequence in the alignment, except the profile sequence,
460       // insert gaps corresponding to each hidden region but where each hidden
461       // column region is shifted backwards by the number of preceding visible
462       // gaps update hidden columns at the same time
463       Iterator<int[]> regions = hiddenColumns.iterator();
464       List<int[]> newhidden = new ArrayList<>();
465
466       int numGapsBefore = 0;
467       int gapPosition = 0;
468       while (regions.hasNext())
469       {
470         // get region coordinates accounting for gaps
471         // we can rely on gaps not being *in* hidden regions because we already
472         // removed those
473         int[] region = regions.next();
474         while (gapPosition < region[0])
475         {
476           gapPosition++;
477           if (gaps.get(gapPosition))
478           {
479             numGapsBefore++;
480           }
481         }
482
483         int left = region[0] - numGapsBefore;
484         int right = region[1] - numGapsBefore;
485         newhidden.add(new int[] { left, right });
486
487         // make a string with number of gaps = length of hidden region
488         StringBuffer sb = new StringBuffer();
489         for (int s = 0; s < right - left + 1; s++)
490         {
491           sb.append(gc);
492         }
493         padGaps(sb, left, profileseq, al);
494
495       }
496       hiddenColumns = newhidden;
497       cursor.resetCursor(hiddenColumns);
498       numColumns = 0;
499     } finally
500     {
501       LOCK.writeLock().unlock();
502     }
503   }
504
505   /**
506    * Pad gaps in all sequences in alignment except profileseq
507    * 
508    * @param sb
509    *          gap string to insert
510    * @param left
511    *          position to insert at
512    * @param profileseq
513    *          sequence not to pad
514    * @param al
515    *          alignment to pad sequences in
516    */
517   private void padGaps(StringBuffer sb, int pos, SequenceI profileseq,
518           AlignmentI al)
519   {
520     // loop over the sequences and pad with gaps where required
521     for (int s = 0, ns = al.getHeight(); s < ns; s++)
522     {
523       SequenceI sqobj = al.getSequenceAt(s);
524       if (sqobj != profileseq)
525       {
526         String sq = al.getSequenceAt(s).getSequenceAsString();
527         if (sq.length() <= pos)
528         {
529           // pad sequence
530           int diff = pos - sq.length() - 1;
531           if (diff > 0)
532           {
533             // pad gaps
534             sq = sq + sb;
535             while ((diff = pos - sq.length() - 1) > 0)
536             {
537               if (diff >= sb.length())
538               {
539                 sq += sb.toString();
540               }
541               else
542               {
543                 char[] buf = new char[diff];
544                 sb.getChars(0, diff, buf, 0);
545                 sq += buf.toString();
546               }
547             }
548           }
549           sq += sb.toString();
550         }
551         else
552         {
553           al.getSequenceAt(s).setSequence(
554                   sq.substring(0, pos) + sb.toString() + sq.substring(pos));
555         }
556       }
557     }
558   }
559
560   /*
561    * Methods which only need read access to the hidden columns collection. 
562    * These methods should use a readLock to prevent other threads changing
563    * the hidden columns collection while it is in use.
564    */
565
566   /**
567    * Output regions data as a string. String is in the format:
568    * reg0[0]<between>reg0[1]<delimiter>reg1[0]<between>reg1[1] ... regn[1]
569    * 
570    * @param delimiter
571    *          string to delimit regions
572    * @param betweenstring
573    *          to put between start and end region values
574    * @return regions formatted according to delimiter and between strings
575    */
576   public String regionsToString(String delimiter, String between)
577   {
578     try
579     {
580       LOCK.readLock().lock();
581       StringBuilder regionBuilder = new StringBuilder();
582
583       Iterator<int[]> it = hiddenColumns.iterator();
584       while (it.hasNext())
585       {
586         int[] range = it.next();
587         regionBuilder.append(delimiter).append(range[0]).append(between)
588                 .append(range[1]);
589         if (!it.hasNext())
590         {
591           regionBuilder.deleteCharAt(0);
592         }
593       }
594
595       return regionBuilder.toString();
596     } finally
597     {
598       LOCK.readLock().unlock();
599     }
600   }
601
602   /**
603    * Find the number of hidden columns
604    * 
605    * @return number of hidden columns
606    */
607   public int getSize()
608   {
609     try
610     {
611       LOCK.readLock().lock();
612
613       if (numColumns == 0)
614       {
615         // numColumns is out of date, so recalculate
616         int size = 0;
617
618         Iterator<int[]> it = hiddenColumns.iterator();
619         while (it.hasNext())
620         {
621           int[] range = it.next();
622           size += range[1] - range[0] + 1;
623         }
624
625         numColumns = size;
626       }
627
628       return numColumns;
629     } finally
630     {
631       LOCK.readLock().unlock();
632     }
633   }
634
635   /**
636    * Get the number of distinct hidden regions
637    * 
638    * @return number of regions
639    */
640   public int getNumberOfRegions()
641   {
642     try
643     {
644       LOCK.readLock().lock();
645       int num = 0;
646       if (hasHiddenColumns())
647       {
648         num = hiddenColumns.size();
649       }
650       return num;
651     } finally
652     {
653       LOCK.readLock().unlock();
654     }
655   }
656
657   @Override
658   public boolean equals(Object obj)
659   {
660     try
661     {
662       LOCK.readLock().lock();
663
664       if (!(obj instanceof HiddenColumns))
665       {
666         return false;
667       }
668       HiddenColumns that = (HiddenColumns) obj;
669
670       /*
671        * check hidden columns are either both null, or match
672        */
673
674       if (that.hiddenColumns.size() != this.hiddenColumns.size())
675       {
676         return false;
677       }
678
679       Iterator<int[]> it = hiddenColumns.iterator();
680       Iterator<int[]> thatit = that.iterator();
681       while (it.hasNext())
682       {
683         int[] thisRange = it.next();
684         int[] thatRange = thatit.next();
685         if (thisRange[0] != thatRange[0] || thisRange[1] != thatRange[1])
686         {
687           return false;
688         }
689       }
690       return true;
691     } finally
692     {
693       LOCK.readLock().unlock();
694     }
695   }
696
697   /**
698    * Return absolute column index for a visible column index
699    * 
700    * @param column
701    *          int column index in alignment view (count from zero)
702    * @return alignment column index for column
703    */
704   public int visibleToAbsoluteColumn(int column)
705   {
706     try
707     {
708       LOCK.readLock().lock();
709       int result = column;
710
711       if (!hiddenColumns.isEmpty())
712       {
713         result += cursor.findRegionForVisColumn(column).getHiddenSoFar();
714       }
715
716       return result;
717     } finally
718     {
719       LOCK.readLock().unlock();
720     }
721   }
722
723   /**
724    * Use this method to find out where a column will appear in the visible
725    * alignment when hidden columns exist. If the column is not visible, then the
726    * index of the next visible column on the left will be returned (or 0 if
727    * there is no visible column on the left)
728    * 
729    * @param hiddenColumn
730    *          the column index in the full alignment including hidden columns
731    * @return the position of the column in the visible alignment
732    */
733   public int absoluteToVisibleColumn(int hiddenColumn)
734   {
735     try
736     {
737       LOCK.readLock().lock();
738       int result = hiddenColumn;
739
740       if (!hiddenColumns.isEmpty())
741       {
742         HiddenCursorPosition cursorPos = cursor
743                 .findRegionForColumn(hiddenColumn);
744         int index = cursorPos.getRegionIndex();
745         int hiddenBeforeCol = cursorPos.getHiddenSoFar();
746     
747         // just subtract hidden cols count - this works fine if column is
748         // visible
749         result = hiddenColumn - hiddenBeforeCol;
750     
751         // now check in case column is hidden - it will be in the returned
752         // hidden region
753         if (index < hiddenColumns.size())
754         {
755           int[] region = hiddenColumns.get(index);
756           if (hiddenColumn >= region[0] && hiddenColumn <= region[1])
757           {
758             // actually col is hidden, return region[0]-1
759             // unless region[0]==0 in which case return 0
760             if (region[0] == 0)
761             {
762               result = 0;
763             }
764             else
765             {
766               result = region[0] - 1 - hiddenBeforeCol;
767             }
768           }
769         }
770       }
771
772       return result; // return the shifted position after removing hidden
773                      // columns.
774     } finally
775     {
776       LOCK.readLock().unlock();
777     }
778   }
779
780   /**
781    * Find the visible column which is a given visible number of columns to the
782    * left of another visible column. i.e. for a startColumn x, the column which
783    * is distance 1 away will be column x-1.
784    * 
785    * @param visibleDistance
786    *          the number of visible columns to offset by
787    * @param startColumn
788    *          the column to start from
789    * @return the position of the column in the visible alignment
790    */
791   public int subtractVisibleColumns(int visibleDistance, int startColumn)
792   {
793     try
794     {
795       LOCK.readLock().lock();
796       int distance = visibleDistance;
797
798       // in case startColumn is in a hidden region, move it to the left
799       int start = visibleToAbsoluteColumn(absoluteToVisibleColumn(startColumn));
800
801       Iterator<int[]> it = new ReverseRegionsIterator(0, start,
802               hiddenColumns);
803
804       while (it.hasNext() && (distance > 0))
805       {
806         int[] region = it.next();
807
808         if (start > region[1])
809         {
810           // subtract the gap to right of region from distance
811           if (start - region[1] <= distance)
812           {
813             distance -= start - region[1];
814             start = region[0] - 1;
815           }
816           else
817           {
818             start = start - distance;
819             distance = 0;
820           }
821         }
822       }
823       return start - distance;
824
825     } finally
826     {
827       LOCK.readLock().unlock();
828     }
829   }
830
831   /**
832    * This method returns the rightmost limit of a region of an alignment with
833    * hidden columns. In otherwords, the next hidden column.
834    * 
835    * @param alPos
836    *          the absolute (visible) alignmentPosition to find the next hidden
837    *          column for
838    * @return the index of the next hidden column, or alPos if there is no next
839    *         hidden column
840    */
841   public int getHiddenBoundaryRight(int alPos)
842   {
843     try
844     {
845       LOCK.readLock().lock();
846       if (!hiddenColumns.isEmpty())
847       {
848         int index = cursor.findRegionForColumn(alPos).getRegionIndex();
849         if (index < hiddenColumns.size())
850         {
851           int[] region = hiddenColumns.get(index);
852           if (alPos < region[0])
853           {
854             return region[0];
855           }
856           else if ((alPos <= region[1])
857                   && (index + 1 < hiddenColumns.size()))
858           {
859             // alPos is within a hidden region, return the next one
860             // if there is one
861             region = hiddenColumns.get(index + 1);
862             return region[0];
863           }
864         }
865       }
866       return alPos;
867     } finally
868     {
869       LOCK.readLock().unlock();
870     }
871   }
872
873   /**
874    * This method returns the leftmost limit of a region of an alignment with
875    * hidden columns. In otherwords, the previous hidden column.
876    * 
877    * @param alPos
878    *          the absolute (visible) alignmentPosition to find the previous
879    *          hidden column for
880    */
881   public int getHiddenBoundaryLeft(int alPos)
882   {
883     try
884     {
885       LOCK.readLock().lock();
886
887       if (!hiddenColumns.isEmpty())
888       {
889         int index = cursor.findRegionForColumn(alPos).getRegionIndex();
890
891         if (index > 0)
892         {
893           int[] region = hiddenColumns.get(index - 1);
894           return region[1];
895         }
896       }
897       return alPos;
898     } finally
899     {
900       LOCK.readLock().unlock();
901     }
902   }
903
904
905   /**
906    * Answers if a column in the alignment is visible
907    * 
908    * @param column
909    *          absolute position of column in the alignment
910    * @return true if column is visible
911    */
912   public boolean isVisible(int column)
913   {
914     try
915     {
916       LOCK.readLock().lock();
917
918       int regionindex = cursor.findRegionForColumn(column).getRegionIndex();
919       if (regionindex > -1 && regionindex < hiddenColumns.size())
920       {
921         int[] region = hiddenColumns.get(regionindex);
922         // already know that column <= region[1] as cursor returns containing
923         // region or region to right
924         if (column >= region[0])
925         {
926           return false;
927         }
928       }
929       return true;
930
931     } finally
932     {
933       LOCK.readLock().unlock();
934     }
935   }
936
937   /**
938    * Get the visible sections of a set of sequences
939    * 
940    * @param start
941    *          sequence position to start from
942    * @param end
943    *          sequence position to end at
944    * @param seqs
945    *          an array of sequences
946    * @return an array of strings encoding the visible parts of each sequence
947    */
948   public String[] getVisibleSequenceStrings(int start, int end,
949           SequenceI[] seqs)
950   {
951     try
952     {
953       LOCK.readLock().lock();
954       int iSize = seqs.length;
955       String[] selections = new String[iSize];
956       if (!hiddenColumns.isEmpty())
957       {
958         for (int i = 0; i < iSize; i++)
959         {
960           StringBuffer visibleSeq = new StringBuffer();
961
962           Iterator<int[]> blocks = new VisibleContigsIterator(start,
963                   end + 1, hiddenColumns);
964
965           while (blocks.hasNext())
966           {
967             int[] block = blocks.next();
968             if (blocks.hasNext())
969             {
970               visibleSeq
971                       .append(seqs[i].getSequence(block[0], block[1] + 1));
972             }
973             else
974             {
975               visibleSeq
976                       .append(seqs[i].getSequence(block[0], block[1]));
977             }
978           }
979
980           selections[i] = visibleSeq.toString();
981         }
982       }
983       else
984       {
985         for (int i = 0; i < iSize; i++)
986         {
987           selections[i] = seqs[i].getSequenceAsString(start, end);
988         }
989       }
990
991       return selections;
992     } finally
993     {
994       LOCK.readLock().unlock();
995     }
996   }
997
998   /**
999    * Locate the first position visible for this sequence. If seq isn't visible
1000    * then return the position of the left side of the hidden boundary region.
1001    * 
1002    * @param seq
1003    *          sequence to find position for
1004    * @return visible start position
1005    */
1006   public int locateVisibleStartOfSequence(SequenceI seq)
1007   {
1008     try
1009     {
1010       LOCK.readLock().lock();
1011       int start = 0;
1012
1013       if (hiddenColumns.isEmpty())
1014       {
1015         return seq.findIndex(seq.getStart()) - 1;
1016       }
1017
1018       // Simply walk along the sequence whilst watching for hidden column
1019       // boundaries
1020       Iterator<int[]> regions = hiddenColumns.iterator();
1021       int hideStart = seq.getLength();
1022       int hideEnd = -1;
1023       int visPrev = 0;
1024       int visNext = 0;
1025       boolean foundStart = false;
1026
1027       // step through the non-gapped positions of the sequence
1028       for (int i = seq.getStart(); i <= seq.getEnd() && (!foundStart); i++)
1029       {
1030         // get alignment position of this residue in the sequence
1031         int p = seq.findIndex(i) - 1;
1032
1033         // update hidden region start/end
1034         while (hideEnd < p && regions.hasNext())
1035         {
1036           int[] region = regions.next();
1037           visPrev = visNext;
1038           visNext += region[0] - visPrev;
1039           hideStart = region[0];
1040           hideEnd = region[1];
1041         }
1042         if (hideEnd < p)
1043         {
1044           hideStart = seq.getLength();
1045         }
1046         // update visible boundary for sequence
1047         if (p < hideStart)
1048         {
1049           start = p;
1050           foundStart = true;
1051         }
1052       }
1053
1054       if (foundStart)
1055       {
1056         return absoluteToVisibleColumn(start);
1057       }
1058       // otherwise, sequence was completely hidden
1059       return visPrev;
1060     } finally
1061     {
1062       LOCK.readLock().unlock();
1063     }
1064   }
1065
1066   /**
1067    * delete any columns in alignmentAnnotation that are hidden (including
1068    * sequence associated annotation).
1069    * 
1070    * @param alignmentAnnotation
1071    */
1072   public void makeVisibleAnnotation(AlignmentAnnotation alignmentAnnotation)
1073   {
1074     if (alignmentAnnotation != null
1075             && alignmentAnnotation.annotations != null)
1076     {
1077       makeVisibleAnnotation(0, alignmentAnnotation.annotations.length,
1078             alignmentAnnotation);
1079     }
1080   }
1081
1082   /**
1083    * delete any columns in alignmentAnnotation that are hidden (including
1084    * sequence associated annotation).
1085    * 
1086    * @param start
1087    *          remove any annotation to the right of this column
1088    * @param end
1089    *          remove any annotation to the left of this column
1090    * @param alignmentAnnotation
1091    *          the annotation to operate on
1092    */
1093   public void makeVisibleAnnotation(int start, int end,
1094           AlignmentAnnotation alignmentAnnotation)
1095   {
1096     try
1097     {
1098       LOCK.readLock().lock();
1099
1100       int startFrom = start;
1101       int endAt = end;
1102
1103       if (alignmentAnnotation != null
1104               && alignmentAnnotation.annotations != null)
1105       {
1106         if (!hiddenColumns.isEmpty())
1107         {
1108           removeHiddenAnnotation(startFrom, endAt, alignmentAnnotation);
1109         }
1110         else
1111         {
1112           alignmentAnnotation.restrict(startFrom, endAt);
1113         }
1114       }
1115     } finally
1116     {
1117       LOCK.readLock().unlock();
1118     }
1119   }
1120
1121   private void removeHiddenAnnotation(int start, int end,
1122           AlignmentAnnotation alignmentAnnotation)
1123   {
1124     // mangle the alignmentAnnotation annotation array
1125     ArrayList<Annotation[]> annels = new ArrayList<>();
1126     Annotation[] els = null;
1127
1128     int w = 0;
1129     
1130     Iterator<int[]> blocks = new VisibleContigsIterator(start, end + 1,
1131             hiddenColumns);
1132
1133     int copylength;
1134     int annotationLength;
1135     while (blocks.hasNext())
1136     {
1137       int[] block = blocks.next();
1138       annotationLength = block[1] - block[0] + 1;
1139     
1140       if (blocks.hasNext())
1141       {
1142         // copy just the visible segment of the annotation row
1143         copylength = annotationLength;
1144       }
1145       else
1146       {
1147         if (annotationLength + block[0] <= alignmentAnnotation.annotations.length)
1148         {
1149           // copy just the visible segment of the annotation row
1150           copylength = annotationLength;
1151         }
1152         else
1153         {
1154           // copy to the end of the annotation row
1155           copylength = alignmentAnnotation.annotations.length - block[0];
1156         }
1157       }
1158       
1159       els = new Annotation[annotationLength];
1160       annels.add(els);
1161       System.arraycopy(alignmentAnnotation.annotations, block[0], els, 0,
1162               copylength);
1163       w += annotationLength;
1164     }
1165     
1166     if (w != 0)
1167     {
1168       alignmentAnnotation.annotations = new Annotation[w];
1169
1170       w = 0;
1171       for (Annotation[] chnk : annels)
1172       {
1173         System.arraycopy(chnk, 0, alignmentAnnotation.annotations, w,
1174                 chnk.length);
1175         w += chnk.length;
1176       }
1177     }
1178   }
1179
1180   /**
1181    * 
1182    * @return true if there are columns hidden
1183    */
1184   public boolean hasHiddenColumns()
1185   {
1186     try
1187     {
1188       LOCK.readLock().lock();
1189
1190       // we don't use getSize()>0 here because it has to iterate over
1191       // the full hiddenColumns collection and so will be much slower
1192       return (!hiddenColumns.isEmpty());
1193     } finally
1194     {
1195       LOCK.readLock().unlock();
1196     }
1197   }
1198
1199   /**
1200    * 
1201    * @return true if there is more than one hidden column region
1202    */
1203   public boolean hasMultiHiddenColumnRegions()
1204   {
1205     try
1206     {
1207       LOCK.readLock().lock();
1208       return !hiddenColumns.isEmpty() && hiddenColumns.size() > 1;
1209     } finally
1210     {
1211       LOCK.readLock().unlock();
1212     }
1213   }
1214
1215
1216   /**
1217    * Returns a hashCode built from hidden column ranges
1218    */
1219   @Override
1220   public int hashCode()
1221   {
1222     try
1223     {
1224       LOCK.readLock().lock();
1225       int hashCode = 1;
1226       Iterator<int[]> it = hiddenColumns.iterator();
1227       while (it.hasNext())
1228       {
1229         int[] hidden = it.next();
1230         hashCode = HASH_MULTIPLIER * hashCode + hidden[0];
1231         hashCode = HASH_MULTIPLIER * hashCode + hidden[1];
1232       }
1233       return hashCode;
1234     } finally
1235     {
1236       LOCK.readLock().unlock();
1237     }
1238   }
1239
1240   /**
1241    * Hide columns corresponding to the marked bits
1242    * 
1243    * @param inserts
1244    *          - columns map to bits starting from zero
1245    */
1246   public void hideColumns(BitSet inserts)
1247   {
1248     try
1249     {
1250       LOCK.writeLock().lock();
1251       for (int firstSet = inserts
1252               .nextSetBit(0), lastSet = 0; firstSet >= 0; firstSet = inserts
1253                       .nextSetBit(lastSet))
1254       {
1255         lastSet = inserts.nextClearBit(firstSet);
1256         hideColumns(firstSet, lastSet - 1);
1257       }
1258       cursor.resetCursor(hiddenColumns);
1259       numColumns = 0;
1260     } finally
1261     {
1262       LOCK.writeLock().unlock();
1263     }
1264   }
1265
1266   /**
1267    * 
1268    * @param inserts
1269    *          BitSet where hidden columns will be marked
1270    */
1271   public void markHiddenRegions(BitSet inserts)
1272   {
1273     try
1274     {
1275       LOCK.readLock().lock();
1276
1277       Iterator<int[]> it = hiddenColumns.iterator();
1278       while (it.hasNext())
1279       {
1280         int[] range = it.next();
1281         inserts.set(range[0], range[1] + 1);
1282       }
1283     } finally
1284     {
1285       LOCK.readLock().unlock();
1286     }
1287   }
1288
1289   /**
1290    * Calculate the visible start and end index of an alignment.
1291    * 
1292    * @param width
1293    *          full alignment width
1294    * @return integer array where: int[0] = startIndex, and int[1] = endIndex
1295    */
1296   public int[] getVisibleStartAndEndIndex(int width)
1297   {
1298     try
1299     {
1300       LOCK.readLock().lock();
1301       int[] alignmentStartEnd = new int[] { 0, width - 1 };
1302       int startPos = alignmentStartEnd[0];
1303       int endPos = alignmentStartEnd[1];
1304
1305       int[] lowestRange = new int[] { -1, -1 };
1306       int[] higestRange = new int[] { -1, -1 };
1307
1308       if (hiddenColumns.isEmpty())
1309       {
1310         return new int[] { startPos, endPos };
1311       }
1312
1313       Iterator<int[]> it = hiddenColumns.iterator();
1314       while (it.hasNext())
1315       {
1316         int[] range = it.next();
1317         lowestRange = (range[0] <= startPos) ? range : lowestRange;
1318         higestRange = (range[1] >= endPos) ? range : higestRange;
1319       }
1320
1321       if (lowestRange[0] == -1) // includes (lowestRange[1] == -1)
1322       {
1323         startPos = alignmentStartEnd[0];
1324       }
1325       else
1326       {
1327         startPos = lowestRange[1] + 1;
1328       }
1329
1330       if (higestRange[0] == -1) // includes (higestRange[1] == -1)
1331       {
1332         endPos = alignmentStartEnd[1];
1333       }
1334       else
1335       {
1336         endPos = higestRange[0] - 1;
1337       }
1338       return new int[] { startPos, endPos };
1339     } finally
1340     {
1341       LOCK.readLock().unlock();
1342     }
1343   }
1344
1345   /**
1346    * Finds the hidden region (if any) which starts or ends at res
1347    * 
1348    * @param res
1349    *          visible residue position, unadjusted for hidden columns
1350    * @return region as [start,end] or null if no matching region is found. If
1351    *         res is adjacent to two regions, returns the left region.
1352    */
1353   public int[] getRegionWithEdgeAtRes(int res)
1354   {
1355     try
1356     {
1357       LOCK.readLock().lock();
1358       int adjres = visibleToAbsoluteColumn(res);
1359
1360       int[] reveal = null;
1361
1362       if (!hiddenColumns.isEmpty())
1363       {
1364         // look for a region ending just before adjres
1365         int regionindex = cursor.findRegionForColumn(adjres - 1)
1366                 .getRegionIndex();
1367         if (regionindex < hiddenColumns.size()
1368                 && hiddenColumns.get(regionindex)[1] == adjres - 1)
1369         {
1370           reveal = hiddenColumns.get(regionindex);
1371         }
1372         // check if the region ends just after adjres
1373         else if (regionindex < hiddenColumns.size()
1374                 && hiddenColumns.get(regionindex)[0] == adjres + 1)
1375         {
1376           reveal = hiddenColumns.get(regionindex);
1377         }
1378       }
1379       return reveal;
1380
1381     } finally
1382     {
1383       LOCK.readLock().unlock();
1384     }
1385   }
1386
1387   /**
1388    * Return an iterator over the hidden regions
1389    */
1390   public Iterator<int[]> iterator()
1391   {
1392     try
1393     {
1394       LOCK.readLock().lock();
1395       return new HiddenColsIterator(hiddenColumns);
1396     } finally
1397     {
1398       LOCK.readLock().unlock();
1399     }
1400   }
1401
1402   /**
1403    * Return a bounded iterator over the hidden regions
1404    * 
1405    * @param start
1406    *          position to start from (inclusive, absolute column position)
1407    * @param end
1408    *          position to end at (inclusive, absolute column position)
1409    * @return
1410    */
1411   public Iterator<int[]> getBoundedIterator(int start, int end)
1412   {
1413     try
1414     {
1415       LOCK.readLock().lock();
1416       return new HiddenColsIterator(start, end, hiddenColumns);
1417     } finally
1418     {
1419       LOCK.readLock().unlock();
1420     }
1421   }
1422
1423   /**
1424    * Return a bounded iterator over the *visible* start positions of hidden
1425    * regions
1426    * 
1427    * @param start
1428    *          position to start from (inclusive, visible column position)
1429    * @param end
1430    *          position to end at (inclusive, visible column position)
1431    */
1432   public Iterator<Integer> getBoundedStartIterator(int start, int end)
1433   {
1434     try
1435     {
1436       LOCK.readLock().lock();
1437
1438       // get absolute position of column in alignment
1439       int absoluteStart = visibleToAbsoluteColumn(start);
1440
1441       // Get cursor position and supply it to the iterator:
1442       // Since we want visible region start, we look for a cursor for the
1443       // (absoluteStart-1), then if absoluteStart is the start of a visible
1444       // region we'll get the cursor pointing to the region before, which is
1445       // what we want
1446       HiddenCursorPosition pos = cursor
1447               .findRegionForColumn(absoluteStart - 1);
1448
1449       return new BoundedStartRegionIterator(pos, start, end,
1450               hiddenColumns);
1451     } finally
1452     {
1453       LOCK.readLock().unlock();
1454     }
1455   }
1456
1457   /**
1458    * Return an iterator over visible *columns* (not regions) between the given
1459    * start and end boundaries
1460    * 
1461    * @param start
1462    *          first column (inclusive)
1463    * @param end
1464    *          last column (inclusive)
1465    */
1466   public Iterator<Integer> getVisibleColsIterator(int start, int end)
1467   {
1468     try
1469     {
1470       LOCK.readLock().lock();
1471       return new VisibleColsIterator(start, end, hiddenColumns);
1472     } finally
1473     {
1474       LOCK.readLock().unlock();
1475     }
1476   }
1477
1478   /**
1479    * return an iterator over visible segments between the given start and end
1480    * boundaries
1481    * 
1482    * @param start
1483    *          first column, inclusive from 0
1484    * @param end
1485    *          last column - not inclusive
1486    * @param useVisibleCoords
1487    *          if true, start and end are visible column positions, not absolute
1488    *          positions*
1489    */
1490   public VisibleContigsIterator getVisContigsIterator(int start,
1491           int end,
1492           boolean useVisibleCoords)
1493   {
1494     int adjstart = start;
1495     int adjend = end;
1496     if (useVisibleCoords)
1497     {
1498       adjstart = visibleToAbsoluteColumn(start);
1499       adjend = visibleToAbsoluteColumn(end);
1500     }
1501
1502     try
1503     {
1504       LOCK.readLock().lock();
1505       return new VisibleContigsIterator(adjstart, adjend, hiddenColumns);
1506     } finally
1507     {
1508       LOCK.readLock().unlock();
1509     }
1510   }
1511 }