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