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