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