+ }
+
+ /**
+ * Test that the addEdit command correctly merges insert gap commands when
+ * possible.
+ */
+ @Test(groups = { "Functional" })
+ public void testAddEdit_multipleInsertGap()
+ {
+ /*
+ * 3 insert gap in a row (aka mouse drag right):
+ */
+ Edit e = new EditCommand().new Edit(Action.INSERT_GAP,
+ new SequenceI[] { seqs[0] }, 1, 1, al);
+ testee.addEdit(e);
+ SequenceI edited = new Sequence("seq0", "a?bcdefghjk");
+ edited.setDatasetSequence(seqs[0].getDatasetSequence());
+ e = new EditCommand().new Edit(Action.INSERT_GAP,
+ new SequenceI[] { edited }, 2, 1, al);
+ testee.addEdit(e);
+ edited = new Sequence("seq0", "a??bcdefghjk");
+ edited.setDatasetSequence(seqs[0].getDatasetSequence());
+ e = new EditCommand().new Edit(Action.INSERT_GAP,
+ new SequenceI[] { edited }, 3, 1, al);
+ testee.addEdit(e);
+ assertEquals(1, testee.getSize());
+ assertEquals(Action.INSERT_GAP, testee.getEdit(0).getAction());
+ assertEquals(1, testee.getEdit(0).getPosition());
+ assertEquals(3, testee.getEdit(0).getNumber());
+
+ /*
+ * Add a non-contiguous edit - should not be merged.
+ */
+ e = new EditCommand().new Edit(Action.INSERT_GAP,
+ new SequenceI[] { edited }, 5, 2, al);
+ testee.addEdit(e);
+ assertEquals(2, testee.getSize());
+ assertEquals(5, testee.getEdit(1).getPosition());
+ assertEquals(2, testee.getEdit(1).getNumber());
+
+ /*
+ * Add a Delete after the Insert - should not be merged.
+ */
+ e = new EditCommand().new Edit(Action.DELETE_GAP,
+ new SequenceI[] { edited }, 6, 2, al);
+ testee.addEdit(e);
+ assertEquals(3, testee.getSize());
+ assertEquals(Action.DELETE_GAP, testee.getEdit(2).getAction());
+ assertEquals(6, testee.getEdit(2).getPosition());
+ assertEquals(2, testee.getEdit(2).getNumber());
+ }
+
+ /**
+ * Test that the addEdit command correctly merges delete gap commands when
+ * possible.
+ */
+ @Test(groups = { "Functional" })
+ public void testAddEdit_multipleDeleteGap()
+ {
+ /*
+ * 3 delete gap in a row (aka mouse drag left):
+ */
+ seqs[0].setSequence("a???bcdefghjk");
+ Edit e = new EditCommand().new Edit(Action.DELETE_GAP,
+ new SequenceI[] { seqs[0] }, 4, 1, al);
+ testee.addEdit(e);
+ assertEquals(1, testee.getSize());
+
+ SequenceI edited = new Sequence("seq0", "a??bcdefghjk");
+ edited.setDatasetSequence(seqs[0].getDatasetSequence());
+ e = new EditCommand().new Edit(Action.DELETE_GAP,
+ new SequenceI[] { edited }, 3, 1, al);
+ testee.addEdit(e);
+ assertEquals(1, testee.getSize());
+
+ edited = new Sequence("seq0", "a?bcdefghjk");
+ edited.setDatasetSequence(seqs[0].getDatasetSequence());
+ e = new EditCommand().new Edit(Action.DELETE_GAP,
+ new SequenceI[] { edited }, 2, 1, al);
+ testee.addEdit(e);
+ assertEquals(1, testee.getSize());
+ assertEquals(Action.DELETE_GAP, testee.getEdit(0).getAction());
+ assertEquals(2, testee.getEdit(0).getPosition());
+ assertEquals(3, testee.getEdit(0).getNumber());
+
+ /*
+ * Add a non-contiguous edit - should not be merged.
+ */
+ e = new EditCommand().new Edit(Action.DELETE_GAP,
+ new SequenceI[] { edited }, 2, 1, al);
+ testee.addEdit(e);
+ assertEquals(2, testee.getSize());
+ assertEquals(Action.DELETE_GAP, testee.getEdit(0).getAction());
+ assertEquals(2, testee.getEdit(1).getPosition());
+ assertEquals(1, testee.getEdit(1).getNumber());
+
+ /*
+ * Add an Insert after the Delete - should not be merged.
+ */
+ e = new EditCommand().new Edit(Action.INSERT_GAP,
+ new SequenceI[] { edited }, 1, 1, al);
+ testee.addEdit(e);
+ assertEquals(3, testee.getSize());
+ assertEquals(Action.INSERT_GAP, testee.getEdit(2).getAction());
+ assertEquals(1, testee.getEdit(2).getPosition());
+ assertEquals(1, testee.getEdit(2).getNumber());
+ }
+
+ /**
+ * Test that the addEdit command correctly handles 'remove gaps' edits for the
+ * case when they appear contiguous but are acting on different sequences.
+ * They should not be merged.
+ */
+ @Test(groups = { "Functional" })
+ public void testAddEdit_removeAllGaps()
+ {
+ seqs[0].setSequence("a???bcdefghjk");
+ Edit e = new EditCommand().new Edit(Action.DELETE_GAP,
+ new SequenceI[] { seqs[0] }, 4, 1, al);
+ testee.addEdit(e);
+
+ seqs[1].setSequence("f??ghjklmnopq");
+ Edit e2 = new EditCommand().new Edit(Action.DELETE_GAP, new SequenceI[]
+ { seqs[1] }, 3, 1, al);
+ testee.addEdit(e2);
+ assertEquals(2, testee.getSize());
+ assertSame(e, testee.getEdit(0));
+ assertSame(e2, testee.getEdit(1));
+ }
+
+ /**
+ * Test that the addEdit command correctly merges insert gap commands acting
+ * on a multi-sequence selection.
+ */
+ @Test(groups = { "Functional" })
+ public void testAddEdit_groupInsertGaps()
+ {
+ /*
+ * 2 insert gap in a row (aka mouse drag right), on two sequences:
+ */
+ Edit e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[] {
+ seqs[0], seqs[1] }, 1, 1, al);
+ testee.addEdit(e);
+ SequenceI seq1edited = new Sequence("seq0", "a?bcdefghjk");
+ seq1edited.setDatasetSequence(seqs[0].getDatasetSequence());
+ SequenceI seq2edited = new Sequence("seq1", "f?ghjklmnopq");
+ seq2edited.setDatasetSequence(seqs[1].getDatasetSequence());
+ e = new EditCommand().new Edit(Action.INSERT_GAP, new SequenceI[] {
+ seq1edited, seq2edited }, 2, 1, al);
+ testee.addEdit(e);
+
+ assertEquals(1, testee.getSize());
+ assertEquals(Action.INSERT_GAP, testee.getEdit(0).getAction());
+ assertEquals(1, testee.getEdit(0).getPosition());
+ assertEquals(2, testee.getEdit(0).getNumber());
+ assertEquals(seqs[0].getDatasetSequence(), testee.getEdit(0)
+ .getSequences()[0].getDatasetSequence());
+ assertEquals(seqs[1].getDatasetSequence(), testee.getEdit(0)
+ .getSequences()[1].getDatasetSequence());
+ }
+
+ /**
+ * Test for 'undoing' a series of gap insertions.
+ * <ul>
+ * <li>Start: ABCDEF insert 2 at pos 1</li>
+ * <li>next: A--BCDEF insert 1 at pos 4</li>
+ * <li>next: A--B-CDEF insert 2 at pos 0</li>
+ * <li>last: --A--B-CDEF</li>
+ * </ul>
+ */
+ @Test(groups = { "Functional" })
+ public void testPriorState_multipleInserts()
+ {
+ EditCommand command = new EditCommand();
+ SequenceI seq = new Sequence("", "--A--B-CDEF");
+ SequenceI ds = new Sequence("", "ABCDEF");
+ seq.setDatasetSequence(ds);
+ SequenceI[] sqs = new SequenceI[] { seq };
+ Edit e = command.new Edit(Action.INSERT_GAP, sqs, 1, 2, '-');
+ command.addEdit(e);
+ e = command.new Edit(Action.INSERT_GAP, sqs, 4, 1, '-');
+ command.addEdit(e);
+ e = command.new Edit(Action.INSERT_GAP, sqs, 0, 2, '-');
+ command.addEdit(e);
+
+ Map<SequenceI, SequenceI> unwound = command.priorState(false);
+ assertEquals("ABCDEF", unwound.get(ds).getSequenceAsString());
+ }
+
+ /**
+ * Test for 'undoing' a series of gap deletions.
+ * <ul>
+ * <li>Start: A-B-C delete 1 at pos 1</li>
+ * <li>Next: AB-C delete 1 at pos 2</li>
+ * <li>End: ABC</li>
+ * </ul>
+ */
+ @Test(groups = { "Functional" })
+ public void testPriorState_removeAllGaps()
+ {
+ EditCommand command = new EditCommand();
+ SequenceI seq = new Sequence("", "ABC");
+ SequenceI ds = new Sequence("", "ABC");
+ seq.setDatasetSequence(ds);
+ SequenceI[] sqs = new SequenceI[] { seq };
+ Edit e = command.new Edit(Action.DELETE_GAP, sqs, 1, 1, '-');
+ command.addEdit(e);
+ e = command.new Edit(Action.DELETE_GAP, sqs, 2, 1, '-');
+ command.addEdit(e);
+
+ Map<SequenceI, SequenceI> unwound = command.priorState(false);
+ assertEquals("A-B-C", unwound.get(ds).getSequenceAsString());
+ }
+
+ /**
+ * Test for 'undoing' a single delete edit.
+ */
+ @Test(groups = { "Functional" })
+ public void testPriorState_singleDelete()
+ {
+ EditCommand command = new EditCommand();
+ SequenceI seq = new Sequence("", "ABCDEF");
+ SequenceI ds = new Sequence("", "ABCDEF");
+ seq.setDatasetSequence(ds);
+ SequenceI[] sqs = new SequenceI[] { seq };
+ Edit e = command.new Edit(Action.DELETE_GAP, sqs, 2, 2, '-');
+ command.addEdit(e);
+
+ Map<SequenceI, SequenceI> unwound = command.priorState(false);
+ assertEquals("AB--CDEF", unwound.get(ds).getSequenceAsString());
+ }
+
+ /**
+ * Test 'undoing' a single gap insertion edit command.
+ */
+ @Test(groups = { "Functional" })
+ public void testPriorState_singleInsert()
+ {
+ EditCommand command = new EditCommand();
+ SequenceI seq = new Sequence("", "AB---CDEF");
+ SequenceI ds = new Sequence("", "ABCDEF");
+ seq.setDatasetSequence(ds);
+ SequenceI[] sqs = new SequenceI[] { seq };
+ Edit e = command.new Edit(Action.INSERT_GAP, sqs, 2, 3, '-');
+ command.addEdit(e);
+
+ Map<SequenceI, SequenceI> unwound = command.priorState(false);
+ SequenceI prior = unwound.get(ds);
+ assertEquals("ABCDEF", prior.getSequenceAsString());
+ assertEquals(1, prior.getStart());
+ assertEquals(6, prior.getEnd());
+ }
+
+ /**
+ * Test 'undoing' a single gap insertion edit command, on a sequence whose
+ * start residue is other than 1
+ */
+ @Test(groups = { "Functional" })
+ public void testPriorState_singleInsertWithOffset()
+ {
+ EditCommand command = new EditCommand();
+ SequenceI seq = new Sequence("", "AB---CDEF", 8, 13);
+ // SequenceI ds = new Sequence("", "ABCDEF", 8, 13);
+ // seq.setDatasetSequence(ds);
+ seq.createDatasetSequence();
+ SequenceI[] sqs = new SequenceI[] { seq };
+ Edit e = command.new Edit(Action.INSERT_GAP, sqs, 2, 3, '-');
+ command.addEdit(e);
+
+ Map<SequenceI, SequenceI> unwound = command.priorState(false);
+ SequenceI prior = unwound.get(seq.getDatasetSequence());
+ assertEquals("ABCDEF", prior.getSequenceAsString());
+ assertEquals(8, prior.getStart());
+ assertEquals(13, prior.getEnd());
+ }
+
+ /**
+ * Test that mimics 'remove all gaps' action. This generates delete gap edits
+ * for contiguous gaps in each sequence separately.
+ */
+ @Test(groups = { "Functional" })
+ public void testPriorState_removeGapsMultipleSeqs()
+ {
+ EditCommand command = new EditCommand();
+ String original1 = "--ABC-DEF";
+ String original2 = "FG-HI--J";
+ String original3 = "M-NOPQ";
+
+ /*
+ * Two edits for the first sequence
+ */
+ SequenceI seq = new Sequence("", "ABC-DEF");
+ SequenceI ds1 = new Sequence("", "ABCDEF");
+ seq.setDatasetSequence(ds1);
+ SequenceI[] sqs = new SequenceI[] { seq };
+ Edit e = command.new Edit(Action.DELETE_GAP, sqs, 0, 2, '-');
+ command.addEdit(e);
+ seq = new Sequence("", "ABCDEF");
+ seq.setDatasetSequence(ds1);
+ sqs = new SequenceI[] { seq };
+ e = command.new Edit(Action.DELETE_GAP, sqs, 3, 1, '-');
+ command.addEdit(e);
+
+ /*
+ * Two edits for the second sequence
+ */
+ seq = new Sequence("", "FGHI--J");
+ SequenceI ds2 = new Sequence("", "FGHIJ");
+ seq.setDatasetSequence(ds2);
+ sqs = new SequenceI[] { seq };
+ e = command.new Edit(Action.DELETE_GAP, sqs, 2, 1, '-');
+ command.addEdit(e);
+ seq = new Sequence("", "FGHIJ");
+ seq.setDatasetSequence(ds2);
+ sqs = new SequenceI[] { seq };
+ e = command.new Edit(Action.DELETE_GAP, sqs, 4, 2, '-');
+ command.addEdit(e);
+
+ /*
+ * One edit for the third sequence.
+ */
+ seq = new Sequence("", "MNOPQ");
+ SequenceI ds3 = new Sequence("", "MNOPQ");
+ seq.setDatasetSequence(ds3);
+ sqs = new SequenceI[] { seq };
+ e = command.new Edit(Action.DELETE_GAP, sqs, 1, 1, '-');
+ command.addEdit(e);
+
+ Map<SequenceI, SequenceI> unwound = command.priorState(false);
+ assertEquals(original1, unwound.get(ds1).getSequenceAsString());
+ assertEquals(original2, unwound.get(ds2).getSequenceAsString());
+ assertEquals(original3, unwound.get(ds3).getSequenceAsString());
+ }
+
+ /**
+ * Test that mimics 'remove all gapped columns' action. This generates a
+ * series Delete Gap edits that each act on all sequences that share a gapped
+ * column region.
+ */
+ @Test(groups = { "Functional" })
+ public void testPriorState_removeGappedCols()
+ {
+ EditCommand command = new EditCommand();
+ String original1 = "--ABC--DEF";
+ String original2 = "-G-HI--J";
+ String original3 = "-M-NO--PQ";
+
+ /*
+ * First edit deletes the first column.
+ */
+ SequenceI seq1 = new Sequence("", "-ABC--DEF");
+ SequenceI ds1 = new Sequence("", "ABCDEF");
+ seq1.setDatasetSequence(ds1);
+ SequenceI seq2 = new Sequence("", "G-HI--J");
+ SequenceI ds2 = new Sequence("", "GHIJ");
+ seq2.setDatasetSequence(ds2);
+ SequenceI seq3 = new Sequence("", "M-NO--PQ");
+ SequenceI ds3 = new Sequence("", "MNOPQ");
+ seq3.setDatasetSequence(ds3);
+ SequenceI[] sqs = new SequenceI[] { seq1, seq2, seq3 };
+ Edit e = command.new Edit(Action.DELETE_GAP, sqs, 0, 1, '-');
+ command.addEdit(e);
+
+ /*
+ * Second edit deletes what is now columns 4 and 5.
+ */
+ seq1 = new Sequence("", "-ABCDEF");
+ seq1.setDatasetSequence(ds1);
+ seq2 = new Sequence("", "G-HIJ");
+ seq2.setDatasetSequence(ds2);
+ seq3 = new Sequence("", "M-NOPQ");
+ seq3.setDatasetSequence(ds3);
+ sqs = new SequenceI[] { seq1, seq2, seq3 };
+ e = command.new Edit(Action.DELETE_GAP, sqs, 4, 2, '-');
+ command.addEdit(e);
+
+ Map<SequenceI, SequenceI> unwound = command.priorState(false);
+ assertEquals(original1, unwound.get(ds1).getSequenceAsString());
+ assertEquals(original2, unwound.get(ds2).getSequenceAsString());
+ assertEquals(original3, unwound.get(ds3).getSequenceAsString());
+ assertEquals(ds1, unwound.get(ds1).getDatasetSequence());
+ assertEquals(ds2, unwound.get(ds2).getDatasetSequence());
+ assertEquals(ds3, unwound.get(ds3).getDatasetSequence());
+ }
+
+ /**
+ * Test a cut action's relocation of sequence features
+ */
+ @Test(groups = { "Functional" })
+ public void testCut_withFeatures()
+ {
+ /*
+ * create sequence features before, after and overlapping
+ * a cut of columns/residues 4-7
+ */
+ SequenceI seq0 = seqs[0];
+ seq0.addSequenceFeature(new SequenceFeature("before", "", 1, 3, 0f,
+ null));
+ seq0.addSequenceFeature(new SequenceFeature("overlap left", "", 2, 6,
+ 0f, null));
+ seq0.addSequenceFeature(new SequenceFeature("internal", "", 5, 6, 0f,
+ null));
+ seq0.addSequenceFeature(new SequenceFeature("overlap right", "", 7, 8,
+ 0f, null));
+ seq0.addSequenceFeature(new SequenceFeature("after", "", 8, 10, 0f,
+ null));
+
+ Edit ec = testee.new Edit(Action.CUT, seqs, 3, 4, al); // cols 3-6 base 0
+ EditCommand.cut(ec, new AlignmentI[] { al });
+
+ List<SequenceFeature> sfs = seq0.getSequenceFeatures();
+ SequenceFeatures.sortFeatures(sfs, true);
+
+ assertEquals(4, sfs.size()); // feature internal to cut has been deleted
+ SequenceFeature sf = sfs.get(0);
+ assertEquals("before", sf.getType());
+ assertEquals(1, sf.getBegin());
+ assertEquals(3, sf.getEnd());
+ sf = sfs.get(1);
+ assertEquals("overlap left", sf.getType());
+ assertEquals(2, sf.getBegin());
+ assertEquals(3, sf.getEnd()); // truncated by cut
+ sf = sfs.get(2);
+ assertEquals("overlap right", sf.getType());
+ assertEquals(4, sf.getBegin()); // shifted left by cut
+ assertEquals(5, sf.getEnd()); // truncated by cut
+ sf = sfs.get(3);
+ assertEquals("after", sf.getType());
+ assertEquals(4, sf.getBegin()); // shifted left by cut
+ assertEquals(6, sf.getEnd()); // shifted left by cut
+ }
+
+ /**
+ * Test a cut action's relocation of sequence features, with full coverage of
+ * all possible feature and cut locations for a 5-position ungapped sequence
+ */
+ @Test(groups = { "Functional" })
+ public void testCut_withFeatures_exhaustive()
+ {
+ /*
+ * create a sequence features on each subrange of 1-5
+ */
+ SequenceI seq0 = new Sequence("seq", "ABCDE");
+ AlignmentI alignment = new Alignment(new SequenceI[] { seq0 });
+ alignment.setDataset(null);
+ for (int from = 1; from <= seq0.getLength(); from++)
+ {
+ for (int to = from; to <= seq0.getLength(); to++)
+ {
+ String desc = String.format("%d-%d", from, to);
+ SequenceFeature sf = new SequenceFeature("test", desc, from, to,
+ 0f,
+ null);
+ sf.setValue("from", Integer.valueOf(from));
+ sf.setValue("to", Integer.valueOf(to));
+ seq0.addSequenceFeature(sf);
+ }
+ }
+ // sanity check
+ List<SequenceFeature> sfs = seq0.getSequenceFeatures();
+ assertEquals(func(5), sfs.size());
+
+ /*
+ * now perform all possible cuts of subranges of 1-5 (followed by Undo)
+ * and validate the resulting remaining sequence features!
+ */
+ SequenceI[] sqs = new SequenceI[] { seq0 };
+
+ // goal is to have this passing for all from/to values!!
+ // for (int from = 0; from < seq0.getLength(); from++)
+ // {
+ // for (int to = from; to < seq0.getLength(); to++)
+ for (int from = 1; from < 3; from++)
+ {
+ for (int to = 2; to < 3; to++)
+ {
+ testee.appendEdit(Action.CUT, sqs, from, (to - from + 1),
+ alignment, true);
+
+ sfs = seq0.getSequenceFeatures();
+
+ /*
+ * confirm the number of features has reduced by the
+ * number of features within the cut region i.e. by
+ * func(length of cut)
+ */
+ String msg = String.format("Cut %d-%d ", from, to);
+ if (to - from == 4)
+ {
+ // all columns cut
+ assertNull(sfs);
+ }
+ else
+ {
+ assertEquals(msg + "wrong number of features left", func(5)
+ - func(to - from + 1), sfs.size());
+ }
+
+ /*
+ * inspect individual features
+ */
+ if (sfs != null)
+ {
+ for (SequenceFeature sf : sfs)
+ {
+ checkFeatureRelocation(sf, from + 1, to + 1);
+ }
+ }
+ /*
+ * undo ready for next cut
+ */
+ testee.undoCommand(new AlignmentI[] { alignment });
+ assertEquals(func(5), seq0.getSequenceFeatures().size());
+ }
+ }
+ }
+
+ /**
+ * Helper method to check a feature has been correctly relocated after a cut
+ *
+ * @param sf
+ * @param from
+ * start of cut (first residue cut)
+ * @param to
+ * end of cut (last residue cut)
+ */
+ private void checkFeatureRelocation(SequenceFeature sf, int from, int to)
+ {
+ // TODO handle the gapped sequence case as well
+ int cutSize = to - from + 1;
+ int oldFrom = ((Integer) sf.getValue("from")).intValue();
+ int oldTo = ((Integer) sf.getValue("to")).intValue();
+
+ String msg = String.format(
+ "Feature %s relocated to %d-%d after cut of %d-%d",
+ sf.getDescription(), sf.getBegin(), sf.getEnd(), from, to);
+ if (oldTo < from)
+ {
+ // before cut region so unchanged
+ assertEquals("1: " + msg, oldFrom, sf.getBegin());
+ assertEquals("2: " + msg, oldTo, sf.getEnd());
+ }
+ else if (oldFrom > to)
+ {
+ // follows cut region - shift by size of cut
+ assertEquals("3: " + msg, oldFrom - cutSize, sf.getBegin());
+ assertEquals("4: " + msg, oldTo - cutSize, sf.getEnd());
+ }
+ else if (oldFrom < from && oldTo > to)
+ {
+ // feature encloses cut region - shrink it right
+ assertEquals("5: " + msg, oldFrom, sf.getBegin());
+ assertEquals("6: " + msg, oldTo - cutSize, sf.getEnd());
+ }
+ else if (oldFrom < from)
+ {
+ // feature overlaps left side of cut region - truncated right
+ assertEquals("7: " + msg, from - 1, sf.getEnd());
+ }
+ else if (oldTo > to)
+ {
+ // feature overlaps right side of cut region - truncated left
+ assertEquals("8: " + msg, from, sf.getBegin());
+ assertEquals("9: " + msg, from + oldTo - to - 1, sf.getEnd());
+ }
+ else
+ {
+ // feature internal to cut - should have been deleted!
+ Assert.fail(msg + " - should have been deleted");
+ }
+ }
+
+ /**
+ * Test a cut action's relocation of sequence features
+ */
+ @Test(groups = { "Functional" })
+ public void testCut_gappedWithFeatures()
+ {
+ /*
+ * create sequence features before, after and overlapping
+ * a cut of columns/residues 4-7
+ */
+ SequenceI seq0 = new Sequence("seq", "A-BCC");
+ seq0.addSequenceFeature(new SequenceFeature("", "", 3, 4, 0f,
+ null));
+ AlignmentI alignment = new Alignment(new SequenceI[] { seq0 });
+ // cut columns of A-B
+ Edit ec = testee.new Edit(Action.CUT, seqs, 0, 3, alignment); // cols 0-3
+ // base 0
+ EditCommand.cut(ec, new AlignmentI[] { alignment });
+
+ /*
+ * feature on CC(3-4) should now be on CC(1-2)
+ */
+ List<SequenceFeature> sfs = seq0.getSequenceFeatures();
+ assertEquals(1, sfs.size());
+ SequenceFeature sf = sfs.get(0);
+ assertEquals(1, sf.getBegin());
+ assertEquals(2, sf.getEnd());