JAL-845 further code/tests/refactoring
[jalview.git] / src / jalview / datamodel / ColumnSelection.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
3  * Copyright (C) 2014 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
25 import java.util.ArrayList;
26 import java.util.List;
27
28 /**
29  * NOTE: Columns are zero based.
30  */
31 public class ColumnSelection
32 {
33   /*
34    * Because autoboxing can convert int to Integer, have to be very careful
35    * using methods remove(int), remove(Integer) and set(int, Integer).
36    * Verification is in unit tests.
37    */
38   List<Integer> selected = new ArrayList<Integer>();
39
40   /*
41    * List of {startCol, endCol} hidden column ranges
42    */
43   List<int[]> hiddenColumns;
44
45   /**
46    * Add a column to the selection
47    * 
48    * @param col
49    *          index of column
50    */
51   public void addElement(int col)
52   {
53     Integer column = new Integer(col);
54     if (!selected.contains(column))
55     {
56       selected.add(column);
57     }
58   }
59
60   /**
61    * clears column selection
62    */
63   public void clear()
64   {
65     selected.clear();
66   }
67
68   /**
69    * Removes column 'col' from selection (NOT the col'th item).
70    * 
71    * @param col
72    *          index of column to be removed
73    */
74   public void removeElement(int col)
75   {
76     selected.remove(new Integer(col));
77   }
78
79   /**
80    * removes a range of columns from the selection
81    * 
82    * @param start
83    *          int - first column in range to be removed
84    * @param end
85    *          int - last col
86    */
87   public void removeElements(int start, int end)
88   {
89     Integer colInt;
90     for (int i = start; i < end; i++)
91     {
92       colInt = new Integer(i);
93       if (selected.contains(colInt))
94       {
95         selected.remove(colInt);
96       }
97     }
98   }
99
100   /**
101    * 
102    * @return list containing selected columns as Integers
103    */
104   public List<Integer> getSelected()
105   {
106     return selected;
107   }
108
109   /**
110    * 
111    * @param col
112    *          index to search for in column selection
113    * 
114    * @return true if Integer(col) is in selection.
115    */
116   public boolean contains(int col)
117   {
118     return selected.contains(new Integer(col));
119   }
120
121   /**
122    * Column number at position i in selection
123    * 
124    * @param i
125    *          index into selected columns
126    * 
127    * @return column number in alignment
128    */
129   public int columnAt(int i)
130   {
131     // autoboxing Integer -> int
132     return selected.get(i);
133   }
134
135   /**
136    * DOCUMENT ME!
137    * 
138    * @return DOCUMENT ME!
139    */
140   public int size()
141   {
142     return selected.size();
143   }
144
145   /**
146    * rightmost selected column
147    * 
148    * @return rightmost column in alignment that is selected
149    */
150   public int getMax()
151   {
152     int max = -1;
153
154     for (int i = 0; i < selected.size(); i++)
155     {
156       if (columnAt(i) > max)
157       {
158         max = columnAt(i);
159       }
160     }
161
162     return max;
163   }
164
165   /**
166    * Leftmost column in selection
167    * 
168    * @return column index of leftmost column in selection
169    */
170   public int getMin()
171   {
172     int min = Integer.MAX_VALUE;
173
174     for (int i = 0; i < selected.size(); i++)
175     {
176       if (columnAt(i) < min)
177       {
178         min = columnAt(i);
179       }
180     }
181
182     return min;
183   }
184
185   /**
186    * propagate shift in alignment columns to column selection
187    * 
188    * @param start
189    *          beginning of edit
190    * @param left
191    *          shift in edit (+ve for removal, or -ve for inserts)
192    */
193   public List<int[]> compensateForEdit(int start, int change)
194   {
195     List<int[]> deletedHiddenColumns = null;
196     for (int i = 0; i < size(); i++)
197     {
198       int temp = columnAt(i);
199
200       if (temp >= start)
201       {
202         // i = index here!
203         selected.set(i, new Integer(temp - change));
204       }
205     }
206
207     if (hiddenColumns != null)
208     {
209       deletedHiddenColumns = new ArrayList<int[]>();
210       int hSize = hiddenColumns.size();
211       for (int i = 0; i < hSize; i++)
212       {
213         int[] region = hiddenColumns.get(i);
214         if (region[0] > start && start + change > region[1])
215         {
216           deletedHiddenColumns.add(region);
217
218           hiddenColumns.remove(i);
219           i--;
220           hSize--;
221           continue;
222         }
223
224         if (region[0] > start)
225         {
226           region[0] -= change;
227           region[1] -= change;
228         }
229
230         if (region[0] < 0)
231         {
232           region[0] = 0;
233         }
234
235       }
236
237       this.revealHiddenColumns(0);
238     }
239
240     return deletedHiddenColumns;
241   }
242
243   /**
244    * propagate shift in alignment columns to column selection special version of
245    * compensateForEdit - allowing for edits within hidden regions
246    * 
247    * @param start
248    *          beginning of edit
249    * @param left
250    *          shift in edit (+ve for removal, or -ve for inserts)
251    */
252   private void compensateForDelEdits(int start, int change)
253   {
254     for (int i = 0; i < size(); i++)
255     {
256       int temp = columnAt(i);
257
258       if (temp >= start)
259       {
260         // i is index position in list here!
261         selected.set(i, new Integer(temp - change));
262       }
263     }
264
265     if (hiddenColumns != null)
266     {
267       for (int i = 0; i < hiddenColumns.size(); i++)
268       {
269         int[] region = hiddenColumns.get(i);
270         if (region[0] >= start)
271         {
272           region[0] -= change;
273         }
274         if (region[1] >= start)
275         {
276           region[1] -= change;
277         }
278         if (region[1] < region[0])
279         {
280           hiddenColumns.remove(i--);
281         }
282
283         if (region[0] < 0)
284         {
285           region[0] = 0;
286         }
287         if (region[1] < 0)
288         {
289           region[1] = 0;
290         }
291       }
292     }
293   }
294
295   /**
296    * Adjust hidden column boundaries based on a series of column additions or
297    * deletions in visible regions.
298    * 
299    * @param shiftrecord
300    * @return
301    */
302   public ShiftList compensateForEdits(ShiftList shiftrecord)
303   {
304     if (shiftrecord != null)
305     {
306       List<int[]> shifts = shiftrecord.getShifts();
307       if (shifts != null && shifts.size() > 0)
308       {
309         int shifted = 0;
310         for (int i = 0, j = shifts.size(); i < j; i++)
311         {
312           int[] sh = shifts.get(i);
313           // compensateForEdit(shifted+sh[0], sh[1]);
314           compensateForDelEdits(shifted + sh[0], sh[1]);
315           shifted -= sh[1];
316         }
317       }
318       return shiftrecord.getInverse();
319     }
320     return null;
321   }
322
323   /**
324    * removes intersection of position,length ranges in deletions from the
325    * start,end regions marked in intervals.
326    * 
327    * @param shifts
328    * @param hiddenColumns2
329    * @return
330    */
331   private boolean pruneIntervalVector(List<int[]> shifts,
332           List<int[]> hiddenColumns2)
333   {
334     boolean pruned = false;
335     int i = 0, j = hiddenColumns2.size() - 1, s = 0, t = shifts.size() - 1;
336     int hr[] = hiddenColumns2.get(i);
337     int sr[] = shifts.get(s);
338     while (i <= j && s <= t)
339     {
340       boolean trailinghn = hr[1] >= sr[0];
341       if (!trailinghn)
342       {
343         if (i < j)
344         {
345           hr = hiddenColumns2.get(++i);
346         }
347         else
348         {
349           i++;
350         }
351         continue;
352       }
353       int endshift = sr[0] + sr[1]; // deletion ranges - -ve means an insert
354       if (endshift < hr[0] || endshift < sr[0])
355       { // leadinghc disjoint or not a deletion
356         if (s < t)
357         {
358           sr = shifts.get(++s);
359         }
360         else
361         {
362           s++;
363         }
364         continue;
365       }
366       boolean leadinghn = hr[0] >= sr[0];
367       boolean leadinghc = hr[0] < endshift;
368       boolean trailinghc = hr[1] < endshift;
369       if (leadinghn)
370       {
371         if (trailinghc)
372         { // deleted hidden region.
373           hiddenColumns2.remove(i);
374           pruned = true;
375           j--;
376           if (i <= j)
377           {
378             hr = hiddenColumns2.remove(i);
379           }
380           continue;
381         }
382         if (leadinghc)
383         {
384           hr[0] = endshift; // clip c terminal region
385           leadinghn = !leadinghn;
386           pruned = true;
387         }
388       }
389       if (!leadinghn)
390       {
391         if (trailinghc)
392         {
393           if (trailinghn)
394           {
395             hr[1] = sr[0] - 1;
396             pruned = true;
397           }
398         }
399         else
400         {
401           // sr contained in hr
402           if (s < t)
403           {
404             sr = shifts.get(++s);
405           }
406           else
407           {
408             s++;
409           }
410           continue;
411         }
412       }
413     }
414     return pruned; // true if any interval was removed or modified by
415     // operations.
416   }
417
418   private boolean pruneColumnList(List<int[]> shifts, List<Integer> list)
419   {
420     int s = 0, t = shifts.size();
421     int[] sr = shifts.get(s++);
422     boolean pruned = false;
423     int i = 0, j = list.size();
424     while (i < j && s <= t)
425     {
426       int c = list.get(i++);
427       if (sr[0] <= c)
428       {
429         if (sr[1] + sr[0] >= c)
430         { // sr[1] -ve means insertion.
431           // remove the (i-1)'th element
432           list.remove(--i);
433           j--;
434         }
435         else
436         {
437           if (s < t)
438           {
439             sr = shifts.get(s);
440           }
441           s++;
442         }
443       }
444     }
445     return pruned;
446   }
447
448   /**
449    * remove any hiddenColumns or selected columns and shift remaining based on a
450    * series of position, range deletions.
451    * 
452    * @param deletions
453    */
454   public void pruneDeletions(ShiftList deletions)
455   {
456     if (deletions != null)
457     {
458       List<int[]> shifts = deletions.getShifts();
459       if (shifts != null && shifts.size() > 0)
460       {
461         // delete any intervals intersecting.
462         if (hiddenColumns != null)
463         {
464           pruneIntervalVector(shifts, hiddenColumns);
465           if (hiddenColumns != null && hiddenColumns.size() == 0)
466           {
467             hiddenColumns = null;
468           }
469         }
470         if (selected != null && selected.size() > 0)
471         {
472           pruneColumnList(shifts, selected);
473           if (selected != null && selected.size() == 0)
474           {
475             selected = null;
476           }
477         }
478         // and shift the rest.
479         this.compensateForEdits(deletions);
480       }
481     }
482   }
483
484   /**
485    * This method is used to return all the HiddenColumn regions less than the
486    * given index.
487    * 
488    * @param end
489    *          int
490    * @return Vector
491    */
492   public List<int[]> getHiddenColumns()
493   {
494     return hiddenColumns;
495   }
496
497   /**
498    * Return absolute column index for a visible column index
499    * 
500    * @param column
501    *          int column index in alignment view
502    * @return alignment column index for column
503    */
504   public int adjustForHiddenColumns(int column)
505   {
506     int result = column;
507     if (hiddenColumns != null)
508     {
509       for (int i = 0; i < hiddenColumns.size(); i++)
510       {
511         int[] region = hiddenColumns.get(i);
512         if (result >= region[0])
513         {
514           result += region[1] - region[0] + 1;
515         }
516       }
517     }
518     return result;
519   }
520
521   /**
522    * Use this method to find out where a column will appear in the visible
523    * alignment when hidden columns exist. If the column is not visible, then the
524    * left-most visible column will always be returned.
525    * 
526    * @param hiddenColumn
527    *          int
528    * @return int
529    */
530   public int findColumnPosition(int hiddenColumn)
531   {
532     int result = hiddenColumn;
533     if (hiddenColumns != null)
534     {
535       int index = 0;
536       int[] region;
537       do
538       {
539         region = hiddenColumns.get(index++);
540         if (hiddenColumn > region[1])
541         {
542           result -= region[1] + 1 - region[0];
543         }
544       } while ((hiddenColumn > region[1]) && (index < hiddenColumns.size()));
545       if (hiddenColumn > region[0] && hiddenColumn < region[1])
546       {
547         return region[0] + hiddenColumn - result;
548       }
549     }
550     return result; // return the shifted position after removing hidden columns.
551   }
552
553   /**
554    * Use this method to determine where the next hiddenRegion starts
555    */
556   public int findHiddenRegionPosition(int hiddenRegion)
557   {
558     int result = 0;
559     if (hiddenColumns != null)
560     {
561       int index = 0;
562       int gaps = 0;
563       do
564       {
565         int[] region = hiddenColumns.get(index);
566         if (hiddenRegion == 0)
567         {
568           return region[0];
569         }
570
571         gaps += region[1] + 1 - region[0];
572         result = region[1] + 1;
573         index++;
574       } while (index < hiddenRegion + 1);
575
576       result -= gaps;
577     }
578
579     return result;
580   }
581
582   /**
583    * THis method returns the rightmost limit of a region of an alignment with
584    * hidden columns. In otherwords, the next hidden column.
585    * 
586    * @param index
587    *          int
588    */
589   public int getHiddenBoundaryRight(int alPos)
590   {
591     if (hiddenColumns != null)
592     {
593       int index = 0;
594       do
595       {
596         int[] region = hiddenColumns.get(index);
597         if (alPos < region[0])
598         {
599           return region[0];
600         }
601
602         index++;
603       } while (index < hiddenColumns.size());
604     }
605
606     return alPos;
607
608   }
609
610   /**
611    * This method returns the leftmost limit of a region of an alignment with
612    * hidden columns. In otherwords, the previous hidden column.
613    * 
614    * @param index
615    *          int
616    */
617   public int getHiddenBoundaryLeft(int alPos)
618   {
619     if (hiddenColumns != null)
620     {
621       int index = hiddenColumns.size() - 1;
622       do
623       {
624         int[] region = hiddenColumns.get(index);
625         if (alPos > region[1])
626         {
627           return region[1];
628         }
629
630         index--;
631       } while (index > -1);
632     }
633
634     return alPos;
635
636   }
637
638   public void hideSelectedColumns()
639   {
640     while (size() > 0)
641     {
642       int column = getSelected().get(0).intValue();
643       hideColumns(column);
644     }
645
646   }
647
648   public void hideColumns(int start, int end)
649   {
650     if (hiddenColumns == null)
651     {
652       hiddenColumns = new ArrayList<int[]>();
653     }
654
655     boolean added = false;
656     boolean overlap = false;
657
658     for (int i = 0; i < hiddenColumns.size(); i++)
659     {
660       int[] region = hiddenColumns.get(i);
661       if (start <= region[1] && end >= region[0])
662       {
663         hiddenColumns.remove(i);
664         overlap = true;
665         break;
666       }
667       else if (end < region[0] && start < region[0])
668       {
669         hiddenColumns.add(i, new int[]
670         { start, end });
671         added = true;
672         break;
673       }
674     }
675
676     if (overlap)
677     {
678       hideColumns(start, end);
679     }
680     else if (!added)
681     {
682       hiddenColumns.add(new int[]
683       { start, end });
684     }
685
686   }
687
688   /**
689    * This method will find a range of selected columns around the column
690    * specified
691    * 
692    * @param res
693    *          int
694    */
695   public void hideColumns(int col)
696   {
697     // First find out range of columns to hide
698     int min = col, max = col + 1;
699     while (contains(min))
700     {
701       removeElement(min);
702       min--;
703     }
704
705     while (contains(max))
706     {
707       removeElement(max);
708       max++;
709     }
710
711     min++;
712     max--;
713     if (min > max)
714     {
715       min = max;
716     }
717
718     hideColumns(min, max);
719   }
720
721   public void revealAllHiddenColumns()
722   {
723     if (hiddenColumns != null)
724     {
725       for (int i = 0; i < hiddenColumns.size(); i++)
726       {
727         int[] region = hiddenColumns.get(i);
728         for (int j = region[0]; j < region[1] + 1; j++)
729         {
730           addElement(j);
731         }
732       }
733     }
734
735     hiddenColumns = null;
736   }
737
738   public void revealHiddenColumns(int res)
739   {
740     for (int i = 0; i < hiddenColumns.size(); i++)
741     {
742       int[] region = hiddenColumns.get(i);
743       if (res == region[0])
744       {
745         for (int j = region[0]; j < region[1] + 1; j++)
746         {
747           addElement(j);
748         }
749
750         hiddenColumns.remove(region);
751         break;
752       }
753     }
754     if (hiddenColumns.size() == 0)
755     {
756       hiddenColumns = null;
757     }
758   }
759
760   public boolean isVisible(int column)
761   {
762     if (hiddenColumns != null)
763     {
764       for (int i = 0; i < hiddenColumns.size(); i++)
765       {
766         int[] region = hiddenColumns.get(i);
767         if (column >= region[0] && column <= region[1])
768         {
769           return false;
770         }
771       }
772     }
773
774     return true;
775   }
776
777   /**
778    * Copy constructor
779    * 
780    * @param copy
781    */
782   public ColumnSelection(ColumnSelection copy)
783   {
784     if (copy != null)
785     {
786       if (copy.selected != null)
787       {
788         selected = new ArrayList<Integer>();
789         for (int i = 0, j = copy.selected.size(); i < j; i++)
790         {
791           selected.add(copy.selected.get(i));
792         }
793       }
794       if (copy.hiddenColumns != null)
795       {
796         hiddenColumns = new ArrayList<int[]>(copy.hiddenColumns.size());
797         for (int i = 0, j = copy.hiddenColumns.size(); i < j; i++)
798         {
799           int[] rh, cp;
800           rh = copy.hiddenColumns.get(i);
801           if (rh != null)
802           {
803             cp = new int[rh.length];
804             System.arraycopy(rh, 0, cp, 0, rh.length);
805             hiddenColumns.add(cp);
806           }
807         }
808       }
809     }
810   }
811
812   /**
813    * ColumnSelection
814    */
815   public ColumnSelection()
816   {
817   }
818
819   public String[] getVisibleSequenceStrings(int start, int end,
820           SequenceI[] seqs)
821   {
822     int i, iSize = seqs.length;
823     String selection[] = new String[iSize];
824     if (hiddenColumns != null && hiddenColumns.size() > 0)
825     {
826       for (i = 0; i < iSize; i++)
827       {
828         StringBuffer visibleSeq = new StringBuffer();
829         List<int[]> regions = getHiddenColumns();
830
831         int blockStart = start, blockEnd = end;
832         int hideStart, hideEnd;
833
834         for (int[] region : regions)
835         {
836           hideStart = region[0];
837           hideEnd = region[1];
838
839           if (hideStart < start)
840           {
841             continue;
842           }
843
844           blockStart = Math.min(blockStart, hideEnd + 1);
845           blockEnd = Math.min(blockEnd, hideStart);
846
847           if (blockStart > blockEnd)
848           {
849             break;
850           }
851
852           visibleSeq.append(seqs[i].getSequence(blockStart, blockEnd));
853
854           blockStart = hideEnd + 1;
855           blockEnd = end;
856         }
857
858         if (end > blockStart)
859         {
860           visibleSeq.append(seqs[i].getSequence(blockStart, end));
861         }
862
863         selection[i] = visibleSeq.toString();
864       }
865     }
866     else
867     {
868       for (i = 0; i < iSize; i++)
869       {
870         selection[i] = seqs[i].getSequenceAsString(start, end);
871       }
872     }
873
874     return selection;
875   }
876
877   /**
878    * return all visible segments between the given start and end boundaries
879    * 
880    * @param start
881    *          (first column inclusive from 0)
882    * @param end
883    *          (last column - not inclusive)
884    * @return int[] {i_start, i_end, ..} where intervals lie in
885    *         start<=i_start<=i_end<end
886    */
887   public int[] getVisibleContigs(int start, int end)
888   {
889     if (hiddenColumns != null && hiddenColumns.size() > 0)
890     {
891       List<int[]> visiblecontigs = new ArrayList<int[]>();
892       List<int[]> regions = getHiddenColumns();
893
894       int vstart = start;
895       int[] region;
896       int hideStart, hideEnd;
897
898       for (int j = 0; vstart < end && j < regions.size(); j++)
899       {
900         region = regions.get(j);
901         hideStart = region[0];
902         hideEnd = region[1];
903
904         if (hideEnd < vstart)
905         {
906           continue;
907         }
908         if (hideStart > vstart)
909         {
910           visiblecontigs.add(new int[]
911           { vstart, hideStart - 1 });
912         }
913         vstart = hideEnd + 1;
914       }
915
916       if (vstart < end)
917       {
918         visiblecontigs.add(new int[]
919         { vstart, end - 1 });
920       }
921       int[] vcontigs = new int[visiblecontigs.size() * 2];
922       for (int i = 0, j = visiblecontigs.size(); i < j; i++)
923       {
924         int[] vc = visiblecontigs.get(i);
925         visiblecontigs.set(i, null);
926         vcontigs[i * 2] = vc[0];
927         vcontigs[i * 2 + 1] = vc[1];
928       }
929       visiblecontigs.clear();
930       return vcontigs;
931     }
932     else
933     {
934       return new int[]
935       { start, end - 1 };
936     }
937   }
938
939   /**
940    * delete any columns in alignmentAnnotation that are hidden (including
941    * sequence associated annotation).
942    * 
943    * @param alignmentAnnotation
944    */
945   public void makeVisibleAnnotation(AlignmentAnnotation alignmentAnnotation)
946   {
947     makeVisibleAnnotation(-1, -1, alignmentAnnotation);
948   }
949
950   /**
951    * delete any columns in alignmentAnnotation that are hidden (including
952    * sequence associated annotation).
953    * 
954    * @param start
955    *          remove any annotation to the right of this column
956    * @param end
957    *          remove any annotation to the left of this column
958    * @param alignmentAnnotation
959    *          the annotation to operate on
960    */
961   public void makeVisibleAnnotation(int start, int end,
962           AlignmentAnnotation alignmentAnnotation)
963   {
964     if (alignmentAnnotation.annotations == null)
965     {
966       return;
967     }
968     if (start == end && end == -1)
969     {
970       start = 0;
971       end = alignmentAnnotation.annotations.length;
972     }
973     if (hiddenColumns != null && hiddenColumns.size() > 0)
974     {
975       // then mangle the alignmentAnnotation annotation array
976       List<Annotation[]> annels = new ArrayList<Annotation[]>();
977       Annotation[] els = null;
978       List<int[]> regions = getHiddenColumns();
979       int blockStart = start, blockEnd = end;
980       int hideStart, hideEnd, w = 0;
981
982       for (int[] region : regions)
983       {
984         hideStart = region[0];
985         hideEnd = region[1];
986
987         if (hideStart < start)
988         {
989           continue;
990         }
991
992         blockStart = Math.min(blockStart, hideEnd + 1);
993         blockEnd = Math.min(blockEnd, hideStart);
994
995         if (blockStart > blockEnd)
996         {
997           break;
998         }
999
1000         annels.add(els = new Annotation[blockEnd - blockStart]);
1001         System.arraycopy(alignmentAnnotation.annotations, blockStart, els,
1002                 0, els.length);
1003         w += els.length;
1004         blockStart = hideEnd + 1;
1005         blockEnd = end;
1006       }
1007
1008       if (end > blockStart)
1009       {
1010         annels.add(els = new Annotation[end - blockStart + 1]);
1011         if ((els.length + blockStart) <= alignmentAnnotation.annotations.length)
1012         {
1013           // copy just the visible segment of the annotation row
1014           System.arraycopy(alignmentAnnotation.annotations, blockStart,
1015                   els, 0, els.length);
1016         }
1017         else
1018         {
1019           // copy to the end of the annotation row
1020           System.arraycopy(alignmentAnnotation.annotations, blockStart,
1021                   els, 0,
1022                   (alignmentAnnotation.annotations.length - blockStart));
1023         }
1024         w += els.length;
1025       }
1026       if (w == 0)
1027       {
1028         return;
1029       }
1030       for (Annotation[] chnk : annels)
1031       {
1032         System.arraycopy(chnk, 0, alignmentAnnotation.annotations, w,
1033                 chnk.length);
1034         w += chnk.length;
1035       }
1036     }
1037     else
1038     {
1039       alignmentAnnotation.restrict(start, end);
1040     }
1041   }
1042
1043   /**
1044    * Invert the column selection from first to end-1. leaves hiddenColumns
1045    * untouched (and unselected)
1046    * 
1047    * @param first
1048    * @param end
1049    */
1050   public void invertColumnSelection(int first, int width)
1051   {
1052     boolean hasHidden = hiddenColumns != null && hiddenColumns.size() > 0;
1053     for (int i = first; i < width; i++)
1054     {
1055       if (contains(i))
1056       {
1057         removeElement(i);
1058       }
1059       else
1060       {
1061         if (!hasHidden || isVisible(i))
1062         {
1063           addElement(i);
1064         }
1065       }
1066     }
1067   }
1068
1069   /**
1070    * add in any unselected columns from the given column selection, excluding
1071    * any that are hidden.
1072    * 
1073    * @param colsel
1074    */
1075   public void addElementsFrom(ColumnSelection colsel)
1076   {
1077     if (colsel != null && colsel.size() > 0)
1078     {
1079       for (Integer eo : colsel.getSelected())
1080       {
1081         if (hiddenColumns != null && isVisible(eo))
1082         {
1083           if (!selected.contains(eo))
1084           {
1085             selected.add(eo);
1086           }
1087         }
1088       }
1089     }
1090   }
1091
1092   /**
1093    * set the selected columns the given column selection, excluding any columns
1094    * that are hidden.
1095    * 
1096    * @param colsel
1097    */
1098   public void setElementsFrom(ColumnSelection colsel)
1099   {
1100     selected = new ArrayList<Integer>();
1101     if (colsel.selected != null && colsel.selected.size() > 0)
1102     {
1103       if (hiddenColumns != null && hiddenColumns.size() > 0)
1104       {
1105         // only select visible columns in this columns selection
1106         selected = new ArrayList<Integer>();
1107         addElementsFrom(colsel);
1108       }
1109       else
1110       {
1111         // add everything regardless
1112         for (Integer e : colsel.getSelected())
1113         {
1114           selected.add(e);
1115         }
1116       }
1117     }
1118   }
1119
1120   /**
1121    * Add gaps into the sequences aligned to profileseq under the given
1122    * AlignmentView
1123    * 
1124    * @param profileseq
1125    * @param al
1126    *          - alignment to have gaps inserted into it
1127    * @param input
1128    *          - alignment view where sequence corresponding to profileseq is
1129    *          first entry
1130    * @return new Column selection for new alignment view, with insertions into
1131    *         profileseq marked as hidden.
1132    */
1133   public static ColumnSelection propagateInsertions(SequenceI profileseq,
1134           Alignment al, AlignmentView input)
1135   {
1136     int profsqpos = 0;
1137
1138     // return propagateInsertions(profileseq, al, )
1139     char gc = al.getGapCharacter();
1140     Object[] alandcolsel = input.getAlignmentAndColumnSelection(gc);
1141     ColumnSelection nview = (ColumnSelection) alandcolsel[1];
1142     SequenceI origseq = ((SequenceI[]) alandcolsel[0])[profsqpos];
1143     nview.propagateInsertions(profileseq, al, origseq);
1144     return nview;
1145   }
1146
1147   /**
1148    * 
1149    * @param profileseq
1150    *          - sequence in al which corresponds to origseq
1151    * @param al
1152    *          - alignment which is to have gaps inserted into it
1153    * @param origseq
1154    *          - sequence corresponding to profileseq which defines gap map for
1155    *          modifying al
1156    */
1157   public void propagateInsertions(SequenceI profileseq, AlignmentI al,
1158           SequenceI origseq)
1159   {
1160     char gc = al.getGapCharacter();
1161     // recover mapping between sequence's non-gap positions and positions
1162     // mapping to view.
1163     pruneDeletions(ShiftList.parseMap(origseq.gapMap()));
1164     int[] viscontigs = getVisibleContigs(0, profileseq.getLength());
1165     int spos = 0;
1166     int offset = 0;
1167     // input.pruneDeletions(ShiftList.parseMap(((SequenceI[])
1168     // alandcolsel[0])[0].gapMap()))
1169     // add profile to visible contigs
1170     for (int v = 0; v < viscontigs.length; v += 2)
1171     {
1172       if (viscontigs[v] > spos)
1173       {
1174         StringBuilder sb = new StringBuilder(64);
1175         for (int s = 0, ns = viscontigs[v] - spos; s < ns; s++)
1176         {
1177           sb.append(gc);
1178         }
1179         for (int s = 0, ns = al.getHeight(); s < ns; s++)
1180         {
1181           SequenceI sqobj = al.getSequenceAt(s);
1182           if (sqobj != profileseq)
1183           {
1184             String sq = al.getSequenceAt(s).getSequenceAsString();
1185             if (sq.length() <= spos + offset)
1186             {
1187               // pad sequence
1188               int diff = spos + offset - sq.length() - 1;
1189               if (diff > 0)
1190               {
1191                 // pad gaps
1192                 sq = sq + sb;
1193                 while ((diff = spos + offset - sq.length() - 1) > 0)
1194                 {
1195                   // sq = sq
1196                   // + ((diff >= sb.length()) ? sb.toString() : sb
1197                   // .substring(0, diff));
1198                   if (diff >= sb.length())
1199                   {
1200                     sq += sb.toString();
1201                   }
1202                   else
1203                   {
1204                     char[] buf = new char[diff];
1205                     sb.getChars(0, diff, buf, 0);
1206                     sq += buf.toString();
1207                   }
1208                 }
1209               }
1210               sq += sb.toString();
1211             }
1212             else
1213             {
1214               al.getSequenceAt(s).setSequence(
1215                       sq.substring(0, spos + offset) + sb.toString()
1216                               + sq.substring(spos + offset));
1217             }
1218           }
1219         }
1220         // offset+=sb.length();
1221       }
1222       spos = viscontigs[v + 1] + 1;
1223     }
1224     if ((offset + spos) < profileseq.getLength())
1225     {
1226       // pad the final region with gaps.
1227       StringBuffer sb = new StringBuffer();
1228       for (int s = 0, ns = profileseq.getLength() - spos - offset; s < ns; s++)
1229       {
1230         sb.append(gc);
1231       }
1232       for (int s = 0, ns = al.getHeight(); s < ns; s++)
1233       {
1234         SequenceI sqobj = al.getSequenceAt(s);
1235         if (sqobj == profileseq)
1236         {
1237           continue;
1238         }
1239         String sq = sqobj.getSequenceAsString();
1240         // pad sequence
1241         int diff = origseq.getLength() - sq.length();
1242         while (diff > 0)
1243         {
1244           // sq = sq
1245           // + ((diff >= sb.length()) ? sb.toString() : sb
1246           // .substring(0, diff));
1247           if (diff >= sb.length())
1248           {
1249             sq += sb.toString();
1250           }
1251           else
1252           {
1253             char[] buf = new char[diff];
1254             sb.getChars(0, diff, buf, 0);
1255             sq += buf.toString();
1256           }
1257           diff = origseq.getLength() - sq.length();
1258         }
1259       }
1260     }
1261   }
1262 }