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