JAL-2446 merged to spike branch
[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.assertTrue;
26 import static org.testng.AssertJUnit.fail;
27
28 import jalview.analysis.AlignmentGenerator;
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   @Test(groups = { "Functional" })
63   public void testSetElementsFrom()
64   {
65     ColumnSelection fromcs = new ColumnSelection();
66     ColumnSelection tocs = new ColumnSelection();
67     HiddenColumns hidden = new HiddenColumns();
68
69     fromcs.addElement(2);
70     fromcs.addElement(3);
71     fromcs.addElement(5);
72
73     tocs.setElementsFrom(fromcs, hidden);
74     assertTrue(tocs.equals(fromcs));
75
76     hidden.hideColumns(4, 6);
77     tocs.setElementsFrom(fromcs, hidden);
78
79     // expect cols 2 and 3 to be selected but not 5
80     ColumnSelection expectcs = new ColumnSelection();
81     expectcs.addElement(2);
82     expectcs.addElement(3);
83     assertTrue(tocs.equals(expectcs));
84   }
85
86   /**
87    * Test the remove method - in particular to verify that remove(int i) removes
88    * the element whose value is i, _NOT_ the i'th element.
89    */
90   @Test(groups = { "Functional" })
91   public void testRemoveElement()
92   {
93     ColumnSelection cs = new ColumnSelection();
94     cs.addElement(2);
95     cs.addElement(5);
96
97     // removing elements not in the list has no effect
98     cs.removeElement(0);
99     cs.removeElement(1);
100     List<Integer> sel = cs.getSelected();
101     assertEquals(2, sel.size());
102     assertEquals(new Integer(2), sel.get(0));
103     assertEquals(new Integer(5), sel.get(1));
104
105     // removing an element in the list removes it
106     cs.removeElement(2);
107     // ...and also from the read-only view
108     assertEquals(1, sel.size());
109     sel = cs.getSelected();
110     assertEquals(1, sel.size());
111     assertEquals(new Integer(5), sel.get(0));
112   }
113
114   /**
115    * Test the method that hides a specified column including any adjacent
116    * selected columns. This is a convenience method for the case where multiple
117    * column regions are selected and then hidden using menu option View | Hide |
118    * Selected Columns.
119    */
120   @Test(groups = { "Functional" })
121   public void testHideColumns_withSelection()
122   {
123     // create random alignment
124     AlignmentGenerator gen = new AlignmentGenerator(false);
125     AlignmentI al = gen.generate(50, 20, 123, 5, 5);
126
127     ColumnSelection cs = new ColumnSelection();
128     // select columns 4-6
129     cs.addElement(4);
130     cs.addElement(5);
131     cs.addElement(6);
132     // hide column 5 (and adjacent):
133     cs.hideSelectedColumns(5, al.getHiddenColumns());
134     // 4,5,6 now hidden:
135     List<int[]> hidden = al.getHiddenColumns().getHiddenRegions();
136     assertEquals(1, hidden.size());
137     assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
138     // none now selected:
139     assertTrue(cs.getSelected().isEmpty());
140
141     // repeat, hiding column 4 (5 and 6)
142     al = gen.generate(50, 20, 123, 5, 5);
143     cs = new ColumnSelection();
144     cs.addElement(4);
145     cs.addElement(5);
146     cs.addElement(6);
147     cs.hideSelectedColumns(4, al.getHiddenColumns());
148     hidden = al.getHiddenColumns().getHiddenRegions();
149     assertEquals(1, hidden.size());
150     assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
151     assertTrue(cs.getSelected().isEmpty());
152
153     // repeat, hiding column (4, 5 and) 6
154     al = gen.generate(50, 20, 123, 5, 5);
155     cs = new ColumnSelection();
156     cs.addElement(4);
157     cs.addElement(5);
158     cs.addElement(6);
159     cs.hideSelectedColumns(6, al.getHiddenColumns());
160     hidden = al.getHiddenColumns().getHiddenRegions();
161     assertEquals(1, hidden.size());
162     assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
163     assertTrue(cs.getSelected().isEmpty());
164
165     // repeat, with _only_ adjacent columns selected
166     al = gen.generate(50, 20, 123, 5, 5);
167     cs = new ColumnSelection();
168     cs.addElement(4);
169     cs.addElement(6);
170     cs.hideSelectedColumns(5, al.getHiddenColumns());
171     hidden = al.getHiddenColumns().getHiddenRegions();
172     assertEquals(1, hidden.size());
173     assertEquals("[4, 6]", Arrays.toString(hidden.get(0)));
174     assertTrue(cs.getSelected().isEmpty());
175   }
176
177   /**
178    * Test the method that hides all (possibly disjoint) selected column ranges
179    */
180   @Test(groups = { "Functional" })
181   public void testHideSelectedColumns()
182   {
183     // create random alignment
184     AlignmentGenerator gen = new AlignmentGenerator(false);
185     AlignmentI al = gen.generate(50, 20, 123, 5, 5);
186
187     ColumnSelection cs = new ColumnSelection();
188     int[] sel = { 2, 3, 4, 7, 8, 9, 20, 21, 22 };
189     for (int col : sel)
190     {
191       cs.addElement(col);
192     }
193
194     HiddenColumns cols = al.getHiddenColumns();
195     cols.hideColumns(15, 18);
196
197     cs.hideSelectedColumns(al);
198     assertTrue(cs.getSelected().isEmpty());
199     List<int[]> hidden = cols.getHiddenRegions();
200     assertEquals(4, hidden.size());
201     assertEquals("[2, 4]", Arrays.toString(hidden.get(0)));
202     assertEquals("[7, 9]", Arrays.toString(hidden.get(1)));
203     assertEquals("[15, 18]", Arrays.toString(hidden.get(2)));
204     assertEquals("[20, 22]", Arrays.toString(hidden.get(3)));
205   }
206
207   /**
208    * Test the method that gets runs of selected columns ordered by column. If
209    * this fails, HideSelectedColumns may also fail
210    */
211   @Test(groups = { "Functional" })
212   public void testGetSelectedRanges()
213   {
214     /*
215      * getSelectedRanges returns ordered columns regardless
216      * of the order in which they are added
217      */
218     ColumnSelection cs = new ColumnSelection();
219     int[] sel = { 4, 3, 7, 21, 9, 20, 8, 22, 2 };
220     for (int col : sel)
221     {
222       cs.addElement(col);
223     }
224     List<int[]> range;
225     range = cs.getSelectedRanges();
226     assertEquals(3, range.size());
227     assertEquals("[2, 4]", Arrays.toString(range.get(0)));
228     assertEquals("[7, 9]", Arrays.toString(range.get(1)));
229     assertEquals("[20, 22]", Arrays.toString(range.get(2)));
230     cs.addElement(0);
231     cs.addElement(1);
232     range = cs.getSelectedRanges();
233     assertEquals(3, range.size());
234     assertEquals("[0, 4]", Arrays.toString(range.get(0)));
235   }
236
237   @Test(groups = { "Functional" })
238   public void testInvertColumnSelection()
239   {
240     // create random alignment
241     AlignmentGenerator gen = new AlignmentGenerator(false);
242     AlignmentI al = gen.generate(50, 20, 123, 5, 5);
243
244     ColumnSelection cs = new ColumnSelection();
245     cs.addElement(4);
246     cs.addElement(6);
247     cs.addElement(8);
248
249     HiddenColumns cols = al.getHiddenColumns();
250     cols.hideColumns(3, 3);
251     cols.hideColumns(6, 6);
252
253     // invert selection from start (inclusive) to end (exclusive)
254     cs.invertColumnSelection(2, 9, al);
255     assertEquals("[2, 5, 7]", cs.getSelected().toString());
256
257     cs.invertColumnSelection(1, 9, al);
258     assertEquals("[1, 4, 8]", cs.getSelected().toString());
259   }
260
261   @Test(groups = { "Functional" })
262   public void testMaxColumnSelection()
263   {
264     ColumnSelection cs = new ColumnSelection();
265     cs.addElement(0);
266     cs.addElement(513);
267     cs.addElement(1);
268     assertEquals(513, cs.getMax());
269     cs.removeElement(513);
270     assertEquals(1, cs.getMax());
271     cs.removeElement(1);
272     assertEquals(0, cs.getMax());
273     cs.addElement(512);
274     cs.addElement(513);
275     assertEquals(513, cs.getMax());
276
277   }
278
279   @Test(groups = { "Functional" })
280   public void testMinColumnSelection()
281   {
282     ColumnSelection cs = new ColumnSelection();
283     cs.addElement(0);
284     cs.addElement(513);
285     cs.addElement(1);
286     assertEquals(0, cs.getMin());
287     cs.removeElement(0);
288     assertEquals(1, cs.getMin());
289     cs.addElement(0);
290     assertEquals(0, cs.getMin());
291   }
292
293   @Test(groups = { "Functional" })
294   public void testEquals()
295   {
296     ColumnSelection cs = new ColumnSelection();
297     cs.addElement(0);
298     cs.addElement(513);
299     cs.addElement(1);
300
301     // same selections added in a different order
302     ColumnSelection cs2 = new ColumnSelection();
303     cs2.addElement(1);
304     cs2.addElement(513);
305     cs2.addElement(0);
306
307     assertTrue(cs.equals(cs2));
308     assertTrue(cs.equals(cs));
309     assertTrue(cs2.equals(cs));
310     assertTrue(cs2.equals(cs2));
311
312     cs2.addElement(12);
313     assertFalse(cs.equals(cs2));
314     assertFalse(cs2.equals(cs));
315
316     cs2.removeElement(12);
317     assertTrue(cs.equals(cs2));
318   }
319
320   /*
321       cs2.hideSelectedColumns(88);
322       assertFalse(cs.equals(cs2));
323       /*
324        * unhiding a column adds it to selection!
325        */
326   /*    cs2.revealHiddenColumns(88);
327       assertFalse(cs.equals(cs2));
328       cs.addElement(88);
329       assertTrue(cs.equals(cs2));
330     */
331
332   /**
333    * Test the method that returns selected columns, in the order in which they
334    * were added
335    */
336   @Test(groups = { "Functional" })
337   public void testGetSelected()
338   {
339     ColumnSelection cs = new ColumnSelection();
340     int[] sel = { 4, 3, 7, 21 };
341     for (int col : sel)
342     {
343       cs.addElement(col);
344     }
345
346     List<Integer> selected = cs.getSelected();
347     assertEquals(4, selected.size());
348     assertEquals("[4, 3, 7, 21]", selected.toString());
349
350     /*
351      * getSelected returns a read-only view of the list
352      * verify the view follows any changes in it
353      */
354     cs.removeElement(7);
355     cs.addElement(1);
356     cs.removeElement(4);
357     assertEquals("[3, 21, 1]", selected.toString());
358   }
359
360   /**
361    * Test to verify that the list returned by getSelection cannot be modified
362    */
363   @Test(groups = { "Functional" })
364   public void testGetSelected_isReadOnly()
365   {
366     ColumnSelection cs = new ColumnSelection();
367     cs.addElement(3);
368
369     List<Integer> selected = cs.getSelected();
370     try
371     {
372       selected.clear();
373       fail("expected exception");
374     } catch (UnsupportedOperationException e)
375     {
376       // expected
377     }
378     try
379     {
380       selected.add(1);
381       fail("expected exception");
382     } catch (UnsupportedOperationException e)
383     {
384       // expected
385     }
386     try
387     {
388       selected.remove(3);
389       fail("expected exception");
390     } catch (UnsupportedOperationException e)
391     {
392       // expected
393     }
394     try
395     {
396       Collections.sort(selected);
397       fail("expected exception");
398     } catch (UnsupportedOperationException e)
399     {
400       // expected
401     }
402   }
403
404   /**
405    * Test that demonstrates a ConcurrentModificationException is thrown if you
406    * change the selection while iterating over it
407    */
408   @Test(
409     groups = "Functional",
410     expectedExceptions = { ConcurrentModificationException.class })
411   public void testGetSelected_concurrentModification()
412   {
413     ColumnSelection cs = new ColumnSelection();
414     cs.addElement(0);
415     cs.addElement(1);
416     cs.addElement(2);
417
418     /*
419      * simulate changing the list under us (e.g. in a separate
420      * thread) while iterating over it -> ConcurrentModificationException
421      */
422     List<Integer> selected = cs.getSelected();
423     for (Integer col : selected)
424     {
425       if (col.intValue() == 0)
426       {
427         cs.removeElement(1);
428       }
429     }
430   }
431
432   @Test(groups = "Functional")
433   public void testMarkColumns()
434   {
435     ColumnSelection cs = new ColumnSelection();
436     cs.addElement(5); // this will be cleared
437     BitSet toMark = new BitSet();
438     toMark.set(1);
439     toMark.set(3);
440     toMark.set(6);
441     toMark.set(9);
442
443     assertTrue(cs.markColumns(toMark, 3, 8, false, false, false));
444     List<Integer> selected = cs.getSelected();
445     assertEquals(2, selected.size());
446     assertTrue(selected.contains(3));
447     assertTrue(selected.contains(6));
448   }
449
450   @Test(groups = "Functional")
451   public void testMarkColumns_extend()
452   {
453     ColumnSelection cs = new ColumnSelection();
454     cs.addElement(1);
455     cs.addElement(5);
456     BitSet toMark = new BitSet();
457     toMark.set(1);
458     toMark.set(3);
459     toMark.set(6);
460     toMark.set(9);
461
462     /*
463      * extending selection of {3, 6} should leave {1, 3, 5, 6} selected
464      */
465     assertTrue(cs.markColumns(toMark, 3, 8, false, true, false));
466     List<Integer> selected = cs.getSelected();
467     assertEquals(4, selected.size());
468     assertTrue(selected.contains(1));
469     assertTrue(selected.contains(3));
470     assertTrue(selected.contains(5));
471     assertTrue(selected.contains(6));
472   }
473
474   @Test(groups = "Functional")
475   public void testMarkColumns_invert()
476   {
477     ColumnSelection cs = new ColumnSelection();
478     cs.addElement(5); // this will be cleared
479     BitSet toMark = new BitSet();
480     toMark.set(1);
481     toMark.set(3);
482     toMark.set(6);
483     toMark.set(9);
484
485     /*
486      * inverted selection of {3, 6} should select {4, 5, 7, 8}
487      */
488     assertTrue(cs.markColumns(toMark, 3, 8, true, false, false));
489     List<Integer> selected = cs.getSelected();
490     assertEquals(4, selected.size());
491     assertTrue(selected.contains(4));
492     assertTrue(selected.contains(5));
493     assertTrue(selected.contains(7));
494     assertTrue(selected.contains(8));
495   }
496
497   @Test(groups = "Functional")
498   public void testMarkColumns_toggle()
499   {
500     ColumnSelection cs = new ColumnSelection();
501     cs.addElement(1); // outside change range
502     cs.addElement(3);
503     cs.addElement(4);
504     cs.addElement(10); // outside change range
505     BitSet toMark = new BitSet();
506     toMark.set(1);
507     toMark.set(3);
508     toMark.set(6);
509     toMark.set(9);
510
511     /*
512      * toggling state of {3, 6} should leave {1, 4, 6, 10} selected
513      */
514     assertTrue(cs.markColumns(toMark, 3, 8, false, false, true));
515     List<Integer> selected = cs.getSelected();
516     assertEquals(4, selected.size());
517     assertTrue(selected.contains(1));
518     assertTrue(selected.contains(4));
519     assertTrue(selected.contains(6));
520     assertTrue(selected.contains(10));
521   }
522
523   @Test(groups = "Functional")
524   public void testCopyConstructor()
525   {
526     ColumnSelection cs = new ColumnSelection();
527     cs.addElement(3);
528     cs.addElement(1);
529
530     ColumnSelection cs2 = new ColumnSelection(cs);
531     assertTrue(cs2.hasSelectedColumns());
532
533     // order of column selection is preserved
534     assertEquals("[3, 1]", cs2.getSelected().toString());
535   }
536
537
538   @Test(groups = { "Functional" })
539   public void testStretchGroup_expand()
540   {
541     /*
542      * test that emulates clicking column 4 (selected)
543      * and dragging right to column 5 (all base 0)
544      */
545     ColumnSelection cs = new ColumnSelection();
546     cs.addElement(4);
547     SequenceGroup sg = new SequenceGroup();
548     sg.setStartRes(4);
549     sg.setEndRes(4);
550     cs.stretchGroup(5, sg, 4, 4);
551     assertEquals(cs.getSelected().size(), 2);
552     assertTrue(cs.contains(4));
553     assertTrue(cs.contains(5));
554     assertEquals(sg.getStartRes(), 4);
555     assertEquals(sg.getEndRes(), 5);
556
557     /*
558      * emulate drag right with columns 10-20 already selected
559      */
560     cs.clear();
561     for (int i = 10; i <= 20; i++)
562     {
563       cs.addElement(i);
564     }
565     assertEquals(cs.getSelected().size(), 11);
566     sg = new SequenceGroup();
567     sg.setStartRes(10);
568     sg.setEndRes(20);
569     cs.stretchGroup(21, sg, 10, 20);
570     assertEquals(cs.getSelected().size(), 12);
571     assertTrue(cs.contains(10));
572     assertTrue(cs.contains(21));
573     assertEquals(sg.getStartRes(), 10);
574     assertEquals(sg.getEndRes(), 21);
575   }
576
577   @Test(groups = { "Functional" })
578   public void testStretchGroup_shrink()
579   {
580     /*
581      * emulate drag left to 19 with columns 10-20 already selected
582      */
583     ColumnSelection cs = new ColumnSelection();
584     for (int i = 10; i <= 20; i++)
585     {
586       cs.addElement(i);
587     }
588     assertEquals(cs.getSelected().size(), 11);
589     SequenceGroup sg = new SequenceGroup();
590     sg.setStartRes(10);
591     sg.setEndRes(20);
592     cs.stretchGroup(19, sg, 10, 20);
593     assertEquals(cs.getSelected().size(), 10);
594     assertTrue(cs.contains(10));
595     assertTrue(cs.contains(19));
596     assertFalse(cs.contains(20));
597     assertEquals(sg.getStartRes(), 10);
598     assertEquals(sg.getEndRes(), 19);
599   }
600 }