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