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