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