JAL-2001 JAL-1722 - store selection as ArrayList and BitSet for fast addition of...
[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.ShiftList;
24 import jalview.viewmodel.annotationfilter.AnnotationFilterParameter;
25 import jalview.viewmodel.annotationfilter.AnnotationFilterParameter.SearchableAnnotationField;
26
27 import java.util.ArrayList;
28 import java.util.BitSet;
29 import java.util.Collections;
30 import java.util.List;
31 import java.util.Vector;
32
33 /**
34  * NOTE: Columns are zero based.
35  */
36 public class ColumnSelection
37 {
38   private class IntList
39   {
40     /*
41      * list of selected columns (ordered by selection order, not column order)
42      */
43     private List<Integer> order = new ArrayList<Integer>();
44
45     /**
46      * bitfield for column selection - allows quick lookup
47      */
48     private BitSet selected = new BitSet();
49
50     /**
51      * adds a new column i to the selection - only if i is not already selected
52      * 
53      * @param i
54      */
55     public void add(int i)
56     {
57       if (!selected.get(i))
58       {
59         order.add(Integer.valueOf(i));
60         selected.set(i);
61       }
62     }
63
64     public void clear()
65     {
66       order.clear();
67       selected.clear();
68     }
69
70     public void remove(int col)
71     {
72
73       Integer colInt = new Integer(col);
74
75       if (selected.get(col))
76       {
77         // if this ever changes to List.remove(), ensure Integer not int
78         // argument
79         // as List.remove(int i) removes the i'th item which is wrong
80         order.remove(colInt);
81         selected.clear(col);
82       }
83     }
84
85     public boolean contains(Integer colInt)
86     {
87       return selected.get(colInt);
88     }
89
90     public boolean isEmpty()
91     {
92       return order.isEmpty();
93     }
94
95     public List<Integer> getList()
96     {
97       return order;
98     }
99
100     public int size()
101     {
102       return order.size();
103     }
104
105     /**
106      * gets the column that was selected first, second or i'th
107      * 
108      * @param i
109      * @return
110      */
111     public int elementAt(int i)
112     {
113       return order.get(i);
114     }
115
116     protected boolean pruneColumnList(final List<int[]> shifts)
117     {
118       int s = 0, t = shifts.size();
119       int[] sr = shifts.get(s++);
120       boolean pruned = false;
121       int i = 0, j = order.size();
122       while (i < j && s <= t)
123       {
124         int c = order.get(i++).intValue();
125         if (sr[0] <= c)
126         {
127           if (sr[1] + sr[0] >= c)
128           { // sr[1] -ve means inseriton.
129             order.remove(--i);
130             selected.clear(c);
131             j--;
132           }
133           else
134           {
135             if (s < t)
136             {
137               sr = shifts.get(s);
138             }
139             s++;
140           }
141         }
142       }
143       return pruned;
144     }
145
146     /**
147      * shift every selected column at or above start by change
148      * 
149      * @param start
150      *          - leftmost column to be shifted
151      * @param change
152      *          - delta for shift
153      */
154     public void compensateForEdits(int start, int change)
155     {
156       BitSet mask = new BitSet();
157       for (int i = 0; i < order.size(); i++)
158       {
159         int temp = order.get(i);
160
161         if (temp >= start)
162         {
163           // clear shifted bits and update List of selected columns
164           selected.clear(temp);
165           mask.set(temp - change);
166           order.set(i, new Integer(temp - change));
167         }
168       }
169       // lastly update the bitfield all at once
170       selected.or(mask);
171     }
172   }
173
174   IntList selected = new IntList();
175   /*
176    * list of hidden column [start, end] ranges; the list is maintained in
177    * ascending start column order
178    */
179   Vector<int[]> hiddenColumns;
180
181   /**
182    * Add a column to the selection
183    * 
184    * @param col
185    *          index of column
186    */
187   public void addElement(int col)
188   {
189     selected.add(col);
190   }
191
192   /**
193    * clears column selection
194    */
195   public void clear()
196   {
197     selected.clear();
198   }
199
200   /**
201    * Removes value 'col' from the selection (not the col'th item)
202    * 
203    * @param col
204    *          index of column to be removed
205    */
206   public void removeElement(int col)
207   {
208     selected.remove(col);
209   }
210
211   /**
212    * removes a range of columns from the selection
213    * 
214    * @param start
215    *          int - first column in range to be removed
216    * @param end
217    *          int - last col
218    */
219   public void removeElements(int start, int end)
220   {
221     Integer colInt;
222     for (int i = start; i < end; i++)
223     {
224       colInt = new Integer(i);
225       if (selected.contains(colInt))
226       {
227         selected.remove(colInt);
228       }
229     }
230   }
231
232   /**
233    * Returns a list of selected columns. The list contains no duplicates but is
234    * not necessarily ordered.
235    */
236   public List<Integer> getSelected()
237   {
238     return selected.getList();
239   }
240
241   /**
242    * 
243    * @param col
244    *          index to search for in column selection
245    * 
246    * @return true if Integer(col) is in selection.
247    */
248   public boolean contains(int col)
249   {
250     return selected.contains(new Integer(col));
251   }
252
253   /**
254    * Answers true if no columns are selected, else false
255    */
256   public boolean isEmpty()
257   {
258     return selected == null || selected.isEmpty();
259   }
260
261   /**
262    * rightmost selected column
263    * 
264    * @return rightmost column in alignment that is selected
265    */
266   public int getMax()
267   {
268     int max = -1;
269
270     for (int sel : getSelected())
271     {
272       if (sel > max)
273       {
274         max = sel;
275       }
276     }
277
278     return max;
279   }
280
281   /**
282    * Leftmost column in selection
283    * 
284    * @return column index of leftmost column in selection
285    */
286   public int getMin()
287   {
288     int min = 1000000000;
289
290     for (int sel : getSelected())
291     {
292       if (sel < min)
293       {
294         min = sel;
295       }
296     }
297
298     return min;
299   }
300
301   /**
302    * propagate shift in alignment columns to column selection
303    * 
304    * @param start
305    *          beginning of edit
306    * @param left
307    *          shift in edit (+ve for removal, or -ve for inserts)
308    */
309   public List<int[]> compensateForEdit(int start, int change)
310   {
311     List<int[]> deletedHiddenColumns = null;
312     selected.compensateForEdits(start, change);
313
314     if (hiddenColumns != null)
315     {
316       deletedHiddenColumns = new ArrayList<int[]>();
317       int hSize = hiddenColumns.size();
318       for (int i = 0; i < hSize; i++)
319       {
320         int[] region = hiddenColumns.elementAt(i);
321         if (region[0] > start && start + change > region[1])
322         {
323           deletedHiddenColumns.add(region);
324
325           hiddenColumns.removeElementAt(i);
326           i--;
327           hSize--;
328           continue;
329         }
330
331         if (region[0] > start)
332         {
333           region[0] -= change;
334           region[1] -= change;
335         }
336
337         if (region[0] < 0)
338         {
339           region[0] = 0;
340         }
341
342       }
343
344       this.revealHiddenColumns(0);
345     }
346
347     return deletedHiddenColumns;
348   }
349
350   /**
351    * propagate shift in alignment columns to column selection special version of
352    * compensateForEdit - allowing for edits within hidden regions
353    * 
354    * @param start
355    *          beginning of edit
356    * @param left
357    *          shift in edit (+ve for removal, or -ve for inserts)
358    */
359   private void compensateForDelEdits(int start, int change)
360   {
361
362     selected.compensateForEdits(start, change);
363
364     if (hiddenColumns != null)
365     {
366       for (int i = 0; i < hiddenColumns.size(); i++)
367       {
368         int[] region = hiddenColumns.elementAt(i);
369         if (region[0] >= start)
370         {
371           region[0] -= change;
372         }
373         if (region[1] >= start)
374         {
375           region[1] -= change;
376         }
377         if (region[1] < region[0])
378         {
379           hiddenColumns.removeElementAt(i--);
380         }
381
382         if (region[0] < 0)
383         {
384           region[0] = 0;
385         }
386         if (region[1] < 0)
387         {
388           region[1] = 0;
389         }
390       }
391     }
392   }
393
394   /**
395    * Adjust hidden column boundaries based on a series of column additions or
396    * deletions in visible regions.
397    * 
398    * @param shiftrecord
399    * @return
400    */
401   public ShiftList compensateForEdits(ShiftList shiftrecord)
402   {
403     if (shiftrecord != null)
404     {
405       final List<int[]> shifts = shiftrecord.getShifts();
406       if (shifts != null && shifts.size() > 0)
407       {
408         int shifted = 0;
409         for (int i = 0, j = shifts.size(); i < j; i++)
410         {
411           int[] sh = shifts.get(i);
412           // compensateForEdit(shifted+sh[0], sh[1]);
413           compensateForDelEdits(shifted + sh[0], sh[1]);
414           shifted -= sh[1];
415         }
416       }
417       return shiftrecord.getInverse();
418     }
419     return null;
420   }
421
422   /**
423    * removes intersection of position,length ranges in deletions from the
424    * start,end regions marked in intervals.
425    * 
426    * @param shifts
427    * @param intervals
428    * @return
429    */
430   private boolean pruneIntervalVector(final List<int[]> shifts,
431           Vector<int[]> intervals)
432   {
433     boolean pruned = false;
434     int i = 0, j = intervals.size() - 1, s = 0, t = shifts.size() - 1;
435     int hr[] = intervals.elementAt(i);
436     int sr[] = shifts.get(s);
437     while (i <= j && s <= t)
438     {
439       boolean trailinghn = hr[1] >= sr[0];
440       if (!trailinghn)
441       {
442         if (i < j)
443         {
444           hr = intervals.elementAt(++i);
445         }
446         else
447         {
448           i++;
449         }
450         continue;
451       }
452       int endshift = sr[0] + sr[1]; // deletion ranges - -ve means an insert
453       if (endshift < hr[0] || endshift < sr[0])
454       { // leadinghc disjoint or not a deletion
455         if (s < t)
456         {
457           sr = shifts.get(++s);
458         }
459         else
460         {
461           s++;
462         }
463         continue;
464       }
465       boolean leadinghn = hr[0] >= sr[0];
466       boolean leadinghc = hr[0] < endshift;
467       boolean trailinghc = hr[1] < endshift;
468       if (leadinghn)
469       {
470         if (trailinghc)
471         { // deleted hidden region.
472           intervals.removeElementAt(i);
473           pruned = true;
474           j--;
475           if (i <= j)
476           {
477             hr = intervals.elementAt(i);
478           }
479           continue;
480         }
481         if (leadinghc)
482         {
483           hr[0] = endshift; // clip c terminal region
484           leadinghn = !leadinghn;
485           pruned = true;
486         }
487       }
488       if (!leadinghn)
489       {
490         if (trailinghc)
491         {
492           if (trailinghn)
493           {
494             hr[1] = sr[0] - 1;
495             pruned = true;
496           }
497         }
498         else
499         {
500           // sr contained in hr
501           if (s < t)
502           {
503             sr = shifts.get(++s);
504           }
505           else
506           {
507             s++;
508           }
509           continue;
510         }
511       }
512     }
513     return pruned; // true if any interval was removed or modified by
514     // operations.
515   }
516
517   /**
518    * remove any hiddenColumns or selected columns and shift remaining based on a
519    * series of position, range deletions.
520    * 
521    * @param deletions
522    */
523   public void pruneDeletions(ShiftList deletions)
524   {
525     if (deletions != null)
526     {
527       final List<int[]> shifts = deletions.getShifts();
528       if (shifts != null && shifts.size() > 0)
529       {
530         // delete any intervals intersecting.
531         if (hiddenColumns != null)
532         {
533           pruneIntervalVector(shifts, hiddenColumns);
534           if (hiddenColumns != null && hiddenColumns.size() == 0)
535           {
536             hiddenColumns = null;
537           }
538         }
539         if (selected != null && selected.size() > 0)
540         {
541           selected.pruneColumnList(shifts);
542           if (selected != null && selected.size() == 0)
543           {
544             selected = null;
545           }
546         }
547         // and shift the rest.
548         this.compensateForEdits(deletions);
549       }
550     }
551   }
552
553   /**
554    * This Method is used to return all the HiddenColumn regions
555    * 
556    * @return empty list or List of hidden column intervals
557    */
558   public List<int[]> getHiddenColumns()
559   {
560     return hiddenColumns == null ? Collections.<int[]> emptyList()
561             : hiddenColumns;
562   }
563
564   /**
565    * Return absolute column index for a visible column index
566    * 
567    * @param column
568    *          int column index in alignment view
569    * @return alignment column index for column
570    */
571   public int adjustForHiddenColumns(int column)
572   {
573     int result = column;
574     if (hiddenColumns != null)
575     {
576       for (int i = 0; i < hiddenColumns.size(); i++)
577       {
578         int[] region = hiddenColumns.elementAt(i);
579         if (result >= region[0])
580         {
581           result += region[1] - region[0] + 1;
582         }
583       }
584     }
585     return result;
586   }
587
588   /**
589    * Use this method to find out where a column will appear in the visible
590    * alignment when hidden columns exist. If the column is not visible, then the
591    * left-most visible column will always be returned.
592    * 
593    * @param hiddenColumn
594    *          int
595    * @return int
596    */
597   public int findColumnPosition(int hiddenColumn)
598   {
599     int result = hiddenColumn;
600     if (hiddenColumns != null)
601     {
602       int index = 0;
603       int[] region;
604       do
605       {
606         region = hiddenColumns.elementAt(index++);
607         if (hiddenColumn > region[1])
608         {
609           result -= region[1] + 1 - region[0];
610         }
611       } while ((hiddenColumn > region[1]) && (index < hiddenColumns.size()));
612       if (hiddenColumn > region[0] && hiddenColumn < region[1])
613       {
614         return region[0] + hiddenColumn - result;
615       }
616     }
617     return result; // return the shifted position after removing hidden columns.
618   }
619
620   /**
621    * Use this method to determine where the next hiddenRegion starts
622    */
623   public int findHiddenRegionPosition(int hiddenRegion)
624   {
625     int result = 0;
626     if (hiddenColumns != null)
627     {
628       int index = 0;
629       int gaps = 0;
630       do
631       {
632         int[] region = hiddenColumns.elementAt(index);
633         if (hiddenRegion == 0)
634         {
635           return region[0];
636         }
637
638         gaps += region[1] + 1 - region[0];
639         result = region[1] + 1;
640         index++;
641       } while (index < hiddenRegion + 1);
642
643       result -= gaps;
644     }
645
646     return result;
647   }
648
649   /**
650    * THis method returns the rightmost limit of a region of an alignment with
651    * hidden columns. In otherwords, the next hidden column.
652    * 
653    * @param index
654    *          int
655    */
656   public int getHiddenBoundaryRight(int alPos)
657   {
658     if (hiddenColumns != null)
659     {
660       int index = 0;
661       do
662       {
663         int[] region = hiddenColumns.elementAt(index);
664         if (alPos < region[0])
665         {
666           return region[0];
667         }
668
669         index++;
670       } while (index < hiddenColumns.size());
671     }
672
673     return alPos;
674
675   }
676
677   /**
678    * This method returns the leftmost limit of a region of an alignment with
679    * hidden columns. In otherwords, the previous hidden column.
680    * 
681    * @param index
682    *          int
683    */
684   public int getHiddenBoundaryLeft(int alPos)
685   {
686     if (hiddenColumns != null)
687     {
688       int index = hiddenColumns.size() - 1;
689       do
690       {
691         int[] region = hiddenColumns.elementAt(index);
692         if (alPos > region[1])
693         {
694           return region[1];
695         }
696
697         index--;
698       } while (index > -1);
699     }
700
701     return alPos;
702
703   }
704
705   public void hideSelectedColumns()
706   {
707     while (!selected.isEmpty())
708     {
709       int column = selected.elementAt(0);
710       hideColumns(column);
711     }
712
713   }
714
715   /**
716    * Adds the specified column range to the hidden columns
717    * 
718    * @param start
719    * @param end
720    */
721   public void hideColumns(int start, int end)
722   {
723     if (hiddenColumns == null)
724     {
725       hiddenColumns = new Vector<int[]>();
726     }
727
728     /*
729      * traverse existing hidden ranges and insert / amend / append as
730      * appropriate
731      */
732     for (int i = 0; i < hiddenColumns.size(); i++)
733     {
734       int[] region = hiddenColumns.elementAt(i);
735
736       if (end < region[0] - 1)
737       {
738         /*
739          * insert discontiguous preceding range
740          */
741         hiddenColumns.insertElementAt(new int[] { start, end }, i);
742         return;
743       }
744
745       if (end <= region[1])
746       {
747         /*
748          * new range overlaps existing, or is contiguous preceding it - adjust
749          * start column
750          */
751         region[0] = Math.min(region[0], start);
752         return;
753       }
754
755       if (start <= region[1] + 1)
756       {
757         /*
758          * new range overlaps existing, or is contiguous following it - adjust
759          * start and end columns
760          */
761         region[0] = Math.min(region[0], start);
762         region[1] = Math.max(region[1], end);
763         return;
764       }
765     }
766
767     /*
768      * remaining case is that the new range follows everything else
769      */
770       hiddenColumns.addElement(new int[] { start, end });
771   }
772
773   /**
774    * Hides the specified column and any adjacent selected columns
775    * 
776    * @param res
777    *          int
778    */
779   public void hideColumns(int col)
780   {
781     /*
782      * deselect column (whether selected or not!)
783      */
784     removeElement(col);
785
786     /*
787      * find adjacent selected columns
788      */
789     int min = col - 1, max = col + 1;
790     while (contains(min))
791     {
792       removeElement(min);
793       min--;
794     }
795
796     while (contains(max))
797     {
798       removeElement(max);
799       max++;
800     }
801
802     /*
803      * min, max are now the closest unselected columns
804      */
805     min++;
806     max--;
807     if (min > max)
808     {
809       min = max;
810     }
811
812     hideColumns(min, max);
813   }
814
815   /**
816    * Unhides, and adds to the selection list, all hidden columns
817    */
818   public void revealAllHiddenColumns()
819   {
820     if (hiddenColumns != null)
821     {
822       for (int i = 0; i < hiddenColumns.size(); i++)
823       {
824         int[] region = hiddenColumns.elementAt(i);
825         for (int j = region[0]; j < region[1] + 1; j++)
826         {
827           addElement(j);
828         }
829       }
830     }
831
832     hiddenColumns = null;
833   }
834
835   /**
836    * Reveals, and marks as selected, the hidden column range with the given
837    * start column
838    * 
839    * @param start
840    */
841   public void revealHiddenColumns(int start)
842   {
843     for (int i = 0; i < hiddenColumns.size(); i++)
844     {
845       int[] region = hiddenColumns.elementAt(i);
846       if (start == region[0])
847       {
848         for (int j = region[0]; j < region[1] + 1; j++)
849         {
850           addElement(j);
851         }
852
853         hiddenColumns.removeElement(region);
854         break;
855       }
856     }
857     if (hiddenColumns.size() == 0)
858     {
859       hiddenColumns = null;
860     }
861   }
862
863   public boolean isVisible(int column)
864   {
865     if (hiddenColumns != null)
866     {
867       for (int[] region : hiddenColumns)
868       {
869         if (column >= region[0] && column <= region[1])
870         {
871           return false;
872         }
873       }
874     }
875
876     return true;
877   }
878
879   /**
880    * Copy constructor
881    * 
882    * @param copy
883    */
884   public ColumnSelection(ColumnSelection copy)
885   {
886     if (copy != null)
887     {
888       if (copy.selected != null)
889       {
890         selected = new IntList();
891         for (int i = 0, j = copy.selected.size(); i < j; i++)
892         {
893           selected.add(copy.selected.elementAt(i));
894         }
895       }
896       if (copy.hiddenColumns != null)
897       {
898         hiddenColumns = new Vector<int[]>(copy.hiddenColumns.size());
899         for (int i = 0, j = copy.hiddenColumns.size(); i < j; i++)
900         {
901           int[] rh, cp;
902           rh = copy.hiddenColumns.elementAt(i);
903           if (rh != null)
904           {
905             cp = new int[rh.length];
906             System.arraycopy(rh, 0, cp, 0, rh.length);
907             hiddenColumns.addElement(cp);
908           }
909         }
910       }
911     }
912   }
913
914   /**
915    * ColumnSelection
916    */
917   public ColumnSelection()
918   {
919   }
920
921   public String[] getVisibleSequenceStrings(int start, int end,
922           SequenceI[] seqs)
923   {
924     int i, iSize = seqs.length;
925     String selection[] = new String[iSize];
926     if (hiddenColumns != null && hiddenColumns.size() > 0)
927     {
928       for (i = 0; i < iSize; i++)
929       {
930         StringBuffer visibleSeq = new StringBuffer();
931         List<int[]> regions = getHiddenColumns();
932
933         int blockStart = start, blockEnd = end;
934         int[] region;
935         int hideStart, hideEnd;
936
937         for (int j = 0; j < regions.size(); j++)
938         {
939           region = regions.get(j);
940           hideStart = region[0];
941           hideEnd = region[1];
942
943           if (hideStart < start)
944           {
945             continue;
946           }
947
948           blockStart = Math.min(blockStart, hideEnd + 1);
949           blockEnd = Math.min(blockEnd, hideStart);
950
951           if (blockStart > blockEnd)
952           {
953             break;
954           }
955
956           visibleSeq.append(seqs[i].getSequence(blockStart, blockEnd));
957
958           blockStart = hideEnd + 1;
959           blockEnd = end;
960         }
961
962         if (end > blockStart)
963         {
964           visibleSeq.append(seqs[i].getSequence(blockStart, end));
965         }
966
967         selection[i] = visibleSeq.toString();
968       }
969     }
970     else
971     {
972       for (i = 0; i < iSize; i++)
973       {
974         selection[i] = seqs[i].getSequenceAsString(start, end);
975       }
976     }
977
978     return selection;
979   }
980
981   /**
982    * return all visible segments between the given start and end boundaries
983    * 
984    * @param start
985    *          (first column inclusive from 0)
986    * @param end
987    *          (last column - not inclusive)
988    * @return int[] {i_start, i_end, ..} where intervals lie in
989    *         start<=i_start<=i_end<end
990    */
991   public int[] getVisibleContigs(int start, int end)
992   {
993     if (hiddenColumns != null && hiddenColumns.size() > 0)
994     {
995       List<int[]> visiblecontigs = new ArrayList<int[]>();
996       List<int[]> regions = getHiddenColumns();
997
998       int vstart = start;
999       int[] region;
1000       int hideStart, hideEnd;
1001
1002       for (int j = 0; vstart < end && j < regions.size(); j++)
1003       {
1004         region = regions.get(j);
1005         hideStart = region[0];
1006         hideEnd = region[1];
1007
1008         if (hideEnd < vstart)
1009         {
1010           continue;
1011         }
1012         if (hideStart > vstart)
1013         {
1014           visiblecontigs.add(new int[] { vstart, hideStart - 1 });
1015         }
1016         vstart = hideEnd + 1;
1017       }
1018
1019       if (vstart < end)
1020       {
1021         visiblecontigs.add(new int[] { vstart, end - 1 });
1022       }
1023       int[] vcontigs = new int[visiblecontigs.size() * 2];
1024       for (int i = 0, j = visiblecontigs.size(); i < j; i++)
1025       {
1026         int[] vc = visiblecontigs.get(i);
1027         visiblecontigs.set(i, null);
1028         vcontigs[i * 2] = vc[0];
1029         vcontigs[i * 2 + 1] = vc[1];
1030       }
1031       visiblecontigs.clear();
1032       return vcontigs;
1033     }
1034     else
1035     {
1036       return new int[] { start, end - 1 };
1037     }
1038   }
1039
1040   /**
1041    * delete any columns in alignmentAnnotation that are hidden (including
1042    * sequence associated annotation).
1043    * 
1044    * @param alignmentAnnotation
1045    */
1046   public void makeVisibleAnnotation(AlignmentAnnotation alignmentAnnotation)
1047   {
1048     makeVisibleAnnotation(-1, -1, alignmentAnnotation);
1049   }
1050
1051   /**
1052    * delete any columns in alignmentAnnotation that are hidden (including
1053    * sequence associated annotation).
1054    * 
1055    * @param start
1056    *          remove any annotation to the right of this column
1057    * @param end
1058    *          remove any annotation to the left of this column
1059    * @param alignmentAnnotation
1060    *          the annotation to operate on
1061    */
1062   public void makeVisibleAnnotation(int start, int end,
1063           AlignmentAnnotation alignmentAnnotation)
1064   {
1065     if (alignmentAnnotation.annotations == null)
1066     {
1067       return;
1068     }
1069     if (start == end && end == -1)
1070     {
1071       start = 0;
1072       end = alignmentAnnotation.annotations.length;
1073     }
1074     if (hiddenColumns != null && hiddenColumns.size() > 0)
1075     {
1076       // then mangle the alignmentAnnotation annotation array
1077       Vector<Annotation[]> annels = new Vector<Annotation[]>();
1078       Annotation[] els = null;
1079       List<int[]> regions = getHiddenColumns();
1080       int blockStart = start, blockEnd = end;
1081       int[] region;
1082       int hideStart, hideEnd, w = 0;
1083
1084       for (int j = 0; j < regions.size(); j++)
1085       {
1086         region = regions.get(j);
1087         hideStart = region[0];
1088         hideEnd = region[1];
1089
1090         if (hideStart < start)
1091         {
1092           continue;
1093         }
1094
1095         blockStart = Math.min(blockStart, hideEnd + 1);
1096         blockEnd = Math.min(blockEnd, hideStart);
1097
1098         if (blockStart > blockEnd)
1099         {
1100           break;
1101         }
1102
1103         annels.addElement(els = new Annotation[blockEnd - blockStart]);
1104         System.arraycopy(alignmentAnnotation.annotations, blockStart, els,
1105                 0, els.length);
1106         w += els.length;
1107         blockStart = hideEnd + 1;
1108         blockEnd = end;
1109       }
1110
1111       if (end > blockStart)
1112       {
1113         annels.addElement(els = new Annotation[end - blockStart + 1]);
1114         if ((els.length + blockStart) <= alignmentAnnotation.annotations.length)
1115         {
1116           // copy just the visible segment of the annotation row
1117           System.arraycopy(alignmentAnnotation.annotations, blockStart,
1118                   els, 0, els.length);
1119         }
1120         else
1121         {
1122           // copy to the end of the annotation row
1123           System.arraycopy(alignmentAnnotation.annotations, blockStart,
1124                   els, 0,
1125                   (alignmentAnnotation.annotations.length - blockStart));
1126         }
1127         w += els.length;
1128       }
1129       if (w == 0)
1130       {
1131         return;
1132       }
1133
1134       alignmentAnnotation.annotations = new Annotation[w];
1135       w = 0;
1136
1137       for (Annotation[] chnk : annels)
1138       {
1139         System.arraycopy(chnk, 0, alignmentAnnotation.annotations, w,
1140                 chnk.length);
1141         w += chnk.length;
1142       }
1143     }
1144     else
1145     {
1146       alignmentAnnotation.restrict(start, end);
1147     }
1148   }
1149
1150   /**
1151    * Invert the column selection from first to end-1. leaves hiddenColumns
1152    * untouched (and unselected)
1153    * 
1154    * @param first
1155    * @param end
1156    */
1157   public void invertColumnSelection(int first, int width)
1158   {
1159     boolean hasHidden = hiddenColumns != null && hiddenColumns.size() > 0;
1160     for (int i = first; i < width; i++)
1161     {
1162       if (contains(i))
1163       {
1164         removeElement(i);
1165       }
1166       else
1167       {
1168         if (!hasHidden || isVisible(i))
1169         {
1170           addElement(i);
1171         }
1172       }
1173     }
1174   }
1175
1176   /**
1177    * add in any unselected columns from the given column selection, excluding
1178    * any that are hidden.
1179    * 
1180    * @param colsel
1181    */
1182   public void addElementsFrom(ColumnSelection colsel)
1183   {
1184     if (colsel != null && !colsel.isEmpty())
1185     {
1186       for (Integer col : colsel.getSelected())
1187       {
1188         if (hiddenColumns != null && isVisible(col.intValue()))
1189         {
1190           selected.add(col);
1191         }
1192       }
1193     }
1194   }
1195
1196   /**
1197    * set the selected columns the given column selection, excluding any columns
1198    * that are hidden.
1199    * 
1200    * @param colsel
1201    */
1202   public void setElementsFrom(ColumnSelection colsel)
1203   {
1204     selected = new IntList();
1205     if (colsel.selected != null && colsel.selected.size() > 0)
1206     {
1207       if (hiddenColumns != null && hiddenColumns.size() > 0)
1208       {
1209         // only select visible columns in this columns selection
1210         addElementsFrom(colsel);
1211       }
1212       else
1213       {
1214         // add everything regardless
1215         for (Integer col : colsel.getSelected())
1216         {
1217           addElement(col);
1218         }
1219       }
1220     }
1221   }
1222
1223   /**
1224    * Add gaps into the sequences aligned to profileseq under the given
1225    * AlignmentView
1226    * 
1227    * @param profileseq
1228    * @param al
1229    *          - alignment to have gaps inserted into it
1230    * @param input
1231    *          - alignment view where sequence corresponding to profileseq is
1232    *          first entry
1233    * @return new Column selection for new alignment view, with insertions into
1234    *         profileseq marked as hidden.
1235    */
1236   public static ColumnSelection propagateInsertions(SequenceI profileseq,
1237           AlignmentI al, AlignmentView input)
1238   {
1239     int profsqpos = 0;
1240
1241     // return propagateInsertions(profileseq, al, )
1242     char gc = al.getGapCharacter();
1243     Object[] alandcolsel = input.getAlignmentAndColumnSelection(gc);
1244     ColumnSelection nview = (ColumnSelection) alandcolsel[1];
1245     SequenceI origseq = ((SequenceI[]) alandcolsel[0])[profsqpos];
1246     nview.propagateInsertions(profileseq, al, origseq);
1247     return nview;
1248   }
1249
1250   /**
1251    * 
1252    * @param profileseq
1253    *          - sequence in al which corresponds to origseq
1254    * @param al
1255    *          - alignment which is to have gaps inserted into it
1256    * @param origseq
1257    *          - sequence corresponding to profileseq which defines gap map for
1258    *          modifying al
1259    */
1260   public void propagateInsertions(SequenceI profileseq, AlignmentI al,
1261           SequenceI origseq)
1262   {
1263     char gc = al.getGapCharacter();
1264     // recover mapping between sequence's non-gap positions and positions
1265     // mapping to view.
1266     pruneDeletions(ShiftList.parseMap(origseq.gapMap()));
1267     int[] viscontigs = getVisibleContigs(0, profileseq.getLength());
1268     int spos = 0;
1269     int offset = 0;
1270     // input.pruneDeletions(ShiftList.parseMap(((SequenceI[])
1271     // alandcolsel[0])[0].gapMap()))
1272     // add profile to visible contigs
1273     for (int v = 0; v < viscontigs.length; v += 2)
1274     {
1275       if (viscontigs[v] > spos)
1276       {
1277         StringBuffer sb = new StringBuffer();
1278         for (int s = 0, ns = viscontigs[v] - spos; s < ns; s++)
1279         {
1280           sb.append(gc);
1281         }
1282         for (int s = 0, ns = al.getHeight(); s < ns; s++)
1283         {
1284           SequenceI sqobj = al.getSequenceAt(s);
1285           if (sqobj != profileseq)
1286           {
1287             String sq = al.getSequenceAt(s).getSequenceAsString();
1288             if (sq.length() <= spos + offset)
1289             {
1290               // pad sequence
1291               int diff = spos + offset - sq.length() - 1;
1292               if (diff > 0)
1293               {
1294                 // pad gaps
1295                 sq = sq + sb;
1296                 while ((diff = spos + offset - sq.length() - 1) > 0)
1297                 {
1298                   // sq = sq
1299                   // + ((diff >= sb.length()) ? sb.toString() : sb
1300                   // .substring(0, diff));
1301                   if (diff >= sb.length())
1302                   {
1303                     sq += sb.toString();
1304                   }
1305                   else
1306                   {
1307                     char[] buf = new char[diff];
1308                     sb.getChars(0, diff, buf, 0);
1309                     sq += buf.toString();
1310                   }
1311                 }
1312               }
1313               sq += sb.toString();
1314             }
1315             else
1316             {
1317               al.getSequenceAt(s).setSequence(
1318                       sq.substring(0, spos + offset) + sb.toString()
1319                               + sq.substring(spos + offset));
1320             }
1321           }
1322         }
1323         // offset+=sb.length();
1324       }
1325       spos = viscontigs[v + 1] + 1;
1326     }
1327     if ((offset + spos) < profileseq.getLength())
1328     {
1329       // pad the final region with gaps.
1330       StringBuffer sb = new StringBuffer();
1331       for (int s = 0, ns = profileseq.getLength() - spos - offset; s < ns; s++)
1332       {
1333         sb.append(gc);
1334       }
1335       for (int s = 0, ns = al.getHeight(); s < ns; s++)
1336       {
1337         SequenceI sqobj = al.getSequenceAt(s);
1338         if (sqobj == profileseq)
1339         {
1340           continue;
1341         }
1342         String sq = sqobj.getSequenceAsString();
1343         // pad sequence
1344         int diff = origseq.getLength() - sq.length();
1345         while (diff > 0)
1346         {
1347           // sq = sq
1348           // + ((diff >= sb.length()) ? sb.toString() : sb
1349           // .substring(0, diff));
1350           if (diff >= sb.length())
1351           {
1352             sq += sb.toString();
1353           }
1354           else
1355           {
1356             char[] buf = new char[diff];
1357             sb.getChars(0, diff, buf, 0);
1358             sq += buf.toString();
1359           }
1360           diff = origseq.getLength() - sq.length();
1361         }
1362       }
1363     }
1364   }
1365
1366   /**
1367    * 
1368    * @return true if there are columns marked
1369    */
1370   public boolean hasSelectedColumns()
1371   {
1372     return (selected != null && selected.size() > 0);
1373   }
1374
1375   /**
1376    * 
1377    * @return true if there are columns hidden
1378    */
1379   public boolean hasHiddenColumns()
1380   {
1381     return hiddenColumns != null && hiddenColumns.size() > 0;
1382   }
1383
1384   /**
1385    * 
1386    * @return true if there are more than one set of columns hidden
1387    */
1388   public boolean hasManyHiddenColumns()
1389   {
1390     return hiddenColumns != null && hiddenColumns.size() > 1;
1391   }
1392
1393   /**
1394    * mark the columns corresponding to gap characters as hidden in the column
1395    * selection
1396    * 
1397    * @param sr
1398    */
1399   public void hideInsertionsFor(SequenceI sr)
1400   {
1401     List<int[]> inserts = sr.getInsertions();
1402     for (int[] r : inserts)
1403     {
1404       hideColumns(r[0], r[1]);
1405     }
1406   }
1407
1408   public boolean filterAnnotations(Annotation[] annotations,
1409           AnnotationFilterParameter filterParams)
1410   {
1411     this.revealAllHiddenColumns();
1412     this.clear();
1413     int count = 0;
1414     do
1415     {
1416       if (annotations[count] != null)
1417       {
1418
1419         boolean itemMatched = false;
1420
1421         if (filterParams.getThresholdType() == AnnotationFilterParameter.ThresholdType.ABOVE_THRESHOLD
1422                 && annotations[count].value >= filterParams
1423                         .getThresholdValue())
1424         {
1425           itemMatched = true;
1426         }
1427         if (filterParams.getThresholdType() == AnnotationFilterParameter.ThresholdType.BELOW_THRESHOLD
1428                 && annotations[count].value <= filterParams
1429                         .getThresholdValue())
1430         {
1431           itemMatched = true;
1432         }
1433
1434         if (filterParams.isFilterAlphaHelix()
1435                 && annotations[count].secondaryStructure == 'H')
1436         {
1437           itemMatched = true;
1438         }
1439
1440         if (filterParams.isFilterBetaSheet()
1441                 && annotations[count].secondaryStructure == 'E')
1442         {
1443           itemMatched = true;
1444         }
1445
1446         if (filterParams.isFilterTurn()
1447                 && annotations[count].secondaryStructure == 'S')
1448         {
1449           itemMatched = true;
1450         }
1451
1452         String regexSearchString = filterParams.getRegexString();
1453         if (regexSearchString != null
1454                 && !filterParams.getRegexSearchFields().isEmpty())
1455         {
1456           List<SearchableAnnotationField> fields = filterParams
1457                   .getRegexSearchFields();
1458           try
1459           {
1460             if (fields.contains(SearchableAnnotationField.DISPLAY_STRING)
1461                     && annotations[count].displayCharacter
1462                             .matches(regexSearchString))
1463             {
1464               itemMatched = true;
1465             }
1466           } catch (java.util.regex.PatternSyntaxException pse)
1467           {
1468             if (annotations[count].displayCharacter
1469                     .equals(regexSearchString))
1470             {
1471               itemMatched = true;
1472             }
1473           }
1474           if (fields.contains(SearchableAnnotationField.DESCRIPTION)
1475                   && annotations[count].description != null
1476                   && annotations[count].description
1477                           .matches(regexSearchString))
1478           {
1479             itemMatched = true;
1480           }
1481         }
1482
1483         if (itemMatched)
1484         {
1485           this.addElement(count);
1486         }
1487       }
1488       count++;
1489     } while (count < annotations.length);
1490     return false;
1491   }
1492 }