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