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