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