/* * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) * Copyright (C) $$Year-Rel$$ The Jalview Authors * * This file is part of Jalview. * * Jalview is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * Jalview is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Jalview. If not, see . * The Jalview Authors are detailed in the 'AUTHORS' file. */ package jalview.datamodel; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.assertSame; import static org.testng.AssertJUnit.assertTrue; import static org.testng.AssertJUnit.fail; import java.util.Arrays; import java.util.BitSet; import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.List; import org.testng.annotations.Test; public class ColumnSelectionTest { @Test(groups = { "Functional" }) public void testAddElement() { ColumnSelection cs = new ColumnSelection(); cs.addElement(2); cs.addElement(5); cs.addElement(3); cs.addElement(5); // ignored List sel = cs.getSelected(); assertEquals("[2, 5, 3]", sel.toString()); } /** * Test the remove method - in particular to verify that remove(int i) removes * the element whose value is i, _NOT_ the i'th element. */ @Test(groups = { "Functional" }) public void testRemoveElement() { ColumnSelection cs = new ColumnSelection(); cs.addElement(2); cs.addElement(5); // removing elements not in the list has no effect cs.removeElement(0); cs.removeElement(1); List sel = cs.getSelected(); assertEquals(2, sel.size()); assertEquals(new Integer(2), sel.get(0)); assertEquals(new Integer(5), sel.get(1)); // removing an element in the list removes it cs.removeElement(2); // ...and also from the read-only view assertEquals(1, sel.size()); sel = cs.getSelected(); assertEquals(1, sel.size()); assertEquals(new Integer(5), sel.get(0)); } /** * Test the method that finds the visible column position of an alignment * column, allowing for hidden columns. */ @Test(groups = { "Functional" }) public void testFindColumnPosition() { ColumnSelection cs = new ColumnSelection(); assertEquals(5, cs.findColumnPosition(5)); // hiding column 6 makes no difference cs.hideColumns(6, 6); assertEquals(5, cs.findColumnPosition(5)); // hiding column 4 moves column 5 to column 4 cs.hideColumns(4, 4); assertEquals(4, cs.findColumnPosition(5)); // hiding columns 1 and 2 moves column 5 to column 2 cs.hideColumns(1, 2); assertEquals(2, cs.findColumnPosition(5)); } /** * Test the code used to locate the reference sequence ruler origin */ @Test(groups = { "Functional" }) public void testLocateVisibleBoundsofSequence() { ColumnSelection cs = new ColumnSelection(); SequenceI seq = new Sequence("RefSeq", "-A-SD-ASD--E---"); assertEquals(2, seq.findIndex(seq.getStart())); // no hidden columns assertEquals( Arrays.toString(new int[] { seq.findIndex(seq.getStart()) - 1, seq.findIndex(seq.getEnd()) - 1, seq.getStart(), seq.getEnd(), seq.findIndex(seq.getStart()) - 1, seq.findIndex(seq.getEnd()) - 1 }), Arrays.toString(cs.locateVisibleBoundsOfSequence(seq))); // hidden column on gap after end of sequence - should not affect bounds cs.hideColumns(13); assertEquals( Arrays.toString(new int[] { seq.findIndex(seq.getStart()) - 1, seq.findIndex(seq.getEnd()) - 1, seq.getStart(), seq.getEnd(), seq.findIndex(seq.getStart()) - 1, seq.findIndex(seq.getEnd()) - 1 }), Arrays.toString(cs.locateVisibleBoundsOfSequence(seq))); cs.revealAllHiddenColumns(); // hidden column on gap before beginning of sequence - should vis bounds by // one cs.hideColumns(0); assertEquals( Arrays.toString(new int[] { seq.findIndex(seq.getStart()) - 2, seq.findIndex(seq.getEnd()) - 2, seq.getStart(), seq.getEnd(), seq.findIndex(seq.getStart()) - 1, seq.findIndex(seq.getEnd()) - 1 }), Arrays.toString(cs.locateVisibleBoundsOfSequence(seq))); cs.revealAllHiddenColumns(); // hide columns around most of sequence - leave one residue remaining cs.hideColumns(1, 3); cs.hideColumns(6, 11); assertEquals("-D", cs.getVisibleSequenceStrings(0, 5, new SequenceI[] { seq })[0]); assertEquals( Arrays.toString(new int[] { 1, 1, 3, 3, seq.findIndex(seq.getStart()) - 1, seq.findIndex(seq.getEnd()) - 1 }), Arrays.toString(cs.locateVisibleBoundsOfSequence(seq))); cs.revealAllHiddenColumns(); // hide whole sequence - should just get location of hidden region // containing sequence cs.hideColumns(1, 11); assertEquals( Arrays.toString(new int[] { 0, 1, 0, 0, seq.findIndex(seq.getStart()) - 1, seq.findIndex(seq.getEnd()) - 1 }), Arrays.toString(cs.locateVisibleBoundsOfSequence(seq))); } @Test(groups={"Functional"}) public void testLocateVisibleBoundsPathologicals() { // test some pathological cases we missed AlignmentI al = new Alignment(new SequenceI[] { new Sequence("refseqGaptest","KTDVTI----------NFI-----G----L")}); ColumnSelection cs = new ColumnSelection(); cs.hideInsertionsFor(al.getSequenceAt(0)); assertEquals( "G", "" + al.getSequenceAt(0).getCharAt( cs.adjustForHiddenColumns(9))); } @Test(groups = { "Functional" }) public void testHideColumns() { ColumnSelection cs = new ColumnSelection(); cs.hideColumns(5); List hidden = cs.getHiddenColumns(); assertEquals(1, hidden.size()); assertEquals("[5, 5]", Arrays.toString(hidden.get(0))); cs.hideColumns(3); assertEquals(2, hidden.size()); // two hidden ranges, in order: assertSame(hidden, cs.getHiddenColumns()); assertEquals("[3, 3]", Arrays.toString(hidden.get(0))); assertEquals("[5, 5]", Arrays.toString(hidden.get(1))); // hiding column 4 expands [3, 3] to [3, 4] // not fancy enough to coalesce this into [3, 5] though cs.hideColumns(4); hidden = cs.getHiddenColumns(); assertEquals(2, hidden.size()); assertEquals("[3, 4]", Arrays.toString(hidden.get(0))); assertEquals("[5, 5]", Arrays.toString(hidden.get(1))); // clear hidden columns (note they are added to selected) cs.revealAllHiddenColumns(); // it is now actually null but getter returns an empty list assertTrue(cs.getHiddenColumns().isEmpty()); cs.hideColumns(3, 6); hidden = cs.getHiddenColumns(); int[] firstHiddenRange = hidden.get(0); assertEquals("[3, 6]", Arrays.toString(firstHiddenRange)); // adding a subrange of already hidden should do nothing cs.hideColumns(4, 5); assertEquals(1, hidden.size()); assertSame(firstHiddenRange, cs.getHiddenColumns().get(0)); cs.hideColumns(3, 5); assertEquals(1, hidden.size()); assertSame(firstHiddenRange, cs.getHiddenColumns().get(0)); cs.hideColumns(4, 6); assertEquals(1, hidden.size()); assertSame(firstHiddenRange, cs.getHiddenColumns().get(0)); cs.hideColumns(3, 6); assertEquals(1, hidden.size()); assertSame(firstHiddenRange, cs.getHiddenColumns().get(0)); cs.revealAllHiddenColumns(); cs.hideColumns(2, 4); hidden = cs.getHiddenColumns(); assertEquals(1, hidden.size()); assertEquals("[2, 4]", Arrays.toString(hidden.get(0))); // extend contiguous with 2 positions overlap cs.hideColumns(3, 5); assertEquals(1, hidden.size()); assertEquals("[2, 5]", Arrays.toString(hidden.get(0))); // extend contiguous with 1 position overlap cs.hideColumns(5, 6); assertEquals(1, hidden.size()); assertEquals("[2, 6]", Arrays.toString(hidden.get(0))); // extend contiguous with overlap both ends: cs.hideColumns(1, 7); assertEquals(1, hidden.size()); assertEquals("[1, 7]", Arrays.toString(hidden.get(0))); } /** * Test the method that hides a specified column including any adjacent * selected columns. This is a convenience method for the case where multiple * column regions are selected and then hidden using menu option View | Hide | * Selected Columns. */ @Test(groups = { "Functional" }) public void testHideColumns_withSelection() { ColumnSelection cs = new ColumnSelection(); // select columns 4-6 cs.addElement(4); cs.addElement(5); cs.addElement(6); // hide column 5 (and adjacent): cs.hideColumns(5); // 4,5,6 now hidden: List hidden = cs.getHiddenColumns(); assertEquals(1, hidden.size()); assertEquals("[4, 6]", Arrays.toString(hidden.get(0))); // none now selected: assertTrue(cs.getSelected().isEmpty()); // repeat, hiding column 4 (5 and 6) cs = new ColumnSelection(); cs.addElement(4); cs.addElement(5); cs.addElement(6); cs.hideColumns(4); hidden = cs.getHiddenColumns(); assertEquals(1, hidden.size()); assertEquals("[4, 6]", Arrays.toString(hidden.get(0))); assertTrue(cs.getSelected().isEmpty()); // repeat, hiding column (4, 5 and) 6 cs = new ColumnSelection(); cs.addElement(4); cs.addElement(5); cs.addElement(6); cs.hideColumns(6); hidden = cs.getHiddenColumns(); assertEquals(1, hidden.size()); assertEquals("[4, 6]", Arrays.toString(hidden.get(0))); assertTrue(cs.getSelected().isEmpty()); // repeat, with _only_ adjacent columns selected cs = new ColumnSelection(); cs.addElement(4); cs.addElement(6); cs.hideColumns(5); hidden = cs.getHiddenColumns(); assertEquals(1, hidden.size()); assertEquals("[4, 6]", Arrays.toString(hidden.get(0))); assertTrue(cs.getSelected().isEmpty()); } /** * Test the method that hides all (possibly disjoint) selected column ranges */ @Test(groups = { "Functional" }) public void testHideSelectedColumns() { ColumnSelection cs = new ColumnSelection(); int[] sel = { 2, 3, 4, 7, 8, 9, 20, 21, 22 }; for (int col : sel) { cs.addElement(col); } cs.hideColumns(15, 18); cs.hideSelectedColumns(); assertTrue(cs.getSelected().isEmpty()); List hidden = cs.getHiddenColumns(); assertEquals(4, hidden.size()); assertEquals("[2, 4]", Arrays.toString(hidden.get(0))); assertEquals("[7, 9]", Arrays.toString(hidden.get(1))); assertEquals("[15, 18]", Arrays.toString(hidden.get(2))); assertEquals("[20, 22]", Arrays.toString(hidden.get(3))); } /** * Test the method that gets runs of selected columns ordered by column. If * this fails, HideSelectedColumns may also fail */ @Test(groups = { "Functional" }) public void testGetSelectedRanges() { /* * getSelectedRanges returns ordered columns regardless * of the order in which they are added */ ColumnSelection cs = new ColumnSelection(); int[] sel = { 4, 3, 7, 21, 9, 20, 8, 22, 2 }; for (int col : sel) { cs.addElement(col); } List range; range = cs.getSelectedRanges(); assertEquals(3, range.size()); assertEquals("[2, 4]", Arrays.toString(range.get(0))); assertEquals("[7, 9]", Arrays.toString(range.get(1))); assertEquals("[20, 22]", Arrays.toString(range.get(2))); cs.addElement(0); cs.addElement(1); range = cs.getSelectedRanges(); assertEquals(3, range.size()); assertEquals("[0, 4]", Arrays.toString(range.get(0))); } /** * Test the method that reveals a range of hidden columns given the start * column of the range */ @Test(groups = { "Functional" }) public void testRevealHiddenColumns() { ColumnSelection cs = new ColumnSelection(); cs.hideColumns(5, 8); cs.addElement(10); cs.revealHiddenColumns(5); // hidden columns list now null but getter returns empty list: assertTrue(cs.getHiddenColumns().isEmpty()); // revealed columns are marked as selected (added to selection): assertEquals("[10, 5, 6, 7, 8]", cs.getSelected().toString()); // calling with a column other than the range start does nothing: cs = new ColumnSelection(); cs.hideColumns(5, 8); List hidden = cs.getHiddenColumns(); cs.revealHiddenColumns(6); assertSame(hidden, cs.getHiddenColumns()); assertTrue(cs.getSelected().isEmpty()); } @Test(groups = { "Functional" }) public void testRevealAllHiddenColumns() { ColumnSelection cs = new ColumnSelection(); cs.hideColumns(5, 8); cs.hideColumns(2, 3); cs.addElement(11); cs.addElement(1); cs.revealAllHiddenColumns(); /* * revealing hidden columns adds them (in order) to the (unordered) * selection list */ assertTrue(cs.getHiddenColumns().isEmpty()); assertEquals("[11, 1, 2, 3, 5, 6, 7, 8]", cs.getSelected().toString()); } @Test(groups = { "Functional" }) public void testIsVisible() { ColumnSelection cs = new ColumnSelection(); cs.hideColumns(2, 4); cs.hideColumns(6, 7); assertTrue(cs.isVisible(0)); assertTrue(cs.isVisible(-99)); assertTrue(cs.isVisible(1)); assertFalse(cs.isVisible(2)); assertFalse(cs.isVisible(3)); assertFalse(cs.isVisible(4)); assertTrue(cs.isVisible(5)); assertFalse(cs.isVisible(6)); assertFalse(cs.isVisible(7)); } @Test(groups = { "Functional" }) public void testGetVisibleContigs() { ColumnSelection cs = new ColumnSelection(); cs.hideColumns(3, 6); cs.hideColumns(8, 9); cs.hideColumns(12, 12); // start position is inclusive, end position exclusive: int[] visible = cs.getVisibleContigs(1, 13); assertEquals("[1, 2, 7, 7, 10, 11]", Arrays.toString(visible)); visible = cs.getVisibleContigs(4, 14); assertEquals("[7, 7, 10, 11, 13, 13]", Arrays.toString(visible)); visible = cs.getVisibleContigs(3, 10); assertEquals("[7, 7]", Arrays.toString(visible)); visible = cs.getVisibleContigs(4, 6); assertEquals("[]", Arrays.toString(visible)); } @Test(groups = { "Functional" }) public void testInvertColumnSelection() { ColumnSelection cs = new ColumnSelection(); cs.addElement(4); cs.addElement(6); cs.addElement(8); cs.hideColumns(3, 3); cs.hideColumns(6, 6); // invert selection from start (inclusive) to end (exclusive) // hidden columns are _not_ changed cs.invertColumnSelection(2, 9); assertEquals("[2, 5, 7]", cs.getSelected().toString()); cs.invertColumnSelection(1, 9); assertEquals("[1, 4, 8]", cs.getSelected().toString()); } @Test(groups = { "Functional" }) public void testMaxColumnSelection() { ColumnSelection cs = new ColumnSelection(); cs.addElement(0); cs.addElement(513); cs.addElement(1); assertEquals(513, cs.getMax()); cs.removeElement(513); assertEquals(1, cs.getMax()); cs.removeElement(1); assertEquals(0, cs.getMax()); cs.addElement(512); cs.addElement(513); assertEquals(513, cs.getMax()); } @Test(groups = { "Functional" }) public void testMinColumnSelection() { ColumnSelection cs = new ColumnSelection(); cs.addElement(0); cs.addElement(513); cs.addElement(1); assertEquals(0, cs.getMin()); cs.removeElement(0); assertEquals(1, cs.getMin()); cs.addElement(0); assertEquals(0, cs.getMin()); } @Test(groups = { "Functional" }) public void testEquals() { ColumnSelection cs = new ColumnSelection(); cs.addElement(0); cs.addElement(513); cs.addElement(1); cs.hideColumns(3); cs.hideColumns(7); cs.hideColumns(5,9); // same selections added in a different order ColumnSelection cs2 = new ColumnSelection(); cs2.addElement(1); cs2.addElement(513); cs2.addElement(0); // with no hidden columns assertFalse(cs.equals(cs2)); assertFalse(cs2.equals(cs)); // with hidden columns added in a different order cs2.hideColumns(6, 9); cs2.hideColumns(5, 8); cs2.hideColumns(3); assertTrue(cs.equals(cs2)); assertTrue(cs.equals(cs)); assertTrue(cs2.equals(cs)); assertTrue(cs2.equals(cs2)); cs2.addElement(12); assertFalse(cs.equals(cs2)); assertFalse(cs2.equals(cs)); cs2.removeElement(12); assertTrue(cs.equals(cs2)); cs2.hideColumns(88); assertFalse(cs.equals(cs2)); /* * unhiding a column adds it to selection! */ cs2.revealHiddenColumns(88); assertFalse(cs.equals(cs2)); cs.addElement(88); assertTrue(cs.equals(cs2)); } /** * Test the method that returns selected columns, in the order in which they * were added */ @Test(groups = { "Functional" }) public void testGetSelected() { ColumnSelection cs = new ColumnSelection(); int[] sel = { 4, 3, 7, 21 }; for (int col : sel) { cs.addElement(col); } List selected = cs.getSelected(); assertEquals(4, selected.size()); assertEquals("[4, 3, 7, 21]", selected.toString()); /* * getSelected returns a read-only view of the list * verify the view follows any changes in it */ cs.removeElement(7); cs.addElement(1); cs.removeElement(4); assertEquals("[3, 21, 1]", selected.toString()); } /** * Test to verify that the list returned by getSelection cannot be modified */ @Test(groups = { "Functional" }) public void testGetSelected_isReadOnly() { ColumnSelection cs = new ColumnSelection(); cs.addElement(3); List selected = cs.getSelected(); try { selected.clear(); fail("expected exception"); } catch (UnsupportedOperationException e) { // expected } try { selected.add(1); fail("expected exception"); } catch (UnsupportedOperationException e) { // expected } try { selected.remove(3); fail("expected exception"); } catch (UnsupportedOperationException e) { // expected } try { Collections.sort(selected); fail("expected exception"); } catch (UnsupportedOperationException e) { // expected } } /** * Test that demonstrates a ConcurrentModificationException is thrown if you * change the selection while iterating over it */ @Test( groups = "Functional", expectedExceptions = { ConcurrentModificationException.class }) public void testGetSelected_concurrentModification() { ColumnSelection cs = new ColumnSelection(); cs.addElement(0); cs.addElement(1); cs.addElement(2); /* * simulate changing the list under us (e.g. in a separate * thread) while iterating over it -> ConcurrentModificationException */ List selected = cs.getSelected(); for (Integer col : selected) { if (col.intValue() == 0) { cs.removeElement(1); } } } @Test(groups = "Functional") public void testMarkColumns() { ColumnSelection cs = new ColumnSelection(); cs.addElement(5); // this will be cleared BitSet toMark = new BitSet(); toMark.set(1); toMark.set(3); toMark.set(6); toMark.set(9); assertTrue(cs.markColumns(toMark, 3, 8, false, false, false)); List selected = cs.getSelected(); assertEquals(2, selected.size()); assertTrue(selected.contains(3)); assertTrue(selected.contains(6)); } @Test(groups = "Functional") public void testMarkColumns_extend() { ColumnSelection cs = new ColumnSelection(); cs.addElement(1); cs.addElement(5); BitSet toMark = new BitSet(); toMark.set(1); toMark.set(3); toMark.set(6); toMark.set(9); /* * extending selection of {3, 6} should leave {1, 3, 5, 6} selected */ assertTrue(cs.markColumns(toMark, 3, 8, false, true, false)); List selected = cs.getSelected(); assertEquals(4, selected.size()); assertTrue(selected.contains(1)); assertTrue(selected.contains(3)); assertTrue(selected.contains(5)); assertTrue(selected.contains(6)); } @Test(groups = "Functional") public void testMarkColumns_invert() { ColumnSelection cs = new ColumnSelection(); cs.addElement(5); // this will be cleared BitSet toMark = new BitSet(); toMark.set(1); toMark.set(3); toMark.set(6); toMark.set(9); /* * inverted selection of {3, 6} should select {4, 5, 7, 8} */ assertTrue(cs.markColumns(toMark, 3, 8, true, false, false)); List selected = cs.getSelected(); assertEquals(4, selected.size()); assertTrue(selected.contains(4)); assertTrue(selected.contains(5)); assertTrue(selected.contains(7)); assertTrue(selected.contains(8)); } @Test(groups = "Functional") public void testMarkColumns_toggle() { ColumnSelection cs = new ColumnSelection(); cs.addElement(1); // outside change range cs.addElement(3); cs.addElement(4); cs.addElement(10); // outside change range BitSet toMark = new BitSet(); toMark.set(1); toMark.set(3); toMark.set(6); toMark.set(9); /* * toggling state of {3, 6} should leave {1, 4, 6, 10} selected */ assertTrue(cs.markColumns(toMark, 3, 8, false, false, true)); List selected = cs.getSelected(); assertEquals(4, selected.size()); assertTrue(selected.contains(1)); assertTrue(selected.contains(4)); assertTrue(selected.contains(6)); assertTrue(selected.contains(10)); } @Test(groups = "Functional") public void testCopyConstructor() { ColumnSelection cs = new ColumnSelection(); cs.addElement(3); cs.addElement(1); cs.hideColumns(10, 11); cs.hideColumns(5, 7); assertEquals("[5, 7]", Arrays.toString(cs.getHiddenColumns().get(0))); ColumnSelection cs2 = new ColumnSelection(cs); assertTrue(cs2.hasSelectedColumns()); assertTrue(cs2.hasHiddenColumns()); // order of column selection is preserved assertEquals("[3, 1]", cs2.getSelected().toString()); assertEquals(2, cs2.getHiddenColumns().size()); // hidden columns are held in column order assertEquals("[5, 7]", Arrays.toString(cs2.getHiddenColumns().get(0))); assertEquals("[10, 11]", Arrays.toString(cs2.getHiddenColumns().get(1))); } }