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