JAL-2370 fix bug when hiding a range including two or more hidden ranges
[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     // and merges to [5, 5] to make [3, 5]
209     cs.hideColumns(4);
210     hidden = cs.getHiddenColumns();
211     assertEquals(1, hidden.size());
212     assertEquals("[3, 5]", Arrays.toString(hidden.get(0)));
213
214     // clear hidden columns (note they are added to selected)
215     cs.revealAllHiddenColumns();
216     // it is now actually null but getter returns an empty list
217     assertTrue(cs.getHiddenColumns().isEmpty());
218
219     cs.hideColumns(3, 6);
220     hidden = cs.getHiddenColumns();
221     int[] firstHiddenRange = hidden.get(0);
222     assertEquals("[3, 6]", Arrays.toString(firstHiddenRange));
223
224     // adding a subrange of already hidden should do nothing
225     cs.hideColumns(4, 5);
226     assertEquals(1, hidden.size());
227     assertSame(firstHiddenRange, cs.getHiddenColumns().get(0));
228     cs.hideColumns(3, 5);
229     assertEquals(1, hidden.size());
230     assertSame(firstHiddenRange, cs.getHiddenColumns().get(0));
231     cs.hideColumns(4, 6);
232     assertEquals(1, hidden.size());
233     assertSame(firstHiddenRange, cs.getHiddenColumns().get(0));
234     cs.hideColumns(3, 6);
235     assertEquals(1, hidden.size());
236     assertSame(firstHiddenRange, cs.getHiddenColumns().get(0));
237
238     cs.revealAllHiddenColumns();
239     cs.hideColumns(2, 4);
240     hidden = cs.getHiddenColumns();
241     assertEquals(1, hidden.size());
242     assertEquals("[2, 4]", Arrays.toString(hidden.get(0)));
243
244     // extend contiguous with 2 positions overlap
245     cs.hideColumns(3, 5);
246     assertEquals(1, hidden.size());
247     assertEquals("[2, 5]", Arrays.toString(hidden.get(0)));
248
249     // extend contiguous with 1 position overlap
250     cs.hideColumns(5, 6);
251     assertEquals(1, hidden.size());
252     assertEquals("[2, 6]", Arrays.toString(hidden.get(0)));
253
254     // extend contiguous with overlap both ends:
255     cs.hideColumns(1, 7);
256     assertEquals(1, hidden.size());
257     assertEquals("[1, 7]", Arrays.toString(hidden.get(0)));
258   }
259
260   /**
261    * Test the method that hides a specified column including any adjacent
262    * selected columns. This is a convenience method for the case where multiple
263    * column regions are selected and then hidden using menu option View | Hide |
264    * Selected Columns.
265    */
266   @Test(groups = { "Functional" })
267   public void testHideColumns_withSelection()
268   {
269     ColumnSelection cs = new ColumnSelection();
270     // select columns 4-6
271     cs.addElement(4);
272     cs.addElement(5);
273     cs.addElement(6);
274     // hide column 5 (and adjacent):
275     cs.hideColumns(5);
276     // 4,5,6 now hidden:
277     List<int[]> hidden = cs.getHiddenColumns();
278     assertEquals(1, hidden.size());
279     assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
280     // none now selected:
281     assertTrue(cs.getSelected().isEmpty());
282
283     // repeat, hiding column 4 (5 and 6)
284     cs = new ColumnSelection();
285     cs.addElement(4);
286     cs.addElement(5);
287     cs.addElement(6);
288     cs.hideColumns(4);
289     hidden = cs.getHiddenColumns();
290     assertEquals(1, hidden.size());
291     assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
292     assertTrue(cs.getSelected().isEmpty());
293
294     // repeat, hiding column (4, 5 and) 6
295     cs = new ColumnSelection();
296     cs.addElement(4);
297     cs.addElement(5);
298     cs.addElement(6);
299     cs.hideColumns(6);
300     hidden = cs.getHiddenColumns();
301     assertEquals(1, hidden.size());
302     assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
303     assertTrue(cs.getSelected().isEmpty());
304
305     // repeat, with _only_ adjacent columns selected
306     cs = new ColumnSelection();
307     cs.addElement(4);
308     cs.addElement(6);
309     cs.hideColumns(5);
310     hidden = cs.getHiddenColumns();
311     assertEquals(1, hidden.size());
312     assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
313     assertTrue(cs.getSelected().isEmpty());
314   }
315
316   /**
317    * Test the method that hides all (possibly disjoint) selected column ranges
318    */
319   @Test(groups = { "Functional" })
320   public void testHideSelectedColumns()
321   {
322     ColumnSelection cs = new ColumnSelection();
323     int[] sel = { 2, 3, 4, 7, 8, 9, 20, 21, 22 };
324     for (int col : sel)
325     {
326       cs.addElement(col);
327     }
328     cs.hideColumns(15, 18);
329
330     cs.hideSelectedColumns();
331     assertTrue(cs.getSelected().isEmpty());
332     List<int[]> hidden = cs.getHiddenColumns();
333     assertEquals(4, hidden.size());
334     assertEquals("[2, 4]", Arrays.toString(hidden.get(0)));
335     assertEquals("[7, 9]", Arrays.toString(hidden.get(1)));
336     assertEquals("[15, 18]", Arrays.toString(hidden.get(2)));
337     assertEquals("[20, 22]", Arrays.toString(hidden.get(3)));
338   }
339
340   /**
341    * Test the method that gets runs of selected columns ordered by column. If
342    * this fails, HideSelectedColumns may also fail
343    */
344   @Test(groups = { "Functional" })
345   public void testGetSelectedRanges()
346   {
347     /*
348      * getSelectedRanges returns ordered columns regardless
349      * of the order in which they are added
350      */
351     ColumnSelection cs = new ColumnSelection();
352     int[] sel = { 4, 3, 7, 21, 9, 20, 8, 22, 2 };
353     for (int col : sel)
354     {
355       cs.addElement(col);
356     }
357     List<int[]> range;
358     range = cs.getSelectedRanges();
359     assertEquals(3, range.size());
360     assertEquals("[2, 4]", Arrays.toString(range.get(0)));
361     assertEquals("[7, 9]", Arrays.toString(range.get(1)));
362     assertEquals("[20, 22]", Arrays.toString(range.get(2)));
363     cs.addElement(0);
364     cs.addElement(1);
365     range = cs.getSelectedRanges();
366     assertEquals(3, range.size());
367     assertEquals("[0, 4]", Arrays.toString(range.get(0)));
368   }
369
370   /**
371    * Test the method that reveals a range of hidden columns given the start
372    * column of the range
373    */
374   @Test(groups = { "Functional" })
375   public void testRevealHiddenColumns()
376   {
377     ColumnSelection cs = new ColumnSelection();
378     cs.hideColumns(5, 8);
379     cs.addElement(10);
380     cs.revealHiddenColumns(5);
381     // hidden columns list now null but getter returns empty list:
382     assertTrue(cs.getHiddenColumns().isEmpty());
383     // revealed columns are marked as selected (added to selection):
384     assertEquals("[10, 5, 6, 7, 8]", cs.getSelected().toString());
385
386     // calling with a column other than the range start does nothing:
387     cs = new ColumnSelection();
388     cs.hideColumns(5, 8);
389     List<int[]> hidden = cs.getHiddenColumns();
390     cs.revealHiddenColumns(6);
391     assertSame(hidden, cs.getHiddenColumns());
392     assertTrue(cs.getSelected().isEmpty());
393   }
394
395   @Test(groups = { "Functional" })
396   public void testRevealAllHiddenColumns()
397   {
398     ColumnSelection cs = new ColumnSelection();
399     cs.hideColumns(5, 8);
400     cs.hideColumns(2, 3);
401     cs.addElement(11);
402     cs.addElement(1);
403     cs.revealAllHiddenColumns();
404
405     /*
406      * revealing hidden columns adds them (in order) to the (unordered)
407      * selection list
408      */
409     assertTrue(cs.getHiddenColumns().isEmpty());
410     assertEquals("[11, 1, 2, 3, 5, 6, 7, 8]", cs.getSelected().toString());
411   }
412
413   @Test(groups = { "Functional" })
414   public void testIsVisible()
415   {
416     ColumnSelection cs = new ColumnSelection();
417     cs.hideColumns(2, 4);
418     cs.hideColumns(6, 7);
419     assertTrue(cs.isVisible(0));
420     assertTrue(cs.isVisible(-99));
421     assertTrue(cs.isVisible(1));
422     assertFalse(cs.isVisible(2));
423     assertFalse(cs.isVisible(3));
424     assertFalse(cs.isVisible(4));
425     assertTrue(cs.isVisible(5));
426     assertFalse(cs.isVisible(6));
427     assertFalse(cs.isVisible(7));
428   }
429
430   @Test(groups = { "Functional" })
431   public void testGetVisibleContigs()
432   {
433     ColumnSelection cs = new ColumnSelection();
434     cs.hideColumns(3, 6);
435     cs.hideColumns(8, 9);
436     cs.hideColumns(12, 12);
437
438     // start position is inclusive, end position exclusive:
439     int[] visible = cs.getVisibleContigs(1, 13);
440     assertEquals("[1, 2, 7, 7, 10, 11]", Arrays.toString(visible));
441
442     visible = cs.getVisibleContigs(4, 14);
443     assertEquals("[7, 7, 10, 11, 13, 13]", Arrays.toString(visible));
444
445     visible = cs.getVisibleContigs(3, 10);
446     assertEquals("[7, 7]", Arrays.toString(visible));
447
448     visible = cs.getVisibleContigs(4, 6);
449     assertEquals("[]", Arrays.toString(visible));
450   }
451
452   @Test(groups = { "Functional" })
453   public void testInvertColumnSelection()
454   {
455     ColumnSelection cs = new ColumnSelection();
456     cs.addElement(4);
457     cs.addElement(6);
458     cs.addElement(8);
459     cs.hideColumns(3, 3);
460     cs.hideColumns(6, 6);
461
462     // invert selection from start (inclusive) to end (exclusive)
463     // hidden columns are _not_ changed
464     cs.invertColumnSelection(2, 9);
465     assertEquals("[2, 5, 7]", cs.getSelected().toString());
466
467     cs.invertColumnSelection(1, 9);
468     assertEquals("[1, 4, 8]", cs.getSelected().toString());
469   }
470
471   @Test(groups = { "Functional" })
472   public void testMaxColumnSelection()
473   {
474     ColumnSelection cs = new ColumnSelection();
475     cs.addElement(0);
476     cs.addElement(513);
477     cs.addElement(1);
478     assertEquals(513, cs.getMax());
479     cs.removeElement(513);
480     assertEquals(1, cs.getMax());
481     cs.removeElement(1);
482     assertEquals(0, cs.getMax());
483     cs.addElement(512);
484     cs.addElement(513);
485     assertEquals(513, cs.getMax());
486
487   }
488
489   @Test(groups = { "Functional" })
490   public void testMinColumnSelection()
491   {
492     ColumnSelection cs = new ColumnSelection();
493     cs.addElement(0);
494     cs.addElement(513);
495     cs.addElement(1);
496     assertEquals(0, cs.getMin());
497     cs.removeElement(0);
498     assertEquals(1, cs.getMin());
499     cs.addElement(0);
500     assertEquals(0, cs.getMin());
501   }
502
503   @Test(groups = { "Functional" })
504   public void testEquals()
505   {
506     ColumnSelection cs = new ColumnSelection();
507     cs.addElement(0);
508     cs.addElement(513);
509     cs.addElement(1);
510     cs.hideColumns(3);
511     cs.hideColumns(7);
512     cs.hideColumns(5, 9);
513
514     // same selections added in a different order
515     ColumnSelection cs2 = new ColumnSelection();
516     cs2.addElement(1);
517     cs2.addElement(513);
518     cs2.addElement(0);
519
520     // with no hidden columns
521     assertFalse(cs.equals(cs2));
522     assertFalse(cs2.equals(cs));
523
524     // with hidden columns added in a different order
525     cs2.hideColumns(6, 9);
526     cs2.hideColumns(5, 8);
527     cs2.hideColumns(3);
528
529     assertTrue(cs.equals(cs2));
530     assertTrue(cs.equals(cs));
531     assertTrue(cs2.equals(cs));
532     assertTrue(cs2.equals(cs2));
533
534     cs2.addElement(12);
535     assertFalse(cs.equals(cs2));
536     assertFalse(cs2.equals(cs));
537
538     cs2.removeElement(12);
539     assertTrue(cs.equals(cs2));
540
541     cs2.hideColumns(88);
542     assertFalse(cs.equals(cs2));
543     /*
544      * unhiding a column adds it to selection!
545      */
546     cs2.revealHiddenColumns(88);
547     assertFalse(cs.equals(cs2));
548     cs.addElement(88);
549     assertTrue(cs.equals(cs2));
550   }
551
552   /**
553    * Test the method that returns selected columns, in the order in which they
554    * were added
555    */
556   @Test(groups = { "Functional" })
557   public void testGetSelected()
558   {
559     ColumnSelection cs = new ColumnSelection();
560     int[] sel = { 4, 3, 7, 21 };
561     for (int col : sel)
562     {
563       cs.addElement(col);
564     }
565
566     List<Integer> selected = cs.getSelected();
567     assertEquals(4, selected.size());
568     assertEquals("[4, 3, 7, 21]", selected.toString());
569
570     /*
571      * getSelected returns a read-only view of the list
572      * verify the view follows any changes in it
573      */
574     cs.removeElement(7);
575     cs.addElement(1);
576     cs.removeElement(4);
577     assertEquals("[3, 21, 1]", selected.toString());
578   }
579
580   /**
581    * Test to verify that the list returned by getSelection cannot be modified
582    */
583   @Test(groups = { "Functional" })
584   public void testGetSelected_isReadOnly()
585   {
586     ColumnSelection cs = new ColumnSelection();
587     cs.addElement(3);
588
589     List<Integer> selected = cs.getSelected();
590     try
591     {
592       selected.clear();
593       fail("expected exception");
594     } catch (UnsupportedOperationException e)
595     {
596       // expected
597     }
598     try
599     {
600       selected.add(1);
601       fail("expected exception");
602     } catch (UnsupportedOperationException e)
603     {
604       // expected
605     }
606     try
607     {
608       selected.remove(3);
609       fail("expected exception");
610     } catch (UnsupportedOperationException e)
611     {
612       // expected
613     }
614     try
615     {
616       Collections.sort(selected);
617       fail("expected exception");
618     } catch (UnsupportedOperationException e)
619     {
620       // expected
621     }
622   }
623
624   /**
625    * Test that demonstrates a ConcurrentModificationException is thrown if you
626    * change the selection while iterating over it
627    */
628   @Test(
629     groups = "Functional",
630     expectedExceptions = { ConcurrentModificationException.class })
631   public void testGetSelected_concurrentModification()
632   {
633     ColumnSelection cs = new ColumnSelection();
634     cs.addElement(0);
635     cs.addElement(1);
636     cs.addElement(2);
637
638     /*
639      * simulate changing the list under us (e.g. in a separate
640      * thread) while iterating over it -> ConcurrentModificationException
641      */
642     List<Integer> selected = cs.getSelected();
643     for (Integer col : selected)
644     {
645       if (col.intValue() == 0)
646       {
647         cs.removeElement(1);
648       }
649     }
650   }
651
652   @Test(groups = "Functional")
653   public void testMarkColumns()
654   {
655     ColumnSelection cs = new ColumnSelection();
656     cs.addElement(5); // this will be cleared
657     BitSet toMark = new BitSet();
658     toMark.set(1);
659     toMark.set(3);
660     toMark.set(6);
661     toMark.set(9);
662
663     assertTrue(cs.markColumns(toMark, 3, 8, false, false, false));
664     List<Integer> selected = cs.getSelected();
665     assertEquals(2, selected.size());
666     assertTrue(selected.contains(3));
667     assertTrue(selected.contains(6));
668   }
669
670   @Test(groups = "Functional")
671   public void testMarkColumns_extend()
672   {
673     ColumnSelection cs = new ColumnSelection();
674     cs.addElement(1);
675     cs.addElement(5);
676     BitSet toMark = new BitSet();
677     toMark.set(1);
678     toMark.set(3);
679     toMark.set(6);
680     toMark.set(9);
681
682     /*
683      * extending selection of {3, 6} should leave {1, 3, 5, 6} selected
684      */
685     assertTrue(cs.markColumns(toMark, 3, 8, false, true, false));
686     List<Integer> selected = cs.getSelected();
687     assertEquals(4, selected.size());
688     assertTrue(selected.contains(1));
689     assertTrue(selected.contains(3));
690     assertTrue(selected.contains(5));
691     assertTrue(selected.contains(6));
692   }
693
694   @Test(groups = "Functional")
695   public void testMarkColumns_invert()
696   {
697     ColumnSelection cs = new ColumnSelection();
698     cs.addElement(5); // this will be cleared
699     BitSet toMark = new BitSet();
700     toMark.set(1);
701     toMark.set(3);
702     toMark.set(6);
703     toMark.set(9);
704
705     /*
706      * inverted selection of {3, 6} should select {4, 5, 7, 8}
707      */
708     assertTrue(cs.markColumns(toMark, 3, 8, true, false, false));
709     List<Integer> selected = cs.getSelected();
710     assertEquals(4, selected.size());
711     assertTrue(selected.contains(4));
712     assertTrue(selected.contains(5));
713     assertTrue(selected.contains(7));
714     assertTrue(selected.contains(8));
715   }
716
717   @Test(groups = "Functional")
718   public void testMarkColumns_toggle()
719   {
720     ColumnSelection cs = new ColumnSelection();
721     cs.addElement(1); // outside change range
722     cs.addElement(3);
723     cs.addElement(4);
724     cs.addElement(10); // outside change range
725     BitSet toMark = new BitSet();
726     toMark.set(1);
727     toMark.set(3);
728     toMark.set(6);
729     toMark.set(9);
730
731     /*
732      * toggling state of {3, 6} should leave {1, 4, 6, 10} selected
733      */
734     assertTrue(cs.markColumns(toMark, 3, 8, false, false, true));
735     List<Integer> selected = cs.getSelected();
736     assertEquals(4, selected.size());
737     assertTrue(selected.contains(1));
738     assertTrue(selected.contains(4));
739     assertTrue(selected.contains(6));
740     assertTrue(selected.contains(10));
741   }
742
743   @Test(groups = "Functional")
744   public void testCopyConstructor()
745   {
746     ColumnSelection cs = new ColumnSelection();
747     cs.addElement(3);
748     cs.addElement(1);
749     cs.hideColumns(10, 11);
750     cs.hideColumns(5, 7);
751     assertEquals("[5, 7]", Arrays.toString(cs.getHiddenColumns().get(0)));
752
753     ColumnSelection cs2 = new ColumnSelection(cs);
754     assertTrue(cs2.hasSelectedColumns());
755     assertTrue(cs2.hasHiddenColumns());
756     // order of column selection is preserved
757     assertEquals("[3, 1]", cs2.getSelected().toString());
758     assertEquals(2, cs2.getHiddenColumns().size());
759     // hidden columns are held in column order
760     assertEquals("[5, 7]", Arrays.toString(cs2.getHiddenColumns().get(0)));
761     assertEquals("[10, 11]", Arrays.toString(cs2.getHiddenColumns().get(1)));
762   }
763
764   /**
765    * Test for the case when a hidden range encloses more one already hidden
766    * range
767    */
768   @Test(groups = { "Functional" })
769   public void testHideColumns_subsumingHidden()
770   {
771     /*
772      * JAL-2370 bug scenario:
773      * two hidden ranges subsumed by a third
774      */
775     ColumnSelection cs = new ColumnSelection();
776     cs.hideColumns(49, 59);
777     cs.hideColumns(69, 79);
778     List<int[]> hidden = cs.getHiddenColumns();
779     assertEquals(2, hidden.size());
780     assertEquals("[49, 59]", Arrays.toString(hidden.get(0)));
781     assertEquals("[69, 79]", Arrays.toString(hidden.get(1)));
782   
783     cs.hideColumns(48, 80);
784     hidden = cs.getHiddenColumns();
785     assertEquals(1, hidden.size());
786     assertEquals("[48, 80]", Arrays.toString(hidden.get(0)));
787
788     /*
789      * another...joining hidden ranges
790      */
791     cs = new ColumnSelection();
792     cs.hideColumns(10, 20);
793     cs.hideColumns(30, 40);
794     cs.hideColumns(50, 60);
795     // hiding 21-49 should merge to one range
796     cs.hideColumns(21, 49);
797     hidden = cs.getHiddenColumns();
798     assertEquals(1, hidden.size());
799     assertEquals("[10, 60]", Arrays.toString(hidden.get(0)));
800
801     /*
802      * another...lef overlap, subsumption, right overlap,
803      * no overlap of existing hidden ranges
804      */
805     cs = new ColumnSelection();
806     cs.hideColumns(10, 20);
807     cs.hideColumns(10, 20);
808     cs.hideColumns(30, 35);
809     cs.hideColumns(40, 50);
810     cs.hideColumns(60, 70);
811
812     cs.hideColumns(15, 45);
813     hidden = cs.getHiddenColumns();
814     assertEquals(2, hidden.size());
815     assertEquals("[10, 50]", Arrays.toString(hidden.get(0)));
816     assertEquals("[60, 70]", Arrays.toString(hidden.get(1)));
817   }
818 }