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