8a67741dc2e36d3db8b4ca1e5524d5776530bbbf
[jalview.git] / src / jalview / datamodel / HiddenColumns.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.datamodel;
22
23 import java.util.ArrayList;
24 import java.util.BitSet;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.concurrent.locks.ReentrantReadWriteLock;
28
29 public class HiddenColumns
30 {
31   private static final int HASH_MULTIPLIER = 31;
32
33   private static final ReentrantReadWriteLock LOCK = new ReentrantReadWriteLock();
34
35   private HiddenColumnsCursor cursor = new HiddenColumnsCursor();
36
37   /*
38    * list of hidden column [start, end] ranges; the list is maintained in
39    * ascending start column order
40    */
41   private ArrayList<int[]> hiddenColumns;
42
43   /**
44    * Constructor
45    */
46   public HiddenColumns()
47   {
48   }
49
50   /**
51    * Copy constructor
52    * 
53    * @param copy
54    */
55   public HiddenColumns(HiddenColumns copy)
56   {
57     try
58     {
59       LOCK.writeLock().lock();
60       if (copy != null)
61       {
62         if (copy.hiddenColumns != null)
63         {
64           hiddenColumns = new ArrayList<>();
65           Iterator<int[]> it = copy.iterator();
66           while (it.hasNext())
67           {
68             hiddenColumns.add(it.next());
69           }
70           cursor.resetCursor(hiddenColumns);
71         }
72       }
73     } finally
74     {
75       LOCK.writeLock().unlock();
76     }
77   }
78
79   /**
80    * Copy constructor within bounds and with offset. Copies hidden column
81    * regions fully contained between start and end, and offsets positions by
82    * subtracting offset.
83    * 
84    * @param copy
85    *          HiddenColumns instance to copy from
86    * @param start
87    *          lower bound to copy from
88    * @param end
89    *          upper bound to copy to
90    * @param offset
91    *          offset to subtract from each region boundary position
92    * 
93    */
94   public HiddenColumns(HiddenColumns copy, int start, int end, int offset)
95   {
96     try
97     {
98       LOCK.writeLock().lock();
99       if (copy != null)
100       {
101         hiddenColumns = new ArrayList<>();
102         Iterator<int[]> it = copy.getBoundedIterator(start, end);
103         while (it.hasNext())
104         {
105           int[] region = it.next();
106           // still need to check boundaries because iterator returns
107           // all overlapping regions and we need contained regions
108           if (region[0] >= start && region[1] <= end)
109           {
110             hiddenColumns.add(
111                     new int[]
112             { region[0] - offset, region[1] - offset });
113           }
114         }
115         cursor.resetCursor(hiddenColumns);
116       }
117     } finally
118     {
119       LOCK.writeLock().unlock();
120     }
121   }
122
123   /**
124    * Output regions data as a string. String is in the format:
125    * reg0[0]<between>reg0[1]<delimiter>reg1[0]<between>reg1[1] ... regn[1]
126    * 
127    * @param delimiter
128    *          string to delimit regions
129    * @param betweenstring
130    *          to put between start and end region values
131    * @return regions formatted according to delimiter and between strings
132    */
133   public String regionsToString(String delimiter, String between)
134   {
135     try
136     {
137       LOCK.readLock().lock();
138       StringBuilder regionBuilder = new StringBuilder();
139       if (hiddenColumns != null)
140       {
141         Iterator<int[]> it = hiddenColumns.iterator();
142         while (it.hasNext())
143         {
144           int[] range = it.next();
145           regionBuilder.append(delimiter).append(range[0]).append(between)
146                   .append(range[1]);
147           if (!it.hasNext())
148           {
149             regionBuilder.deleteCharAt(0);
150           }
151         }
152       }
153       return regionBuilder.toString();
154     } finally
155     {
156       LOCK.readLock().unlock();
157     }
158   }
159
160   /**
161    * Find the number of hidden columns
162    * 
163    * @return number of hidden columns
164    */
165   public int getSize()
166   {
167     try
168     {
169       LOCK.readLock().lock();
170       int size = 0;
171       if (hiddenColumns != null)
172       {
173         Iterator<int[]> it = hiddenColumns.iterator();
174         while (it.hasNext())
175         {
176           int[] range = it.next();
177           size += range[1] - range[0] + 1;
178         }
179       }
180       return size;
181     } finally
182     {
183       LOCK.readLock().unlock();
184     }
185   }
186
187   /**
188    * Get the number of distinct hidden regions
189    * 
190    * @return number of regions
191    */
192   public int getNumberOfRegions()
193   {
194     try
195     {
196       LOCK.readLock().lock();
197       int num = 0;
198       if (hasHiddenColumns())
199       {
200         num = hiddenColumns.size();
201       }
202       return num;
203     } finally
204     {
205       LOCK.readLock().unlock();
206     }
207   }
208
209   @Override
210   public boolean equals(Object obj)
211   {
212     try
213     {
214       LOCK.readLock().lock();
215
216       if (!(obj instanceof HiddenColumns))
217       {
218         return false;
219       }
220       HiddenColumns that = (HiddenColumns) obj;
221
222       /*
223        * check hidden columns are either both null, or match
224        */
225       if (this.hiddenColumns == null)
226       {
227         return (that.hiddenColumns == null);
228       }
229       if (that.hiddenColumns == null
230               || that.hiddenColumns.size() != this.hiddenColumns.size())
231       {
232         return false;
233       }
234
235       Iterator<int[]> it = hiddenColumns.iterator();
236       Iterator<int[]> thatit = that.iterator();
237       while (it.hasNext())
238       {
239         int[] thisRange = it.next();
240         int[] thatRange = thatit.next();
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
267       if (hiddenColumns != null)
268       {
269         result += cursor.getHiddenOffset(column);
270       }
271
272       return result;
273     } finally
274     {
275       LOCK.readLock().unlock();
276     }
277   }
278
279   /**
280    * Use this method to find out where a column will appear in the visible
281    * alignment when hidden columns exist. If the column is not visible, then the
282    * left-most visible column will always be returned.
283    * 
284    * @param hiddenColumn
285    *          the column index in the full alignment including hidden columns
286    * @return the position of the column in the visible alignment
287    */
288   public int findColumnPosition(int hiddenColumn)
289   {
290     try
291     {
292       /*    LOCK.readLock().lock();
293       int result = hiddenColumn;
294       int[] region = null;
295       if (hiddenColumns != null)
296       {
297         Iterator<int[]> it = new RegionsIterator(0, hiddenColumn,
298                 hiddenColumns, cursor);
299         while (it.hasNext())
300         {
301           region = it.next();
302           if (hiddenColumn > region[1])
303           {
304             result -= region[1] + 1 - region[0];
305           }
306         }
307       
308         if (region != null && hiddenColumn >= region[0]
309                 && hiddenColumn <= region[1])
310         {
311           // Here the hidden column is within a region, so
312           // we want to return the position of region[0]-1, adjusted for any
313           // earlier hidden columns.
314           // Calculate the difference between the actual hidden col position
315           // and region[0]-1, and then subtract from result to convert result
316           // from the adjusted hiddenColumn value to the adjusted region[0]-1
317           // value.
318       
319           // However, if the region begins at 0 we cannot return region[0]-1
320           // just return 0
321           if (region[0] == 0)
322           {
323             return 0;
324           }
325           else
326           {
327             return result - (hiddenColumn - region[0] + 1);
328           }
329         }
330       }
331       return result; // return the shifted position after removing hidden
332                      // columns.
333         } finally
334         {
335       LOCK.readLock().unlock();
336         }*/
337
338       LOCK.readLock().lock();
339       int result = hiddenColumn;
340       // int[] region = null;
341       if (hiddenColumns != null)
342       {
343         int index = cursor.findRegionForColumn(hiddenColumn);
344         int hiddenBeforeCol = cursor.getHiddenSoFar();
345     
346         // just subtract hidden cols count - this works fine if column is
347         // visible
348         result = hiddenColumn - hiddenBeforeCol;
349     
350         // now check in case column is hidden - it will be in the returned
351         // hidden region
352         if (index < hiddenColumns.size())
353         {
354           int[] region = hiddenColumns.get(index);
355           if (hiddenColumn >= region[0] && hiddenColumn <= region[1])
356           {
357             // actually col is hidden, return region[0]-1
358             // unless region[0]==0 in which case return 0
359             if (region[0] == 0)
360             {
361               result = 0;
362             }
363             else
364             {
365               result = region[0] - 1 - hiddenBeforeCol;
366             }
367           }
368         }
369       }
370       System.out.println(hiddenColumn + " " + result);
371       return result; // return the shifted position after removing hidden
372                      // columns.
373     } finally
374     {
375       LOCK.readLock().unlock();
376     }
377   }
378
379   /**
380    * Find the visible column which is a given visible number of columns to the
381    * left of another visible column. i.e. for a startColumn x, the column which
382    * is distance 1 away will be column x-1.
383    * 
384    * @param visibleDistance
385    *          the number of visible columns to offset by
386    * @param startColumn
387    *          the column to start from
388    * @return the position of the column in the visible alignment
389    */
390   public int subtractVisibleColumns(int visibleDistance, int startColumn)
391   {
392     try
393     {
394       LOCK.readLock().lock();
395       int distance = visibleDistance;
396
397       // in case startColumn is in a hidden region, move it to the left
398       int start = adjustForHiddenColumns(findColumnPosition(startColumn));
399
400       Iterator<int[]> it = new ReverseRegionsIterator(0, start,
401               hiddenColumns);
402
403       while (it.hasNext() && (distance > 0))
404       {
405         int[] region = it.next();
406
407         if (start > region[1])
408         {
409           // subtract the gap to right of region from distance
410           if (start - region[1] <= distance)
411           {
412             distance -= start - region[1];
413             start = region[0] - 1;
414           }
415           else
416           {
417             start = start - distance;
418             distance = 0;
419           }
420         }
421       }
422
423       return start - distance;
424
425     } finally
426     {
427       LOCK.readLock().unlock();
428     }
429   }
430
431   /**
432    * This method returns the rightmost limit of a region of an alignment with
433    * hidden columns. In otherwords, the next hidden column.
434    * 
435    * @param alPos
436    *          the absolute (visible) alignmentPosition to find the next hidden
437    *          column for
438    */
439   public int getHiddenBoundaryRight(int alPos)
440   {
441     try
442     {
443       LOCK.readLock().lock();
444       if (hiddenColumns != null)
445       {
446         int index = cursor.findRegionForColumn(alPos);
447         if (index < hiddenColumns.size())
448         {
449           int[] region = hiddenColumns.get(index);
450           if (alPos < region[0])
451           {
452             return region[0];
453           }
454           else if ((alPos <= region[1])
455                   && (index + 1 < hiddenColumns.size()))
456           {
457             // alPos is within a hidden region, return the next one
458             // if there is one
459             region = hiddenColumns.get(index + 1);
460             return region[0];
461           }
462         }
463       }
464       return alPos;
465     } finally
466     {
467       LOCK.readLock().unlock();
468     }
469   }
470
471   /**
472    * This method returns the leftmost limit of a region of an alignment with
473    * hidden columns. In otherwords, the previous hidden column.
474    * 
475    * @param alPos
476    *          the absolute (visible) alignmentPosition to find the previous
477    *          hidden column for
478    */
479   public int getHiddenBoundaryLeft(int alPos)
480   {
481     try
482     {
483       LOCK.readLock().lock();
484
485       if (hiddenColumns != null)
486       {
487         int index = cursor.findRegionForColumn(alPos);
488
489         if (index > 0)
490         {
491           int[] region = hiddenColumns.get(index - 1);
492           return region[1];
493         }
494       }
495       return alPos;
496     } finally
497     {
498       LOCK.readLock().unlock();
499     }
500   }
501
502   /**
503    * Adds the specified column range to the hidden columns collection
504    * 
505    * @param start
506    *          start of range to add (absolute position in alignment)
507    * @param end
508    *          end of range to add (absolute position in alignment)
509    */
510   public void hideColumns(int start, int end)
511   {
512     boolean wasAlreadyLocked = false;
513     try
514     {
515       // check if the write lock was already locked by this thread,
516       // as this method can be called internally in loops within HiddenColumns
517       if (!LOCK.isWriteLockedByCurrentThread())
518       {
519         LOCK.writeLock().lock();
520       }
521       else
522       {
523         wasAlreadyLocked = true;
524       }
525
526       if (hiddenColumns == null)
527       {
528         hiddenColumns = new ArrayList<>();
529       }
530
531       /*
532        * new range follows everything else; check first to avoid looping over whole hiddenColumns collection
533        */
534       if (hiddenColumns.isEmpty()
535               || start > hiddenColumns.get(hiddenColumns.size() - 1)[1])
536       {
537         hiddenColumns.add(new int[] { start, end });
538       }
539       else
540       {
541         /*
542          * traverse existing hidden ranges and insert / amend / append as
543          * appropriate
544          */
545         boolean added = false;
546         for (int i = 0; !added && i < hiddenColumns.size(); i++)
547         {
548           added = insertRangeAtRegion(i, start, end);
549         } // for
550       }
551       if (!wasAlreadyLocked)
552       {
553         cursor.resetCursor(hiddenColumns);
554       }
555     } finally
556     {
557       if (!wasAlreadyLocked)
558       {
559         LOCK.writeLock().unlock();
560       }
561     }
562   }
563
564   /**
565    * Insert [start, range] at the region at index i in hiddenColumns, if
566    * feasible
567    * 
568    * @param i
569    *          index to insert at
570    * @param start
571    *          start of range to insert
572    * @param end
573    *          end of range to insert
574    * @return true if range was successfully inserted
575    */
576   private boolean insertRangeAtRegion(int i, int start, int end)
577   {
578     boolean added = false;
579
580     int[] region = hiddenColumns.get(i);
581     if (end < region[0] - 1)
582     {
583       /*
584        * insert discontiguous preceding range
585        */
586       hiddenColumns.add(i, new int[] { start, end });
587       added = true;
588     }
589     else if (end <= region[1])
590     {
591       /*
592        * new range overlaps existing, or is contiguous preceding it - adjust
593        * start column
594        */
595       region[0] = Math.min(region[0], start);
596       added = true;
597     }
598     else if (start <= region[1] + 1)
599     {
600       /*
601        * new range overlaps existing, or is contiguous following it - adjust
602        * start and end columns
603        */
604       region[0] = Math.min(region[0], start);
605       region[1] = Math.max(region[1], end);
606
607       /*
608        * also update or remove any subsequent ranges 
609        * that are overlapped
610        */
611       while (i < hiddenColumns.size() - 1)
612       {
613         int[] nextRegion = hiddenColumns.get(i + 1);
614         if (nextRegion[0] > end + 1)
615         {
616           /*
617            * gap to next hidden range - no more to update
618            */
619           break;
620         }
621         region[1] = Math.max(nextRegion[1], end);
622         hiddenColumns.subList(i + 1, i + 2).clear();
623       }
624       added = true;
625     }
626     return added;
627   }
628
629   /**
630    * Answers if a column in the alignment is visible
631    * 
632    * @param column
633    *          absolute position of column in the alignment
634    * @return true if column is visible
635    */
636   public boolean isVisible(int column)
637   {
638     try
639     {
640       LOCK.readLock().lock();
641
642       Iterator<int[]> it = new RegionsIterator(column, column,
643               hiddenColumns, cursor);
644       while (it.hasNext())
645       {
646         int[] region = it.next();
647         if (column >= region[0] && column <= region[1])
648         {
649           return false;
650         }
651       }
652
653       return true;
654     } finally
655     {
656       LOCK.readLock().unlock();
657     }
658   }
659
660   /**
661    * Get the visible sections of a set of sequences
662    * 
663    * @param start
664    *          sequence position to start from
665    * @param end
666    *          sequence position to end at
667    * @param seqs
668    *          an array of sequences
669    * @return an array of strings encoding the visible parts of each sequence
670    */
671   public String[] getVisibleSequenceStrings(int start, int end,
672           SequenceI[] seqs)
673   {
674     try
675     {
676       LOCK.readLock().lock();
677       int iSize = seqs.length;
678       String[] selections = new String[iSize];
679       if (hiddenColumns != null && hiddenColumns.size() > 0)
680       {
681         for (int i = 0; i < iSize; i++)
682         {
683           StringBuffer visibleSeq = new StringBuffer();
684
685           Iterator<int[]> blocks = new VisibleContigsIterator(start,
686                   end + 1, hiddenColumns);
687
688           while (blocks.hasNext())
689           {
690             int[] block = blocks.next();
691             if (blocks.hasNext())
692             {
693               visibleSeq
694                       .append(seqs[i].getSequence(block[0], block[1] + 1));
695             }
696             else
697             {
698               visibleSeq
699                       .append(seqs[i].getSequence(block[0], block[1]));
700             }
701           }
702
703           selections[i] = visibleSeq.toString();
704         }
705       }
706       else
707       {
708         for (int i = 0; i < iSize; i++)
709         {
710           selections[i] = seqs[i].getSequenceAsString(start, end);
711         }
712       }
713
714       return selections;
715     } finally
716     {
717       LOCK.readLock().unlock();
718     }
719   }
720
721   /**
722    * Locate the first position visible for this sequence. If seq isn't visible
723    * then return the position of the left side of the hidden boundary region.
724    * 
725    * @param seq
726    *          sequence to find position for
727    * @return visible start position
728    */
729   public int locateVisibleStartOfSequence(SequenceI seq)
730   {
731     try
732     {
733       LOCK.readLock().lock();
734       int start = 0;
735
736       if (hiddenColumns == null || hiddenColumns.size() == 0)
737       {
738         return seq.findIndex(seq.getStart()) - 1;
739       }
740
741       // Simply walk along the sequence whilst watching for hidden column
742       // boundaries
743       Iterator<int[]> regions = hiddenColumns.iterator();
744       int hideStart = seq.getLength();
745       int hideEnd = -1;
746       int visPrev = 0;
747       int visNext = 0;
748       boolean foundStart = false;
749
750       // step through the non-gapped positions of the sequence
751       for (int i = seq.getStart(); i <= seq.getEnd() && (!foundStart); i++)
752       {
753         // get alignment position of this residue in the sequence
754         int p = seq.findIndex(i) - 1;
755
756         // update hidden region start/end
757         while (hideEnd < p && regions.hasNext())
758         {
759           int[] region = regions.next();
760           visPrev = visNext;
761           visNext += region[0] - visPrev;
762           hideStart = region[0];
763           hideEnd = region[1];
764         }
765         if (hideEnd < p)
766         {
767           hideStart = seq.getLength();
768         }
769         // update visible boundary for sequence
770         if (p < hideStart)
771         {
772           start = p;
773           foundStart = true;
774         }
775       }
776
777       if (foundStart)
778       {
779         return findColumnPosition(start);
780       }
781       // otherwise, sequence was completely hidden
782       return visPrev;
783     } finally
784     {
785       LOCK.readLock().unlock();
786     }
787   }
788
789   /**
790    * delete any columns in alignmentAnnotation that are hidden (including
791    * sequence associated annotation).
792    * 
793    * @param alignmentAnnotation
794    */
795   public void makeVisibleAnnotation(AlignmentAnnotation alignmentAnnotation)
796   {
797     makeVisibleAnnotation(0, alignmentAnnotation.annotations.length,
798             alignmentAnnotation);
799   }
800
801   /**
802    * delete any columns in alignmentAnnotation that are hidden (including
803    * sequence associated annotation).
804    * 
805    * @param start
806    *          remove any annotation to the right of this column
807    * @param end
808    *          remove any annotation to the left of this column
809    * @param alignmentAnnotation
810    *          the annotation to operate on
811    */
812   public void makeVisibleAnnotation(int start, int end,
813           AlignmentAnnotation alignmentAnnotation)
814   {
815     try
816     {
817       LOCK.readLock().lock();
818
819       int startFrom = start;
820       int endAt = end;
821
822       if (alignmentAnnotation.annotations != null)
823       {
824         if (hiddenColumns != null && hiddenColumns.size() > 0)
825         {
826           removeHiddenAnnotation(startFrom, endAt, alignmentAnnotation);
827         }
828         else
829         {
830           alignmentAnnotation.restrict(startFrom, endAt);
831         }
832       }
833     } finally
834     {
835       LOCK.readLock().unlock();
836     }
837   }
838
839   private void removeHiddenAnnotation(int start, int end,
840           AlignmentAnnotation alignmentAnnotation)
841   {
842     // mangle the alignmentAnnotation annotation array
843     ArrayList<Annotation[]> annels = new ArrayList<>();
844     Annotation[] els = null;
845
846     int w = 0;
847     
848     Iterator<int[]> blocks = new VisibleContigsIterator(start, end + 1,
849             hiddenColumns);
850
851     int copylength;
852     int annotationLength;
853     while (blocks.hasNext())
854     {
855       int[] block = blocks.next();
856       annotationLength = block[1] - block[0] + 1;
857     
858       if (blocks.hasNext())
859       {
860         // copy just the visible segment of the annotation row
861         copylength = annotationLength;
862       }
863       else
864       {
865         if (annotationLength + block[0] <= alignmentAnnotation.annotations.length)
866         {
867           // copy just the visible segment of the annotation row
868           copylength = annotationLength;
869         }
870         else
871         {
872           // copy to the end of the annotation row
873           copylength = alignmentAnnotation.annotations.length - block[0];
874         }
875       }
876       
877       els = new Annotation[annotationLength];
878       annels.add(els);
879       System.arraycopy(alignmentAnnotation.annotations, block[0], els, 0,
880               copylength);
881       w += annotationLength;
882     }
883     
884     if (w != 0)
885     {
886       alignmentAnnotation.annotations = new Annotation[w];
887
888       w = 0;
889       for (Annotation[] chnk : annels)
890       {
891         System.arraycopy(chnk, 0, alignmentAnnotation.annotations, w,
892                 chnk.length);
893         w += chnk.length;
894       }
895     }
896   }
897
898   /**
899    * 
900    * @return true if there are columns hidden
901    */
902   public boolean hasHiddenColumns()
903   {
904     try
905     {
906       LOCK.readLock().lock();
907       return hiddenColumns != null && hiddenColumns.size() > 0;
908     } finally
909     {
910       LOCK.readLock().unlock();
911     }
912   }
913
914   /**
915    * 
916    * @return true if there are more than one set of columns hidden
917    */
918   public boolean hasManyHiddenColumns()
919   {
920     try
921     {
922       LOCK.readLock().lock();
923       return hiddenColumns != null && hiddenColumns.size() > 1;
924     } finally
925     {
926       LOCK.readLock().unlock();
927     }
928   }
929
930   /**
931    * mark the columns corresponding to gap characters as hidden in the column
932    * selection
933    * 
934    * @param sr
935    */
936   public void hideInsertionsFor(SequenceI sr)
937   {
938     try
939     {
940       LOCK.writeLock().lock();
941       List<int[]> inserts = sr.getInsertions();
942       for (int[] r : inserts)
943       {
944         hideColumns(r[0], r[1]);
945       }
946       cursor.resetCursor(hiddenColumns);
947     } finally
948     {
949       LOCK.writeLock().unlock();
950     }
951   }
952
953   /**
954    * Unhides, and adds to the selection list, all hidden columns
955    */
956   public void revealAllHiddenColumns(ColumnSelection sel)
957   {
958     try
959     {
960       LOCK.writeLock().lock();
961       if (hiddenColumns != null)
962       {
963         Iterator<int[]> it = hiddenColumns.iterator();
964         while (it.hasNext())
965         {
966           int[] region = it.next();
967           for (int j = region[0]; j < region[1] + 1; j++)
968           {
969             sel.addElement(j);
970           }
971         }
972         hiddenColumns = null;
973         cursor.resetCursor(hiddenColumns);
974       }
975     } finally
976     {
977       LOCK.writeLock().unlock();
978     }
979   }
980
981   /**
982    * Reveals, and marks as selected, the hidden column range with the given
983    * start column
984    * 
985    * @param start
986    */
987   public void revealHiddenColumns(int start, ColumnSelection sel)
988   {
989     try
990     {
991       LOCK.writeLock().lock();
992       Iterator<int[]> it = new RegionsIterator(start, start, hiddenColumns,
993               cursor);
994       while (it.hasNext())
995       {
996         int[] region = it.next();
997         if (start == region[0])
998         {
999           for (int j = region[0]; j < region[1] + 1; j++)
1000           {
1001             sel.addElement(j);
1002           }
1003           it.remove();
1004           break;
1005         }
1006         else if (start < region[0])
1007         {
1008           break; // passed all possible matching regions
1009         }
1010       }
1011
1012       if (hiddenColumns.size() == 0)
1013       {
1014         hiddenColumns = null;
1015       }
1016       cursor.resetCursor(hiddenColumns);
1017     } finally
1018     {
1019       LOCK.writeLock().unlock();
1020     }
1021   }
1022
1023   /**
1024    * Add gaps into the sequences aligned to profileseq under the given
1025    * AlignmentView
1026    * 
1027    * @param profileseq
1028    * @param al
1029    *          - alignment to have gaps inserted into it
1030    * @param input
1031    *          - alignment view where sequence corresponding to profileseq is
1032    *          first entry
1033    * @return new HiddenColumns for new alignment view, with insertions into
1034    *         profileseq marked as hidden.
1035    */
1036   public static HiddenColumns propagateInsertions(SequenceI profileseq,
1037           AlignmentI al, AlignmentView input)
1038   {
1039     int profsqpos = 0;
1040
1041     char gc = al.getGapCharacter();
1042     Object[] alandhidden = input.getAlignmentAndHiddenColumns(gc);
1043     HiddenColumns nview = (HiddenColumns) alandhidden[1];
1044     SequenceI origseq = ((SequenceI[]) alandhidden[0])[profsqpos];
1045     nview.propagateInsertions(profileseq, al, origseq);
1046     return nview;
1047   }
1048
1049   /**
1050    * 
1051    * @param profileseq
1052    *          - sequence in al which corresponds to origseq
1053    * @param al
1054    *          - alignment which is to have gaps inserted into it
1055    * @param origseq
1056    *          - sequence corresponding to profileseq which defines gap map for
1057    *          modifying al
1058    */
1059   private void propagateInsertions(SequenceI profileseq, AlignmentI al,
1060           SequenceI origseq)
1061   {
1062     try
1063     {
1064       LOCK.writeLock().lock();
1065
1066       char gc = al.getGapCharacter();
1067
1068       // take the set of hidden columns, and the set of gaps in origseq,
1069       // and remove all the hidden gaps from hiddenColumns
1070
1071       // first get the gaps as a Bitset
1072       BitSet gaps = origseq.gapBitset();
1073
1074       // now calculate hidden ^ not(gap)
1075       BitSet hidden = new BitSet();
1076       markHiddenRegions(hidden);
1077       hidden.andNot(gaps);
1078       hiddenColumns = null;
1079       this.hideMarkedBits(hidden);
1080
1081       // for each sequence in the alignment, except the profile sequence,
1082       // insert gaps corresponding to each hidden region
1083       // but where each hidden column region is shifted backwards by the number
1084       // of
1085       // preceding visible gaps
1086       // update hidden columns at the same time
1087       Iterator<int[]> regions = hiddenColumns.iterator();
1088       ArrayList<int[]> newhidden = new ArrayList<>();
1089
1090       int numGapsBefore = 0;
1091       int gapPosition = 0;
1092       while (regions.hasNext())
1093       {
1094         // get region coordinates accounting for gaps
1095         // we can rely on gaps not being *in* hidden regions because we already
1096         // removed those
1097         int[] region = regions.next();
1098         while (gapPosition < region[0])
1099         {
1100           gapPosition++;
1101           if (gaps.get(gapPosition))
1102           {
1103             numGapsBefore++;
1104           }
1105         }
1106
1107         int left = region[0] - numGapsBefore;
1108         int right = region[1] - numGapsBefore;
1109         newhidden.add(new int[] { left, right });
1110
1111         // make a string with number of gaps = length of hidden region
1112         StringBuffer sb = new StringBuffer();
1113         for (int s = 0; s < right - left + 1; s++)
1114         {
1115           sb.append(gc);
1116         }
1117         padGaps(sb, left, profileseq, al);
1118
1119       }
1120       hiddenColumns = newhidden;
1121       cursor.resetCursor(hiddenColumns);
1122     } finally
1123     {
1124       LOCK.writeLock().unlock();
1125     }
1126   }
1127
1128   /**
1129    * Pad gaps in all sequences in alignment except profileseq
1130    * 
1131    * @param sb
1132    *          gap string to insert
1133    * @param left
1134    *          position to insert at
1135    * @param profileseq
1136    *          sequence not to pad
1137    * @param al
1138    *          alignment to pad sequences in
1139    */
1140   private void padGaps(StringBuffer sb, int pos, SequenceI profileseq,
1141           AlignmentI al)
1142   {
1143     // loop over the sequences and pad with gaps where required
1144     for (int s = 0, ns = al.getHeight(); s < ns; s++)
1145     {
1146       SequenceI sqobj = al.getSequenceAt(s);
1147       if (sqobj != profileseq)
1148       {
1149         String sq = al.getSequenceAt(s).getSequenceAsString();
1150         if (sq.length() <= pos)
1151         {
1152           // pad sequence
1153           int diff = pos - sq.length() - 1;
1154           if (diff > 0)
1155           {
1156             // pad gaps
1157             sq = sq + sb;
1158             while ((diff = pos - sq.length() - 1) > 0)
1159             {
1160               if (diff >= sb.length())
1161               {
1162                 sq += sb.toString();
1163               }
1164               else
1165               {
1166                 char[] buf = new char[diff];
1167                 sb.getChars(0, diff, buf, 0);
1168                 sq += buf.toString();
1169               }
1170             }
1171           }
1172           sq += sb.toString();
1173         }
1174         else
1175         {
1176           al.getSequenceAt(s).setSequence(
1177                   sq.substring(0, pos) + sb.toString() + sq.substring(pos));
1178         }
1179       }
1180     }
1181   }
1182
1183   /**
1184    * Returns a hashCode built from hidden column ranges
1185    */
1186   @Override
1187   public int hashCode()
1188   {
1189     try
1190     {
1191       LOCK.readLock().lock();
1192       int hashCode = 1;
1193       Iterator<int[]> it = hiddenColumns.iterator();
1194       while (it.hasNext())
1195       {
1196         int[] hidden = it.next();
1197         hashCode = HASH_MULTIPLIER * hashCode + hidden[0];
1198         hashCode = HASH_MULTIPLIER * hashCode + hidden[1];
1199       }
1200       return hashCode;
1201     } finally
1202     {
1203       LOCK.readLock().unlock();
1204     }
1205   }
1206
1207   /**
1208    * Hide columns corresponding to the marked bits
1209    * 
1210    * @param inserts
1211    *          - columns map to bits starting from zero
1212    */
1213   public void hideMarkedBits(BitSet inserts)
1214   {
1215     try
1216     {
1217       LOCK.writeLock().lock();
1218       for (int firstSet = inserts
1219               .nextSetBit(0), lastSet = 0; firstSet >= 0; firstSet = inserts
1220                       .nextSetBit(lastSet))
1221       {
1222         lastSet = inserts.nextClearBit(firstSet);
1223         hideColumns(firstSet, lastSet - 1);
1224       }
1225       cursor.resetCursor(hiddenColumns);
1226     } finally
1227     {
1228       LOCK.writeLock().unlock();
1229     }
1230   }
1231
1232   /**
1233    * 
1234    * @param inserts
1235    *          BitSet where hidden columns will be marked
1236    */
1237   public void markHiddenRegions(BitSet inserts)
1238   {
1239     try
1240     {
1241       LOCK.readLock().lock();
1242       if (hiddenColumns == null)
1243       {
1244         return;
1245       }
1246       Iterator<int[]> it = hiddenColumns.iterator();
1247       while (it.hasNext())
1248       {
1249         int[] range = it.next();
1250         inserts.set(range[0], range[1] + 1);
1251       }
1252     } finally
1253     {
1254       LOCK.readLock().unlock();
1255     }
1256   }
1257
1258   /**
1259    * Calculate the visible start and end index of an alignment.
1260    * 
1261    * @param width
1262    *          full alignment width
1263    * @return integer array where: int[0] = startIndex, and int[1] = endIndex
1264    */
1265   public int[] getVisibleStartAndEndIndex(int width)
1266   {
1267     try
1268     {
1269       LOCK.readLock().lock();
1270       int[] alignmentStartEnd = new int[] { 0, width - 1 };
1271       int startPos = alignmentStartEnd[0];
1272       int endPos = alignmentStartEnd[1];
1273
1274       int[] lowestRange = new int[] { -1, -1 };
1275       int[] higestRange = new int[] { -1, -1 };
1276
1277       if (hiddenColumns == null)
1278       {
1279         return new int[] { startPos, endPos };
1280       }
1281
1282       Iterator<int[]> it = hiddenColumns.iterator();
1283       while (it.hasNext())
1284       {
1285         int[] range = it.next();
1286         lowestRange = (range[0] <= startPos) ? range : lowestRange;
1287         higestRange = (range[1] >= endPos) ? range : higestRange;
1288       }
1289
1290       if (lowestRange[0] == -1 && lowestRange[1] == -1)
1291       {
1292         startPos = alignmentStartEnd[0];
1293       }
1294       else
1295       {
1296         startPos = lowestRange[1] + 1;
1297       }
1298
1299       if (higestRange[0] == -1 && higestRange[1] == -1)
1300       {
1301         endPos = alignmentStartEnd[1];
1302       }
1303       else
1304       {
1305         endPos = higestRange[0] - 1;
1306       }
1307       return new int[] { startPos, endPos };
1308     } finally
1309     {
1310       LOCK.readLock().unlock();
1311     }
1312   }
1313
1314   /**
1315    * Finds the hidden region (if any) which starts or ends at res
1316    * 
1317    * @param res
1318    *          visible residue position, unadjusted for hidden columns
1319    * @return region as [start,end] or null if no matching region is found
1320    */
1321   public int[] getRegionWithEdgeAtRes(int res)
1322   {
1323     try
1324     {
1325       LOCK.readLock().lock();
1326       int adjres = adjustForHiddenColumns(res);
1327
1328       int[] reveal = null;
1329
1330       if (hiddenColumns != null)
1331       {
1332         int regionindex = cursor.findRegionForColumn(adjres - 1);
1333         if (hiddenColumns.get(regionindex)[1] == adjres - 1)
1334         {
1335           reveal = hiddenColumns.get(regionindex);
1336         }
1337         else
1338         {
1339           regionindex = cursor.findRegionForColumn(adjres + 1);
1340           if (hiddenColumns.get(regionindex)[0] == adjres + 1)
1341           {
1342             reveal = hiddenColumns.get(regionindex);
1343           }
1344         }
1345       }
1346       return reveal;
1347
1348     } finally
1349     {
1350       LOCK.readLock().unlock();
1351     }
1352   }
1353
1354   /**
1355    * Return an iterator over the hidden regions
1356    */
1357   public Iterator<int[]> iterator()
1358   {
1359     try
1360     {
1361       LOCK.readLock().lock();
1362       return new BoundedHiddenColsIterator(hiddenColumns);
1363     } finally
1364     {
1365       LOCK.readLock().unlock();
1366     }
1367   }
1368
1369   /**
1370    * Return a bounded iterator over the hidden regions
1371    * 
1372    * @param start
1373    *          position to start from (inclusive, absolute column position)
1374    * @param end
1375    *          position to end at (inclusive, absolute column position)
1376    * @return
1377    */
1378   public Iterator<int[]> getBoundedIterator(int start, int end)
1379   {
1380     try
1381     {
1382       LOCK.readLock().lock();
1383       return new BoundedHiddenColsIterator(start, end, hiddenColumns);
1384     } finally
1385     {
1386       LOCK.readLock().unlock();
1387     }
1388   }
1389
1390   /**
1391    * Return a bounded iterator over the *visible* start positions of hidden
1392    * regions
1393    * 
1394    * @param start
1395    *          position to start from (inclusive, visible column position)
1396    * @param end
1397    *          position to end at (inclusive, visible column position)
1398    */
1399   public Iterator<Integer> getBoundedStartIterator(int start, int end)
1400   {
1401     try
1402     {
1403       LOCK.readLock().lock();
1404       return new BoundedStartRegionIterator(start, end, hiddenColumns);
1405     } finally
1406     {
1407       LOCK.readLock().unlock();
1408     }
1409   }
1410
1411   /**
1412    * Return an iterator over visible *columns* (not regions) between the given
1413    * start and end boundaries
1414    * 
1415    * @param start
1416    *          first column (inclusive)
1417    * @param end
1418    *          last column (inclusive)
1419    */
1420   public Iterator<Integer> getVisibleColsIterator(int start, int end)
1421   {
1422     try
1423     {
1424       LOCK.readLock().lock();
1425       return new VisibleColsIterator(start, end, hiddenColumns);
1426     } finally
1427     {
1428       LOCK.readLock().unlock();
1429     }
1430   }
1431
1432   /**
1433    * return an iterator over visible segments between the given start and end
1434    * boundaries
1435    * 
1436    * @param start
1437    *          (first column inclusive from 0)
1438    * @param end
1439    *          (last column - not inclusive)
1440    */
1441   public Iterator<int[]> getVisContigsIterator(int start, int end)
1442   {
1443     try
1444     {
1445       LOCK.readLock().lock();
1446       return new VisibleContigsIterator(start, end, hiddenColumns);
1447     } finally
1448     {
1449       LOCK.readLock().unlock();
1450     }
1451   }
1452
1453   /**
1454    * return an iterator over visible segments between the given start and end
1455    * boundaries
1456    * 
1457    * @param start
1458    *          (first column - inclusive from 0)
1459    * @param end
1460    *          (last column - inclusive)
1461    * @param useVisibleCoords
1462    *          if true, start and end are visible column positions, not absolute
1463    *          positions
1464    */
1465   public Iterator<int[]> getVisibleBlocksIterator(int start, int end,
1466           boolean useVisibleCoords)
1467   {
1468     if (useVisibleCoords)
1469     {
1470       // TODO
1471       // we should really just convert start and end here with
1472       // adjustForHiddenColumns
1473       // and then create a VisibleContigsIterator
1474       // but without a cursor this will be horribly slow in some situations
1475       // ... so until then...
1476       return new VisibleBlocksVisBoundsIterator(start, end, true);
1477     }
1478     else
1479     {
1480       try
1481       {
1482         LOCK.readLock().lock();
1483         return new VisibleContigsIterator(start, end + 1, hiddenColumns);
1484       } finally
1485     {
1486         LOCK.readLock().unlock();
1487       }
1488     }
1489   }
1490
1491   /**
1492    * An iterator which iterates over visible regions in a range. The range is
1493    * specified in terms of visible column positions. Provides a special
1494    * "endsAtHidden" indicator to allow callers to determine if the final visible
1495    * column is adjacent to a hidden region.
1496    */
1497   public class VisibleBlocksVisBoundsIterator implements Iterator<int[]>
1498   {
1499     private List<int[]> vcontigs = new ArrayList<>();
1500
1501     private int currentPosition = 0;
1502
1503     private boolean endsAtHidden = false;
1504
1505     /**
1506      * Constructor for iterator over visible regions in a range.
1507      * 
1508      * @param start
1509      *          start position in terms of visible column position
1510      * @param end
1511      *          end position in terms of visible column position
1512      * @param usecopy
1513      *          whether to use a local copy of hidden columns
1514      */
1515     VisibleBlocksVisBoundsIterator(int start, int end, boolean usecopy)
1516     {
1517       /* actually this implementation always uses a local copy but this may change in future */
1518       try
1519       {
1520         if (usecopy)
1521         {
1522           LOCK.readLock().lock();
1523         }
1524
1525         if (hiddenColumns != null && hiddenColumns.size() > 0)
1526         {
1527           int blockStart = start;
1528           int blockEnd = end;
1529           int hiddenSoFar = 0;
1530           int visSoFar = 0;
1531
1532           // iterate until a region begins within (start,end]
1533           int i = 0;
1534           while ((i < hiddenColumns.size())
1535                   && (hiddenColumns.get(i)[0] <= blockStart + hiddenSoFar))
1536           {
1537             hiddenSoFar += hiddenColumns.get(i)[1] - hiddenColumns.get(i)[0]
1538                     + 1;
1539             i++;
1540           }
1541
1542           blockStart += hiddenSoFar; // convert start to absolute position
1543           blockEnd += hiddenSoFar; // convert end to absolute position
1544
1545           // iterate from start to end, adding each visible region. Positions
1546           // are
1547           // absolute, and all hidden regions which overlap [start,end] are
1548           // used.
1549           while (i < hiddenColumns.size()
1550                   && (hiddenColumns.get(i)[0] <= blockEnd))
1551           {
1552             int[] region = hiddenColumns.get(i);
1553
1554             // end position of this visible region is either just before the
1555             // start of the next hidden region, or the absolute position of
1556             // 'end', whichever is lowest
1557             blockEnd = Math.min(blockEnd, region[0] - 1);
1558
1559             vcontigs.add(new int[] { blockStart, blockEnd });
1560
1561             visSoFar += blockEnd - blockStart + 1;
1562
1563             // next visible region starts after this hidden region
1564             blockStart = region[1] + 1;
1565
1566             hiddenSoFar += region[1] - region[0] + 1;
1567
1568             // reset blockEnd to absolute position of 'end', assuming we've now
1569             // passed all hidden regions before end
1570             blockEnd = end + hiddenSoFar;
1571
1572             i++;
1573           }
1574           if (visSoFar < end - start + 1)
1575           {
1576             // the number of visible columns we've accounted for is less than
1577             // the number specified by end-start; work out the end position of
1578             // the last visible region
1579             blockEnd = blockStart + end - start - visSoFar;
1580             vcontigs.add(new int[] { blockStart, blockEnd });
1581
1582             // if the last visible region ends at the next hidden region, set
1583             // endsAtHidden=true
1584             if (i < hiddenColumns.size()
1585                     && hiddenColumns.get(i)[0] - 1 == blockEnd)
1586             {
1587               endsAtHidden = true;
1588             }
1589           }
1590         }
1591         else
1592         {
1593           // there are no hidden columns, return a single visible contig
1594           vcontigs.add(new int[] { start, end });
1595           endsAtHidden = false;
1596         }
1597       } finally
1598       {
1599         if (usecopy)
1600         {
1601           LOCK.readLock().unlock();
1602         }
1603       }
1604     }
1605
1606     @Override
1607     public boolean hasNext()
1608     {
1609       return (currentPosition < vcontigs.size());
1610     }
1611
1612     @Override
1613     public int[] next()
1614     {
1615       int[] result = vcontigs.get(currentPosition);
1616       currentPosition++;
1617       return result;
1618     }
1619
1620     public boolean endsAtHidden()
1621     {
1622       return endsAtHidden;
1623     }
1624   }
1625 }