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