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