JAL-2388 last separation of hidden columns/col selection
[jalview.git] / src / jalview / datamodel / HiddenColumns.java
1 package jalview.datamodel;
2
3 import jalview.util.Comparison;
4 import jalview.util.ShiftList;
5
6 import java.util.ArrayList;
7 import java.util.Collections;
8 import java.util.List;
9 import java.util.Vector;
10
11 public class HiddenColumns
12 {
13   /*
14    * list of hidden column [start, end] ranges; the list is maintained in
15    * ascending start column order
16    */
17   private Vector<int[]> hiddenColumns;
18
19   /**
20    * This Method is used to return all the HiddenColumn regions
21    * 
22    * @return empty list or List of hidden column intervals
23    */
24   public List<int[]> getListOfCols()
25   {
26     return hiddenColumns == null ? Collections.<int[]> emptyList()
27             : hiddenColumns;
28   }
29
30   public boolean hasHidden()
31   {
32     return (hiddenColumns != null) && (!hiddenColumns.isEmpty());
33   }
34
35   @Override
36   public boolean equals(Object obj)
37   {
38     if (!(obj instanceof HiddenColumns))
39     {
40       return false;
41     }
42     HiddenColumns that = (HiddenColumns) obj;
43
44     /*
45      * check hidden columns are either both null, or match
46      */
47     if (this.hiddenColumns == null)
48     {
49       return (that.hiddenColumns == null);
50     }
51     if (that.hiddenColumns == null
52             || that.hiddenColumns.size() != this.hiddenColumns.size())
53     {
54       return false;
55     }
56     int i = 0;
57     for (int[] thisRange : hiddenColumns)
58     {
59       int[] thatRange = that.hiddenColumns.get(i++);
60       if (thisRange[0] != thatRange[0] || thisRange[1] != thatRange[1])
61       {
62         return false;
63       }
64     }
65     return true;
66   }
67
68   /**
69    * Return absolute column index for a visible column index
70    * 
71    * @param column
72    *          int column index in alignment view (count from zero)
73    * @return alignment column index for column
74    */
75   public int adjustForHiddenColumns(int column)
76   {
77     int result = column;
78     if (hiddenColumns != null)
79     {
80       for (int i = 0; i < hiddenColumns.size(); i++)
81       {
82         int[] region = hiddenColumns.elementAt(i);
83         if (result >= region[0])
84         {
85           result += region[1] - region[0] + 1;
86         }
87       }
88     }
89     return result;
90   }
91
92   /**
93    * Use this method to find out where a column will appear in the visible
94    * alignment when hidden columns exist. If the column is not visible, then the
95    * left-most visible column will always be returned.
96    * 
97    * @param hiddenColumn
98    *          the column index in the full alignment including hidden columns
99    * @return the position of the column in the visible alignment
100    */
101   public int findColumnPosition(int hiddenColumn)
102   {
103     int result = hiddenColumn;
104     if (hiddenColumns != null)
105     {
106       int index = 0;
107       int[] region;
108       do
109       {
110         region = hiddenColumns.elementAt(index++);
111         if (hiddenColumn > region[1])
112         {
113           result -= region[1] + 1 - region[0];
114         }
115       } while ((hiddenColumn > region[1]) && (index < hiddenColumns.size()));
116
117       if (hiddenColumn >= region[0] && hiddenColumn <= region[1])
118       {
119         // Here the hidden column is within a region, so
120         // we want to return the position of region[0]-1, adjusted for any
121         // earlier hidden columns.
122         // Calculate the difference between the actual hidden col position
123         // and region[0]-1, and then subtract from result to convert result from
124         // the adjusted hiddenColumn value to the adjusted region[0]-1 value
125
126         // However, if the region begins at 0 we cannot return region[0]-1
127         // just return 0
128         if (region[0] == 0)
129         {
130           return 0;
131         }
132         else
133         {
134           return result - (hiddenColumn - region[0] + 1);
135         }
136       }
137     }
138     return result; // return the shifted position after removing hidden columns.
139   }
140
141   /**
142    * Find the visible column which is a given visible number of columns to the
143    * left of another visible column. i.e. for a startColumn x, the column which
144    * is distance 1 away will be column x-1.
145    * 
146    * @param visibleDistance
147    *          the number of visible columns to offset by
148    * @param startColumn
149    *          the column to start from
150    * @return the position of the column in the visible alignment
151    */
152   public int subtractVisibleColumns(int visibleDistance, int startColumn)
153   {
154     int distance = visibleDistance;
155
156     // in case startColumn is in a hidden region, move it to the left
157     int start = adjustForHiddenColumns(findColumnPosition(startColumn));
158
159     // get index of hidden region to left of start
160     int index = getHiddenIndexLeft(start);
161     if (index == -1)
162     {
163       // no hidden regions to left of startColumn
164       return start - distance;
165     }
166
167     // walk backwards through the alignment subtracting the counts of visible
168     // columns from distance
169     int[] region;
170     int gap = 0;
171     int nextstart = start;
172
173     while ((index > -1) && (distance - gap > 0))
174     {
175       // subtract the gap to right of region from distance
176       distance -= gap;
177       start = nextstart;
178
179       // calculate the next gap
180       region = hiddenColumns.get(index);
181       gap = start - region[1];
182
183       // set start to just to left of current region
184       nextstart = region[0] - 1;
185       index--;
186     }
187
188     if (distance - gap > 0)
189     {
190       // fell out of loop because there are no more hidden regions
191       distance -= gap;
192       return nextstart - distance;
193     }
194     return start - distance;
195
196   }
197
198   /**
199    * Use this method to determine where the next hiddenRegion starts
200    * 
201    * @param hiddenRegion
202    *          index of hidden region (counts from 0)
203    * @return column number in visible view
204    */
205   public int findHiddenRegionPosition(int hiddenRegion)
206   {
207     int result = 0;
208     if (hiddenColumns != null)
209     {
210       int index = 0;
211       int gaps = 0;
212       do
213       {
214         int[] region = hiddenColumns.elementAt(index);
215         if (hiddenRegion == 0)
216         {
217           return region[0];
218         }
219
220         gaps += region[1] + 1 - region[0];
221         result = region[1] + 1;
222         index++;
223       } while (index <= hiddenRegion);
224
225       result -= gaps;
226     }
227
228     return result;
229   }
230
231   /**
232    * This method returns the rightmost limit of a region of an alignment with
233    * hidden columns. In otherwords, the next hidden column.
234    * 
235    * @param index
236    *          int
237    */
238   public int getHiddenBoundaryRight(int alPos)
239   {
240     if (hiddenColumns != null)
241     {
242       int index = 0;
243       do
244       {
245         int[] region = hiddenColumns.elementAt(index);
246         if (alPos < region[0])
247         {
248           return region[0];
249         }
250
251         index++;
252       } while (index < hiddenColumns.size());
253     }
254
255     return alPos;
256
257   }
258
259   /**
260    * This method returns the leftmost limit of a region of an alignment with
261    * hidden columns. In otherwords, the previous hidden column.
262    * 
263    * @param index
264    *          int
265    */
266   public int getHiddenBoundaryLeft(int alPos)
267   {
268     if (hiddenColumns != null)
269     {
270       int index = hiddenColumns.size() - 1;
271       do
272       {
273         int[] region = hiddenColumns.elementAt(index);
274         if (alPos > region[1])
275         {
276           return region[1];
277         }
278
279         index--;
280       } while (index > -1);
281     }
282
283     return alPos;
284
285   }
286
287   /**
288    * This method returns the index of the hidden region to the left of a column
289    * position. If the column is in a hidden region it returns the index of the
290    * region to the left. If there is no hidden region to the left it returns -1.
291    * 
292    * @param pos
293    *          int
294    */
295   private int getHiddenIndexLeft(int pos)
296   {
297     if (hiddenColumns != null)
298     {
299       int index = hiddenColumns.size() - 1;
300       do
301       {
302         int[] region = hiddenColumns.elementAt(index);
303         if (pos > region[1])
304         {
305           return index;
306         }
307
308         index--;
309       } while (index > -1);
310     }
311
312     return -1;
313
314   }
315
316   /**
317    * Adds the specified column range to the hidden columns
318    * 
319    * @param start
320    * @param end
321    */
322   public void hideColumns(int start, int end)
323   {
324     if (hiddenColumns == null)
325     {
326       hiddenColumns = new Vector<int[]>();
327     }
328
329     /*
330      * traverse existing hidden ranges and insert / amend / append as
331      * appropriate
332      */
333     for (int i = 0; i < hiddenColumns.size(); i++)
334     {
335       int[] region = hiddenColumns.elementAt(i);
336
337       if (end < region[0] - 1)
338       {
339         /*
340          * insert discontiguous preceding range
341          */
342         hiddenColumns.insertElementAt(new int[] { start, end }, i);
343         return;
344       }
345
346       if (end <= region[1])
347       {
348         /*
349          * new range overlaps existing, or is contiguous preceding it - adjust
350          * start column
351          */
352         region[0] = Math.min(region[0], start);
353         return;
354       }
355
356       if (start <= region[1] + 1)
357       {
358         /*
359          * new range overlaps existing, or is contiguous following it - adjust
360          * start and end columns
361          */
362         region[0] = Math.min(region[0], start);
363         region[1] = Math.max(region[1], end);
364
365         /*
366          * also update or remove any subsequent ranges 
367          * that are overlapped
368          */
369         while (i < hiddenColumns.size() - 1)
370         {
371           int[] nextRegion = hiddenColumns.get(i + 1);
372           if (nextRegion[0] > end + 1)
373           {
374             /*
375              * gap to next hidden range - no more to update
376              */
377             break;
378           }
379           region[1] = Math.max(nextRegion[1], end);
380           hiddenColumns.remove(i + 1);
381         }
382         return;
383       }
384     }
385
386     /*
387      * remaining case is that the new range follows everything else
388      */
389     hiddenColumns.addElement(new int[] { start, end });
390   }
391
392   public boolean isVisible(int column)
393   {
394     if (hiddenColumns != null)
395     {
396       for (int[] region : hiddenColumns)
397       {
398         if (column >= region[0] && column <= region[1])
399         {
400           return false;
401         }
402       }
403     }
404
405     return true;
406   }
407
408   /**
409    * ColumnSelection
410    */
411   public HiddenColumns()
412   {
413   }
414
415   /**
416    * Copy constructor
417    * 
418    * @param copy
419    */
420   public HiddenColumns(HiddenColumns copy)
421   {
422     if (copy != null)
423     {
424       if (copy.hiddenColumns != null)
425       {
426         hiddenColumns = new Vector<int[]>(copy.hiddenColumns.size());
427         for (int i = 0, j = copy.hiddenColumns.size(); i < j; i++)
428         {
429           int[] rh, cp;
430           rh = copy.hiddenColumns.elementAt(i);
431           if (rh != null)
432           {
433             cp = new int[rh.length];
434             System.arraycopy(rh, 0, cp, 0, rh.length);
435             hiddenColumns.addElement(cp);
436           }
437         }
438       }
439     }
440   }
441
442   /**
443    * propagate shift in alignment columns to column selection
444    * 
445    * @param start
446    *          beginning of edit
447    * @param left
448    *          shift in edit (+ve for removal, or -ve for inserts)
449    */
450   public List<int[]> compensateForEdit(int start, int change,
451           ColumnSelection sel)
452   {
453     List<int[]> deletedHiddenColumns = null;
454
455     if (hiddenColumns != null)
456     {
457       deletedHiddenColumns = new ArrayList<int[]>();
458       int hSize = hiddenColumns.size();
459       for (int i = 0; i < hSize; i++)
460       {
461         int[] region = hiddenColumns.elementAt(i);
462         if (region[0] > start && start + change > region[1])
463         {
464           deletedHiddenColumns.add(region);
465
466           hiddenColumns.removeElementAt(i);
467           i--;
468           hSize--;
469           continue;
470         }
471
472         if (region[0] > start)
473         {
474           region[0] -= change;
475           region[1] -= change;
476         }
477
478         if (region[0] < 0)
479         {
480           region[0] = 0;
481         }
482
483       }
484
485       this.revealHiddenColumns(0, sel);
486     }
487
488     return deletedHiddenColumns;
489   }
490
491   /**
492    * propagate shift in alignment columns to column selection special version of
493    * compensateForEdit - allowing for edits within hidden regions
494    * 
495    * @param start
496    *          beginning of edit
497    * @param left
498    *          shift in edit (+ve for removal, or -ve for inserts)
499    */
500   public void compensateForDelEdits(int start, int change)
501   {
502     if (hiddenColumns != null)
503     {
504       for (int i = 0; i < hiddenColumns.size(); i++)
505       {
506         int[] region = hiddenColumns.elementAt(i);
507         if (region[0] >= start)
508         {
509           region[0] -= change;
510         }
511         if (region[1] >= start)
512         {
513           region[1] -= change;
514         }
515         if (region[1] < region[0])
516         {
517           hiddenColumns.removeElementAt(i--);
518         }
519
520         if (region[0] < 0)
521         {
522           region[0] = 0;
523         }
524         if (region[1] < 0)
525         {
526           region[1] = 0;
527         }
528       }
529     }
530   }
531
532   /**
533    * return all visible segments between the given start and end boundaries
534    * 
535    * @param start
536    *          (first column inclusive from 0)
537    * @param end
538    *          (last column - not inclusive)
539    * @return int[] {i_start, i_end, ..} where intervals lie in
540    *         start<=i_start<=i_end<end
541    */
542   public int[] getVisibleContigs(int start, int end)
543   {
544     if (hiddenColumns != null && hiddenColumns.size() > 0)
545     {
546       List<int[]> visiblecontigs = new ArrayList<int[]>();
547       List<int[]> regions = getListOfCols();
548
549       int vstart = start;
550       int[] region;
551       int hideStart, hideEnd;
552
553       for (int j = 0; vstart < end && j < regions.size(); j++)
554       {
555         region = regions.get(j);
556         hideStart = region[0];
557         hideEnd = region[1];
558
559         if (hideEnd < vstart)
560         {
561           continue;
562         }
563         if (hideStart > vstart)
564         {
565           visiblecontigs.add(new int[] { vstart, hideStart - 1 });
566         }
567         vstart = hideEnd + 1;
568       }
569
570       if (vstart < end)
571       {
572         visiblecontigs.add(new int[] { vstart, end - 1 });
573       }
574       int[] vcontigs = new int[visiblecontigs.size() * 2];
575       for (int i = 0, j = visiblecontigs.size(); i < j; i++)
576       {
577         int[] vc = visiblecontigs.get(i);
578         visiblecontigs.set(i, null);
579         vcontigs[i * 2] = vc[0];
580         vcontigs[i * 2 + 1] = vc[1];
581       }
582       visiblecontigs.clear();
583       return vcontigs;
584     }
585     else
586     {
587       return new int[] { start, end - 1 };
588     }
589   }
590
591   public String[] getVisibleSequenceStrings(int start, int end,
592           SequenceI[] seqs)
593   {
594     int i, iSize = seqs.length;
595     String selections[] = new String[iSize];
596     if (hiddenColumns != null && hiddenColumns.size() > 0)
597     {
598       for (i = 0; i < iSize; i++)
599       {
600         StringBuffer visibleSeq = new StringBuffer();
601         List<int[]> regions = getListOfCols();
602
603         int blockStart = start, blockEnd = end;
604         int[] region;
605         int hideStart, hideEnd;
606
607         for (int j = 0; j < regions.size(); j++)
608         {
609           region = regions.get(j);
610           hideStart = region[0];
611           hideEnd = region[1];
612
613           if (hideStart < start)
614           {
615             continue;
616           }
617
618           blockStart = Math.min(blockStart, hideEnd + 1);
619           blockEnd = Math.min(blockEnd, hideStart);
620
621           if (blockStart > blockEnd)
622           {
623             break;
624           }
625
626           visibleSeq.append(seqs[i].getSequence(blockStart, blockEnd));
627
628           blockStart = hideEnd + 1;
629           blockEnd = end;
630         }
631
632         if (end > blockStart)
633         {
634           visibleSeq.append(seqs[i].getSequence(blockStart, end));
635         }
636
637         selections[i] = visibleSeq.toString();
638       }
639     }
640     else
641     {
642       for (i = 0; i < iSize; i++)
643       {
644         selections[i] = seqs[i].getSequenceAsString(start, end);
645       }
646     }
647
648     return selections;
649   }
650
651   /**
652    * Locate the first and last position visible for this sequence. if seq isn't
653    * visible then return the position of the left and right of the hidden
654    * boundary region, and the corresponding alignment column indices for the
655    * extent of the sequence
656    * 
657    * @param seq
658    * @return int[] { visible start, visible end, first seqpos, last seqpos,
659    *         alignment index for seq start, alignment index for seq end }
660    */
661   public int[] locateVisibleBoundsOfSequence(SequenceI seq)
662   {
663     int fpos = seq.getStart(), lpos = seq.getEnd();
664     int start = 0;
665
666     if (hiddenColumns == null || hiddenColumns.size() == 0)
667     {
668       int ifpos = seq.findIndex(fpos) - 1, ilpos = seq.findIndex(lpos) - 1;
669       return new int[] { ifpos, ilpos, fpos, lpos, ifpos, ilpos };
670     }
671
672     // Simply walk along the sequence whilst watching for hidden column
673     // boundaries
674     List<int[]> regions = getListOfCols();
675     int spos = fpos, lastvispos = -1, rcount = 0, hideStart = seq
676             .getLength(), hideEnd = -1;
677     int visPrev = 0, visNext = 0, firstP = -1, lastP = -1;
678     boolean foundStart = false;
679     for (int p = 0, pLen = seq.getLength(); spos <= seq.getEnd()
680             && p < pLen; p++)
681     {
682       if (!Comparison.isGap(seq.getCharAt(p)))
683       {
684         // keep track of first/last column
685         // containing sequence data regardless of visibility
686         if (firstP == -1)
687         {
688           firstP = p;
689         }
690         lastP = p;
691         // update hidden region start/end
692         while (hideEnd < p && rcount < regions.size())
693         {
694           int[] region = regions.get(rcount++);
695           visPrev = visNext;
696           visNext += region[0] - visPrev;
697           hideStart = region[0];
698           hideEnd = region[1];
699         }
700         if (hideEnd < p)
701         {
702           hideStart = seq.getLength();
703         }
704         // update visible boundary for sequence
705         if (p < hideStart)
706         {
707           if (!foundStart)
708           {
709             fpos = spos;
710             start = p;
711             foundStart = true;
712           }
713           lastvispos = p;
714           lpos = spos;
715         }
716         // look for next sequence position
717         spos++;
718       }
719     }
720     if (foundStart)
721     {
722       return new int[] { findColumnPosition(start),
723           findColumnPosition(lastvispos), fpos, lpos, firstP, lastP };
724     }
725     // otherwise, sequence was completely hidden
726     return new int[] { visPrev, visNext, 0, 0, firstP, lastP };
727   }
728
729   /**
730    * delete any columns in alignmentAnnotation that are hidden (including
731    * sequence associated annotation).
732    * 
733    * @param alignmentAnnotation
734    */
735   public void makeVisibleAnnotation(AlignmentAnnotation alignmentAnnotation)
736   {
737     makeVisibleAnnotation(-1, -1, alignmentAnnotation);
738   }
739
740   /**
741    * delete any columns in alignmentAnnotation that are hidden (including
742    * sequence associated annotation).
743    * 
744    * @param start
745    *          remove any annotation to the right of this column
746    * @param end
747    *          remove any annotation to the left of this column
748    * @param alignmentAnnotation
749    *          the annotation to operate on
750    */
751   public void makeVisibleAnnotation(int start, int end,
752           AlignmentAnnotation alignmentAnnotation)
753   {
754     if (alignmentAnnotation.annotations == null)
755     {
756       return;
757     }
758     if (start == end && end == -1)
759     {
760       start = 0;
761       end = alignmentAnnotation.annotations.length;
762     }
763     if (hiddenColumns != null && hiddenColumns.size() > 0)
764     {
765       // then mangle the alignmentAnnotation annotation array
766       Vector<Annotation[]> annels = new Vector<Annotation[]>();
767       Annotation[] els = null;
768       List<int[]> regions = getListOfCols();
769       int blockStart = start, blockEnd = end;
770       int[] region;
771       int hideStart, hideEnd, w = 0;
772
773       for (int j = 0; j < regions.size(); j++)
774       {
775         region = regions.get(j);
776         hideStart = region[0];
777         hideEnd = region[1];
778
779         if (hideStart < start)
780         {
781           continue;
782         }
783
784         blockStart = Math.min(blockStart, hideEnd + 1);
785         blockEnd = Math.min(blockEnd, hideStart);
786
787         if (blockStart > blockEnd)
788         {
789           break;
790         }
791
792         annels.addElement(els = new Annotation[blockEnd - blockStart]);
793         System.arraycopy(alignmentAnnotation.annotations, blockStart, els,
794                 0, els.length);
795         w += els.length;
796         blockStart = hideEnd + 1;
797         blockEnd = end;
798       }
799
800       if (end > blockStart)
801       {
802         annels.addElement(els = new Annotation[end - blockStart + 1]);
803         if ((els.length + blockStart) <= alignmentAnnotation.annotations.length)
804         {
805           // copy just the visible segment of the annotation row
806           System.arraycopy(alignmentAnnotation.annotations, blockStart,
807                   els, 0, els.length);
808         }
809         else
810         {
811           // copy to the end of the annotation row
812           System.arraycopy(alignmentAnnotation.annotations, blockStart,
813                   els, 0,
814                   (alignmentAnnotation.annotations.length - blockStart));
815         }
816         w += els.length;
817       }
818       if (w == 0)
819       {
820         return;
821       }
822
823       alignmentAnnotation.annotations = new Annotation[w];
824       w = 0;
825
826       for (Annotation[] chnk : annels)
827       {
828         System.arraycopy(chnk, 0, alignmentAnnotation.annotations, w,
829                 chnk.length);
830         w += chnk.length;
831       }
832     }
833     else
834     {
835       alignmentAnnotation.restrict(start, end);
836     }
837   }
838
839   /**
840    * 
841    * @return true if there are columns hidden
842    */
843   public boolean hasHiddenColumns()
844   {
845     return hiddenColumns != null && hiddenColumns.size() > 0;
846   }
847
848   /**
849    * 
850    * @return true if there are more than one set of columns hidden
851    */
852   public boolean hasManyHiddenColumns()
853   {
854     return hiddenColumns != null && hiddenColumns.size() > 1;
855   }
856
857   /**
858    * mark the columns corresponding to gap characters as hidden in the column
859    * selection
860    * 
861    * @param sr
862    */
863   public void hideInsertionsFor(SequenceI sr)
864   {
865     List<int[]> inserts = sr.getInsertions();
866     for (int[] r : inserts)
867     {
868       hideColumns(r[0], r[1]);
869     }
870   }
871
872   /**
873    * Unhides, and adds to the selection list, all hidden columns
874    */
875   public void revealAllHiddenColumns(ColumnSelection sel)
876   {
877     if (hiddenColumns != null)
878     {
879       for (int i = 0; i < hiddenColumns.size(); i++)
880       {
881         int[] region = hiddenColumns.elementAt(i);
882         for (int j = region[0]; j < region[1] + 1; j++)
883         {
884           sel.addElement(j);
885         }
886       }
887     }
888
889     hiddenColumns = null;
890   }
891
892   /**
893    * Reveals, and marks as selected, the hidden column range with the given
894    * start column
895    * 
896    * @param start
897    */
898   public void revealHiddenColumns(int start, ColumnSelection sel)
899   {
900     for (int i = 0; i < hiddenColumns.size(); i++)
901     {
902       int[] region = hiddenColumns.elementAt(i);
903       if (start == region[0])
904       {
905         for (int j = region[0]; j < region[1] + 1; j++)
906         {
907           sel.addElement(j);
908         }
909
910         hiddenColumns.removeElement(region);
911         break;
912       }
913     }
914     if (hiddenColumns.size() == 0)
915     {
916       hiddenColumns = null;
917     }
918   }
919
920   /**
921    * removes intersection of position,length ranges in deletions from the
922    * start,end regions marked in intervals.
923    * 
924    * @param shifts
925    * @param intervals
926    * @return
927    */
928   private boolean pruneIntervalVector(final List<int[]> shifts,
929           Vector<int[]> intervals)
930   {
931     boolean pruned = false;
932     int i = 0, j = intervals.size() - 1, s = 0, t = shifts.size() - 1;
933     int hr[] = intervals.elementAt(i);
934     int sr[] = shifts.get(s);
935     while (i <= j && s <= t)
936     {
937       boolean trailinghn = hr[1] >= sr[0];
938       if (!trailinghn)
939       {
940         if (i < j)
941         {
942           hr = intervals.elementAt(++i);
943         }
944         else
945         {
946           i++;
947         }
948         continue;
949       }
950       int endshift = sr[0] + sr[1]; // deletion ranges - -ve means an insert
951       if (endshift < hr[0] || endshift < sr[0])
952       { // leadinghc disjoint or not a deletion
953         if (s < t)
954         {
955           sr = shifts.get(++s);
956         }
957         else
958         {
959           s++;
960         }
961         continue;
962       }
963       boolean leadinghn = hr[0] >= sr[0];
964       boolean leadinghc = hr[0] < endshift;
965       boolean trailinghc = hr[1] < endshift;
966       if (leadinghn)
967       {
968         if (trailinghc)
969         { // deleted hidden region.
970           intervals.removeElementAt(i);
971           pruned = true;
972           j--;
973           if (i <= j)
974           {
975             hr = intervals.elementAt(i);
976           }
977           continue;
978         }
979         if (leadinghc)
980         {
981           hr[0] = endshift; // clip c terminal region
982           leadinghn = !leadinghn;
983           pruned = true;
984         }
985       }
986       if (!leadinghn)
987       {
988         if (trailinghc)
989         {
990           if (trailinghn)
991           {
992             hr[1] = sr[0] - 1;
993             pruned = true;
994           }
995         }
996         else
997         {
998           // sr contained in hr
999           if (s < t)
1000           {
1001             sr = shifts.get(++s);
1002           }
1003           else
1004           {
1005             s++;
1006           }
1007           continue;
1008         }
1009       }
1010     }
1011     return pruned; // true if any interval was removed or modified by
1012     // operations.
1013   }
1014
1015   /**
1016    * remove any hiddenColumns or selected columns and shift remaining based on a
1017    * series of position, range deletions.
1018    * 
1019    * @param deletions
1020    */
1021   public void pruneDeletions(List<int[]> shifts)
1022   {
1023     // delete any intervals intersecting.
1024     if (hiddenColumns != null)
1025     {
1026       pruneIntervalVector(shifts, hiddenColumns);
1027       if (hiddenColumns != null && hiddenColumns.size() == 0)
1028       {
1029         hiddenColumns = null;
1030       }
1031     }
1032   }
1033
1034   /**
1035    * Add gaps into the sequences aligned to profileseq under the given
1036    * AlignmentView
1037    * 
1038    * @param profileseq
1039    * @param al
1040    *          - alignment to have gaps inserted into it
1041    * @param input
1042    *          - alignment view where sequence corresponding to profileseq is
1043    *          first entry
1044    * @return new HiddenColumns for new alignment view, with insertions into
1045    *         profileseq marked as hidden.
1046    */
1047   public static HiddenColumns propagateInsertions(SequenceI profileseq,
1048           AlignmentI al, AlignmentView input)
1049   {
1050     int profsqpos = 0;
1051
1052     char gc = al.getGapCharacter();
1053     Object[] alandhidden = input.getAlignmentAndHiddenColumns(gc);
1054     HiddenColumns nview = (HiddenColumns) alandhidden[1];
1055     SequenceI origseq = ((SequenceI[]) alandhidden[0])[profsqpos];
1056     nview.propagateInsertions(profileseq, al, origseq);
1057     return nview;
1058   }
1059
1060   /**
1061    * 
1062    * @param profileseq
1063    *          - sequence in al which corresponds to origseq
1064    * @param al
1065    *          - alignment which is to have gaps inserted into it
1066    * @param origseq
1067    *          - sequence corresponding to profileseq which defines gap map for
1068    *          modifying al
1069    */
1070   private void propagateInsertions(SequenceI profileseq, AlignmentI al,
1071           SequenceI origseq)
1072   {
1073     char gc = al.getGapCharacter();
1074     // recover mapping between sequence's non-gap positions and positions
1075     // mapping to view.
1076     pruneDeletions(ShiftList.parseMap(origseq.gapMap()));
1077     int[] viscontigs = al.getHiddenColumns().getVisibleContigs(0,
1078             profileseq.getLength());
1079     int spos = 0;
1080     int offset = 0;
1081
1082     // add profile to visible contigs
1083     for (int v = 0; v < viscontigs.length; v += 2)
1084     {
1085       if (viscontigs[v] > spos)
1086       {
1087         StringBuffer sb = new StringBuffer();
1088         for (int s = 0, ns = viscontigs[v] - spos; s < ns; s++)
1089         {
1090           sb.append(gc);
1091         }
1092         for (int s = 0, ns = al.getHeight(); s < ns; s++)
1093         {
1094           SequenceI sqobj = al.getSequenceAt(s);
1095           if (sqobj != profileseq)
1096           {
1097             String sq = al.getSequenceAt(s).getSequenceAsString();
1098             if (sq.length() <= spos + offset)
1099             {
1100               // pad sequence
1101               int diff = spos + offset - sq.length() - 1;
1102               if (diff > 0)
1103               {
1104                 // pad gaps
1105                 sq = sq + sb;
1106                 while ((diff = spos + offset - sq.length() - 1) > 0)
1107                 {
1108                   // sq = sq
1109                   // + ((diff >= sb.length()) ? sb.toString() : sb
1110                   // .substring(0, diff));
1111                   if (diff >= sb.length())
1112                   {
1113                     sq += sb.toString();
1114                   }
1115                   else
1116                   {
1117                     char[] buf = new char[diff];
1118                     sb.getChars(0, diff, buf, 0);
1119                     sq += buf.toString();
1120                   }
1121                 }
1122               }
1123               sq += sb.toString();
1124             }
1125             else
1126             {
1127               al.getSequenceAt(s).setSequence(
1128                       sq.substring(0, spos + offset) + sb.toString()
1129                               + sq.substring(spos + offset));
1130             }
1131           }
1132         }
1133         // offset+=sb.length();
1134       }
1135       spos = viscontigs[v + 1] + 1;
1136     }
1137     if ((offset + spos) < profileseq.getLength())
1138     {
1139       // pad the final region with gaps.
1140       StringBuffer sb = new StringBuffer();
1141       for (int s = 0, ns = profileseq.getLength() - spos - offset; s < ns; s++)
1142       {
1143         sb.append(gc);
1144       }
1145       for (int s = 0, ns = al.getHeight(); s < ns; s++)
1146       {
1147         SequenceI sqobj = al.getSequenceAt(s);
1148         if (sqobj == profileseq)
1149         {
1150           continue;
1151         }
1152         String sq = sqobj.getSequenceAsString();
1153         // pad sequence
1154         int diff = origseq.getLength() - sq.length();
1155         while (diff > 0)
1156         {
1157           // sq = sq
1158           // + ((diff >= sb.length()) ? sb.toString() : sb
1159           // .substring(0, diff));
1160           if (diff >= sb.length())
1161           {
1162             sq += sb.toString();
1163           }
1164           else
1165           {
1166             char[] buf = new char[diff];
1167             sb.getChars(0, diff, buf, 0);
1168             sq += buf.toString();
1169           }
1170           diff = origseq.getLength() - sq.length();
1171         }
1172       }
1173     }
1174   }
1175
1176   /**
1177    * remove any hiddenColumns or selected columns and shift remaining based on a
1178    * series of position, range deletions.
1179    * 
1180    * @param deletions
1181    */
1182   private void pruneDeletions(ShiftList deletions)
1183   {
1184     if (deletions != null)
1185     {
1186       final List<int[]> shifts = deletions.getShifts();
1187       if (shifts != null && shifts.size() > 0)
1188       {
1189         pruneDeletions(shifts);
1190
1191         // and shift the rest.
1192         this.compensateForEdits(deletions);
1193       }
1194     }
1195   }
1196
1197   /**
1198    * Adjust hidden column boundaries based on a series of column additions or
1199    * deletions in visible regions.
1200    * 
1201    * @param shiftrecord
1202    * @return
1203    */
1204   private ShiftList compensateForEdits(ShiftList shiftrecord)
1205   {
1206     if (shiftrecord != null)
1207     {
1208       final List<int[]> shifts = shiftrecord.getShifts();
1209       if (shifts != null && shifts.size() > 0)
1210       {
1211         int shifted = 0;
1212         for (int i = 0, j = shifts.size(); i < j; i++)
1213         {
1214           int[] sh = shifts.get(i);
1215           compensateForDelEdits(shifted + sh[0], sh[1]);
1216           shifted -= sh[1];
1217         }
1218       }
1219       return shiftrecord.getInverse();
1220     }
1221     return null;
1222   }
1223
1224   /**
1225    * Returns a hashCode built from hidden column ranges
1226    */
1227   public int hashCode(int hc)
1228   {
1229     int hashCode = hc;
1230     if (hiddenColumns != null)
1231     {
1232       for (int[] hidden : hiddenColumns)
1233       {
1234         hashCode = 31 * hashCode + hidden[0];
1235         hashCode = 31 * hashCode + hidden[1];
1236       }
1237     }
1238     return hashCode;
1239   }
1240
1241 }