a9ad4c2b3247c0b86186cd6881e27ccc338ebb4d
[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 columns 1 and 2 moves column 5 to column 2
109     cs.hideColumns(1, 2);
110     assertEquals(2, cs.findColumnPosition(5));
111   }
112
113   /**
114    * Test the code used to locate the reference sequence ruler origin
115    */
116   @Test(groups = { "Functional" })
117   public void testLocateVisibleBoundsofSequence()
118   {
119     ColumnSelection cs = new ColumnSelection();
120     SequenceI seq = new Sequence("RefSeq", "-A-SD-ASD--E---");
121     assertEquals(2, seq.findIndex(seq.getStart()));
122
123     // no hidden columns
124     assertEquals(
125             Arrays.toString(new int[] { seq.findIndex(seq.getStart()) - 1,
126                 seq.findIndex(seq.getEnd()) - 1, seq.getStart(),
127                 seq.getEnd(), seq.findIndex(seq.getStart()) - 1,
128                 seq.findIndex(seq.getEnd()) - 1 }),
129             Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
130
131     // hidden column on gap after end of sequence - should not affect bounds
132     cs.hideColumns(13);
133     assertEquals(
134             Arrays.toString(new int[] { seq.findIndex(seq.getStart()) - 1,
135                 seq.findIndex(seq.getEnd()) - 1, seq.getStart(),
136                 seq.getEnd(), seq.findIndex(seq.getStart()) - 1,
137                 seq.findIndex(seq.getEnd()) - 1 }),
138             Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
139
140     cs.revealAllHiddenColumns();
141     // hidden column on gap before beginning of sequence - should vis bounds by
142     // one
143     cs.hideColumns(0);
144     assertEquals(
145             Arrays.toString(new int[] { seq.findIndex(seq.getStart()) - 2,
146                 seq.findIndex(seq.getEnd()) - 2, seq.getStart(),
147                 seq.getEnd(), seq.findIndex(seq.getStart()) - 1,
148                 seq.findIndex(seq.getEnd()) - 1 }),
149             Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
150
151     cs.revealAllHiddenColumns();
152     // hide columns around most of sequence - leave one residue remaining
153     cs.hideColumns(1, 3);
154     cs.hideColumns(6, 11);
155     assertEquals("-D",
156             cs.getVisibleSequenceStrings(0, 5, new SequenceI[] { seq })[0]);
157     assertEquals(
158             Arrays.toString(new int[] { 1, 1, 3, 3,
159                 seq.findIndex(seq.getStart()) - 1,
160                 seq.findIndex(seq.getEnd()) - 1 }),
161             Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
162     cs.revealAllHiddenColumns();
163
164     // hide whole sequence - should just get location of hidden region
165     // containing sequence
166     cs.hideColumns(1, 11);
167     assertEquals(
168             Arrays.toString(new int[] { 0, 1, 0, 0,
169                 seq.findIndex(seq.getStart()) - 1,
170                 seq.findIndex(seq.getEnd()) - 1 }),
171             Arrays.toString(cs.locateVisibleBoundsOfSequence(seq)));
172
173   }
174
175   @Test(groups = { "Functional" })
176   public void testLocateVisibleBoundsPathologicals()
177   {
178     // test some pathological cases we missed
179     AlignmentI al = new Alignment(new SequenceI[] { new Sequence(
180             "refseqGaptest", "KTDVTI----------NFI-----G----L") });
181     ColumnSelection cs = new ColumnSelection();
182     cs.hideInsertionsFor(al.getSequenceAt(0));
183     assertEquals(
184             "G",
185             ""
186                     + al.getSequenceAt(0).getCharAt(
187                             cs.adjustForHiddenColumns(9)));
188
189   }
190
191   @Test(groups = { "Functional" })
192   public void testHideColumns()
193   {
194     ColumnSelection cs = new ColumnSelection();
195     cs.hideColumns(5);
196     List<int[]> hidden = cs.getHiddenColumns();
197     assertEquals(1, hidden.size());
198     assertEquals("[5, 5]", Arrays.toString(hidden.get(0)));
199
200     cs.hideColumns(3);
201     assertEquals(2, hidden.size());
202     // two hidden ranges, in order:
203     assertSame(hidden, cs.getHiddenColumns());
204     assertEquals("[3, 3]", Arrays.toString(hidden.get(0)));
205     assertEquals("[5, 5]", Arrays.toString(hidden.get(1)));
206
207     // hiding column 4 expands [3, 3] to [3, 4]
208     // not fancy enough to coalesce this into [3, 5] though
209     cs.hideColumns(4);
210     hidden = cs.getHiddenColumns();
211     assertEquals(2, hidden.size());
212     assertEquals("[3, 4]", Arrays.toString(hidden.get(0)));
213     assertEquals("[5, 5]", Arrays.toString(hidden.get(1)));
214
215     // clear hidden columns (note they are added to selected)
216     cs.revealAllHiddenColumns();
217     // it is now actually null but getter returns an empty list
218     assertTrue(cs.getHiddenColumns().isEmpty());
219
220     cs.hideColumns(3, 6);
221     hidden = cs.getHiddenColumns();
222     int[] firstHiddenRange = hidden.get(0);
223     assertEquals("[3, 6]", Arrays.toString(firstHiddenRange));
224
225     // adding a subrange of already hidden should do nothing
226     cs.hideColumns(4, 5);
227     assertEquals(1, hidden.size());
228     assertSame(firstHiddenRange, cs.getHiddenColumns().get(0));
229     cs.hideColumns(3, 5);
230     assertEquals(1, hidden.size());
231     assertSame(firstHiddenRange, cs.getHiddenColumns().get(0));
232     cs.hideColumns(4, 6);
233     assertEquals(1, hidden.size());
234     assertSame(firstHiddenRange, cs.getHiddenColumns().get(0));
235     cs.hideColumns(3, 6);
236     assertEquals(1, hidden.size());
237     assertSame(firstHiddenRange, cs.getHiddenColumns().get(0));
238
239     cs.revealAllHiddenColumns();
240     cs.hideColumns(2, 4);
241     hidden = cs.getHiddenColumns();
242     assertEquals(1, hidden.size());
243     assertEquals("[2, 4]", Arrays.toString(hidden.get(0)));
244
245     // extend contiguous with 2 positions overlap
246     cs.hideColumns(3, 5);
247     assertEquals(1, hidden.size());
248     assertEquals("[2, 5]", Arrays.toString(hidden.get(0)));
249
250     // extend contiguous with 1 position overlap
251     cs.hideColumns(5, 6);
252     assertEquals(1, hidden.size());
253     assertEquals("[2, 6]", Arrays.toString(hidden.get(0)));
254
255     // extend contiguous with overlap both ends:
256     cs.hideColumns(1, 7);
257     assertEquals(1, hidden.size());
258     assertEquals("[1, 7]", Arrays.toString(hidden.get(0)));
259   }
260
261   /**
262    * Test the method that hides a specified column including any adjacent
263    * selected columns. This is a convenience method for the case where multiple
264    * column regions are selected and then hidden using menu option View | Hide |
265    * Selected Columns.
266    */
267   @Test(groups = { "Functional" })
268   public void testHideColumns_withSelection()
269   {
270     ColumnSelection cs = new ColumnSelection();
271     // select columns 4-6
272     cs.addElement(4);
273     cs.addElement(5);
274     cs.addElement(6);
275     // hide column 5 (and adjacent):
276     cs.hideColumns(5);
277     // 4,5,6 now hidden:
278     List<int[]> hidden = cs.getHiddenColumns();
279     assertEquals(1, hidden.size());
280     assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
281     // none now selected:
282     assertTrue(cs.getSelected().isEmpty());
283
284     // repeat, hiding column 4 (5 and 6)
285     cs = new ColumnSelection();
286     cs.addElement(4);
287     cs.addElement(5);
288     cs.addElement(6);
289     cs.hideColumns(4);
290     hidden = cs.getHiddenColumns();
291     assertEquals(1, hidden.size());
292     assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
293     assertTrue(cs.getSelected().isEmpty());
294
295     // repeat, hiding column (4, 5 and) 6
296     cs = new ColumnSelection();
297     cs.addElement(4);
298     cs.addElement(5);
299     cs.addElement(6);
300     cs.hideColumns(6);
301     hidden = cs.getHiddenColumns();
302     assertEquals(1, hidden.size());
303     assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
304     assertTrue(cs.getSelected().isEmpty());
305
306     // repeat, with _only_ adjacent columns selected
307     cs = new ColumnSelection();
308     cs.addElement(4);
309     cs.addElement(6);
310     cs.hideColumns(5);
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
317   /**
318    * Test the method that hides all (possibly disjoint) selected column ranges
319    */
320   @Test(groups = { "Functional" })
321   public void testHideSelectedColumns()
322   {
323     ColumnSelection cs = new ColumnSelection();
324     int[] sel = { 2, 3, 4, 7, 8, 9, 20, 21, 22 };
325     for (int col : sel)
326     {
327       cs.addElement(col);
328     }
329     cs.hideColumns(15, 18);
330
331     cs.hideSelectedColumns();
332     assertTrue(cs.getSelected().isEmpty());
333     List<int[]> hidden = cs.getHiddenColumns();
334     assertEquals(4, hidden.size());
335     assertEquals("[2, 4]", Arrays.toString(hidden.get(0)));
336     assertEquals("[7, 9]", Arrays.toString(hidden.get(1)));
337     assertEquals("[15, 18]", Arrays.toString(hidden.get(2)));
338     assertEquals("[20, 22]", Arrays.toString(hidden.get(3)));
339   }
340
341   /**
342    * Test the method that gets runs of selected columns ordered by column. If
343    * this fails, HideSelectedColumns may also fail
344    */
345   @Test(groups = { "Functional" })
346   public void testGetSelectedRanges()
347   {
348     /*
349      * getSelectedRanges returns ordered columns regardless
350      * of the order in which they are added
351      */
352     ColumnSelection cs = new ColumnSelection();
353     int[] sel = { 4, 3, 7, 21, 9, 20, 8, 22, 2 };
354     for (int col : sel)
355     {
356       cs.addElement(col);
357     }
358     List<int[]> range;
359     range = cs.getSelectedRanges();
360     assertEquals(3, range.size());
361     assertEquals("[2, 4]", Arrays.toString(range.get(0)));
362     assertEquals("[7, 9]", Arrays.toString(range.get(1)));
363     assertEquals("[20, 22]", Arrays.toString(range.get(2)));
364     cs.addElement(0);
365     cs.addElement(1);
366     range = cs.getSelectedRanges();
367     assertEquals(3, range.size());
368     assertEquals("[0, 4]", Arrays.toString(range.get(0)));
369   }
370
371   /**
372    * Test the method that reveals a range of hidden columns given the start
373    * column of the range
374    */
375   @Test(groups = { "Functional" })
376   public void testRevealHiddenColumns()
377   {
378     ColumnSelection cs = new ColumnSelection();
379     cs.hideColumns(5, 8);
380     cs.addElement(10);
381     cs.revealHiddenColumns(5);
382     // hidden columns list now null but getter returns empty list:
383     assertTrue(cs.getHiddenColumns().isEmpty());
384     // revealed columns are marked as selected (added to selection):
385     assertEquals("[10, 5, 6, 7, 8]", cs.getSelected().toString());
386
387     // calling with a column other than the range start does nothing:
388     cs = new ColumnSelection();
389     cs.hideColumns(5, 8);
390     List<int[]> hidden = cs.getHiddenColumns();
391     cs.revealHiddenColumns(6);
392     assertSame(hidden, cs.getHiddenColumns());
393     assertTrue(cs.getSelected().isEmpty());
394   }
395
396   @Test(groups = { "Functional" })
397   public void testRevealAllHiddenColumns()
398   {
399     ColumnSelection cs = new ColumnSelection();
400     cs.hideColumns(5, 8);
401     cs.hideColumns(2, 3);
402     cs.addElement(11);
403     cs.addElement(1);
404     cs.revealAllHiddenColumns();
405
406     /*
407      * revealing hidden columns adds them (in order) to the (unordered)
408      * selection list
409      */
410     assertTrue(cs.getHiddenColumns().isEmpty());
411     assertEquals("[11, 1, 2, 3, 5, 6, 7, 8]", cs.getSelected().toString());
412   }
413
414   @Test(groups = { "Functional" })
415   public void testIsVisible()
416   {
417     ColumnSelection cs = new ColumnSelection();
418     cs.hideColumns(2, 4);
419     cs.hideColumns(6, 7);
420     assertTrue(cs.isVisible(0));
421     assertTrue(cs.isVisible(-99));
422     assertTrue(cs.isVisible(1));
423     assertFalse(cs.isVisible(2));
424     assertFalse(cs.isVisible(3));
425     assertFalse(cs.isVisible(4));
426     assertTrue(cs.isVisible(5));
427     assertFalse(cs.isVisible(6));
428     assertFalse(cs.isVisible(7));
429   }
430
431   @Test(groups = { "Functional" })
432   public void testGetVisibleContigs()
433   {
434     ColumnSelection cs = new ColumnSelection();
435     cs.hideColumns(3, 6);
436     cs.hideColumns(8, 9);
437     cs.hideColumns(12, 12);
438
439     // start position is inclusive, end position exclusive:
440     int[] visible = cs.getVisibleContigs(1, 13);
441     assertEquals("[1, 2, 7, 7, 10, 11]", Arrays.toString(visible));
442
443     visible = cs.getVisibleContigs(4, 14);
444     assertEquals("[7, 7, 10, 11, 13, 13]", Arrays.toString(visible));
445
446     visible = cs.getVisibleContigs(3, 10);
447     assertEquals("[7, 7]", Arrays.toString(visible));
448
449     visible = cs.getVisibleContigs(4, 6);
450     assertEquals("[]", Arrays.toString(visible));
451   }
452
453   @Test(groups = { "Functional" })
454   public void testInvertColumnSelection()
455   {
456     ColumnSelection cs = new ColumnSelection();
457     cs.addElement(4);
458     cs.addElement(6);
459     cs.addElement(8);
460     cs.hideColumns(3, 3);
461     cs.hideColumns(6, 6);
462
463     // invert selection from start (inclusive) to end (exclusive)
464     // hidden columns are _not_ changed
465     cs.invertColumnSelection(2, 9);
466     assertEquals("[2, 5, 7]", cs.getSelected().toString());
467
468     cs.invertColumnSelection(1, 9);
469     assertEquals("[1, 4, 8]", cs.getSelected().toString());
470   }
471
472   @Test(groups = { "Functional" })
473   public void testMaxColumnSelection()
474   {
475     ColumnSelection cs = new ColumnSelection();
476     cs.addElement(0);
477     cs.addElement(513);
478     cs.addElement(1);
479     assertEquals(513, cs.getMax());
480     cs.removeElement(513);
481     assertEquals(1, cs.getMax());
482     cs.removeElement(1);
483     assertEquals(0, cs.getMax());
484     cs.addElement(512);
485     cs.addElement(513);
486     assertEquals(513, cs.getMax());
487
488   }
489
490   @Test(groups = { "Functional" })
491   public void testMinColumnSelection()
492   {
493     ColumnSelection cs = new ColumnSelection();
494     cs.addElement(0);
495     cs.addElement(513);
496     cs.addElement(1);
497     assertEquals(0, cs.getMin());
498     cs.removeElement(0);
499     assertEquals(1, cs.getMin());
500     cs.addElement(0);
501     assertEquals(0, cs.getMin());
502   }
503
504   @Test(groups = { "Functional" })
505   public void testEquals()
506   {
507     ColumnSelection cs = new ColumnSelection();
508     cs.addElement(0);
509     cs.addElement(513);
510     cs.addElement(1);
511     cs.hideColumns(3);
512     cs.hideColumns(7);
513     cs.hideColumns(5, 9);
514
515     // same selections added in a different order
516     ColumnSelection cs2 = new ColumnSelection();
517     cs2.addElement(1);
518     cs2.addElement(513);
519     cs2.addElement(0);
520
521     // with no hidden columns
522     assertFalse(cs.equals(cs2));
523     assertFalse(cs2.equals(cs));
524
525     // with hidden columns added in a different order
526     cs2.hideColumns(6, 9);
527     cs2.hideColumns(5, 8);
528     cs2.hideColumns(3);
529
530     assertTrue(cs.equals(cs2));
531     assertTrue(cs.equals(cs));
532     assertTrue(cs2.equals(cs));
533     assertTrue(cs2.equals(cs2));
534
535     cs2.addElement(12);
536     assertFalse(cs.equals(cs2));
537     assertFalse(cs2.equals(cs));
538
539     cs2.removeElement(12);
540     assertTrue(cs.equals(cs2));
541
542     cs2.hideColumns(88);
543     assertFalse(cs.equals(cs2));
544     /*
545      * unhiding a column adds it to selection!
546      */
547     cs2.revealHiddenColumns(88);
548     assertFalse(cs.equals(cs2));
549     cs.addElement(88);
550     assertTrue(cs.equals(cs2));
551   }
552
553   /**
554    * Test the method that returns selected columns, in the order in which they
555    * were added
556    */
557   @Test(groups = { "Functional" })
558   public void testGetSelected()
559   {
560     ColumnSelection cs = new ColumnSelection();
561     int[] sel = { 4, 3, 7, 21 };
562     for (int col : sel)
563     {
564       cs.addElement(col);
565     }
566
567     List<Integer> selected = cs.getSelected();
568     assertEquals(4, selected.size());
569     assertEquals("[4, 3, 7, 21]", selected.toString());
570
571     /*
572      * getSelected returns a read-only view of the list
573      * verify the view follows any changes in it
574      */
575     cs.removeElement(7);
576     cs.addElement(1);
577     cs.removeElement(4);
578     assertEquals("[3, 21, 1]", selected.toString());
579   }
580
581   /**
582    * Test to verify that the list returned by getSelection cannot be modified
583    */
584   @Test(groups = { "Functional" })
585   public void testGetSelected_isReadOnly()
586   {
587     ColumnSelection cs = new ColumnSelection();
588     cs.addElement(3);
589
590     List<Integer> selected = cs.getSelected();
591     try
592     {
593       selected.clear();
594       fail("expected exception");
595     } catch (UnsupportedOperationException e)
596     {
597       // expected
598     }
599     try
600     {
601       selected.add(1);
602       fail("expected exception");
603     } catch (UnsupportedOperationException e)
604     {
605       // expected
606     }
607     try
608     {
609       selected.remove(3);
610       fail("expected exception");
611     } catch (UnsupportedOperationException e)
612     {
613       // expected
614     }
615     try
616     {
617       Collections.sort(selected);
618       fail("expected exception");
619     } catch (UnsupportedOperationException e)
620     {
621       // expected
622     }
623   }
624
625   /**
626    * Test that demonstrates a ConcurrentModificationException is thrown if you
627    * change the selection while iterating over it
628    */
629   @Test(
630     groups = "Functional",
631     expectedExceptions = { ConcurrentModificationException.class })
632   public void testGetSelected_concurrentModification()
633   {
634     ColumnSelection cs = new ColumnSelection();
635     cs.addElement(0);
636     cs.addElement(1);
637     cs.addElement(2);
638
639     /*
640      * simulate changing the list under us (e.g. in a separate
641      * thread) while iterating over it -> ConcurrentModificationException
642      */
643     List<Integer> selected = cs.getSelected();
644     for (Integer col : selected)
645     {
646       if (col.intValue() == 0)
647       {
648         cs.removeElement(1);
649       }
650     }
651   }
652
653   @Test(groups = "Functional")
654   public void testMarkColumns()
655   {
656     ColumnSelection cs = new ColumnSelection();
657     cs.addElement(5); // this will be cleared
658     BitSet toMark = new BitSet();
659     toMark.set(1);
660     toMark.set(3);
661     toMark.set(6);
662     toMark.set(9);
663
664     assertTrue(cs.markColumns(toMark, 3, 8, false, false, false));
665     List<Integer> selected = cs.getSelected();
666     assertEquals(2, selected.size());
667     assertTrue(selected.contains(3));
668     assertTrue(selected.contains(6));
669   }
670
671   @Test(groups = "Functional")
672   public void testMarkColumns_extend()
673   {
674     ColumnSelection cs = new ColumnSelection();
675     cs.addElement(1);
676     cs.addElement(5);
677     BitSet toMark = new BitSet();
678     toMark.set(1);
679     toMark.set(3);
680     toMark.set(6);
681     toMark.set(9);
682
683     /*
684      * extending selection of {3, 6} should leave {1, 3, 5, 6} selected
685      */
686     assertTrue(cs.markColumns(toMark, 3, 8, false, true, false));
687     List<Integer> selected = cs.getSelected();
688     assertEquals(4, selected.size());
689     assertTrue(selected.contains(1));
690     assertTrue(selected.contains(3));
691     assertTrue(selected.contains(5));
692     assertTrue(selected.contains(6));
693   }
694
695   @Test(groups = "Functional")
696   public void testMarkColumns_invert()
697   {
698     ColumnSelection cs = new ColumnSelection();
699     cs.addElement(5); // this will be cleared
700     BitSet toMark = new BitSet();
701     toMark.set(1);
702     toMark.set(3);
703     toMark.set(6);
704     toMark.set(9);
705
706     /*
707      * inverted selection of {3, 6} should select {4, 5, 7, 8}
708      */
709     assertTrue(cs.markColumns(toMark, 3, 8, true, false, false));
710     List<Integer> selected = cs.getSelected();
711     assertEquals(4, selected.size());
712     assertTrue(selected.contains(4));
713     assertTrue(selected.contains(5));
714     assertTrue(selected.contains(7));
715     assertTrue(selected.contains(8));
716   }
717
718   @Test(groups = "Functional")
719   public void testMarkColumns_toggle()
720   {
721     ColumnSelection cs = new ColumnSelection();
722     cs.addElement(1); // outside change range
723     cs.addElement(3);
724     cs.addElement(4);
725     cs.addElement(10); // outside change range
726     BitSet toMark = new BitSet();
727     toMark.set(1);
728     toMark.set(3);
729     toMark.set(6);
730     toMark.set(9);
731
732     /*
733      * toggling state of {3, 6} should leave {1, 4, 6, 10} selected
734      */
735     assertTrue(cs.markColumns(toMark, 3, 8, false, false, true));
736     List<Integer> selected = cs.getSelected();
737     assertEquals(4, selected.size());
738     assertTrue(selected.contains(1));
739     assertTrue(selected.contains(4));
740     assertTrue(selected.contains(6));
741     assertTrue(selected.contains(10));
742   }
743
744   @Test(groups = "Functional")
745   public void testCopyConstructor()
746   {
747     ColumnSelection cs = new ColumnSelection();
748     cs.addElement(3);
749     cs.addElement(1);
750     cs.hideColumns(10, 11);
751     cs.hideColumns(5, 7);
752     assertEquals("[5, 7]", Arrays.toString(cs.getHiddenColumns().get(0)));
753
754     ColumnSelection cs2 = new ColumnSelection(cs);
755     assertTrue(cs2.hasSelectedColumns());
756     assertTrue(cs2.hasHiddenColumns());
757     // order of column selection is preserved
758     assertEquals("[3, 1]", cs2.getSelected().toString());
759     assertEquals(2, cs2.getHiddenColumns().size());
760     // hidden columns are held in column order
761     assertEquals("[5, 7]", Arrays.toString(cs2.getHiddenColumns().get(0)));
762     assertEquals("[10, 11]", Arrays.toString(cs2.getHiddenColumns().get(1)));
763   }
764 }