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