JAL-2429 Fix to ColumnSelection::findColumnPosition
[jalview.git] / test / jalview / datamodel / ColumnSelectionTest.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 static org.testng.AssertJUnit.assertEquals;
24 import static org.testng.AssertJUnit.assertFalse;
25 import static org.testng.AssertJUnit.assertSame;
26 import static org.testng.AssertJUnit.assertTrue;
27 import static org.testng.AssertJUnit.fail;
28
29 import jalview.gui.JvOptionPane;
30
31 import java.util.Arrays;
32 import java.util.BitSet;
33 import java.util.Collections;
34 import java.util.ConcurrentModificationException;
35 import java.util.List;
36
37 import org.testng.annotations.BeforeClass;
38 import org.testng.annotations.Test;
39
40 public class ColumnSelectionTest
41 {
42
43   @BeforeClass(alwaysRun = true)
44   public void setUpJvOptionPane()
45   {
46     JvOptionPane.setInteractiveMode(false);
47     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
48   }
49
50   @Test(groups = { "Functional" })
51   public void testAddElement()
52   {
53     ColumnSelection cs = new ColumnSelection();
54     cs.addElement(2);
55     cs.addElement(5);
56     cs.addElement(3);
57     cs.addElement(5); // ignored
58     List<Integer> sel = cs.getSelected();
59     assertEquals("[2, 5, 3]", sel.toString());
60   }
61
62   /**
63    * Test the remove method - in particular to verify that remove(int i) removes
64    * the element whose value is i, _NOT_ the i'th element.
65    */
66   @Test(groups = { "Functional" })
67   public void testRemoveElement()
68   {
69     ColumnSelection cs = new ColumnSelection();
70     cs.addElement(2);
71     cs.addElement(5);
72
73     // removing elements not in the list has no effect
74     cs.removeElement(0);
75     cs.removeElement(1);
76     List<Integer> sel = cs.getSelected();
77     assertEquals(2, sel.size());
78     assertEquals(new Integer(2), sel.get(0));
79     assertEquals(new Integer(5), sel.get(1));
80
81     // removing an element in the list removes it
82     cs.removeElement(2);
83     // ...and also from the read-only view
84     assertEquals(1, sel.size());
85     sel = cs.getSelected();
86     assertEquals(1, sel.size());
87     assertEquals(new Integer(5), sel.get(0));
88   }
89
90   /**
91    * Test the method that finds the visible column position of an alignment
92    * column, allowing for hidden columns.
93    */
94   @Test(groups = { "Functional" })
95   public void testFindColumnPosition()
96   {
97     ColumnSelection cs = new ColumnSelection();
98     assertEquals(5, cs.findColumnPosition(5));
99
100     // hiding column 6 makes no difference
101     cs.hideColumns(6, 6);
102     assertEquals(5, cs.findColumnPosition(5));
103
104     // hiding column 4 moves column 5 to column 4
105     cs.hideColumns(4, 4);
106     assertEquals(4, cs.findColumnPosition(5));
107
108     // hiding column 4 moves column 4 to position 3
109     assertEquals(3, cs.findColumnPosition(4));
110
111     // hiding columns 1 and 2 moves column 5 to column 2
112     cs.hideColumns(1, 2);
113     assertEquals(2, cs.findColumnPosition(5));
114
115     // check with > 1 hidden column regions
116     // where some columns are in the hidden regions
117     ColumnSelection cs2 = new ColumnSelection();
118     cs2.hideColumns(5, 10);
119     cs2.hideColumns(20, 27);
120     cs2.hideColumns(40, 44);
121
122     // hiding columns 5-10 and 20-27 moves column 8 to column 4
123     assertEquals(4, cs2.findColumnPosition(8));
124
125     // and moves column 24 to 13
126     assertEquals(13, cs2.findColumnPosition(24));
127
128     // and moves column 28 to 14
129     assertEquals(14, cs2.findColumnPosition(28));
130
131     // and moves column 40 to 25
132     assertEquals(25, cs2.findColumnPosition(40));
133   }
134
135   /**
136    * Test the code used to locate the reference sequence ruler origin
137    */
138   @Test(groups = { "Functional" })
139   public void testLocateVisibleBoundsofSequence()
140   {
141     ColumnSelection cs = new ColumnSelection();
142     SequenceI seq = new Sequence("RefSeq", "-A-SD-ASD--E---");
143     assertEquals(2, seq.findIndex(seq.getStart()));
144
145     // no hidden columns
146     assertEquals(
147             Arrays.toString(new int[] { seq.findIndex(seq.getStart()) - 1,
148                 seq.findIndex(seq.getEnd()) - 1, seq.getStart(),
149                 seq.getEnd(), seq.findIndex(seq.getStart()) - 1,
150                 seq.findIndex(seq.getEnd()) - 1 }),
151             Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
152
153     // hidden column on gap after end of sequence - should not affect bounds
154     cs.hideColumns(13);
155     assertEquals(
156             Arrays.toString(new int[] { seq.findIndex(seq.getStart()) - 1,
157                 seq.findIndex(seq.getEnd()) - 1, seq.getStart(),
158                 seq.getEnd(), seq.findIndex(seq.getStart()) - 1,
159                 seq.findIndex(seq.getEnd()) - 1 }),
160             Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
161
162     cs.revealAllHiddenColumns();
163     // hidden column on gap before beginning of sequence - should vis bounds by
164     // one
165     cs.hideColumns(0);
166     assertEquals(
167             Arrays.toString(new int[] { seq.findIndex(seq.getStart()) - 2,
168                 seq.findIndex(seq.getEnd()) - 2, seq.getStart(),
169                 seq.getEnd(), seq.findIndex(seq.getStart()) - 1,
170                 seq.findIndex(seq.getEnd()) - 1 }),
171             Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
172
173     cs.revealAllHiddenColumns();
174     // hide columns around most of sequence - leave one residue remaining
175     cs.hideColumns(1, 3);
176     cs.hideColumns(6, 11);
177     assertEquals("-D",
178             cs.getVisibleSequenceStrings(0, 5, new SequenceI[] { seq })[0]);
179     assertEquals(
180             Arrays.toString(new int[] { 1, 1, 3, 3,
181                 seq.findIndex(seq.getStart()) - 1,
182                 seq.findIndex(seq.getEnd()) - 1 }),
183             Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
184     cs.revealAllHiddenColumns();
185
186     // hide whole sequence - should just get location of hidden region
187     // containing sequence
188     cs.hideColumns(1, 11);
189     assertEquals(
190             Arrays.toString(new int[] { 0, 1, 0, 0,
191                 seq.findIndex(seq.getStart()) - 1,
192                 seq.findIndex(seq.getEnd()) - 1 }),
193             Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
194
195   }
196
197   @Test(groups = { "Functional" })
198   public void testLocateVisibleBoundsPathologicals()
199   {
200     // test some pathological cases we missed
201     AlignmentI al = new Alignment(new SequenceI[] { new Sequence(
202             "refseqGaptest", "KTDVTI----------NFI-----G----L") });
203     ColumnSelection cs = new ColumnSelection();
204     cs.hideInsertionsFor(al.getSequenceAt(0));
205     assertEquals(
206             "G",
207             ""
208                     + al.getSequenceAt(0).getCharAt(
209                             cs.adjustForHiddenColumns(9)));
210
211   }
212
213   @Test(groups = { "Functional" })
214   public void testHideColumns()
215   {
216     ColumnSelection cs = new ColumnSelection();
217     cs.hideColumns(5);
218     List<int[]> hidden = cs.getHiddenColumns();
219     assertEquals(1, hidden.size());
220     assertEquals("[5, 5]", Arrays.toString(hidden.get(0)));
221
222     cs.hideColumns(3);
223     assertEquals(2, hidden.size());
224     // two hidden ranges, in order:
225     assertSame(hidden, cs.getHiddenColumns());
226     assertEquals("[3, 3]", Arrays.toString(hidden.get(0)));
227     assertEquals("[5, 5]", Arrays.toString(hidden.get(1)));
228
229     // hiding column 4 expands [3, 3] to [3, 4]
230     // and merges to [5, 5] to make [3, 5]
231     cs.hideColumns(4);
232     hidden = cs.getHiddenColumns();
233     assertEquals(1, hidden.size());
234     assertEquals("[3, 5]", Arrays.toString(hidden.get(0)));
235
236     // clear hidden columns (note they are added to selected)
237     cs.revealAllHiddenColumns();
238     // it is now actually null but getter returns an empty list
239     assertTrue(cs.getHiddenColumns().isEmpty());
240
241     cs.hideColumns(3, 6);
242     hidden = cs.getHiddenColumns();
243     int[] firstHiddenRange = hidden.get(0);
244     assertEquals("[3, 6]", Arrays.toString(firstHiddenRange));
245
246     // adding a subrange of already hidden should do nothing
247     cs.hideColumns(4, 5);
248     assertEquals(1, hidden.size());
249     assertSame(firstHiddenRange, cs.getHiddenColumns().get(0));
250     cs.hideColumns(3, 5);
251     assertEquals(1, hidden.size());
252     assertSame(firstHiddenRange, cs.getHiddenColumns().get(0));
253     cs.hideColumns(4, 6);
254     assertEquals(1, hidden.size());
255     assertSame(firstHiddenRange, cs.getHiddenColumns().get(0));
256     cs.hideColumns(3, 6);
257     assertEquals(1, hidden.size());
258     assertSame(firstHiddenRange, cs.getHiddenColumns().get(0));
259
260     cs.revealAllHiddenColumns();
261     cs.hideColumns(2, 4);
262     hidden = cs.getHiddenColumns();
263     assertEquals(1, hidden.size());
264     assertEquals("[2, 4]", Arrays.toString(hidden.get(0)));
265
266     // extend contiguous with 2 positions overlap
267     cs.hideColumns(3, 5);
268     assertEquals(1, hidden.size());
269     assertEquals("[2, 5]", Arrays.toString(hidden.get(0)));
270
271     // extend contiguous with 1 position overlap
272     cs.hideColumns(5, 6);
273     assertEquals(1, hidden.size());
274     assertEquals("[2, 6]", Arrays.toString(hidden.get(0)));
275
276     // extend contiguous with overlap both ends:
277     cs.hideColumns(1, 7);
278     assertEquals(1, hidden.size());
279     assertEquals("[1, 7]", Arrays.toString(hidden.get(0)));
280   }
281
282   /**
283    * Test the method that hides a specified column including any adjacent
284    * selected columns. This is a convenience method for the case where multiple
285    * column regions are selected and then hidden using menu option View | Hide |
286    * Selected Columns.
287    */
288   @Test(groups = { "Functional" })
289   public void testHideColumns_withSelection()
290   {
291     ColumnSelection cs = new ColumnSelection();
292     // select columns 4-6
293     cs.addElement(4);
294     cs.addElement(5);
295     cs.addElement(6);
296     // hide column 5 (and adjacent):
297     cs.hideColumns(5);
298     // 4,5,6 now hidden:
299     List<int[]> hidden = cs.getHiddenColumns();
300     assertEquals(1, hidden.size());
301     assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
302     // none now selected:
303     assertTrue(cs.getSelected().isEmpty());
304
305     // repeat, hiding column 4 (5 and 6)
306     cs = new ColumnSelection();
307     cs.addElement(4);
308     cs.addElement(5);
309     cs.addElement(6);
310     cs.hideColumns(4);
311     hidden = cs.getHiddenColumns();
312     assertEquals(1, hidden.size());
313     assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
314     assertTrue(cs.getSelected().isEmpty());
315
316     // repeat, hiding column (4, 5 and) 6
317     cs = new ColumnSelection();
318     cs.addElement(4);
319     cs.addElement(5);
320     cs.addElement(6);
321     cs.hideColumns(6);
322     hidden = cs.getHiddenColumns();
323     assertEquals(1, hidden.size());
324     assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
325     assertTrue(cs.getSelected().isEmpty());
326
327     // repeat, with _only_ adjacent columns selected
328     cs = new ColumnSelection();
329     cs.addElement(4);
330     cs.addElement(6);
331     cs.hideColumns(5);
332     hidden = cs.getHiddenColumns();
333     assertEquals(1, hidden.size());
334     assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
335     assertTrue(cs.getSelected().isEmpty());
336   }
337
338   /**
339    * Test the method that hides all (possibly disjoint) selected column ranges
340    */
341   @Test(groups = { "Functional" })
342   public void testHideSelectedColumns()
343   {
344     ColumnSelection cs = new ColumnSelection();
345     int[] sel = { 2, 3, 4, 7, 8, 9, 20, 21, 22 };
346     for (int col : sel)
347     {
348       cs.addElement(col);
349     }
350     cs.hideColumns(15, 18);
351
352     cs.hideSelectedColumns();
353     assertTrue(cs.getSelected().isEmpty());
354     List<int[]> hidden = cs.getHiddenColumns();
355     assertEquals(4, hidden.size());
356     assertEquals("[2, 4]", Arrays.toString(hidden.get(0)));
357     assertEquals("[7, 9]", Arrays.toString(hidden.get(1)));
358     assertEquals("[15, 18]", Arrays.toString(hidden.get(2)));
359     assertEquals("[20, 22]", Arrays.toString(hidden.get(3)));
360   }
361
362   /**
363    * Test the method that gets runs of selected columns ordered by column. If
364    * this fails, HideSelectedColumns may also fail
365    */
366   @Test(groups = { "Functional" })
367   public void testGetSelectedRanges()
368   {
369     /*
370      * getSelectedRanges returns ordered columns regardless
371      * of the order in which they are added
372      */
373     ColumnSelection cs = new ColumnSelection();
374     int[] sel = { 4, 3, 7, 21, 9, 20, 8, 22, 2 };
375     for (int col : sel)
376     {
377       cs.addElement(col);
378     }
379     List<int[]> range;
380     range = cs.getSelectedRanges();
381     assertEquals(3, range.size());
382     assertEquals("[2, 4]", Arrays.toString(range.get(0)));
383     assertEquals("[7, 9]", Arrays.toString(range.get(1)));
384     assertEquals("[20, 22]", Arrays.toString(range.get(2)));
385     cs.addElement(0);
386     cs.addElement(1);
387     range = cs.getSelectedRanges();
388     assertEquals(3, range.size());
389     assertEquals("[0, 4]", Arrays.toString(range.get(0)));
390   }
391
392   /**
393    * Test the method that reveals a range of hidden columns given the start
394    * column of the range
395    */
396   @Test(groups = { "Functional" })
397   public void testRevealHiddenColumns()
398   {
399     ColumnSelection cs = new ColumnSelection();
400     cs.hideColumns(5, 8);
401     cs.addElement(10);
402     cs.revealHiddenColumns(5);
403     // hidden columns list now null but getter returns empty list:
404     assertTrue(cs.getHiddenColumns().isEmpty());
405     // revealed columns are marked as selected (added to selection):
406     assertEquals("[10, 5, 6, 7, 8]", cs.getSelected().toString());
407
408     // calling with a column other than the range start does nothing:
409     cs = new ColumnSelection();
410     cs.hideColumns(5, 8);
411     List<int[]> hidden = cs.getHiddenColumns();
412     cs.revealHiddenColumns(6);
413     assertSame(hidden, cs.getHiddenColumns());
414     assertTrue(cs.getSelected().isEmpty());
415   }
416
417   @Test(groups = { "Functional" })
418   public void testRevealAllHiddenColumns()
419   {
420     ColumnSelection cs = new ColumnSelection();
421     cs.hideColumns(5, 8);
422     cs.hideColumns(2, 3);
423     cs.addElement(11);
424     cs.addElement(1);
425     cs.revealAllHiddenColumns();
426
427     /*
428      * revealing hidden columns adds them (in order) to the (unordered)
429      * selection list
430      */
431     assertTrue(cs.getHiddenColumns().isEmpty());
432     assertEquals("[11, 1, 2, 3, 5, 6, 7, 8]", cs.getSelected().toString());
433   }
434
435   @Test(groups = { "Functional" })
436   public void testIsVisible()
437   {
438     ColumnSelection cs = new ColumnSelection();
439     cs.hideColumns(2, 4);
440     cs.hideColumns(6, 7);
441     assertTrue(cs.isVisible(0));
442     assertTrue(cs.isVisible(-99));
443     assertTrue(cs.isVisible(1));
444     assertFalse(cs.isVisible(2));
445     assertFalse(cs.isVisible(3));
446     assertFalse(cs.isVisible(4));
447     assertTrue(cs.isVisible(5));
448     assertFalse(cs.isVisible(6));
449     assertFalse(cs.isVisible(7));
450   }
451
452   @Test(groups = { "Functional" })
453   public void testGetVisibleContigs()
454   {
455     ColumnSelection cs = new ColumnSelection();
456     cs.hideColumns(3, 6);
457     cs.hideColumns(8, 9);
458     cs.hideColumns(12, 12);
459
460     // start position is inclusive, end position exclusive:
461     int[] visible = cs.getVisibleContigs(1, 13);
462     assertEquals("[1, 2, 7, 7, 10, 11]", Arrays.toString(visible));
463
464     visible = cs.getVisibleContigs(4, 14);
465     assertEquals("[7, 7, 10, 11, 13, 13]", Arrays.toString(visible));
466
467     visible = cs.getVisibleContigs(3, 10);
468     assertEquals("[7, 7]", Arrays.toString(visible));
469
470     visible = cs.getVisibleContigs(4, 6);
471     assertEquals("[]", Arrays.toString(visible));
472   }
473
474   @Test(groups = { "Functional" })
475   public void testInvertColumnSelection()
476   {
477     ColumnSelection cs = new ColumnSelection();
478     cs.addElement(4);
479     cs.addElement(6);
480     cs.addElement(8);
481     cs.hideColumns(3, 3);
482     cs.hideColumns(6, 6);
483
484     // invert selection from start (inclusive) to end (exclusive)
485     // hidden columns are _not_ changed
486     cs.invertColumnSelection(2, 9);
487     assertEquals("[2, 5, 7]", cs.getSelected().toString());
488
489     cs.invertColumnSelection(1, 9);
490     assertEquals("[1, 4, 8]", cs.getSelected().toString());
491   }
492
493   @Test(groups = { "Functional" })
494   public void testMaxColumnSelection()
495   {
496     ColumnSelection cs = new ColumnSelection();
497     cs.addElement(0);
498     cs.addElement(513);
499     cs.addElement(1);
500     assertEquals(513, cs.getMax());
501     cs.removeElement(513);
502     assertEquals(1, cs.getMax());
503     cs.removeElement(1);
504     assertEquals(0, cs.getMax());
505     cs.addElement(512);
506     cs.addElement(513);
507     assertEquals(513, cs.getMax());
508
509   }
510
511   @Test(groups = { "Functional" })
512   public void testMinColumnSelection()
513   {
514     ColumnSelection cs = new ColumnSelection();
515     cs.addElement(0);
516     cs.addElement(513);
517     cs.addElement(1);
518     assertEquals(0, cs.getMin());
519     cs.removeElement(0);
520     assertEquals(1, cs.getMin());
521     cs.addElement(0);
522     assertEquals(0, cs.getMin());
523   }
524
525   @Test(groups = { "Functional" })
526   public void testEquals()
527   {
528     ColumnSelection cs = new ColumnSelection();
529     cs.addElement(0);
530     cs.addElement(513);
531     cs.addElement(1);
532     cs.hideColumns(3);
533     cs.hideColumns(7);
534     cs.hideColumns(5, 9);
535
536     // same selections added in a different order
537     ColumnSelection cs2 = new ColumnSelection();
538     cs2.addElement(1);
539     cs2.addElement(513);
540     cs2.addElement(0);
541
542     // with no hidden columns
543     assertFalse(cs.equals(cs2));
544     assertFalse(cs2.equals(cs));
545
546     // with hidden columns added in a different order
547     cs2.hideColumns(6, 9);
548     cs2.hideColumns(5, 8);
549     cs2.hideColumns(3);
550
551     assertTrue(cs.equals(cs2));
552     assertTrue(cs.equals(cs));
553     assertTrue(cs2.equals(cs));
554     assertTrue(cs2.equals(cs2));
555
556     cs2.addElement(12);
557     assertFalse(cs.equals(cs2));
558     assertFalse(cs2.equals(cs));
559
560     cs2.removeElement(12);
561     assertTrue(cs.equals(cs2));
562
563     cs2.hideColumns(88);
564     assertFalse(cs.equals(cs2));
565     /*
566      * unhiding a column adds it to selection!
567      */
568     cs2.revealHiddenColumns(88);
569     assertFalse(cs.equals(cs2));
570     cs.addElement(88);
571     assertTrue(cs.equals(cs2));
572   }
573
574   /**
575    * Test the method that returns selected columns, in the order in which they
576    * were added
577    */
578   @Test(groups = { "Functional" })
579   public void testGetSelected()
580   {
581     ColumnSelection cs = new ColumnSelection();
582     int[] sel = { 4, 3, 7, 21 };
583     for (int col : sel)
584     {
585       cs.addElement(col);
586     }
587
588     List<Integer> selected = cs.getSelected();
589     assertEquals(4, selected.size());
590     assertEquals("[4, 3, 7, 21]", selected.toString());
591
592     /*
593      * getSelected returns a read-only view of the list
594      * verify the view follows any changes in it
595      */
596     cs.removeElement(7);
597     cs.addElement(1);
598     cs.removeElement(4);
599     assertEquals("[3, 21, 1]", selected.toString());
600   }
601
602   /**
603    * Test to verify that the list returned by getSelection cannot be modified
604    */
605   @Test(groups = { "Functional" })
606   public void testGetSelected_isReadOnly()
607   {
608     ColumnSelection cs = new ColumnSelection();
609     cs.addElement(3);
610
611     List<Integer> selected = cs.getSelected();
612     try
613     {
614       selected.clear();
615       fail("expected exception");
616     } catch (UnsupportedOperationException e)
617     {
618       // expected
619     }
620     try
621     {
622       selected.add(1);
623       fail("expected exception");
624     } catch (UnsupportedOperationException e)
625     {
626       // expected
627     }
628     try
629     {
630       selected.remove(3);
631       fail("expected exception");
632     } catch (UnsupportedOperationException e)
633     {
634       // expected
635     }
636     try
637     {
638       Collections.sort(selected);
639       fail("expected exception");
640     } catch (UnsupportedOperationException e)
641     {
642       // expected
643     }
644   }
645
646   /**
647    * Test that demonstrates a ConcurrentModificationException is thrown if you
648    * change the selection while iterating over it
649    */
650   @Test(
651     groups = "Functional",
652     expectedExceptions = { ConcurrentModificationException.class })
653   public void testGetSelected_concurrentModification()
654   {
655     ColumnSelection cs = new ColumnSelection();
656     cs.addElement(0);
657     cs.addElement(1);
658     cs.addElement(2);
659
660     /*
661      * simulate changing the list under us (e.g. in a separate
662      * thread) while iterating over it -> ConcurrentModificationException
663      */
664     List<Integer> selected = cs.getSelected();
665     for (Integer col : selected)
666     {
667       if (col.intValue() == 0)
668       {
669         cs.removeElement(1);
670       }
671     }
672   }
673
674   @Test(groups = "Functional")
675   public void testMarkColumns()
676   {
677     ColumnSelection cs = new ColumnSelection();
678     cs.addElement(5); // this will be cleared
679     BitSet toMark = new BitSet();
680     toMark.set(1);
681     toMark.set(3);
682     toMark.set(6);
683     toMark.set(9);
684
685     assertTrue(cs.markColumns(toMark, 3, 8, false, false, false));
686     List<Integer> selected = cs.getSelected();
687     assertEquals(2, selected.size());
688     assertTrue(selected.contains(3));
689     assertTrue(selected.contains(6));
690   }
691
692   @Test(groups = "Functional")
693   public void testMarkColumns_extend()
694   {
695     ColumnSelection cs = new ColumnSelection();
696     cs.addElement(1);
697     cs.addElement(5);
698     BitSet toMark = new BitSet();
699     toMark.set(1);
700     toMark.set(3);
701     toMark.set(6);
702     toMark.set(9);
703
704     /*
705      * extending selection of {3, 6} should leave {1, 3, 5, 6} selected
706      */
707     assertTrue(cs.markColumns(toMark, 3, 8, false, true, false));
708     List<Integer> selected = cs.getSelected();
709     assertEquals(4, selected.size());
710     assertTrue(selected.contains(1));
711     assertTrue(selected.contains(3));
712     assertTrue(selected.contains(5));
713     assertTrue(selected.contains(6));
714   }
715
716   @Test(groups = "Functional")
717   public void testMarkColumns_invert()
718   {
719     ColumnSelection cs = new ColumnSelection();
720     cs.addElement(5); // this will be cleared
721     BitSet toMark = new BitSet();
722     toMark.set(1);
723     toMark.set(3);
724     toMark.set(6);
725     toMark.set(9);
726
727     /*
728      * inverted selection of {3, 6} should select {4, 5, 7, 8}
729      */
730     assertTrue(cs.markColumns(toMark, 3, 8, true, false, false));
731     List<Integer> selected = cs.getSelected();
732     assertEquals(4, selected.size());
733     assertTrue(selected.contains(4));
734     assertTrue(selected.contains(5));
735     assertTrue(selected.contains(7));
736     assertTrue(selected.contains(8));
737   }
738
739   @Test(groups = "Functional")
740   public void testMarkColumns_toggle()
741   {
742     ColumnSelection cs = new ColumnSelection();
743     cs.addElement(1); // outside change range
744     cs.addElement(3);
745     cs.addElement(4);
746     cs.addElement(10); // outside change range
747     BitSet toMark = new BitSet();
748     toMark.set(1);
749     toMark.set(3);
750     toMark.set(6);
751     toMark.set(9);
752
753     /*
754      * toggling state of {3, 6} should leave {1, 4, 6, 10} selected
755      */
756     assertTrue(cs.markColumns(toMark, 3, 8, false, false, true));
757     List<Integer> selected = cs.getSelected();
758     assertEquals(4, selected.size());
759     assertTrue(selected.contains(1));
760     assertTrue(selected.contains(4));
761     assertTrue(selected.contains(6));
762     assertTrue(selected.contains(10));
763   }
764
765   @Test(groups = "Functional")
766   public void testCopyConstructor()
767   {
768     ColumnSelection cs = new ColumnSelection();
769     cs.addElement(3);
770     cs.addElement(1);
771     cs.hideColumns(10, 11);
772     cs.hideColumns(5, 7);
773     assertEquals("[5, 7]", Arrays.toString(cs.getHiddenColumns().get(0)));
774
775     ColumnSelection cs2 = new ColumnSelection(cs);
776     assertTrue(cs2.hasSelectedColumns());
777     assertTrue(cs2.hasHiddenColumns());
778     // order of column selection is preserved
779     assertEquals("[3, 1]", cs2.getSelected().toString());
780     assertEquals(2, cs2.getHiddenColumns().size());
781     // hidden columns are held in column order
782     assertEquals("[5, 7]", Arrays.toString(cs2.getHiddenColumns().get(0)));
783     assertEquals("[10, 11]", Arrays.toString(cs2.getHiddenColumns().get(1)));
784   }
785
786   /**
787    * Test for the case when a hidden range encloses more one already hidden
788    * range
789    */
790   @Test(groups = { "Functional" })
791   public void testHideColumns_subsumingHidden()
792   {
793     /*
794      * JAL-2370 bug scenario:
795      * two hidden ranges subsumed by a third
796      */
797     ColumnSelection cs = new ColumnSelection();
798     cs.hideColumns(49, 59);
799     cs.hideColumns(69, 79);
800     List<int[]> hidden = cs.getHiddenColumns();
801     assertEquals(2, hidden.size());
802     assertEquals("[49, 59]", Arrays.toString(hidden.get(0)));
803     assertEquals("[69, 79]", Arrays.toString(hidden.get(1)));
804   
805     cs.hideColumns(48, 80);
806     hidden = cs.getHiddenColumns();
807     assertEquals(1, hidden.size());
808     assertEquals("[48, 80]", Arrays.toString(hidden.get(0)));
809
810     /*
811      * another...joining hidden ranges
812      */
813     cs = new ColumnSelection();
814     cs.hideColumns(10, 20);
815     cs.hideColumns(30, 40);
816     cs.hideColumns(50, 60);
817     // hiding 21-49 should merge to one range
818     cs.hideColumns(21, 49);
819     hidden = cs.getHiddenColumns();
820     assertEquals(1, hidden.size());
821     assertEquals("[10, 60]", Arrays.toString(hidden.get(0)));
822
823     /*
824      * another...lef overlap, subsumption, right overlap,
825      * no overlap of existing hidden ranges
826      */
827     cs = new ColumnSelection();
828     cs.hideColumns(10, 20);
829     cs.hideColumns(10, 20);
830     cs.hideColumns(30, 35);
831     cs.hideColumns(40, 50);
832     cs.hideColumns(60, 70);
833
834     cs.hideColumns(15, 45);
835     hidden = cs.getHiddenColumns();
836     assertEquals(2, hidden.size());
837     assertEquals("[10, 50]", Arrays.toString(hidden.get(0)));
838     assertEquals("[60, 70]", Arrays.toString(hidden.get(1)));
839   }
840
841   @Test(groups = { "Functional" })
842   public void testStretchGroup_expand()
843   {
844     /*
845      * test that emulates clicking column 4 (selected)
846      * and dragging right to column 5 (all base 0)
847      */
848     ColumnSelection cs = new ColumnSelection();
849     cs.addElement(4);
850     SequenceGroup sg = new SequenceGroup();
851     sg.setStartRes(4);
852     sg.setEndRes(4);
853     cs.stretchGroup(5, sg, 4, 4);
854     assertEquals(cs.getSelected().size(), 2);
855     assertTrue(cs.contains(4));
856     assertTrue(cs.contains(5));
857     assertEquals(sg.getStartRes(), 4);
858     assertEquals(sg.getEndRes(), 5);
859
860     /*
861      * emulate drag right with columns 10-20 already selected
862      */
863     cs.clear();
864     for (int i = 10; i <= 20; i++)
865     {
866       cs.addElement(i);
867     }
868     assertEquals(cs.getSelected().size(), 11);
869     sg = new SequenceGroup();
870     sg.setStartRes(10);
871     sg.setEndRes(20);
872     cs.stretchGroup(21, sg, 10, 20);
873     assertEquals(cs.getSelected().size(), 12);
874     assertTrue(cs.contains(10));
875     assertTrue(cs.contains(21));
876     assertEquals(sg.getStartRes(), 10);
877     assertEquals(sg.getEndRes(), 21);
878   }
879
880   @Test(groups = { "Functional" })
881   public void testStretchGroup_shrink()
882   {
883     /*
884      * emulate drag left to 19 with columns 10-20 already selected
885      */
886     ColumnSelection cs = new ColumnSelection();
887     for (int i = 10; i <= 20; i++)
888     {
889       cs.addElement(i);
890     }
891     assertEquals(cs.getSelected().size(), 11);
892     SequenceGroup sg = new SequenceGroup();
893     sg.setStartRes(10);
894     sg.setEndRes(20);
895     cs.stretchGroup(19, sg, 10, 20);
896     assertEquals(cs.getSelected().size(), 10);
897     assertTrue(cs.contains(10));
898     assertTrue(cs.contains(19));
899     assertFalse(cs.contains(20));
900     assertEquals(sg.getStartRes(), 10);
901     assertEquals(sg.getEndRes(), 19);
902   }
903 }