JAL-2674 Last moves to iterators
[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.NoSuchElementException;
28 import java.util.concurrent.locks.ReentrantReadWriteLock;
29
30 public class HiddenColumns
31 {
32   private static final int HASH_MULTIPLIER = 31;
33
34   private static final ReentrantReadWriteLock LOCK = new ReentrantReadWriteLock();
35
36   /*
37    * list of hidden column [start, end] ranges; the list is maintained in
38    * ascending start column order
39    */
40   private ArrayList<int[]> hiddenColumns;
41
42   /**
43    * Constructor
44    */
45   public HiddenColumns()
46   {
47   }
48
49   /**
50    * Copy constructor
51    * 
52    * @param copy
53    */
54   public HiddenColumns(HiddenColumns copy)
55   {
56     try
57     {
58       LOCK.writeLock().lock();
59       if (copy != null)
60       {
61         if (copy.hiddenColumns != null)
62         {
63           hiddenColumns = new ArrayList<>();
64           Iterator<int[]> it = copy.iterator();
65           while (it.hasNext())
66           {
67             hiddenColumns.add(it.next());
68           }
69         }
70       }
71     } finally
72     {
73       LOCK.writeLock().unlock();
74     }
75   }
76
77   /**
78    * Copy constructor within bounds and with offset. Copies hidden column
79    * regions fully contained between start and end, and offsets positions by
80    * subtracting offset.
81    * 
82    * @param copy
83    *          HiddenColumns instance to copy from
84    * @param start
85    *          lower bound to copy from
86    * @param end
87    *          upper bound to copy to
88    * @param offset
89    *          offset to subtract from each region boundary position
90    * 
91    */
92   public HiddenColumns(HiddenColumns copy, int start, int end, int offset)
93   {
94     try
95     {
96       LOCK.writeLock().lock();
97       if (copy != null)
98       {
99         hiddenColumns = new ArrayList<>();
100         Iterator<int[]> it = copy.getBoundedIterator(start, end);
101         while (it.hasNext())
102         {
103           int[] region = it.next();
104           // still need to check boundaries because iterator returns
105           // all overlapping regions and we need contained regions
106           if (region[0] >= start && region[1] <= end)
107           {
108             hiddenColumns.add(
109                     new int[]
110             { region[0] - offset, region[1] - offset });
111           }
112         }
113       }
114     } finally
115     {
116       LOCK.writeLock().unlock();
117     }
118   }
119
120   /**
121    * Output regions data as a string. String is in the format:
122    * reg0[0]<between>reg0[1]<delimiter>reg1[0]<between>reg1[1] ... regn[1]
123    * 
124    * @param delimiter
125    *          string to delimit regions
126    * @param betweenstring
127    *          to put between start and end region values
128    * @return regions formatted according to delimiter and between strings
129    */
130   public String regionsToString(String delimiter, String between)
131   {
132     try
133     {
134       LOCK.readLock().lock();
135       StringBuilder regionBuilder = new StringBuilder();
136       Iterator<int[]> it = new RegionsIterator();
137       while (it.hasNext())
138       {
139         int[] range = it.next();
140         regionBuilder.append(delimiter).append(range[0]).append(between)
141                   .append(range[1]);
142         if (!it.hasNext())
143         {
144           regionBuilder.deleteCharAt(0);
145         }
146       }
147       return regionBuilder.toString();
148     } finally
149     {
150       LOCK.readLock().unlock();
151     }
152   }
153
154   /**
155    * Find the number of hidden columns
156    * 
157    * @return number of hidden columns
158    */
159   public int getSize()
160   {
161     try
162     {
163       LOCK.readLock().lock();
164       int size = 0;
165       Iterator<int[]> it = new RegionsIterator();
166       while (it.hasNext())
167       {
168         int[] range = it.next();
169         size += range[1] - range[0] + 1;
170       }
171
172       return size;
173     } finally
174     {
175       LOCK.readLock().unlock();
176     }
177   }
178
179   /**
180    * Get the number of distinct hidden regions
181    * 
182    * @return number of regions
183    */
184   public int getNumberOfRegions()
185   {
186     try
187     {
188       LOCK.readLock().lock();
189       int num = 0;
190       if (hasHiddenColumns())
191       {
192         num = hiddenColumns.size();
193       }
194       return num;
195     } finally
196     {
197       LOCK.readLock().unlock();
198     }
199   }
200
201   @Override
202   public boolean equals(Object obj)
203   {
204     try
205     {
206       LOCK.readLock().lock();
207
208       if (!(obj instanceof HiddenColumns))
209       {
210         return false;
211       }
212       HiddenColumns that = (HiddenColumns) obj;
213
214       /*
215        * check hidden columns are either both null, or match
216        */
217       if (this.hiddenColumns == null)
218       {
219         return (that.hiddenColumns == null);
220       }
221       if (that.hiddenColumns == null
222               || that.hiddenColumns.size() != this.hiddenColumns.size())
223       {
224         return false;
225       }
226
227       Iterator<int[]> it = new RegionsIterator();
228       Iterator<int[]> thatit = that.iterator();
229       while (it.hasNext())
230       {
231         int[] thisRange = it.next();
232         int[] thatRange = thatit.next();
233         if (thisRange[0] != thatRange[0] || thisRange[1] != thatRange[1])
234         {
235           return false;
236         }
237       }
238       return true;
239     } finally
240     {
241       LOCK.readLock().unlock();
242     }
243   }
244
245   /**
246    * Return absolute column index for a visible column index
247    * 
248    * @param column
249    *          int column index in alignment view (count from zero)
250    * @return alignment column index for column
251    */
252   public int adjustForHiddenColumns(int column)
253   {
254     try
255     {
256       LOCK.readLock().lock();
257       int result = column;
258
259       Iterator<int[]> it = new RegionsIterator();
260       while (it.hasNext())
261       {
262         int[] region = it.next();
263         if (result >= region[0])
264         {
265           result += region[1] - region[0] + 1;
266         }
267       }
268
269       return result;
270     } finally
271     {
272       LOCK.readLock().unlock();
273     }
274   }
275
276   /**
277    * Use this method to find out where a column will appear in the visible
278    * alignment when hidden columns exist. If the column is not visible, then the
279    * left-most visible column will always be returned.
280    * 
281    * @param hiddenColumn
282    *          the column index in the full alignment including hidden columns
283    * @return the position of the column in the visible alignment
284    */
285   public int findColumnPosition(int hiddenColumn)
286   {
287     try
288     {
289       LOCK.readLock().lock();
290       int result = hiddenColumn;
291       int[] region = null;
292       if (hiddenColumns != null)
293       {
294         Iterator<int[]> it = new RegionsIterator(0,
295                 hiddenColumn);
296         while (it.hasNext())
297         {
298           region = it.next();
299           if (hiddenColumn > region[1])
300           {
301             result -= region[1] + 1 - region[0];
302           }
303         }
304
305         if (region != null && hiddenColumn >= region[0]
306                 && hiddenColumn <= region[1])
307         {
308           // Here the hidden column is within a region, so
309           // we want to return the position of region[0]-1, adjusted for any
310           // earlier hidden columns.
311           // Calculate the difference between the actual hidden col position
312           // and region[0]-1, and then subtract from result to convert result
313           // from the adjusted hiddenColumn value to the adjusted region[0]-1
314           // value.
315
316           // However, if the region begins at 0 we cannot return region[0]-1
317           // just return 0
318           if (region[0] == 0)
319           {
320             return 0;
321           }
322           else
323           {
324             return result - (hiddenColumn - region[0] + 1);
325           }
326         }
327       }
328       return result; // return the shifted position after removing hidden
329                      // columns.
330     } finally
331     {
332       LOCK.readLock().unlock();
333     }
334   }
335
336   /**
337    * Find the visible column which is a given visible number of columns to the
338    * left of another visible column. i.e. for a startColumn x, the column which
339    * is distance 1 away will be column x-1.
340    * 
341    * @param visibleDistance
342    *          the number of visible columns to offset by
343    * @param startColumn
344    *          the column to start from
345    * @return the position of the column in the visible alignment
346    */
347   public int subtractVisibleColumns(int visibleDistance, int startColumn)
348   {
349     try
350     {
351       LOCK.readLock().lock();
352       int distance = visibleDistance;
353
354       // in case startColumn is in a hidden region, move it to the left
355       int start = adjustForHiddenColumns(findColumnPosition(startColumn));
356
357       Iterator<int[]> it = new ReverseRegionsIterator(0, start);
358
359       while (it.hasNext() && (distance > 0))
360       {
361         int[] region = it.next();
362
363         if (start > region[1])
364         {
365           // subtract the gap to right of region from distance
366           if (start - region[1] <= distance)
367           {
368             distance -= start - region[1];
369             start = region[0] - 1;
370           }
371           else
372           {
373             start = start - distance;
374             distance = 0;
375           }
376         }
377       }
378
379       return start - distance;
380
381     } finally
382     {
383       LOCK.readLock().unlock();
384     }
385   }
386
387   /**
388    * This method returns the rightmost limit of a region of an alignment with
389    * hidden columns. In otherwords, the next hidden column.
390    * 
391    * @param alPos
392    *          the (visible) alignmentPosition to find the next hidden column for
393    */
394   public int getHiddenBoundaryRight(int alPos)
395   {
396     try
397     {
398       LOCK.readLock().lock();
399       if (hiddenColumns != null)
400       {
401         Iterator<int[]> it = new RegionsIterator();
402         while (it.hasNext())
403         {
404           int[] region = it.next();
405           if (alPos < region[0])
406           {
407             return region[0];
408           }
409         }
410       }
411       return alPos;
412     } finally
413     {
414       LOCK.readLock().unlock();
415     }
416   }
417
418   /**
419    * This method returns the leftmost limit of a region of an alignment with
420    * hidden columns. In otherwords, the previous hidden column.
421    * 
422    * @param alPos
423    *          the (visible) alignmentPosition to find the previous hidden column
424    *          for
425    */
426   public int getHiddenBoundaryLeft(int alPos)
427   {
428     try
429     {
430       LOCK.readLock().lock();
431
432       Iterator<int[]> it = new ReverseRegionsIterator(0, alPos);
433       while (it.hasNext())
434       {
435         int[] region = it.next();
436         if (alPos > region[1])
437         {
438           return region[1];
439         }
440       }
441
442       return alPos;
443     } finally
444     {
445       LOCK.readLock().unlock();
446     }
447   }
448
449   /**
450    * Adds the specified column range to the hidden columns collection
451    * 
452    * @param start
453    *          start of range to add (absolute position in alignment)
454    * @param end
455    *          end of range to add (absolute position in alignment)
456    */
457   public void hideColumns(int start, int end)
458   {
459     boolean wasAlreadyLocked = false;
460     try
461     {
462       // check if the write lock was already locked by this thread,
463       // as this method can be called internally in loops within HiddenColumns
464       if (!LOCK.isWriteLockedByCurrentThread())
465       {
466         LOCK.writeLock().lock();
467       }
468       else
469       {
470         wasAlreadyLocked = true;
471       }
472
473       if (hiddenColumns == null)
474       {
475         hiddenColumns = new ArrayList<>();
476       }
477
478       /*
479        * new range follows everything else; check first to avoid looping over whole hiddenColumns collection
480        */
481       if (hiddenColumns.isEmpty()
482               || start > hiddenColumns.get(hiddenColumns.size() - 1)[1])
483       {
484         hiddenColumns.add(new int[] { start, end });
485       }
486       else
487       {
488         /*
489          * traverse existing hidden ranges and insert / amend / append as
490          * appropriate
491          */
492         boolean added = false;
493         for (int i = 0; !added && i < hiddenColumns.size(); i++)
494         {
495           added = insertRangeAtRegion(i, start, end);
496         } // for
497       }
498     } finally
499     {
500       if (!wasAlreadyLocked)
501       {
502         LOCK.writeLock().unlock();
503       }
504     }
505   }
506
507   private boolean insertRangeAtRegion(int i, int start, int end)
508   {
509     boolean added = false;
510
511     int[] region = hiddenColumns.get(i);
512     if (end < region[0] - 1)
513     {
514       /*
515        * insert discontiguous preceding range
516        */
517       hiddenColumns.add(i, new int[] { start, end });
518       added = true;
519     }
520     else if (end <= region[1])
521     {
522       /*
523        * new range overlaps existing, or is contiguous preceding it - adjust
524        * start column
525        */
526       region[0] = Math.min(region[0], start);
527       added = true;
528     }
529     else if (start <= region[1] + 1)
530     {
531       /*
532        * new range overlaps existing, or is contiguous following it - adjust
533        * start and end columns
534        */
535       region[0] = Math.min(region[0], start);
536       region[1] = Math.max(region[1], end);
537
538       /*
539        * also update or remove any subsequent ranges 
540        * that are overlapped
541        */
542       while (i < hiddenColumns.size() - 1)
543       {
544         int[] nextRegion = hiddenColumns.get(i + 1);
545         if (nextRegion[0] > end + 1)
546         {
547           /*
548            * gap to next hidden range - no more to update
549            */
550           break;
551         }
552         region[1] = Math.max(nextRegion[1], end);
553         hiddenColumns.remove(i + 1);
554       }
555       added = true;
556     }
557     return added;
558   }
559
560   /**
561    * Answers if a column in the alignment is visible
562    * 
563    * @param column
564    *          absolute position of column in the alignment
565    * @return true if column is visible
566    */
567   public boolean isVisible(int column)
568   {
569     try
570     {
571       LOCK.readLock().lock();
572
573       Iterator<int[]> it = new RegionsIterator();
574       while (it.hasNext())
575       {
576         int[] region = it.next();
577         if (column >= region[0] && column <= region[1])
578         {
579           return false;
580         }
581       }
582
583       return true;
584     } finally
585     {
586       LOCK.readLock().unlock();
587     }
588   }
589
590   /**
591    * Get the visible sections of a set of sequences
592    * 
593    * @param start
594    *          sequence position to start from
595    * @param end
596    *          sequence position to end at
597    * @param seqs
598    *          an array of sequences
599    * @return an array of strings encoding the visible parts of each sequence
600    */
601   public String[] getVisibleSequenceStrings(int start, int end,
602           SequenceI[] seqs)
603   {
604     try
605     {
606       LOCK.readLock().lock();
607       int iSize = seqs.length;
608       String[] selections = new String[iSize];
609       if (hiddenColumns != null && hiddenColumns.size() > 0)
610       {
611         for (int i = 0; i < iSize; i++)
612         {
613           StringBuffer visibleSeq = new StringBuffer();
614
615           Iterator<int[]> blocks = new VisibleContigsIterator(start,
616                   end + 1, false);
617
618           while (blocks.hasNext())
619           {
620             int[] block = blocks.next();
621             if (blocks.hasNext())
622             {
623               visibleSeq
624                       .append(seqs[i].getSequence(block[0], block[1] + 1));
625             }
626             else
627             {
628               visibleSeq
629                       .append(seqs[i].getSequence(block[0], block[1]));
630             }
631           }
632
633           selections[i] = visibleSeq.toString();
634         }
635       }
636       else
637       {
638         for (int i = 0; i < iSize; i++)
639         {
640           selections[i] = seqs[i].getSequenceAsString(start, end);
641         }
642       }
643
644       return selections;
645     } finally
646     {
647       LOCK.readLock().unlock();
648     }
649   }
650
651   /**
652    * Locate the first position visible for this sequence. If seq isn't visible
653    * then return the position of the left side of the hidden boundary region.
654    * 
655    * @param seq
656    *          sequence to find position for
657    * @return visible start position
658    */
659   public int locateVisibleStartOfSequence(SequenceI seq)
660   {
661     try
662     {
663       LOCK.readLock().lock();
664       int start = 0;
665
666       if (hiddenColumns == null || hiddenColumns.size() == 0)
667       {
668         return seq.findIndex(seq.getStart()) - 1;
669       }
670
671       // Simply walk along the sequence whilst watching for hidden column
672       // boundaries
673       Iterator<int[]> regions = new RegionsIterator();
674       int hideStart = seq.getLength();
675       int hideEnd = -1;
676       int visPrev = 0;
677       int visNext = 0;
678       boolean foundStart = false;
679
680       // step through the non-gapped positions of the sequence
681       for (int i = seq.getStart(); i <= seq.getEnd() && (!foundStart); i++)
682       {
683         // get alignment position of this residue in the sequence
684         int p = seq.findIndex(i) - 1;
685
686         // update hidden region start/end
687         while (hideEnd < p && regions.hasNext())
688         {
689           int[] region = regions.next();
690           visPrev = visNext;
691           visNext += region[0] - visPrev;
692           hideStart = region[0];
693           hideEnd = region[1];
694         }
695         if (hideEnd < p)
696         {
697           hideStart = seq.getLength();
698         }
699         // update visible boundary for sequence
700         if (p < hideStart)
701         {
702           start = p;
703           foundStart = true;
704         }
705       }
706
707       if (foundStart)
708       {
709         return findColumnPosition(start);
710       }
711       // otherwise, sequence was completely hidden
712       return visPrev;
713     } finally
714     {
715       LOCK.readLock().unlock();
716     }
717   }
718
719   /**
720    * delete any columns in alignmentAnnotation that are hidden (including
721    * sequence associated annotation).
722    * 
723    * @param alignmentAnnotation
724    */
725   public void makeVisibleAnnotation(AlignmentAnnotation alignmentAnnotation)
726   {
727     makeVisibleAnnotation(0, alignmentAnnotation.annotations.length,
728             alignmentAnnotation);
729   }
730
731   /**
732    * delete any columns in alignmentAnnotation that are hidden (including
733    * sequence associated annotation).
734    * 
735    * @param start
736    *          remove any annotation to the right of this column
737    * @param end
738    *          remove any annotation to the left of this column
739    * @param alignmentAnnotation
740    *          the annotation to operate on
741    */
742   public void makeVisibleAnnotation(int start, int end,
743           AlignmentAnnotation alignmentAnnotation)
744   {
745     try
746     {
747       LOCK.readLock().lock();
748
749       int startFrom = start;
750       int endAt = end;
751
752       if (alignmentAnnotation.annotations != null)
753       {
754         if (hiddenColumns != null && hiddenColumns.size() > 0)
755         {
756           removeHiddenAnnotation(startFrom, endAt, alignmentAnnotation);
757         }
758         else
759         {
760           alignmentAnnotation.restrict(startFrom, endAt);
761         }
762       }
763     } finally
764     {
765       LOCK.readLock().unlock();
766     }
767   }
768
769   private void removeHiddenAnnotation(int start, int end,
770           AlignmentAnnotation alignmentAnnotation)
771   {
772     // mangle the alignmentAnnotation annotation array
773     ArrayList<Annotation[]> annels = new ArrayList<>();
774     Annotation[] els = null;
775
776     int w = 0;
777     
778     Iterator<int[]> blocks = new VisibleContigsIterator(start, end + 1,
779             false);
780
781     int copylength;
782     int annotationLength;
783     while (blocks.hasNext())
784     {
785       int[] block = blocks.next();
786       annotationLength = block[1] - block[0] + 1;
787     
788       if (blocks.hasNext())
789       {
790         // copy just the visible segment of the annotation row
791         copylength = annotationLength;
792       }
793       else
794       {
795         if (annotationLength + block[0] <= alignmentAnnotation.annotations.length)
796         {
797           // copy just the visible segment of the annotation row
798           copylength = annotationLength;
799         }
800         else
801         {
802           // copy to the end of the annotation row
803           copylength = alignmentAnnotation.annotations.length - block[0];
804         }
805       }
806       
807       els = new Annotation[annotationLength];
808       annels.add(els);
809       System.arraycopy(alignmentAnnotation.annotations, block[0], els, 0,
810               copylength);
811       w += annotationLength;
812     }
813     
814     if (w != 0)
815     {
816       alignmentAnnotation.annotations = new Annotation[w];
817
818       w = 0;
819       for (Annotation[] chnk : annels)
820       {
821         System.arraycopy(chnk, 0, alignmentAnnotation.annotations, w,
822                 chnk.length);
823         w += chnk.length;
824       }
825     }
826   }
827
828   /**
829    * 
830    * @return true if there are columns hidden
831    */
832   public boolean hasHiddenColumns()
833   {
834     try
835     {
836       LOCK.readLock().lock();
837       return hiddenColumns != null && hiddenColumns.size() > 0;
838     } finally
839     {
840       LOCK.readLock().unlock();
841     }
842   }
843
844   /**
845    * 
846    * @return true if there are more than one set of columns hidden
847    */
848   public boolean hasManyHiddenColumns()
849   {
850     try
851     {
852       LOCK.readLock().lock();
853       return hiddenColumns != null && hiddenColumns.size() > 1;
854     } finally
855     {
856       LOCK.readLock().unlock();
857     }
858   }
859
860   /**
861    * mark the columns corresponding to gap characters as hidden in the column
862    * selection
863    * 
864    * @param sr
865    */
866   public void hideInsertionsFor(SequenceI sr)
867   {
868     try
869     {
870       LOCK.writeLock().lock();
871       List<int[]> inserts = sr.getInsertions();
872       for (int[] r : inserts)
873       {
874         hideColumns(r[0], r[1]);
875       }
876     } finally
877     {
878       LOCK.writeLock().unlock();
879     }
880   }
881
882   /**
883    * Unhides, and adds to the selection list, all hidden columns
884    */
885   public void revealAllHiddenColumns(ColumnSelection sel)
886   {
887     try
888     {
889       LOCK.writeLock().lock();
890       Iterator<int[]> it = new RegionsIterator();
891       while (it.hasNext())
892       {
893         int[] region = it.next();
894         for (int j = region[0]; j < region[1] + 1; j++)
895         {
896           sel.addElement(j);
897         }
898       }
899
900       hiddenColumns = null;
901     } finally
902     {
903       LOCK.writeLock().unlock();
904     }
905   }
906
907   /**
908    * Reveals, and marks as selected, the hidden column range with the given
909    * start column
910    * 
911    * @param start
912    */
913   public void revealHiddenColumns(int start, ColumnSelection sel)
914   {
915     try
916     {
917       LOCK.writeLock().lock();
918       Iterator<int[]> it = new RegionsIterator();
919       while (it.hasNext())
920       {
921         int[] region = it.next();
922         if (start == region[0])
923         {
924           for (int j = region[0]; j < region[1] + 1; j++)
925           {
926             sel.addElement(j);
927           }
928
929           hiddenColumns.remove(region);
930           break;
931         }
932         else if (start < region[0])
933         {
934           break; // passed all possible matching regions
935         }
936       }
937
938       if (hiddenColumns.size() == 0)
939       {
940         hiddenColumns = null;
941       }
942     } finally
943     {
944       LOCK.writeLock().unlock();
945     }
946   }
947
948   /**
949    * Add gaps into the sequences aligned to profileseq under the given
950    * AlignmentView
951    * 
952    * @param profileseq
953    * @param al
954    *          - alignment to have gaps inserted into it
955    * @param input
956    *          - alignment view where sequence corresponding to profileseq is
957    *          first entry
958    * @return new HiddenColumns for new alignment view, with insertions into
959    *         profileseq marked as hidden.
960    */
961   public static HiddenColumns propagateInsertions(SequenceI profileseq,
962           AlignmentI al, AlignmentView input)
963   {
964     int profsqpos = 0;
965
966     char gc = al.getGapCharacter();
967     Object[] alandhidden = input.getAlignmentAndHiddenColumns(gc);
968     HiddenColumns nview = (HiddenColumns) alandhidden[1];
969     SequenceI origseq = ((SequenceI[]) alandhidden[0])[profsqpos];
970     nview.propagateInsertions(profileseq, al, origseq);
971     return nview;
972   }
973
974   /**
975    * 
976    * @param profileseq
977    *          - sequence in al which corresponds to origseq
978    * @param al
979    *          - alignment which is to have gaps inserted into it
980    * @param origseq
981    *          - sequence corresponding to profileseq which defines gap map for
982    *          modifying al
983    */
984   private void propagateInsertions(SequenceI profileseq, AlignmentI al,
985           SequenceI origseq)
986   {
987     try
988     {
989       LOCK.writeLock().lock();
990
991       char gc = al.getGapCharacter();
992
993       // take the set of hidden columns, and the set of gaps in origseq,
994       // and remove all the hidden gaps from hiddenColumns
995
996       // first get the gaps as a Bitset
997       BitSet gaps = origseq.gapBitset();
998
999       // now calculate hidden ^ not(gap)
1000       BitSet hidden = new BitSet();
1001       markHiddenRegions(hidden);
1002       hidden.andNot(gaps);
1003       hiddenColumns = null;
1004       this.hideMarkedBits(hidden);
1005
1006       // for each sequence in the alignment, except the profile sequence,
1007       // insert gaps corresponding to each hidden region
1008       // but where each hidden column region is shifted backwards by the number
1009       // of
1010       // preceding visible gaps
1011       // update hidden columns at the same time
1012       Iterator<int[]> regions = new RegionsIterator();
1013       ArrayList<int[]> newhidden = new ArrayList<>();
1014
1015       int numGapsBefore = 0;
1016       int gapPosition = 0;
1017       while (regions.hasNext())
1018       {
1019         // get region coordinates accounting for gaps
1020         // we can rely on gaps not being *in* hidden regions because we already
1021         // removed those
1022         int[] region = regions.next();
1023         while (gapPosition < region[0])
1024         {
1025           gapPosition++;
1026           if (gaps.get(gapPosition))
1027           {
1028             numGapsBefore++;
1029           }
1030         }
1031
1032         int left = region[0] - numGapsBefore;
1033         int right = region[1] - numGapsBefore;
1034         newhidden.add(new int[] { left, right });
1035
1036         // make a string with number of gaps = length of hidden region
1037         StringBuffer sb = new StringBuffer();
1038         for (int s = 0; s < right - left + 1; s++)
1039         {
1040           sb.append(gc);
1041         }
1042         padGaps(sb, left, profileseq, al);
1043
1044       }
1045       hiddenColumns = newhidden;
1046     } finally
1047     {
1048       LOCK.writeLock().unlock();
1049     }
1050   }
1051
1052   /**
1053    * Pad gaps in all sequences in alignment except profileseq
1054    * 
1055    * @param sb
1056    *          gap string to insert
1057    * @param left
1058    *          position to insert at
1059    * @param profileseq
1060    *          sequence not to pad
1061    * @param al
1062    *          alignment to pad sequences in
1063    */
1064   private void padGaps(StringBuffer sb, int pos, SequenceI profileseq,
1065           AlignmentI al)
1066   {
1067     // loop over the sequences and pad with gaps where required
1068     for (int s = 0, ns = al.getHeight(); s < ns; s++)
1069     {
1070       SequenceI sqobj = al.getSequenceAt(s);
1071       if (sqobj != profileseq)
1072       {
1073         String sq = al.getSequenceAt(s).getSequenceAsString();
1074         if (sq.length() <= pos)
1075         {
1076           // pad sequence
1077           int diff = pos - sq.length() - 1;
1078           if (diff > 0)
1079           {
1080             // pad gaps
1081             sq = sq + sb;
1082             while ((diff = pos - sq.length() - 1) > 0)
1083             {
1084               if (diff >= sb.length())
1085               {
1086                 sq += sb.toString();
1087               }
1088               else
1089               {
1090                 char[] buf = new char[diff];
1091                 sb.getChars(0, diff, buf, 0);
1092                 sq += buf.toString();
1093               }
1094             }
1095           }
1096           sq += sb.toString();
1097         }
1098         else
1099         {
1100           al.getSequenceAt(s).setSequence(
1101                   sq.substring(0, pos) + sb.toString() + sq.substring(pos));
1102         }
1103       }
1104     }
1105   }
1106
1107   /**
1108    * Returns a hashCode built from hidden column ranges
1109    */
1110   @Override
1111   public int hashCode()
1112   {
1113     try
1114     {
1115       LOCK.readLock().lock();
1116       int hashCode = 1;
1117       Iterator<int[]> it = new RegionsIterator();
1118       while (it.hasNext())
1119       {
1120         int[] hidden = it.next();
1121         hashCode = HASH_MULTIPLIER * hashCode + hidden[0];
1122         hashCode = HASH_MULTIPLIER * hashCode + hidden[1];
1123       }
1124       return hashCode;
1125     } finally
1126     {
1127       LOCK.readLock().unlock();
1128     }
1129   }
1130
1131   /**
1132    * Hide columns corresponding to the marked bits
1133    * 
1134    * @param inserts
1135    *          - columns map to bits starting from zero
1136    */
1137   public void hideMarkedBits(BitSet inserts)
1138   {
1139     try
1140     {
1141       LOCK.writeLock().lock();
1142       for (int firstSet = inserts
1143               .nextSetBit(0), lastSet = 0; firstSet >= 0; firstSet = inserts
1144                       .nextSetBit(lastSet))
1145       {
1146         lastSet = inserts.nextClearBit(firstSet);
1147         hideColumns(firstSet, lastSet - 1);
1148       }
1149     } finally
1150     {
1151       LOCK.writeLock().unlock();
1152     }
1153   }
1154
1155   /**
1156    * 
1157    * @param inserts
1158    *          BitSet where hidden columns will be marked
1159    */
1160   public void markHiddenRegions(BitSet inserts)
1161   {
1162     try
1163     {
1164       LOCK.readLock().lock();
1165       if (hiddenColumns == null)
1166       {
1167         return;
1168       }
1169       Iterator<int[]> it = new RegionsIterator();
1170       while (it.hasNext())
1171       {
1172         int[] range = it.next();
1173         inserts.set(range[0], range[1] + 1);
1174       }
1175     } finally
1176     {
1177       LOCK.readLock().unlock();
1178     }
1179   }
1180
1181   /**
1182    * Calculate the visible start and end index of an alignment.
1183    * 
1184    * @param width
1185    *          full alignment width
1186    * @return integer array where: int[0] = startIndex, and int[1] = endIndex
1187    */
1188   public int[] getVisibleStartAndEndIndex(int width)
1189   {
1190     try
1191     {
1192       LOCK.readLock().lock();
1193       int[] alignmentStartEnd = new int[] { 0, width - 1 };
1194       int startPos = alignmentStartEnd[0];
1195       int endPos = alignmentStartEnd[1];
1196
1197       int[] lowestRange = new int[] { -1, -1 };
1198       int[] higestRange = new int[] { -1, -1 };
1199
1200       if (hiddenColumns == null)
1201       {
1202         return new int[] { startPos, endPos };
1203       }
1204
1205       Iterator<int[]> it = new RegionsIterator();
1206       while (it.hasNext())
1207       {
1208         int[] range = it.next();
1209         lowestRange = (range[0] <= startPos) ? range : lowestRange;
1210         higestRange = (range[1] >= endPos) ? range : higestRange;
1211       }
1212
1213       if (lowestRange[0] == -1 && lowestRange[1] == -1)
1214       {
1215         startPos = alignmentStartEnd[0];
1216       }
1217       else
1218       {
1219         startPos = lowestRange[1] + 1;
1220       }
1221
1222       if (higestRange[0] == -1 && higestRange[1] == -1)
1223       {
1224         endPos = alignmentStartEnd[1];
1225       }
1226       else
1227       {
1228         endPos = higestRange[0] - 1;
1229       }
1230       return new int[] { startPos, endPos };
1231     } finally
1232     {
1233       LOCK.readLock().unlock();
1234     }
1235
1236   }
1237
1238   /**
1239    * Finds the hidden region (if any) which starts or ends at res
1240    * 
1241    * @param res
1242    *          visible residue position, unadjusted for hidden columns
1243    * @return region as [start,end] or null if no matching region is found
1244    */
1245   public int[] getRegionWithEdgeAtRes(int res)
1246   {
1247     try
1248     {
1249       LOCK.readLock().lock();
1250       int adjres = adjustForHiddenColumns(res);
1251
1252       int[] reveal = null;
1253       Iterator<int[]> it = new RegionsIterator(adjres - 2,
1254               adjres + 2);
1255       while (it.hasNext())
1256       {
1257         int[] region = it.next();
1258         if (adjres + 1 == region[0] || adjres - 1 == region[1])
1259         {
1260           reveal = region;
1261           break;
1262         }
1263       }
1264       return reveal;
1265     } finally
1266     {
1267       LOCK.readLock().unlock();
1268     }
1269   }
1270
1271   /**
1272    * Return an iterator over the hidden regions
1273    */
1274   public Iterator<int[]> iterator()
1275   {
1276     return new BoundedHiddenColsIterator();
1277   }
1278
1279   /**
1280    * Return a bounded iterator over the hidden regions
1281    * 
1282    * @param start
1283    *          position to start from (inclusive, absolute column position)
1284    * @param end
1285    *          position to end at (inclusive, absolute column position)
1286    * @return
1287    */
1288   public Iterator<int[]> getBoundedIterator(int start, int end)
1289   {
1290     return new BoundedHiddenColsIterator(start, end);
1291   }
1292
1293   /**
1294    * Return a bounded iterator over the *visible* start positions of hidden
1295    * regions
1296    * 
1297    * @param start
1298    *          position to start from (inclusive, visible column position)
1299    * @param end
1300    *          position to end at (inclusive, visible column position)
1301    */
1302   public Iterator<Integer> getBoundedStartIterator(int start, int end)
1303   {
1304     return new BoundedStartRegionIterator(start, end, true);
1305   }
1306
1307   /**
1308    * Return an iterator over visible *columns* (not regions) between the given
1309    * start and end boundaries
1310    * 
1311    * @param start
1312    *          first column (inclusive)
1313    * @param end
1314    *          last column (inclusive)
1315    */
1316   public Iterator<Integer> getVisibleColsIterator(int start, int end)
1317   {
1318     return new VisibleColsIterator(start, end);
1319   }
1320
1321   /**
1322    * return an iterator over visible segments between the given start and end
1323    * boundaries
1324    * 
1325    * @param start
1326    *          (first column inclusive from 0)
1327    * @param end
1328    *          (last column - not inclusive)
1329    */
1330   public Iterator<int[]> getVisContigsIterator(int start, int end)
1331   {
1332     // return new VisibleBlocksIterator(start, end, true)
1333     return new VisibleContigsIterator(start, end, true);
1334   }
1335
1336   /**
1337    * return an iterator over visible segments between the given start and end
1338    * boundaries
1339    * 
1340    * @param start
1341    *          (first column - inclusive from 0)
1342    * @param end
1343    *          (last column - inclusive)
1344    * @param useVisibleCoords
1345    *          if true, start and end are visible column positions, not absolute
1346    *          positions
1347    */
1348   public Iterator<int[]> getVisibleBlocksIterator(int start, int end,
1349           boolean useVisibleCoords)
1350   {
1351     if (useVisibleCoords)
1352     {
1353       // TODO
1354       // we should really just convert start and end here with
1355       // adjustForHiddenColumns
1356       // and then create a VisibleContigsIterator
1357       // but without a cursor this will be horribly slow in some situations
1358       // ... so until then...
1359       return new VisibleBlocksVisBoundsIterator(start, end, true);
1360     }
1361     else
1362     {
1363       return new VisibleContigsIterator(start, end - 1, true);
1364     }
1365   }
1366
1367   /**
1368    * A local iterator which iterates over hidden column regions in a range.
1369    * Intended for use ONLY within the HiddenColumns class, because it works
1370    * directly with the hiddenColumns collection without locking (callers should
1371    * lock hiddenColumns).
1372    */
1373   private class RegionsIterator implements Iterator<int[]>
1374   {
1375     // start position to iterate from
1376     private int start;
1377
1378     // end position to iterate to
1379     private int end;
1380
1381     // current index in hiddenColumns
1382     private int currentPosition = 0;
1383
1384     // current column in hiddenColumns
1385     private int[] nextRegion = null;
1386
1387     // Constructor with bounds
1388     RegionsIterator(int lowerBound, int upperBound)
1389     {
1390       init(lowerBound, upperBound);
1391     }
1392
1393     // Unbounded constructor
1394     RegionsIterator()
1395     {
1396       if (hiddenColumns != null)
1397       {
1398         // iterator over full hiddenColumns collection
1399         int last = hiddenColumns.get(hiddenColumns.size() - 1)[1];
1400         init(0, last);
1401       }
1402       else
1403       {
1404         // empty iterator
1405         init(0, 0);
1406       }
1407     }
1408
1409     /**
1410      * Construct an iterator over hiddenColums bounded at
1411      * [lowerBound,upperBound]
1412      * 
1413      * @param lowerBound
1414      *          lower bound to iterate from
1415      * @param upperBound
1416      *          upper bound to iterate to
1417      */
1418     private void init(int lowerBound, int upperBound)
1419     {
1420       start = lowerBound;
1421       end = upperBound;
1422
1423       if (hiddenColumns != null)
1424       {
1425         // iterate until a region overlaps with [start,end]
1426         currentPosition = 0;
1427         while ((currentPosition < hiddenColumns.size())
1428                 && (hiddenColumns.get(currentPosition)[1] < start))
1429         {
1430           currentPosition++;
1431         }
1432         if (currentPosition < hiddenColumns.size())
1433         {
1434           nextRegion = hiddenColumns.get(currentPosition);
1435         }
1436       }
1437     }
1438
1439     @Override
1440     public boolean hasNext()
1441     {
1442       return (hiddenColumns != null) && (nextRegion != null)
1443               && (nextRegion[0] <= end);
1444     }
1445
1446     @Override
1447     public int[] next()
1448     {
1449       int[] region = nextRegion;
1450       currentPosition++;
1451       if (currentPosition < hiddenColumns.size())
1452       {
1453         nextRegion = hiddenColumns.get(currentPosition);
1454       }
1455       else
1456       {
1457         nextRegion = null;
1458       }
1459       return region;
1460     }
1461
1462   }
1463
1464   /**
1465    * A local iterator which reverse iterates over hidden column regions in a
1466    * range. Intended for use ONLY within the HiddenColumns class, because it
1467    * works directly with the hiddenColumns collection without locking (callers
1468    * should lock hiddenColumns).
1469    */
1470   private class ReverseRegionsIterator implements Iterator<int[]>
1471   {
1472     // start position to iterate to
1473     private int start;
1474
1475     // end position to iterate from
1476     private int end;
1477
1478     // current index in hiddenColumns
1479     private int currentPosition = 0;
1480
1481     // current column in hiddenColumns
1482     private int[] nextRegion = null;
1483
1484     // Constructor with bounds
1485     ReverseRegionsIterator(int lowerBound, int upperBound)
1486     {
1487       init(lowerBound, upperBound);
1488     }
1489
1490     /**
1491      * Construct an iterator over hiddenColums bounded at
1492      * [lowerBound,upperBound]
1493      * 
1494      * @param lowerBound
1495      *          lower bound to iterate to
1496      * @param upperBound
1497      *          upper bound to iterate from
1498      */
1499     private void init(int lowerBound, int upperBound)
1500     {
1501       start = lowerBound;
1502       end = upperBound;
1503
1504       if (hiddenColumns != null)
1505       {
1506         // iterate until a region overlaps with [start,end]
1507         currentPosition = hiddenColumns.size() - 1;
1508         while (currentPosition >= 0
1509                 && hiddenColumns.get(currentPosition)[1] > end)
1510         {
1511           currentPosition--;
1512         }
1513         if (currentPosition >= 0)
1514         {
1515           nextRegion = hiddenColumns.get(currentPosition);
1516         }
1517       }
1518     }
1519
1520     @Override
1521     public boolean hasNext()
1522     {
1523       return (hiddenColumns != null) && (nextRegion != null)
1524               && (nextRegion[1] >= start);
1525     }
1526
1527     @Override
1528     public int[] next()
1529     {
1530       int[] region = nextRegion;
1531       currentPosition--;
1532       if (currentPosition >= 0)
1533       {
1534         nextRegion = hiddenColumns.get(currentPosition);
1535       }
1536       else
1537       {
1538         nextRegion = null;
1539       }
1540       return region;
1541     }
1542
1543   }
1544
1545   /**
1546    * An iterator which iterates over hidden column regions in a range. Works
1547    * with a copy of the hidden columns collection. Intended to be used by
1548    * callers OUTSIDE of HiddenColumns.
1549    */
1550   private class BoundedHiddenColsIterator implements Iterator<int[]>
1551   {
1552     // start position to iterate from
1553     private int start;
1554
1555     // end position to iterate to
1556     private int end;
1557
1558     // current index in hiddenColumns
1559     private int currentPosition = 0;
1560
1561     // current column in hiddenColumns
1562     private int[] currentRegion;
1563
1564     // local copy or reference to hiddenColumns
1565     private List<int[]> localHidden;
1566
1567     /**
1568      * Unbounded constructor
1569      */
1570     BoundedHiddenColsIterator()
1571     {
1572       if (hiddenColumns != null)
1573       {
1574         int last = hiddenColumns.get(hiddenColumns.size() - 1)[1];
1575         init(0, last);
1576       }
1577       else
1578       {
1579         init(0, 0);
1580       }
1581     }
1582
1583     /**
1584      * Construct an iterator over hiddenColums bounded at
1585      * [lowerBound,upperBound]
1586      * 
1587      * @param lowerBound
1588      *          lower bound to iterate from
1589      * @param upperBound
1590      *          upper bound to iterate to
1591      */
1592     BoundedHiddenColsIterator(int lowerBound, int upperBound)
1593     {
1594       init(lowerBound, upperBound);
1595     }
1596
1597     /**
1598      * Construct an iterator over hiddenColums bounded at
1599      * [lowerBound,upperBound]
1600      * 
1601      * @param lowerBound
1602      *          lower bound to iterate from
1603      * @param upperBound
1604      *          upper bound to iterate to
1605      */
1606     private void init(int lowerBound, int upperBound)
1607     {
1608       start = lowerBound;
1609       end = upperBound;
1610
1611       try
1612       {
1613         LOCK.readLock().lock();
1614         
1615         if (hiddenColumns != null)
1616         {
1617           localHidden = new ArrayList<>();
1618
1619           // iterate until a region overlaps with [start,end]
1620           int i = 0;
1621           while ((i < hiddenColumns.size())
1622                   && (hiddenColumns.get(i)[1] < start))
1623           {
1624             i++;
1625           }
1626
1627           // iterate from start to end, adding each hidden region. Positions are
1628           // absolute, and all regions which *overlap* [start,end] are added.
1629           while (i < hiddenColumns.size()
1630                   && (hiddenColumns.get(i)[0] <= end))
1631           {
1632             int[] rh = hiddenColumns.get(i);
1633             int[] cp = new int[2];
1634             System.arraycopy(rh, 0, cp, 0, rh.length);
1635             localHidden.add(cp);
1636             i++;
1637           }
1638         }
1639       }
1640       finally
1641       {
1642         LOCK.readLock().unlock();
1643       }
1644     }
1645
1646     @Override
1647     public boolean hasNext()
1648     {
1649       return (localHidden != null)
1650               && (currentPosition < localHidden.size());
1651     }
1652
1653     @Override
1654     public int[] next()
1655     {
1656       currentRegion = localHidden.get(currentPosition);
1657       currentPosition++;
1658       return currentRegion;
1659     }
1660   }
1661
1662   /**
1663    * An iterator which iterates over visible start positions of hidden column
1664    * regions in a range.
1665    */
1666   private class BoundedStartRegionIterator implements Iterator<Integer>
1667   {
1668     // start position to iterate from
1669     private int start;
1670
1671     // end position to iterate to
1672     private int end;
1673
1674     // current index in hiddenColumns
1675     private int currentPosition = 0;
1676
1677     // local copy or reference to hiddenColumns
1678     private List<Integer> positions = null;
1679
1680     /**
1681      * Construct an iterator over hiddenColums bounded at
1682      * [lowerBound,upperBound]
1683      * 
1684      * @param lowerBound
1685      *          lower bound to iterate from
1686      * @param upperBound
1687      *          upper bound to iterate to
1688      * @param useCopyCols
1689      *          whether to make a local copy of hiddenColumns for iteration (set
1690      *          to true if calling from outwith the HiddenColumns class)
1691      */
1692     BoundedStartRegionIterator(int lowerBound, int upperBound,
1693             boolean useCopy)
1694     {
1695       start = lowerBound;
1696       end = upperBound;
1697       
1698       try
1699       {
1700         if (useCopy)
1701         {
1702           // assume that if useCopy is false the calling code has locked
1703           // hiddenColumns
1704           LOCK.readLock().lock();
1705         }
1706
1707         if (hiddenColumns != null)
1708         {
1709           positions = new ArrayList<>(hiddenColumns.size());
1710
1711           // navigate to start, keeping count of hidden columns
1712           int i = 0;
1713           int hiddenSoFar = 0;
1714           while ((i < hiddenColumns.size())
1715                   && (hiddenColumns.get(i)[0] < start + hiddenSoFar))
1716           {
1717             int[] region = hiddenColumns.get(i);
1718             hiddenSoFar += region[1] - region[0] + 1;
1719             i++;
1720           }
1721
1722           // iterate from start to end, adding start positions of each
1723           // hidden region. Positions are visible columns count, not absolute
1724           while (i < hiddenColumns.size()
1725                   && (hiddenColumns.get(i)[0] <= end + hiddenSoFar))
1726           {
1727             int[] region = hiddenColumns.get(i);
1728             positions.add(region[0] - hiddenSoFar);
1729             hiddenSoFar += region[1] - region[0] + 1;
1730             i++;
1731           }
1732         }
1733         else
1734         {
1735           positions = new ArrayList<>();
1736         }
1737       } finally
1738       {
1739         if (useCopy)
1740         {
1741           LOCK.readLock().unlock();
1742         }
1743       }
1744     }
1745
1746     @Override
1747     public boolean hasNext()
1748     {
1749       return (currentPosition < positions.size());
1750     }
1751
1752     /**
1753      * Get next hidden region start position
1754      * 
1755      * @return the start position in *visible* coordinates
1756      */
1757     @Override
1758     public Integer next()
1759     {
1760       int result = positions.get(currentPosition);
1761       currentPosition++;
1762       return result;
1763     }
1764   }
1765
1766   /**
1767    * Iterator over the visible *columns* (not regions) as determined by the set
1768    * of hidden columns. Uses a local copy of hidden columns.
1769    * 
1770    * @author kmourao
1771    *
1772    */
1773   private class VisibleColsIterator implements Iterator<Integer>
1774   {
1775     private int last;
1776
1777     private int current;
1778
1779     private int next;
1780
1781     private List<int[]> localHidden = new ArrayList<>();
1782
1783     private int nexthiddenregion;
1784
1785     VisibleColsIterator(int firstcol, int lastcol)
1786     {
1787       last = lastcol;
1788       current = firstcol;
1789       next = firstcol;
1790       nexthiddenregion = 0;
1791
1792       LOCK.readLock().lock();
1793
1794       if (hiddenColumns != null)
1795       {
1796         int i = 0;
1797         for (i = 0; i < hiddenColumns.size()
1798                 && (current <= hiddenColumns.get(i)[0]); ++i)
1799         {
1800           if (current >= hiddenColumns.get(i)[0]
1801                   && current <= hiddenColumns.get(i)[1])
1802           {
1803             // current is hidden, move to right
1804             current = hiddenColumns.get(i)[1] + 1;
1805             next = current;
1806             nexthiddenregion = i + 1;
1807           }
1808         }
1809
1810         for (i = hiddenColumns.size() - 1; i >= 0
1811                 && (last >= hiddenColumns.get(i)[1]); --i)
1812         {
1813           if (last >= hiddenColumns.get(i)[0]
1814                   && last <= hiddenColumns.get(i)[1])
1815           {
1816             // last is hidden, move to left
1817             last = hiddenColumns.get(i)[0] - 1;
1818           }
1819         }
1820
1821         // make a local copy of the bit we need
1822         i = nexthiddenregion;
1823         while (i < hiddenColumns.size() && hiddenColumns.get(i)[0] <= last)
1824         {
1825           int[] region = new int[] { hiddenColumns.get(i)[0],
1826               hiddenColumns.get(i)[1] };
1827           localHidden.add(region);
1828           i++;
1829         }
1830       }
1831
1832       LOCK.readLock().unlock();
1833     }
1834
1835     @Override
1836     public boolean hasNext()
1837     {
1838       return next <= last;
1839     }
1840
1841     @Override
1842     public Integer next()
1843     {
1844       if (next > last)
1845       {
1846         throw new NoSuchElementException();
1847       }
1848       current = next;
1849       if ((localHidden != null)
1850               && (nexthiddenregion < localHidden.size()))
1851       {
1852         // still some more hidden regions
1853         if (next + 1 < localHidden.get(nexthiddenregion)[0])
1854         {
1855           // next+1 is still before the next hidden region
1856           next++;
1857         }
1858         else if ((next + 1 >= localHidden.get(nexthiddenregion)[0])
1859                 && (next + 1 <= localHidden.get(nexthiddenregion)[1]))
1860         {
1861           // next + 1 is in the next hidden region
1862           next = localHidden.get(nexthiddenregion)[1] + 1;
1863           nexthiddenregion++;
1864         }
1865       }
1866       else
1867       {
1868         // finished with hidden regions, just increment normally
1869         next++;
1870       }
1871       return current;
1872     }
1873
1874     @Override
1875     public void remove()
1876     {
1877       throw new UnsupportedOperationException();
1878     }
1879   }
1880
1881   /**
1882    * An iterator which iterates over visible regions in a range.
1883    */
1884   private class VisibleContigsIterator implements Iterator<int[]>
1885   {
1886     private List<int[]> vcontigs = new ArrayList<>();
1887
1888     private int currentPosition = 0;
1889
1890     VisibleContigsIterator(int start, int end, boolean usecopy)
1891     {
1892       try
1893       {
1894         if (usecopy)
1895         {
1896           LOCK.readLock().lock();
1897         }
1898
1899         if (hiddenColumns != null && hiddenColumns.size() > 0)
1900         {
1901           int vstart = start;
1902           int hideStart;
1903           int hideEnd;
1904
1905           for (int[] region : hiddenColumns)
1906           {
1907             hideStart = region[0];
1908             hideEnd = region[1];
1909
1910             // navigate to start
1911             if (hideEnd < vstart)
1912             {
1913               continue;
1914             }
1915             if (hideStart > vstart)
1916             {
1917               int[] contig = new int[] { vstart, hideStart - 1 };
1918               vcontigs.add(contig);
1919             }
1920             vstart = hideEnd + 1;
1921
1922             // exit if we're past the end
1923             if (vstart >= end)
1924             {
1925               break;
1926             }
1927           }
1928
1929           if (vstart < end)
1930           {
1931             int[] contig = new int[] { vstart, end - 1 };
1932             vcontigs.add(contig);
1933           }
1934         }
1935         else
1936         {
1937           int[] contig = new int[] { start, end - 1 };
1938           vcontigs.add(contig);
1939         }
1940       } finally
1941       {
1942         if (usecopy)
1943         {
1944           LOCK.readLock().unlock();
1945         }
1946       }
1947     }
1948
1949     @Override
1950     public boolean hasNext()
1951     {
1952       return (currentPosition < vcontigs.size());
1953     }
1954
1955     @Override
1956     public int[] next()
1957     {
1958       int[] result = vcontigs.get(currentPosition);
1959       currentPosition++;
1960       return result;
1961     }
1962   }
1963
1964   /**
1965    * An iterator which iterates over visible regions in a range. The range is
1966    * specified in terms of visible column positions. Provides a special
1967    * "endsAtHidden" indicator to allow callers to determine if the final visible
1968    * column is adjacent to a hidden region.
1969    */
1970   public class VisibleBlocksVisBoundsIterator implements Iterator<int[]>
1971   {
1972     private List<int[]> vcontigs = new ArrayList<>();
1973
1974     private int currentPosition = 0;
1975
1976     private boolean endsAtHidden = false;
1977
1978     /**
1979      * Constructor for iterator over visible regions in a range.
1980      * 
1981      * @param start
1982      *          start position in terms of visible column position
1983      * @param end
1984      *          end position in terms of visible column position
1985      * @param usecopy
1986      *          whether to use a local copy of hidden columns
1987      */
1988     VisibleBlocksVisBoundsIterator(int start, int end, boolean usecopy)
1989     {
1990       /* actually this implementation always uses a local copy but this may change in future */
1991       try
1992       {
1993         if (usecopy)
1994         {
1995           LOCK.readLock().lock();
1996         }
1997
1998         if (hiddenColumns != null && hiddenColumns.size() > 0)
1999         {
2000           int blockStart = start;
2001           int blockEnd = end;
2002           int hiddenSoFar = 0;
2003           int visSoFar = 0;
2004
2005           // iterate until a region begins within (start,end]
2006           int i = 0;
2007           while ((i < hiddenColumns.size())
2008                   && (hiddenColumns.get(i)[0] <= blockStart + hiddenSoFar))
2009           {
2010             hiddenSoFar += hiddenColumns.get(i)[1] - hiddenColumns.get(i)[0]
2011                     + 1;
2012             i++;
2013           }
2014
2015           blockStart += hiddenSoFar; // convert start to absolute position
2016           blockEnd += hiddenSoFar; // convert end to absolute position
2017
2018           // iterate from start to end, adding each visible region. Positions
2019           // are
2020           // absolute, and all hidden regions which overlap [start,end] are
2021           // used.
2022           while (i < hiddenColumns.size()
2023                   && (hiddenColumns.get(i)[0] <= blockEnd))
2024           {
2025             int[] region = hiddenColumns.get(i);
2026
2027             // end position of this visible region is either just before the
2028             // start of the next hidden region, or the absolute position of
2029             // 'end', whichever is lowest
2030             blockEnd = Math.min(blockEnd, region[0] - 1);
2031
2032             vcontigs.add(new int[] { blockStart, blockEnd });
2033
2034             visSoFar += blockEnd - blockStart + 1;
2035
2036             // next visible region starts after this hidden region
2037             blockStart = region[1] + 1;
2038
2039             hiddenSoFar += region[1] - region[0] + 1;
2040
2041             // reset blockEnd to absolute position of 'end', assuming we've now
2042             // passed all hidden regions before end
2043             blockEnd = end + hiddenSoFar;
2044
2045             i++;
2046           }
2047           if (visSoFar < end - start)
2048           {
2049             // the number of visible columns we've accounted for is less than
2050             // the number specified by end-start; work out the end position of
2051             // the last visible region
2052             blockEnd = blockStart + end - start - visSoFar;
2053             vcontigs.add(new int[] { blockStart, blockEnd });
2054
2055             // if the last visible region ends at the next hidden region, set
2056             // endsAtHidden=true
2057             if (i < hiddenColumns.size()
2058                     && hiddenColumns.get(i)[0] - 1 == blockEnd)
2059             {
2060               endsAtHidden = true;
2061             }
2062           }
2063         }
2064         else
2065         {
2066           // there are no hidden columns, return a single visible contig
2067           vcontigs.add(new int[] { start, end });
2068           endsAtHidden = false;
2069         }
2070       } finally
2071       {
2072         if (usecopy)
2073         {
2074           LOCK.readLock().unlock();
2075         }
2076       }
2077     }
2078
2079     @Override
2080     public boolean hasNext()
2081     {
2082       return (currentPosition < vcontigs.size());
2083     }
2084
2085     @Override
2086     public int[] next()
2087     {
2088       int[] result = vcontigs.get(currentPosition);
2089       currentPosition++;
2090       return result;
2091     }
2092
2093     public boolean endsAtHidden()
2094     {
2095       return endsAtHidden;
2096     }
2097   }
2098 }