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