JAL-2674 iterator for visible blocks with absolute positions
[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 fpos = seq.getStart();
705       int lpos = seq.getEnd();
706       int start = 0;
707
708       if (hiddenColumns == null || hiddenColumns.size() == 0)
709       {
710         int ifpos = seq.findIndex(fpos) - 1;
711         int ilpos = seq.findIndex(lpos) - 1;
712         return new int[] { ifpos, ifpos, ilpos };
713       }
714
715       // Simply walk along the sequence whilst watching for hidden column
716       // boundaries
717       Iterator<int[]> regions = iterator();
718       int spos = fpos;
719       int hideStart = seq.getLength();
720       int hideEnd = -1;
721       int visPrev = 0;
722       int visNext = 0;
723       int firstP = -1;
724       int lastP = -1;
725       boolean foundStart = false;
726       for (int p = 0, pLen = seq.getLength(); spos <= seq.getEnd()
727               && p < pLen; p++)
728       {
729         if (!Comparison.isGap(seq.getCharAt(p)))
730         {
731           // keep track of first/last column
732           // containing sequence data regardless of visibility
733           if (firstP == -1)
734           {
735             firstP = p;
736           }
737           lastP = p;
738           // update hidden region start/end
739           while (hideEnd < p && regions.hasNext())
740           {
741             int[] region = regions.next();
742             visPrev = visNext;
743             visNext += region[0] - visPrev;
744             hideStart = region[0];
745             hideEnd = region[1];
746           }
747           if (hideEnd < p)
748           {
749             hideStart = seq.getLength();
750           }
751           // update visible boundary for sequence
752           if (p < hideStart)
753           {
754             if (!foundStart)
755             {
756               fpos = spos;
757               start = p;
758               foundStart = true;
759             }
760             lpos = spos;
761           }
762           // look for next sequence position
763           spos++;
764         }
765       }
766       if (foundStart)
767       {
768         return new int[] { findColumnPosition(start), firstP, lastP };
769       }
770       // otherwise, sequence was completely hidden
771       return new int[] { visPrev, firstP, lastP };
772     } finally
773     {
774       LOCK.readLock().unlock();
775     }
776   }
777
778   /**
779    * delete any columns in alignmentAnnotation that are hidden (including
780    * sequence associated annotation).
781    * 
782    * @param alignmentAnnotation
783    */
784   public void makeVisibleAnnotation(AlignmentAnnotation alignmentAnnotation)
785   {
786     makeVisibleAnnotation(0, alignmentAnnotation.annotations.length,
787             alignmentAnnotation);
788   }
789
790   /**
791    * delete any columns in alignmentAnnotation that are hidden (including
792    * sequence associated annotation).
793    * 
794    * @param start
795    *          remove any annotation to the right of this column
796    * @param end
797    *          remove any annotation to the left of this column
798    * @param alignmentAnnotation
799    *          the annotation to operate on
800    */
801   public void makeVisibleAnnotation(int start, int end,
802           AlignmentAnnotation alignmentAnnotation)
803   {
804     try
805     {
806       LOCK.readLock().lock();
807
808       int startFrom = start;
809       int endAt = end;
810
811       if (alignmentAnnotation.annotations != null)
812       {
813         if (hiddenColumns != null && hiddenColumns.size() > 0)
814         {
815           removeHiddenAnnotation(startFrom, endAt, alignmentAnnotation);
816         }
817         else
818         {
819           alignmentAnnotation.restrict(startFrom, endAt);
820         }
821       }
822     } finally
823     {
824       LOCK.readLock().unlock();
825     }
826   }
827
828   private void removeHiddenAnnotation(int start, int end,
829           AlignmentAnnotation alignmentAnnotation)
830   {
831     // mangle the alignmentAnnotation annotation array
832     Vector<Annotation[]> annels = new Vector<>();
833     Annotation[] els = null;
834
835     int w = 0;
836     
837     Iterator<int[]> blocks = new VisibleBlocksIterator(start, end,
838             false);
839     int copylength;
840     int annotationLength;
841     while (blocks.hasNext())
842     {
843       int[] block = blocks.next();
844     
845       if (blocks.hasNext())
846       {
847         annotationLength = block[1] - block[0];
848         // copy just the visible segment of the annotation row
849         copylength = annotationLength;
850       }
851       else
852       {
853         annotationLength = block[1] - block[0] + 1;
854         if (annotationLength + block[0] <= alignmentAnnotation.annotations.length)
855         {
856           // copy just the visible segment of the annotation row
857           copylength = annotationLength;
858         }
859         else
860         {
861           // copy to the end of the annotation row
862           copylength = alignmentAnnotation.annotations.length - block[0];
863         }
864       }
865       
866       els = new Annotation[annotationLength];
867       annels.addElement(els);
868       System.arraycopy(alignmentAnnotation.annotations, block[0], els, 0,
869               copylength);
870       w += annotationLength;
871     }
872     
873     if (w != 0)
874     {
875       alignmentAnnotation.annotations = new Annotation[w];
876
877       w = 0;
878       for (Annotation[] chnk : annels)
879       {
880         System.arraycopy(chnk, 0, alignmentAnnotation.annotations, w,
881                 chnk.length);
882         w += chnk.length;
883       }
884     }
885   }
886
887   /**
888    * 
889    * @return true if there are columns hidden
890    */
891   public boolean hasHiddenColumns()
892   {
893     try
894     {
895       LOCK.readLock().lock();
896       return hiddenColumns != null && hiddenColumns.size() > 0;
897     } finally
898     {
899       LOCK.readLock().unlock();
900     }
901   }
902
903   /**
904    * 
905    * @return true if there are more than one set of columns hidden
906    */
907   public boolean hasManyHiddenColumns()
908   {
909     try
910     {
911       LOCK.readLock().lock();
912       return hiddenColumns != null && hiddenColumns.size() > 1;
913     } finally
914     {
915       LOCK.readLock().unlock();
916     }
917   }
918
919   /**
920    * mark the columns corresponding to gap characters as hidden in the column
921    * selection
922    * 
923    * @param sr
924    */
925   public void hideInsertionsFor(SequenceI sr)
926   {
927     try
928     {
929       LOCK.writeLock().lock();
930       List<int[]> inserts = sr.getInsertions();
931       for (int[] r : inserts)
932       {
933         hideColumns(r[0], r[1]);
934       }
935     } finally
936     {
937       LOCK.writeLock().unlock();
938     }
939   }
940
941   /**
942    * Unhides, and adds to the selection list, all hidden columns
943    */
944   public void revealAllHiddenColumns(ColumnSelection sel)
945   {
946     try
947     {
948       LOCK.writeLock().lock();
949       if (hiddenColumns != null)
950       {
951         for (int i = 0; i < hiddenColumns.size(); i++)
952         {
953           int[] region = hiddenColumns.get(i);
954           for (int j = region[0]; j < region[1] + 1; j++)
955           {
956             sel.addElement(j);
957           }
958         }
959       }
960
961       hiddenColumns = null;
962     } finally
963     {
964       LOCK.writeLock().unlock();
965     }
966   }
967
968   /**
969    * Reveals, and marks as selected, the hidden column range with the given
970    * start column
971    * 
972    * @param start
973    */
974   public void revealHiddenColumns(int start, ColumnSelection sel)
975   {
976     try
977     {
978       LOCK.writeLock().lock();
979       for (int i = 0; i < hiddenColumns.size(); i++)
980       {
981         int[] region = hiddenColumns.get(i);
982         if (start == region[0])
983         {
984           for (int j = region[0]; j < region[1] + 1; j++)
985           {
986             sel.addElement(j);
987           }
988
989           hiddenColumns.remove(region);
990           break;
991         }
992       }
993       if (hiddenColumns.size() == 0)
994       {
995         hiddenColumns = null;
996       }
997     } finally
998     {
999       LOCK.writeLock().unlock();
1000     }
1001   }
1002
1003   /**
1004    * Add gaps into the sequences aligned to profileseq under the given
1005    * AlignmentView
1006    * 
1007    * @param profileseq
1008    * @param al
1009    *          - alignment to have gaps inserted into it
1010    * @param input
1011    *          - alignment view where sequence corresponding to profileseq is
1012    *          first entry
1013    * @return new HiddenColumns for new alignment view, with insertions into
1014    *         profileseq marked as hidden.
1015    */
1016   public static HiddenColumns propagateInsertions(SequenceI profileseq,
1017           AlignmentI al, AlignmentView input)
1018   {
1019     int profsqpos = 0;
1020
1021     char gc = al.getGapCharacter();
1022     Object[] alandhidden = input.getAlignmentAndHiddenColumns(gc);
1023     HiddenColumns nview = (HiddenColumns) alandhidden[1];
1024     SequenceI origseq = ((SequenceI[]) alandhidden[0])[profsqpos];
1025     nview.propagateInsertions(profileseq, al, origseq);
1026     return nview;
1027   }
1028
1029   /**
1030    * 
1031    * @param profileseq
1032    *          - sequence in al which corresponds to origseq
1033    * @param al
1034    *          - alignment which is to have gaps inserted into it
1035    * @param origseq
1036    *          - sequence corresponding to profileseq which defines gap map for
1037    *          modifying al
1038    */
1039   private void propagateInsertions(SequenceI profileseq, AlignmentI al,
1040           SequenceI origseq)
1041   {
1042     try
1043     {
1044       LOCK.writeLock().lock();
1045
1046       char gc = al.getGapCharacter();
1047
1048       // take the set of hidden columns, and the set of gaps in origseq,
1049       // and remove all the hidden gaps from hiddenColumns
1050
1051       // first get the gaps as a Bitset
1052       BitSet gaps = origseq.gapBitset();
1053
1054       // now calculate hidden ^ not(gap)
1055       BitSet hidden = new BitSet();
1056       markHiddenRegions(hidden);
1057       hidden.andNot(gaps);
1058       hiddenColumns = null;
1059       this.hideMarkedBits(hidden);
1060
1061       // for each sequence in the alignment, except the profile sequence,
1062       // insert gaps corresponding to each hidden region
1063       // but where each hidden column region is shifted backwards by the number
1064       // of
1065       // preceding visible gaps
1066       // update hidden columns at the same time
1067       Iterator<int[]> regions = iterator();
1068       ArrayList<int[]> newhidden = new ArrayList<>();
1069
1070       int numGapsBefore = 0;
1071       int gapPosition = 0;
1072       while (regions.hasNext())
1073       {
1074         // get region coordinates accounting for gaps
1075         // we can rely on gaps not being *in* hidden regions because we already
1076         // removed those
1077         int[] region = regions.next();
1078         while (gapPosition < region[0])
1079         {
1080           gapPosition++;
1081           if (gaps.get(gapPosition))
1082           {
1083             numGapsBefore++;
1084           }
1085         }
1086
1087         int left = region[0] - numGapsBefore;
1088         int right = region[1] - numGapsBefore;
1089         newhidden.add(new int[] { left, right });
1090
1091         // make a string with number of gaps = length of hidden region
1092         StringBuffer sb = new StringBuffer();
1093         for (int s = 0; s < right - left + 1; s++)
1094         {
1095           sb.append(gc);
1096         }
1097         padGaps(sb, left, profileseq, al);
1098
1099       }
1100       hiddenColumns = newhidden;
1101     } finally
1102     {
1103       LOCK.writeLock().unlock();
1104     }
1105   }
1106
1107   /**
1108    * Pad gaps in all sequences in alignment except profileseq
1109    * 
1110    * @param sb
1111    *          gap string to insert
1112    * @param left
1113    *          position to insert at
1114    * @param profileseq
1115    *          sequence not to pad
1116    * @param al
1117    *          alignment to pad sequences in
1118    */
1119   private void padGaps(StringBuffer sb, int pos, SequenceI profileseq,
1120           AlignmentI al)
1121   {
1122     // loop over the sequences and pad with gaps where required
1123     for (int s = 0, ns = al.getHeight(); s < ns; s++)
1124     {
1125       SequenceI sqobj = al.getSequenceAt(s);
1126       if (sqobj != profileseq)
1127       {
1128         String sq = al.getSequenceAt(s).getSequenceAsString();
1129         if (sq.length() <= pos)
1130         {
1131           // pad sequence
1132           int diff = pos - sq.length() - 1;
1133           if (diff > 0)
1134           {
1135             // pad gaps
1136             sq = sq + sb;
1137             while ((diff = pos - sq.length() - 1) > 0)
1138             {
1139               if (diff >= sb.length())
1140               {
1141                 sq += sb.toString();
1142               }
1143               else
1144               {
1145                 char[] buf = new char[diff];
1146                 sb.getChars(0, diff, buf, 0);
1147                 sq += buf.toString();
1148               }
1149             }
1150           }
1151           sq += sb.toString();
1152         }
1153         else
1154         {
1155           al.getSequenceAt(s).setSequence(
1156                   sq.substring(0, pos) + sb.toString() + sq.substring(pos));
1157         }
1158       }
1159     }
1160   }
1161
1162   /**
1163    * Returns a hashCode built from hidden column ranges
1164    */
1165   @Override
1166   public int hashCode()
1167   {
1168     try
1169     {
1170       LOCK.readLock().lock();
1171       int hashCode = 1;
1172       if (hiddenColumns != null)
1173       {
1174         for (int[] hidden : hiddenColumns)
1175         {
1176           hashCode = HASH_MULTIPLIER * hashCode + hidden[0];
1177           hashCode = HASH_MULTIPLIER * hashCode + hidden[1];
1178         }
1179       }
1180       return hashCode;
1181     } finally
1182     {
1183       LOCK.readLock().unlock();
1184     }
1185   }
1186
1187   /**
1188    * Hide columns corresponding to the marked bits
1189    * 
1190    * @param inserts
1191    *          - columns map to bits starting from zero
1192    */
1193   public void hideMarkedBits(BitSet inserts)
1194   {
1195     try
1196     {
1197       LOCK.writeLock().lock();
1198       for (int firstSet = inserts
1199               .nextSetBit(0), lastSet = 0; firstSet >= 0; firstSet = inserts
1200                       .nextSetBit(lastSet))
1201       {
1202         lastSet = inserts.nextClearBit(firstSet);
1203         hideColumns(firstSet, lastSet - 1);
1204       }
1205     } finally
1206     {
1207       LOCK.writeLock().unlock();
1208     }
1209   }
1210
1211   /**
1212    * 
1213    * @param inserts
1214    *          BitSet where hidden columns will be marked
1215    */
1216   public void markHiddenRegions(BitSet inserts)
1217   {
1218     try
1219     {
1220       LOCK.readLock().lock();
1221       if (hiddenColumns == null)
1222       {
1223         return;
1224       }
1225       for (int[] range : hiddenColumns)
1226       {
1227         inserts.set(range[0], range[1] + 1);
1228       }
1229     } finally
1230     {
1231       LOCK.readLock().unlock();
1232     }
1233   }
1234
1235   /**
1236    * Calculate the visible start and end index of an alignment.
1237    * 
1238    * @param width
1239    *          full alignment width
1240    * @return integer array where: int[0] = startIndex, and int[1] = endIndex
1241    */
1242   public int[] getVisibleStartAndEndIndex(int width)
1243   {
1244     try
1245     {
1246       LOCK.readLock().lock();
1247       int[] alignmentStartEnd = new int[] { 0, width - 1 };
1248       int startPos = alignmentStartEnd[0];
1249       int endPos = alignmentStartEnd[1];
1250
1251       int[] lowestRange = new int[] { -1, -1 };
1252       int[] higestRange = new int[] { -1, -1 };
1253
1254       if (hiddenColumns == null)
1255       {
1256         return new int[] { startPos, endPos };
1257       }
1258
1259       for (int[] hiddenCol : hiddenColumns)
1260       {
1261         lowestRange = (hiddenCol[0] <= startPos) ? hiddenCol : lowestRange;
1262         higestRange = (hiddenCol[1] >= endPos) ? hiddenCol : higestRange;
1263       }
1264
1265       if (lowestRange[0] == -1 && lowestRange[1] == -1)
1266       {
1267         startPos = alignmentStartEnd[0];
1268       }
1269       else
1270       {
1271         startPos = lowestRange[1] + 1;
1272       }
1273
1274       if (higestRange[0] == -1 && higestRange[1] == -1)
1275       {
1276         endPos = alignmentStartEnd[1];
1277       }
1278       else
1279       {
1280         endPos = higestRange[0] - 1;
1281       }
1282       return new int[] { startPos, endPos };
1283     } finally
1284     {
1285       LOCK.readLock().unlock();
1286     }
1287
1288   }
1289
1290   /**
1291    * Finds the hidden region (if any) which starts or ends at res
1292    * 
1293    * @param res
1294    *          visible residue position, unadjusted for hidden columns
1295    * @return region as [start,end] or null if no matching region is found
1296    */
1297   public int[] getRegionWithEdgeAtRes(int res)
1298   {
1299     try
1300     {
1301       LOCK.readLock().lock();
1302       int adjres = adjustForHiddenColumns(res);
1303
1304       int[] reveal = null;
1305       if (hiddenColumns != null)
1306       {
1307         for (int[] region : hiddenColumns)
1308         {
1309           if (adjres + 1 == region[0] || adjres - 1 == region[1])
1310           {
1311             reveal = region;
1312             break;
1313           }
1314         }
1315       }
1316       return reveal;
1317     } finally
1318     {
1319       LOCK.readLock().unlock();
1320     }
1321   }
1322
1323   /**
1324    * Return an iterator over the hidden regions
1325    */
1326   public Iterator<int[]> iterator()
1327   {
1328     if (hiddenColumns != null)
1329     {
1330       int last = hiddenColumns.get(hiddenColumns.size() - 1)[1];
1331       return new BoundedHiddenColsIterator(0, last, true);
1332     }
1333     else
1334     {
1335       return new BoundedHiddenColsIterator(0, 0, true);
1336     }
1337   }
1338
1339   /**
1340    * Return a bounded iterator over the hidden regions
1341    * 
1342    * @param start
1343    *          position to start from (inclusive, absolute column position)
1344    * @param end
1345    *          position to end at (inclusive, absolute column position)
1346    * @return
1347    */
1348   public Iterator<int[]> getBoundedIterator(int start, int end)
1349   {
1350     return new BoundedHiddenColsIterator(start, end, true);
1351   }
1352
1353   /**
1354    * Return a bounded iterator over the *visible* start positions of hidden
1355    * regions
1356    * 
1357    * @param start
1358    *          position to start from (inclusive, visible column position)
1359    * @param end
1360    *          position to end at (inclusive, visible column position)
1361    */
1362   public Iterator<Integer> getBoundedStartIterator(int start, int end)
1363   {
1364     return new BoundedStartRegionIterator(start, end, true);
1365   }
1366
1367   /**
1368    * Return an iterator over visible columns between the given start and end
1369    * boundaries
1370    * 
1371    * @param start
1372    *          first column (inclusive)
1373    * @param end
1374    *          last column (inclusive)
1375    */
1376   public Iterator<Integer> getVisibleColsIterator(int start, int end)
1377   {
1378     return new VisibleColsIterator(start, end, true);
1379   }
1380
1381   /**
1382    * return an iterator over visible segments between the given start and end
1383    * boundaries
1384    * 
1385    * @param start
1386    *          (first column inclusive from 0)
1387    * @param end
1388    *          (last column - not inclusive)
1389    */
1390   public Iterator<int[]> getVisContigsIterator(int start, int end)
1391   {
1392     return new VisibleContigsIterator(start, end, true);
1393   }
1394
1395   /**
1396    * An iterator which iterates over hidden column regions in a range.
1397    */
1398   private class BoundedHiddenColsIterator implements Iterator<int[]>
1399   {
1400     private int start; // start position to iterate from
1401
1402     private int end; // end position to iterate to
1403
1404     // current index in hiddenColumns
1405     private int currentPosition = 0;
1406
1407     // current column in hiddenColumns
1408     private int[] currentRegion;
1409
1410     // whether to make a local copy of hiddenColumns
1411     private final boolean useCopy;
1412
1413     // local copy or reference to hiddenColumns
1414     private List<int[]> localHidden;
1415
1416     /**
1417      * Construct an iterator over hiddenColums bounded at
1418      * [lowerBound,upperBound]
1419      * 
1420      * @param lowerBound
1421      *          lower bound to iterate from
1422      * @param upperBound
1423      *          upper bound to iterate to
1424      * @param useCopyCols
1425      *          whether to make a local copy of hiddenColumns for iteration (set
1426      *          to true if calling from outwith the HiddenColumns class)
1427      */
1428     BoundedHiddenColsIterator(int lowerBound, int upperBound,
1429             boolean useCopyCols)
1430     {
1431       start = lowerBound;
1432       end = upperBound;
1433       useCopy = useCopyCols;
1434
1435       try
1436       {
1437         if (useCopy)
1438         {
1439           // assume that if useCopy is false the calling code has locked
1440           // hiddenColumns
1441           LOCK.readLock().lock();
1442         }
1443         
1444         if (hiddenColumns != null)
1445         {
1446           localHidden = new ArrayList<>();
1447
1448           // iterate until a region overlaps with [start,end]
1449           int i = 0;
1450           while ((i < hiddenColumns.size())
1451                   && (hiddenColumns.get(i)[1] < start))
1452           {
1453             i++;
1454           }
1455
1456           // iterate from start to end, adding each hidden region. Positions are
1457           // absolute, and all regions which *overlap* [start,end] are added.
1458           while (i < hiddenColumns.size()
1459                   && (hiddenColumns.get(i)[0] <= end))
1460           {
1461             int[] rh;
1462             int[] cp;
1463             rh = hiddenColumns.get(i);
1464             if (rh != null)
1465             {
1466               cp = new int[rh.length];
1467               System.arraycopy(rh, 0, cp, 0, rh.length);
1468               localHidden.add(cp);
1469             }
1470             i++;
1471           }
1472         }
1473       }
1474       finally
1475       {
1476         if (useCopy)
1477         {
1478           LOCK.readLock().unlock();
1479         }
1480       }
1481     }
1482
1483     @Override
1484     public boolean hasNext()
1485     {
1486       return (localHidden != null)
1487               && (currentPosition < localHidden.size());
1488     }
1489
1490     @Override
1491     public int[] next()
1492     {
1493       currentRegion = localHidden.get(currentPosition);
1494       currentPosition++;
1495       return currentRegion;
1496     }
1497   }
1498
1499   /**
1500    * An iterator which iterates over visible start positions of hidden column
1501    * regions in a range.
1502    */
1503   private class BoundedStartRegionIterator implements Iterator<Integer>
1504   {
1505     // start position to iterate from
1506     private int start;
1507
1508     // end position to iterate to
1509     private int end;
1510
1511     // current index in hiddenColumns
1512     private int currentPosition = 0;
1513
1514     // local copy or reference to hiddenColumns
1515     private List<Integer> positions = null;
1516
1517     /**
1518      * Construct an iterator over hiddenColums bounded at
1519      * [lowerBound,upperBound]
1520      * 
1521      * @param lowerBound
1522      *          lower bound to iterate from
1523      * @param upperBound
1524      *          upper bound to iterate to
1525      * @param useCopyCols
1526      *          whether to make a local copy of hiddenColumns for iteration (set
1527      *          to true if calling from outwith the HiddenColumns class)
1528      */
1529     BoundedStartRegionIterator(int lowerBound, int upperBound,
1530             boolean useCopy)
1531     {
1532       start = lowerBound;
1533       end = upperBound;
1534       
1535       try
1536       {
1537         if (useCopy)
1538         {
1539           // assume that if useCopy is false the calling code has locked
1540           // hiddenColumns
1541           LOCK.readLock().lock();
1542         }
1543
1544         if (hiddenColumns != null)
1545         {
1546           positions = new ArrayList<>(hiddenColumns.size());
1547
1548           // navigate to start, keeping count of hidden columns
1549           int i = 0;
1550           int hiddenSoFar = 0;
1551           while ((i < hiddenColumns.size())
1552                   && (hiddenColumns.get(i)[0] < start + hiddenSoFar))
1553           {
1554             int[] region = hiddenColumns.get(i);
1555             hiddenSoFar += region[1] - region[0] + 1;
1556             i++;
1557           }
1558
1559           // iterate from start to end, adding start positions of each
1560           // hidden region. Positions are visible columns count, not absolute
1561           while (i < hiddenColumns.size()
1562                   && (hiddenColumns.get(i)[0] <= end + hiddenSoFar))
1563           {
1564             int[] region = hiddenColumns.get(i);
1565             positions.add(region[0] - hiddenSoFar);
1566             hiddenSoFar += region[1] - region[0] + 1;
1567             i++;
1568           }
1569         }
1570         else
1571         {
1572           positions = new ArrayList<>();
1573         }
1574       } finally
1575       {
1576         if (useCopy)
1577         {
1578           LOCK.readLock().unlock();
1579         }
1580       }
1581     }
1582
1583     @Override
1584     public boolean hasNext()
1585     {
1586       return (currentPosition < positions.size());
1587     }
1588
1589     /**
1590      * Get next hidden region start position
1591      * 
1592      * @return the start position in *visible* coordinates
1593      */
1594     @Override
1595     public Integer next()
1596     {
1597       int result = positions.get(currentPosition);
1598       currentPosition++;
1599       return result;
1600     }
1601   }
1602
1603   private class VisibleColsIterator implements Iterator<Integer>
1604   {
1605     private int last;
1606
1607     private int current;
1608
1609     private int next;
1610
1611     private List<int[]> localHidden = new ArrayList<>();
1612
1613     private int lasthiddenregion;
1614
1615     VisibleColsIterator(int firstcol, int lastcol, boolean useCopy)
1616     {
1617       last = lastcol;
1618       current = firstcol;
1619       next = firstcol;
1620       lasthiddenregion = -1;
1621
1622       try
1623       {
1624         if (useCopy)
1625         {
1626           // assume that if useCopy is false the calling code has locked
1627           // hiddenColumns
1628           LOCK.readLock().lock();
1629         }
1630
1631         if (hiddenColumns != null)
1632         {
1633           int i = 0;
1634           for (i = 0; i < hiddenColumns.size()
1635                   && (current < hiddenColumns.get(i)[0]); ++i)
1636           {
1637             if (current >= hiddenColumns.get(i)[0]
1638                     && current <= hiddenColumns.get(i)[1])
1639             {
1640               // current is hidden, move to right
1641               current = hiddenColumns.get(i)[1] + 1;
1642               next = current;
1643             }
1644           }
1645
1646           lasthiddenregion = i - 1;
1647
1648           for (i = hiddenColumns.size() - 1; i >= 0
1649                   && (last > hiddenColumns.get(i)[1]); --i)
1650           {
1651             if (last >= hiddenColumns.get(i)[0]
1652                     && last <= hiddenColumns.get(i)[1])
1653             {
1654               // last is hidden, move to left
1655               last = hiddenColumns.get(i)[0] - 1;
1656             }
1657           }
1658
1659           // make a local copy of the bit we need
1660           i = lasthiddenregion + 1;
1661           while (i < hiddenColumns.size()
1662                   && hiddenColumns.get(i)[0] <= last)
1663           {
1664             int[] region = new int[] { hiddenColumns.get(i)[0],
1665                 hiddenColumns.get(i)[1] };
1666             localHidden.add(region);
1667             i++;
1668           }
1669           lasthiddenregion = -1;
1670         }
1671       } finally
1672       {
1673         if (useCopy)
1674         {
1675           LOCK.readLock().unlock();
1676         }
1677       }
1678     }
1679
1680     @Override
1681     public boolean hasNext()
1682     {
1683       return next <= last;
1684     }
1685
1686     @Override
1687     public Integer next()
1688     {
1689       if (next > last)
1690       {
1691         throw new NoSuchElementException();
1692       }
1693       current = next;
1694       if ((localHidden != null)
1695               && (lasthiddenregion + 1 < localHidden.size()))
1696       {
1697         // still some more hidden regions
1698         if (next + 1 < localHidden.get(lasthiddenregion + 1)[0])
1699         {
1700           // next+1 is still before the next hidden region
1701           next++;
1702         }
1703         else if ((next + 1 >= localHidden.get(lasthiddenregion + 1)[0])
1704                 && (next + 1 <= localHidden.get(lasthiddenregion + 1)[1]))
1705         {
1706           // next + 1 is in the next hidden region
1707           next = localHidden.get(lasthiddenregion + 1)[1] + 1;
1708           lasthiddenregion++;
1709         }
1710       }
1711       else
1712       {
1713         // finished with hidden regions, just increment normally
1714         next++;
1715       }
1716       return current;
1717     }
1718
1719     @Override
1720     public void remove()
1721     {
1722       throw new UnsupportedOperationException();
1723     }
1724   }
1725
1726   /**
1727    * An iterator which iterates over visible regions in a range.
1728    */
1729   private class VisibleContigsIterator implements Iterator<int[]>
1730   {
1731     private List<int[]> vcontigs = new ArrayList<>();
1732
1733     private int currentPosition = 0;
1734
1735     VisibleContigsIterator(int start, int end, boolean usecopy)
1736     {
1737       try
1738       {
1739         if (usecopy)
1740         {
1741           LOCK.readLock().lock();
1742         }
1743
1744         if (hiddenColumns != null && hiddenColumns.size() > 0)
1745         {
1746           int vstart = start;
1747           int hideStart;
1748           int hideEnd;
1749
1750           for (int[] region : hiddenColumns)
1751           {
1752             hideStart = region[0];
1753             hideEnd = region[1];
1754
1755             // navigate to start
1756             if (hideEnd < vstart)
1757             {
1758               continue;
1759             }
1760             if (hideStart > vstart)
1761             {
1762               int[] contig = new int[] { vstart, hideStart - 1 };
1763               vcontigs.add(contig);
1764             }
1765             vstart = hideEnd + 1;
1766
1767             // exit if we're past the end
1768             if (vstart >= end)
1769             {
1770               break;
1771             }
1772           }
1773
1774           if (vstart < end)
1775           {
1776             int[] contig = new int[] { vstart, end - 1 };
1777             vcontigs.add(contig);
1778           }
1779         }
1780         else
1781         {
1782           int[] contig = new int[] { start, end - 1 };
1783           vcontigs.add(contig);
1784         }
1785       } finally
1786       {
1787         if (usecopy)
1788         {
1789           LOCK.readLock().unlock();
1790         }
1791       }
1792     }
1793
1794     @Override
1795     public boolean hasNext()
1796     {
1797       return (currentPosition < vcontigs.size());
1798     }
1799
1800     @Override
1801     public int[] next()
1802     {
1803       int[] result = vcontigs.get(currentPosition);
1804       currentPosition++;
1805       return result;
1806     }
1807   }
1808
1809   /**
1810    * An iterator which iterates over visible regions in a range.
1811    */
1812   private class VisibleBlocksIterator implements Iterator<int[]>
1813   {
1814     private List<int[]> vcontigs = new ArrayList<>();
1815
1816     private int currentPosition = 0;
1817
1818     VisibleBlocksIterator(int start, int end, boolean usecopy)
1819     {
1820       try
1821       {
1822         if (usecopy)
1823         {
1824           LOCK.readLock().lock();
1825         }
1826
1827         if (hiddenColumns != null && hiddenColumns.size() > 0)
1828         {
1829           int blockStart = start;
1830           int blockEnd = end;
1831
1832           // iterate until a region overlaps with [start,end]
1833           int i = 0;
1834           while ((i < hiddenColumns.size())
1835                   && (hiddenColumns.get(i)[1] < start))
1836           {
1837             i++;
1838           }
1839
1840           // iterate from start to end, adding each hidden region. Positions are
1841           // absolute, and all regions which *overlap* [start,end] are used.
1842           while (i < hiddenColumns.size()
1843                   && (hiddenColumns.get(i)[0] <= end))
1844           {
1845             int[] region = hiddenColumns.get(i);
1846
1847             blockStart = Math.min(blockStart, region[1] + 1);
1848             blockEnd = Math.min(blockEnd, region[0]);
1849
1850             int[] contig = new int[] { blockStart, blockEnd };
1851             vcontigs.add(contig);
1852
1853             blockStart = region[1] + 1;
1854             blockEnd = end;
1855
1856             i++;
1857           }
1858
1859           if (end > blockStart)
1860           {
1861             int[] contig = new int[] { blockStart, end };
1862             vcontigs.add(contig);
1863           }
1864         }
1865         else
1866         {
1867           int[] contig = new int[] { start, end };
1868           vcontigs.add(contig);
1869         }
1870       } finally
1871       {
1872         if (usecopy)
1873         {
1874           LOCK.readLock().unlock();
1875         }
1876       }
1877     }
1878
1879     @Override
1880     public boolean hasNext()
1881     {
1882       return (currentPosition < vcontigs.size());
1883     }
1884
1885     @Override
1886     public int[] next()
1887     {
1888       int[] result = vcontigs.get(currentPosition);
1889       currentPosition++;
1890       return result;
1891     }
1892   }
1893
1894 }