JAL-3751 only merge strictly contiguous ranges of mappings
[jalview.git] / test / jalview / util / MappingUtilsTest.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.util;
22
23 import static org.testng.AssertJUnit.assertEquals;
24 import static org.testng.AssertJUnit.assertFalse;
25 import static org.testng.AssertJUnit.assertSame;
26 import static org.testng.AssertJUnit.assertTrue;
27
28 import java.awt.Color;
29 import java.io.IOException;
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.Iterator;
33 import java.util.List;
34
35 import org.testng.annotations.BeforeClass;
36 import org.testng.annotations.Test;
37
38 import jalview.api.AlignViewportI;
39 import jalview.bin.Cache;
40 import jalview.commands.EditCommand;
41 import jalview.commands.EditCommand.Action;
42 import jalview.commands.EditCommand.Edit;
43 import jalview.datamodel.AlignedCodonFrame;
44 import jalview.datamodel.Alignment;
45 import jalview.datamodel.AlignmentI;
46 import jalview.datamodel.ColumnSelection;
47 import jalview.datamodel.HiddenColumns;
48 import jalview.datamodel.SearchResultMatchI;
49 import jalview.datamodel.SearchResultsI;
50 import jalview.datamodel.Sequence;
51 import jalview.datamodel.SequenceGroup;
52 import jalview.datamodel.SequenceI;
53 import jalview.gui.AlignViewport;
54 import jalview.gui.JvOptionPane;
55 import jalview.io.DataSourceType;
56 import jalview.io.FileFormat;
57 import jalview.io.FileFormatI;
58 import jalview.io.FormatAdapter;
59
60 public class MappingUtilsTest
61 {
62   @BeforeClass(alwaysRun = true)
63   public void setUp()
64   {
65     Cache.initLogger();
66   }
67   
68
69   @BeforeClass(alwaysRun = true)
70   public void setUpJvOptionPane()
71   {
72     JvOptionPane.setInteractiveMode(false);
73     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
74   }
75
76   private AlignViewportI dnaView;
77
78   private AlignViewportI proteinView;
79
80   /**
81    * Simple test of mapping with no intron involved.
82    */
83   @Test(groups = { "Functional" })
84   public void testBuildSearchResults()
85   {
86     final Sequence seq1 = new Sequence("Seq1/5-10", "C-G-TA-GC");
87     seq1.createDatasetSequence();
88
89     final Sequence aseq1 = new Sequence("Seq1/12-13", "-P-R");
90     aseq1.createDatasetSequence();
91
92     /*
93      * Map dna bases 5-10 to protein residues 12-13
94      */
95     AlignedCodonFrame acf = new AlignedCodonFrame();
96     MapList map = new MapList(new int[] { 5, 10 }, new int[] { 12, 13 }, 3,
97             1);
98     acf.addMap(seq1.getDatasetSequence(), aseq1.getDatasetSequence(), map);
99     List<AlignedCodonFrame> acfList = Arrays.asList(new AlignedCodonFrame[]
100     { acf });
101
102     /*
103      * Check protein residue 12 maps to codon 5-7, 13 to codon 8-10
104      */
105     SearchResultsI sr = MappingUtils.buildSearchResults(aseq1, 12, acfList);
106     assertEquals(1, sr.getResults().size());
107     SearchResultMatchI m = sr.getResults().get(0);
108     assertEquals(seq1.getDatasetSequence(), m.getSequence());
109     assertEquals(5, m.getStart());
110     assertEquals(7, m.getEnd());
111     sr = MappingUtils.buildSearchResults(aseq1, 13, acfList);
112     assertEquals(1, sr.getResults().size());
113     m = sr.getResults().get(0);
114     assertEquals(seq1.getDatasetSequence(), m.getSequence());
115     assertEquals(8, m.getStart());
116     assertEquals(10, m.getEnd());
117
118     /*
119      * Check inverse mappings, from codons 5-7, 8-10 to protein 12, 13
120      */
121     for (int i = 5; i < 11; i++)
122     {
123       sr = MappingUtils.buildSearchResults(seq1, i, acfList);
124       assertEquals(1, sr.getResults().size());
125       m = sr.getResults().get(0);
126       assertEquals(aseq1.getDatasetSequence(), m.getSequence());
127       int residue = i > 7 ? 13 : 12;
128       assertEquals(residue, m.getStart());
129       assertEquals(residue, m.getEnd());
130     }
131   }
132
133   /**
134    * Simple test of mapping with introns involved.
135    */
136   @Test(groups = { "Functional" })
137   public void testBuildSearchResults_withIntron()
138   {
139     final Sequence seq1 = new Sequence("Seq1/5-17", "c-G-tAGa-GcAgCtt");
140     seq1.createDatasetSequence();
141
142     final Sequence aseq1 = new Sequence("Seq1/8-9", "-E-D");
143     aseq1.createDatasetSequence();
144
145     /*
146      * Map dna bases [6, 8, 9], [11, 13, 115] to protein residues 8 and 9
147      */
148     AlignedCodonFrame acf = new AlignedCodonFrame();
149     MapList map = new MapList(new int[] { 6, 6, 8, 9, 11, 11, 13, 13, 15,
150         15 }, new int[] { 8, 9 }, 3, 1);
151     acf.addMap(seq1.getDatasetSequence(), aseq1.getDatasetSequence(), map);
152     List<AlignedCodonFrame> acfList = Arrays.asList(new AlignedCodonFrame[]
153     { acf });
154
155     /*
156      * Check protein residue 8 maps to [6, 8, 9]
157      */
158     SearchResultsI sr = MappingUtils.buildSearchResults(aseq1, 8, acfList);
159     assertEquals(2, sr.getResults().size());
160     SearchResultMatchI m = sr.getResults().get(0);
161     assertEquals(seq1.getDatasetSequence(), m.getSequence());
162     assertEquals(6, m.getStart());
163     assertEquals(6, m.getEnd());
164     m = sr.getResults().get(1);
165     assertEquals(seq1.getDatasetSequence(), m.getSequence());
166     assertEquals(8, m.getStart());
167     assertEquals(9, m.getEnd());
168
169     /*
170      * Check protein residue 9 maps to [11, 13, 15]
171      */
172     sr = MappingUtils.buildSearchResults(aseq1, 9, acfList);
173     assertEquals(3, sr.getResults().size());
174     m = sr.getResults().get(0);
175     assertEquals(seq1.getDatasetSequence(), m.getSequence());
176     assertEquals(11, m.getStart());
177     assertEquals(11, m.getEnd());
178     m = sr.getResults().get(1);
179     assertEquals(seq1.getDatasetSequence(), m.getSequence());
180     assertEquals(13, m.getStart());
181     assertEquals(13, m.getEnd());
182     m = sr.getResults().get(2);
183     assertEquals(seq1.getDatasetSequence(), m.getSequence());
184     assertEquals(15, m.getStart());
185     assertEquals(15, m.getEnd());
186
187     /*
188      * Check inverse mappings, from codons to protein
189      */
190     for (int i = 5; i < 18; i++)
191     {
192       sr = MappingUtils.buildSearchResults(seq1, i, acfList);
193       int residue = (i == 6 || i == 8 || i == 9) ? 8 : (i == 11 || i == 13
194               || i == 15 ? 9 : 0);
195       if (residue == 0)
196       {
197         assertEquals(0, sr.getResults().size());
198         continue;
199       }
200       assertEquals(1, sr.getResults().size());
201       m = sr.getResults().get(0);
202       assertEquals(aseq1.getDatasetSequence(), m.getSequence());
203       assertEquals(residue, m.getStart());
204       assertEquals(residue, m.getEnd());
205     }
206   }
207
208   /**
209    * Test mapping a sequence group made of entire sequences.
210    * 
211    * @throws IOException
212    */
213   @Test(groups = { "Functional" })
214   public void testMapSequenceGroup_sequences() throws IOException
215   {
216     /*
217      * Set up dna and protein Seq1/2/3 with mappings (held on the protein
218      * viewport).
219      */
220     AlignmentI cdna = loadAlignment(">Seq1\nACG\n>Seq2\nTGA\n>Seq3\nTAC\n",
221             FileFormat.Fasta);
222     cdna.setDataset(null);
223     AlignmentI protein = loadAlignment(">Seq1\nK\n>Seq2\nL\n>Seq3\nQ\n",
224             FileFormat.Fasta);
225     protein.setDataset(null);
226     AlignedCodonFrame acf = new AlignedCodonFrame();
227     MapList map = new MapList(new int[] { 1, 3 }, new int[] { 1, 1 }, 3, 1);
228     for (int seq = 0; seq < 3; seq++)
229     {
230       acf.addMap(cdna.getSequenceAt(seq).getDatasetSequence(), protein
231               .getSequenceAt(seq).getDatasetSequence(), map);
232     }
233     List<AlignedCodonFrame> acfList = Arrays.asList(new AlignedCodonFrame[]
234     { acf });
235
236     AlignViewportI dnaView = new AlignViewport(cdna);
237     AlignViewportI proteinView = new AlignViewport(protein);
238     protein.setCodonFrames(acfList);
239
240     /*
241      * Select Seq1 and Seq3 in the protein (startRes=endRes=0)
242      */
243     SequenceGroup sg = new SequenceGroup();
244     sg.setColourText(true);
245     sg.setIdColour(Color.GREEN);
246     sg.setOutlineColour(Color.LIGHT_GRAY);
247     sg.addSequence(protein.getSequenceAt(0), false);
248     sg.addSequence(protein.getSequenceAt(2), false);
249
250     /*
251      * Verify the mapped sequence group in dna
252      */
253     SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(sg,
254             proteinView, dnaView);
255     assertTrue(mappedGroup.getColourText());
256     assertSame(sg.getIdColour(), mappedGroup.getIdColour());
257     assertSame(sg.getOutlineColour(), mappedGroup.getOutlineColour());
258     assertEquals(2, mappedGroup.getSequences().size());
259     assertSame(cdna.getSequenceAt(0), mappedGroup.getSequences().get(0));
260     assertSame(cdna.getSequenceAt(2), mappedGroup.getSequences().get(1));
261     assertEquals(0, mappedGroup.getStartRes());
262     assertEquals(2, mappedGroup.getEndRes());
263
264     /*
265      * Verify mapping sequence group from dna to protein
266      */
267     sg.clear();
268     sg.addSequence(cdna.getSequenceAt(1), false);
269     sg.addSequence(cdna.getSequenceAt(0), false);
270     sg.setStartRes(0);
271     sg.setEndRes(2);
272     mappedGroup = MappingUtils.mapSequenceGroup(sg, dnaView, proteinView);
273     assertTrue(mappedGroup.getColourText());
274     assertSame(sg.getIdColour(), mappedGroup.getIdColour());
275     assertSame(sg.getOutlineColour(), mappedGroup.getOutlineColour());
276     assertEquals(2, mappedGroup.getSequences().size());
277     assertSame(protein.getSequenceAt(1), mappedGroup.getSequences().get(0));
278     assertSame(protein.getSequenceAt(0), mappedGroup.getSequences().get(1));
279     assertEquals(0, mappedGroup.getStartRes());
280     assertEquals(0, mappedGroup.getEndRes());
281   }
282
283   /**
284    * Helper method to load an alignment and ensure dataset sequences are set up.
285    * 
286    * @param data
287    * @param format
288    *          TODO
289    * @return
290    * @throws IOException
291    */
292   protected AlignmentI loadAlignment(final String data, FileFormatI format)
293           throws IOException
294   {
295     AlignmentI a = new FormatAdapter().readFile(data,
296             DataSourceType.PASTE, format);
297     a.setDataset(null);
298     return a;
299   }
300
301   /**
302    * Test mapping a column selection in protein to its dna equivalent
303    * 
304    * @throws IOException
305    */
306   @Test(groups = { "Functional" })
307   public void testMapColumnSelection_proteinToDna() throws IOException
308   {
309     setupMappedAlignments();
310
311     ColumnSelection colsel = new ColumnSelection();
312     HiddenColumns hidden = new HiddenColumns();
313
314     /*
315      * Column 0 in protein picks up Seq2/L, Seq3/G which map to cols 0-4 and 0-3
316      * in dna respectively, overall 0-4
317      */
318     colsel.addElement(0);
319     ColumnSelection cs = new ColumnSelection();
320     HiddenColumns hs = new HiddenColumns();
321     MappingUtils.mapColumnSelection(colsel, hidden, proteinView, dnaView,
322             cs, hs);
323     assertEquals("[0, 1, 2, 3, 4]", cs.getSelected().toString());
324
325     /*
326      * Column 1 in protein picks up Seq1/K which maps to cols 0-3 in dna
327      */
328     cs.clear();
329     colsel.clear();
330     colsel.addElement(1);
331     MappingUtils.mapColumnSelection(colsel, hidden, proteinView, dnaView,
332             cs, hs);
333     assertEquals("[0, 1, 2, 3]", cs.getSelected().toString());
334
335     /*
336      * Column 2 in protein picks up gaps only - no mapping
337      */
338     cs.clear();
339     colsel.clear();
340     colsel.addElement(2);
341     MappingUtils.mapColumnSelection(colsel, hidden, proteinView,
342             dnaView, cs, hs);
343     assertEquals("[]", cs.getSelected().toString());
344
345     /*
346      * Column 3 in protein picks up Seq1/P, Seq2/Q, Seq3/S which map to columns
347      * 6-9, 6-10, 5-8 respectively, overall to 5-10
348      */
349     cs.clear();
350     colsel.clear();
351     colsel.addElement(3);
352     MappingUtils.mapColumnSelection(colsel, hidden, proteinView,
353             dnaView, cs, hs);
354     assertEquals("[5, 6, 7, 8, 9, 10]", cs.getSelected().toString());
355
356     /*
357      * Combine selection of columns 1 and 3 to get a discontiguous mapped
358      * selection
359      */
360     cs.clear();
361     colsel.clear();
362     colsel.addElement(1);
363     colsel.addElement(3);
364     MappingUtils.mapColumnSelection(colsel, hidden, proteinView,
365             dnaView, cs, hs);
366     assertEquals("[0, 1, 2, 3, 5, 6, 7, 8, 9, 10]", cs.getSelected()
367             .toString());
368   }
369
370   /**
371    * Set up mappings for tests from 3 dna to 3 protein sequences. Sequences have
372    * offset start positions for a more general test case.
373    * 
374    * @throws IOException
375    */
376   protected void setupMappedAlignments() throws IOException
377   {
378     /*
379      * Map (upper-case = coding):
380      * Seq1/10-18 AC-GctGtC-T to Seq1/40 -K-P
381      * Seq2/20-27 Tc-GA-G-T-T to Seq2/20-27 L--Q
382      * Seq3/30-38 TtTT-AaCGg- to Seq3/60-61\nG--S
383      */
384     AlignmentI cdna = loadAlignment(">Seq1/10-18\nAC-GctGtC-T\n"
385             + ">Seq2/20-27\nTc-GA-G-T-Tc\n" + ">Seq3/30-38\nTtTT-AaCGg-\n",
386             FileFormat.Fasta);
387     cdna.setDataset(null);
388     AlignmentI protein = loadAlignment(
389             ">Seq1/40-41\n-K-P\n>Seq2/50-51\nL--Q\n>Seq3/60-61\nG--S\n",
390             FileFormat.Fasta);
391     protein.setDataset(null);
392
393     // map first dna to first protein seq
394     AlignedCodonFrame acf = new AlignedCodonFrame();
395     MapList map = new MapList(new int[] { 10, 12, 15, 15, 17, 18 },
396             new int[] { 40, 41 }, 3, 1);
397     acf.addMap(cdna.getSequenceAt(0).getDatasetSequence(), protein
398             .getSequenceAt(0).getDatasetSequence(), map);
399
400     // map second dna to second protein seq
401     map = new MapList(new int[] { 20, 20, 22, 23, 24, 26 }, new int[] { 50,
402         51 }, 3, 1);
403     acf.addMap(cdna.getSequenceAt(1).getDatasetSequence(), protein
404             .getSequenceAt(1).getDatasetSequence(), map);
405
406     // map third dna to third protein seq
407     map = new MapList(new int[] { 30, 30, 32, 34, 36, 37 }, new int[] { 60,
408         61 }, 3, 1);
409     acf.addMap(cdna.getSequenceAt(2).getDatasetSequence(), protein
410             .getSequenceAt(2).getDatasetSequence(), map);
411     List<AlignedCodonFrame> acfList = Arrays.asList(new AlignedCodonFrame[]
412     { acf });
413
414     dnaView = new AlignViewport(cdna);
415     proteinView = new AlignViewport(protein);
416     protein.setCodonFrames(acfList);
417   }
418
419   /**
420    * Test mapping a column selection in dna to its protein equivalent
421    * 
422    * @throws IOException
423    */
424   @Test(groups = { "Functional" })
425   public void testMapColumnSelection_dnaToProtein() throws IOException
426   {
427     setupMappedAlignments();
428
429     ColumnSelection colsel = new ColumnSelection();
430     HiddenColumns hidden = new HiddenColumns();
431
432     /*
433      * Column 0 in dna picks up first bases which map to residue 1, columns 0-1
434      * in protein.
435      */
436     ColumnSelection cs = new ColumnSelection();
437     HiddenColumns hs = new HiddenColumns();
438     colsel.addElement(0);
439     MappingUtils.mapColumnSelection(colsel, hidden, dnaView, proteinView,
440             cs, hs);
441     assertEquals("[0, 1]", cs.getSelected().toString());
442
443     /*
444      * Columns 3-5 in dna map to the first residues in protein Seq1, Seq2, and
445      * the first two in Seq3. Overall to columns 0, 1, 3 (col2 is all gaps).
446      */
447     colsel.addElement(3);
448     colsel.addElement(4);
449     colsel.addElement(5);
450     cs.clear();
451     MappingUtils.mapColumnSelection(colsel, hidden, dnaView, proteinView,
452             cs, hs);
453     assertEquals("[0, 1, 3]", cs.getSelected().toString());
454   }
455
456   @Test(groups = { "Functional" })
457   public void testMapColumnSelection_null() throws IOException
458   {
459     setupMappedAlignments();
460     ColumnSelection cs = new ColumnSelection();
461     HiddenColumns hs = new HiddenColumns();
462     MappingUtils.mapColumnSelection(null, null, dnaView, proteinView, cs,
463             hs);
464     assertTrue("mapped selection not empty", cs.getSelected().isEmpty());
465   }
466
467   /**
468    * Tests for the method that converts a series of [start, end] ranges to
469    * single positions
470    */
471   @Test(groups = { "Functional" })
472   public void testFlattenRanges()
473   {
474     assertEquals("[1, 2, 3, 4]",
475             Arrays.toString(MappingUtils.flattenRanges(new int[] { 1, 4 })));
476     assertEquals(
477             "[1, 2, 3, 4]",
478             Arrays.toString(MappingUtils.flattenRanges(new int[] { 1, 2, 3,
479                 4 })));
480     assertEquals(
481             "[1, 2, 3, 4]",
482             Arrays.toString(MappingUtils.flattenRanges(new int[] { 1, 1, 2,
483                 2, 3, 3, 4, 4 })));
484     assertEquals(
485             "[1, 2, 3, 4, 7, 8, 9, 12]",
486             Arrays.toString(MappingUtils.flattenRanges(new int[] { 1, 4, 7,
487                 9, 12, 12 })));
488     // trailing unpaired start position is ignored:
489     assertEquals(
490             "[1, 2, 3, 4, 7, 8, 9, 12]",
491             Arrays.toString(MappingUtils.flattenRanges(new int[] { 1, 4, 7,
492                 9, 12, 12, 15 })));
493   }
494
495   /**
496    * Test mapping a sequence group made of entire columns.
497    * 
498    * @throws IOException
499    */
500   @Test(groups = { "Functional" })
501   public void testMapSequenceGroup_columns() throws IOException
502   {
503     /*
504      * Set up dna and protein Seq1/2/3 with mappings (held on the protein
505      * viewport).
506      */
507     AlignmentI cdna = loadAlignment(
508             ">Seq1\nACGGCA\n>Seq2\nTGACAG\n>Seq3\nTACGTA\n",
509             FileFormat.Fasta);
510     cdna.setDataset(null);
511     AlignmentI protein = loadAlignment(">Seq1\nKA\n>Seq2\nLQ\n>Seq3\nQV\n",
512             FileFormat.Fasta);
513     protein.setDataset(null);
514     AlignedCodonFrame acf = new AlignedCodonFrame();
515     MapList map = new MapList(new int[] { 1, 6 }, new int[] { 1, 2 }, 3, 1);
516     for (int seq = 0; seq < 3; seq++)
517     {
518       acf.addMap(cdna.getSequenceAt(seq).getDatasetSequence(), protein
519               .getSequenceAt(seq).getDatasetSequence(), map);
520     }
521     List<AlignedCodonFrame> acfList = Arrays.asList(new AlignedCodonFrame[]
522     { acf });
523
524     AlignViewportI dnaView = new AlignViewport(cdna);
525     AlignViewportI proteinView = new AlignViewport(protein);
526     protein.setCodonFrames(acfList);
527
528     /*
529      * Select all sequences, column 2 in the protein
530      */
531     SequenceGroup sg = new SequenceGroup();
532     sg.setColourText(true);
533     sg.setIdColour(Color.GREEN);
534     sg.setOutlineColour(Color.LIGHT_GRAY);
535     sg.addSequence(protein.getSequenceAt(0), false);
536     sg.addSequence(protein.getSequenceAt(1), false);
537     sg.addSequence(protein.getSequenceAt(2), false);
538     sg.setStartRes(1);
539     sg.setEndRes(1);
540
541     /*
542      * Verify the mapped sequence group in dna
543      */
544     SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(sg,
545             proteinView, dnaView);
546     assertTrue(mappedGroup.getColourText());
547     assertSame(sg.getIdColour(), mappedGroup.getIdColour());
548     assertSame(sg.getOutlineColour(), mappedGroup.getOutlineColour());
549     assertEquals(3, mappedGroup.getSequences().size());
550     assertSame(cdna.getSequenceAt(0), mappedGroup.getSequences().get(0));
551     assertSame(cdna.getSequenceAt(1), mappedGroup.getSequences().get(1));
552     assertSame(cdna.getSequenceAt(2), mappedGroup.getSequences().get(2));
553     assertEquals(3, mappedGroup.getStartRes());
554     assertEquals(5, mappedGroup.getEndRes());
555
556     /*
557      * Verify mapping sequence group from dna to protein
558      */
559     sg.clear();
560     sg.addSequence(cdna.getSequenceAt(0), false);
561     sg.addSequence(cdna.getSequenceAt(1), false);
562     sg.addSequence(cdna.getSequenceAt(2), false);
563     // select columns 2 and 3 in DNA which span protein columns 0 and 1
564     sg.setStartRes(2);
565     sg.setEndRes(3);
566     mappedGroup = MappingUtils.mapSequenceGroup(sg, dnaView, proteinView);
567     assertTrue(mappedGroup.getColourText());
568     assertSame(sg.getIdColour(), mappedGroup.getIdColour());
569     assertSame(sg.getOutlineColour(), mappedGroup.getOutlineColour());
570     assertEquals(3, mappedGroup.getSequences().size());
571     assertSame(protein.getSequenceAt(0), mappedGroup.getSequences().get(0));
572     assertSame(protein.getSequenceAt(1), mappedGroup.getSequences().get(1));
573     assertSame(protein.getSequenceAt(2), mappedGroup.getSequences().get(2));
574     assertEquals(0, mappedGroup.getStartRes());
575     assertEquals(1, mappedGroup.getEndRes());
576   }
577
578   /**
579    * Test mapping a sequence group made of a sequences/columns region.
580    * 
581    * @throws IOException
582    */
583   @Test(groups = { "Functional" })
584   public void testMapSequenceGroup_region() throws IOException
585   {
586     /*
587      * Set up gapped dna and protein Seq1/2/3 with mappings (held on the protein
588      * viewport).
589      */
590     AlignmentI cdna = loadAlignment(
591             ">Seq1\nA-CG-GC--AT-CA\n>Seq2\n-TG-AC-AG-T-AT\n>Seq3\n-T--ACG-TAAT-G\n",
592             FileFormat.Fasta);
593     cdna.setDataset(null);
594     AlignmentI protein = loadAlignment(
595             ">Seq1\n-KA-S\n>Seq2\n--L-QY\n>Seq3\nQ-V-M\n", FileFormat.Fasta);
596     protein.setDataset(null);
597     AlignedCodonFrame acf = new AlignedCodonFrame();
598     MapList map = new MapList(new int[] { 1, 9 }, new int[] { 1, 3 }, 3, 1);
599     for (int seq = 0; seq < 3; seq++)
600     {
601       acf.addMap(cdna.getSequenceAt(seq).getDatasetSequence(), protein
602               .getSequenceAt(seq).getDatasetSequence(), map);
603     }
604     List<AlignedCodonFrame> acfList = Arrays.asList(new AlignedCodonFrame[]
605     { acf });
606
607     AlignViewportI dnaView = new AlignViewport(cdna);
608     AlignViewportI proteinView = new AlignViewport(protein);
609     protein.setCodonFrames(acfList);
610
611     /*
612      * Select Seq1 and Seq2 in the protein, column 1 (K/-). Expect mapped
613      * sequence group to cover Seq1, columns 0-3 (ACG). Because the selection
614      * only includes a gap in Seq2 there is no mappable selection region in the
615      * corresponding DNA.
616      */
617     SequenceGroup sg = new SequenceGroup();
618     sg.setColourText(true);
619     sg.setIdColour(Color.GREEN);
620     sg.setOutlineColour(Color.LIGHT_GRAY);
621     sg.addSequence(protein.getSequenceAt(0), false);
622     sg.addSequence(protein.getSequenceAt(1), false);
623     sg.setStartRes(1);
624     sg.setEndRes(1);
625
626     /*
627      * Verify the mapped sequence group in dna
628      */
629     SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(sg,
630             proteinView, dnaView);
631     assertTrue(mappedGroup.getColourText());
632     assertSame(sg.getIdColour(), mappedGroup.getIdColour());
633     assertSame(sg.getOutlineColour(), mappedGroup.getOutlineColour());
634     assertEquals(1, mappedGroup.getSequences().size());
635     assertSame(cdna.getSequenceAt(0), mappedGroup.getSequences().get(0));
636     // Seq2 in protein has a gap in column 1 - ignored
637     // Seq1 has K which should map to columns 0-3 in Seq1
638     assertEquals(0, mappedGroup.getStartRes());
639     assertEquals(3, mappedGroup.getEndRes());
640
641     /*
642      * Now select cols 2-4 in protein. These cover Seq1:AS Seq2:LQ Seq3:VM which
643      * extend over DNA columns 3-12, 1-7, 6-13 respectively, or 1-13 overall.
644      */
645     sg.setStartRes(2);
646     sg.setEndRes(4);
647     mappedGroup = MappingUtils.mapSequenceGroup(sg, proteinView, dnaView);
648     assertEquals(1, mappedGroup.getStartRes());
649     assertEquals(13, mappedGroup.getEndRes());
650
651     /*
652      * Verify mapping sequence group from dna to protein
653      */
654     sg.clear();
655     sg.addSequence(cdna.getSequenceAt(0), false);
656
657     // select columns 4,5 - includes Seq1:codon2 (A) only
658     sg.setStartRes(4);
659     sg.setEndRes(5);
660     mappedGroup = MappingUtils.mapSequenceGroup(sg, dnaView, proteinView);
661     assertEquals(2, mappedGroup.getStartRes());
662     assertEquals(2, mappedGroup.getEndRes());
663
664     // add Seq2 to dna selection cols 4-5 include codons 1 and 2 (LQ)
665     sg.addSequence(cdna.getSequenceAt(1), false);
666     mappedGroup = MappingUtils.mapSequenceGroup(sg, dnaView, proteinView);
667     assertEquals(2, mappedGroup.getStartRes());
668     assertEquals(4, mappedGroup.getEndRes());
669
670     // add Seq3 to dna selection cols 4-5 include codon 1 (Q)
671     sg.addSequence(cdna.getSequenceAt(2), false);
672     mappedGroup = MappingUtils.mapSequenceGroup(sg, dnaView, proteinView);
673     assertEquals(0, mappedGroup.getStartRes());
674     assertEquals(4, mappedGroup.getEndRes());
675   }
676
677   @Test(groups = { "Functional" })
678   public void testFindMappingsForSequence()
679   {
680     SequenceI seq1 = new Sequence("Seq1", "ABC");
681     SequenceI seq2 = new Sequence("Seq2", "ABC");
682     SequenceI seq3 = new Sequence("Seq3", "ABC");
683     SequenceI seq4 = new Sequence("Seq4", "ABC");
684     seq1.createDatasetSequence();
685     seq2.createDatasetSequence();
686     seq3.createDatasetSequence();
687     seq4.createDatasetSequence();
688
689     /*
690      * Create mappings from seq1 to seq2, seq2 to seq1, seq3 to seq1
691      */
692     AlignedCodonFrame acf1 = new AlignedCodonFrame();
693     MapList map = new MapList(new int[] { 1, 3 }, new int[] { 1, 3 }, 1, 1);
694     acf1.addMap(seq1.getDatasetSequence(), seq2.getDatasetSequence(), map);
695     AlignedCodonFrame acf2 = new AlignedCodonFrame();
696     acf2.addMap(seq2.getDatasetSequence(), seq1.getDatasetSequence(), map);
697     AlignedCodonFrame acf3 = new AlignedCodonFrame();
698     acf3.addMap(seq3.getDatasetSequence(), seq1.getDatasetSequence(), map);
699
700     List<AlignedCodonFrame> mappings = new ArrayList<>();
701     mappings.add(acf1);
702     mappings.add(acf2);
703     mappings.add(acf3);
704
705     /*
706      * Seq1 has three mappings
707      */
708     List<AlignedCodonFrame> result = MappingUtils.findMappingsForSequence(
709             seq1, mappings);
710     assertEquals(3, result.size());
711     assertTrue(result.contains(acf1));
712     assertTrue(result.contains(acf2));
713     assertTrue(result.contains(acf3));
714
715     /*
716      * Seq2 has two mappings
717      */
718     result = MappingUtils.findMappingsForSequence(seq2, mappings);
719     assertEquals(2, result.size());
720     assertTrue(result.contains(acf1));
721     assertTrue(result.contains(acf2));
722
723     /*
724      * Seq3 has one mapping
725      */
726     result = MappingUtils.findMappingsForSequence(seq3, mappings);
727     assertEquals(1, result.size());
728     assertTrue(result.contains(acf3));
729
730     /*
731      * Seq4 has no mappings
732      */
733     result = MappingUtils.findMappingsForSequence(seq4, mappings);
734     assertEquals(0, result.size());
735
736     result = MappingUtils.findMappingsForSequence(null, mappings);
737     assertEquals(0, result.size());
738
739     result = MappingUtils.findMappingsForSequence(seq1, null);
740     assertEquals(0, result.size());
741
742     result = MappingUtils.findMappingsForSequence(null, null);
743     assertEquals(0, result.size());
744   }
745
746   /**
747    * just like the one above, but this time, we provide a set of sequences to
748    * subselect the mapping search
749    */
750   @Test(groups = { "Functional" })
751   public void testFindMappingsForSequenceAndOthers()
752   {
753     SequenceI seq1 = new Sequence("Seq1", "ABC");
754     SequenceI seq2 = new Sequence("Seq2", "ABC");
755     SequenceI seq3 = new Sequence("Seq3", "ABC");
756     SequenceI seq4 = new Sequence("Seq4", "ABC");
757     seq1.createDatasetSequence();
758     seq2.createDatasetSequence();
759     seq3.createDatasetSequence();
760     seq4.createDatasetSequence();
761
762     /*
763      * Create mappings from seq1 to seq2, seq2 to seq1, seq3 to seq1, seq3 to seq4
764      */
765     AlignedCodonFrame acf1 = new AlignedCodonFrame();
766     MapList map = new MapList(new int[] { 1, 3 }, new int[] { 1, 3 }, 1, 1);
767     acf1.addMap(seq1.getDatasetSequence(), seq2.getDatasetSequence(), map);
768     AlignedCodonFrame acf2 = new AlignedCodonFrame();
769     acf2.addMap(seq2.getDatasetSequence(), seq1.getDatasetSequence(), map);
770     AlignedCodonFrame acf3 = new AlignedCodonFrame();
771     acf3.addMap(seq3.getDatasetSequence(), seq1.getDatasetSequence(), map);
772     AlignedCodonFrame acf4 = new AlignedCodonFrame();
773     acf4.addMap(seq3.getDatasetSequence(), seq4.getDatasetSequence(), map);
774
775     List<AlignedCodonFrame> mappings = new ArrayList<>();
776     mappings.add(acf1);
777     mappings.add(acf2);
778     mappings.add(acf3);
779     mappings.add(acf4);
780
781     /*
782      * test for null args
783      */
784     List<AlignedCodonFrame> result = MappingUtils
785             .findMappingsForSequenceAndOthers(null, mappings,
786                     Arrays.asList(new SequenceI[] { seq1, seq2 }));
787     assertTrue(result.isEmpty());
788
789     result = MappingUtils.findMappingsForSequenceAndOthers(seq1, null,
790             Arrays.asList(new SequenceI[] { seq1, seq2 }));
791     assertTrue(result.isEmpty());
792
793     /*
794      * Seq1 has three mappings, but filter argument will only accept
795      * those to seq2
796      */
797     result = MappingUtils.findMappingsForSequenceAndOthers(
798             seq1,
799             mappings,
800             Arrays.asList(new SequenceI[] { seq1, seq2,
801                 seq1.getDatasetSequence() }));
802     assertEquals(2, result.size());
803     assertTrue(result.contains(acf1));
804     assertTrue(result.contains(acf2));
805     assertFalse("Did not expect to find mapping acf3 - subselect failed",
806             result.contains(acf3));
807     assertFalse(
808             "Did not expect to find mapping acf4 - doesn't involve sequence",
809             result.contains(acf4));
810
811     /*
812      * and verify the no filter case
813      */
814     result = MappingUtils.findMappingsForSequenceAndOthers(seq1, mappings,
815             null);
816     assertEquals(3, result.size());
817     assertTrue(result.contains(acf1));
818     assertTrue(result.contains(acf2));
819     assertTrue(result.contains(acf3));
820   }
821
822   @Test(groups = { "Functional" })
823   public void testMapEditCommand()
824   {
825     SequenceI dna = new Sequence("Seq1", "---ACG---GCATCA", 8, 16);
826     SequenceI protein = new Sequence("Seq2", "-T-AS", 5, 7);
827     dna.createDatasetSequence();
828     protein.createDatasetSequence();
829     AlignedCodonFrame acf = new AlignedCodonFrame();
830     MapList map = new MapList(new int[] { 8, 16 }, new int[] { 5, 7 }, 3, 1);
831     acf.addMap(dna.getDatasetSequence(), protein.getDatasetSequence(), map);
832     List<AlignedCodonFrame> mappings = new ArrayList<>();
833     mappings.add(acf);
834
835     AlignmentI prot = new Alignment(new SequenceI[] { protein });
836     prot.setCodonFrames(mappings);
837     AlignmentI nuc = new Alignment(new SequenceI[] { dna });
838
839     /*
840      * construct and perform the edit command to turn "-T-AS" in to "-T-A--S"
841      * i.e. insert two gaps at column 4
842      */
843     EditCommand ec = new EditCommand();
844     final Edit edit = ec.new Edit(Action.INSERT_GAP,
845             new SequenceI[] { protein }, 4, 2, '-');
846     ec.appendEdit(edit, prot, true, null);
847
848     /*
849      * the mapped edit command should be to insert 6 gaps before base 4 in the
850      * nucleotide sequence, which corresponds to aligned column 12 in the dna
851      */
852     EditCommand mappedEdit = MappingUtils.mapEditCommand(ec, false, nuc,
853             '-', mappings);
854     assertEquals(1, mappedEdit.getEdits().size());
855     Edit e = mappedEdit.getEdits().get(0);
856     assertEquals(1, e.getSequences().length);
857     assertEquals(dna, e.getSequences()[0]);
858     assertEquals(12, e.getPosition());
859     assertEquals(6, e.getNumber());
860   }
861
862   /**
863    * Tests for the method that converts a series of [start, end] ranges to
864    * single positions, where the mapping is to a reverse strand i.e. start is
865    * greater than end point mapped to
866    */
867   @Test(groups = { "Functional" })
868   public void testFlattenRanges_reverseStrand()
869   {
870     assertEquals("[4, 3, 2, 1]",
871             Arrays.toString(MappingUtils.flattenRanges(new int[] { 4, 1 })));
872     assertEquals(
873             "[4, 3, 2, 1]",
874             Arrays.toString(MappingUtils.flattenRanges(new int[] { 4, 3, 2,
875                 1 })));
876     assertEquals(
877             "[4, 3, 2, 1]",
878             Arrays.toString(MappingUtils.flattenRanges(new int[] { 4, 4, 3,
879                 3, 2, 2, 1, 1 })));
880     assertEquals(
881             "[12, 9, 8, 7, 4, 3, 2, 1]",
882             Arrays.toString(MappingUtils.flattenRanges(new int[] { 12, 12,
883                 9, 7, 4, 1 })));
884     // forwards and backwards anyone?
885     assertEquals(
886             "[4, 5, 6, 3, 2, 1]",
887             Arrays.toString(MappingUtils.flattenRanges(new int[] { 4, 6, 3,
888                 1 })));
889     // backwards and forwards
890     assertEquals(
891             "[3, 2, 1, 4, 5, 6]",
892             Arrays.toString(MappingUtils.flattenRanges(new int[] { 3, 1, 4,
893                 6 })));
894     // trailing unpaired start position is ignored:
895     assertEquals(
896             "[12, 9, 8, 7, 4, 3, 2]",
897             Arrays.toString(MappingUtils.flattenRanges(new int[] { 12, 12,
898                 9, 7, 4, 2, 1 })));
899   }
900
901   /**
902    * Test mapping a column selection including hidden columns
903    * 
904    * @throws IOException
905    */
906   @Test(groups = { "Functional" })
907   public void testMapColumnSelection_hiddenColumns() throws IOException
908   {
909     setupMappedAlignments();
910
911     ColumnSelection proteinSelection = new ColumnSelection();
912     HiddenColumns hiddenCols = new HiddenColumns();
913
914     /*
915      * Column 0 in protein picks up Seq2/L, Seq3/G which map to cols 0-4 and 0-3
916      * in dna respectively, overall 0-4
917      */
918     proteinSelection.hideSelectedColumns(0, hiddenCols);
919     ColumnSelection dnaSelection = new ColumnSelection();
920     HiddenColumns dnaHidden = new HiddenColumns();
921     MappingUtils.mapColumnSelection(proteinSelection, hiddenCols,
922             proteinView, dnaView, dnaSelection, dnaHidden);
923     assertEquals("[]", dnaSelection.getSelected().toString());
924     Iterator<int[]> regions = dnaHidden.iterator();
925     assertEquals(1, dnaHidden.getNumberOfRegions());
926     assertEquals("[0, 4]", Arrays.toString(regions.next()));
927
928     /*
929      * Column 1 in protein picks up Seq1/K which maps to cols 0-3 in dna
930      */
931     dnaSelection = new ColumnSelection();
932     dnaHidden = new HiddenColumns();
933     hiddenCols.revealAllHiddenColumns(proteinSelection);
934     // the unhidden columns are now marked selected!
935     assertEquals("[0]", proteinSelection.getSelected().toString());
936     // deselect these or hideColumns will be expanded to include 0
937     proteinSelection.clear();
938     proteinSelection.hideSelectedColumns(1, hiddenCols);
939     MappingUtils.mapColumnSelection(proteinSelection, hiddenCols,
940             proteinView, dnaView, dnaSelection, dnaHidden);
941     regions = dnaHidden.iterator();
942     assertEquals(1, dnaHidden.getNumberOfRegions());
943     assertEquals("[0, 3]", Arrays.toString(regions.next()));
944
945     /*
946      * Column 2 in protein picks up gaps only - no mapping
947      */
948     dnaSelection = new ColumnSelection();
949     dnaHidden = new HiddenColumns();
950     hiddenCols.revealAllHiddenColumns(proteinSelection);
951     proteinSelection.clear();
952     proteinSelection.hideSelectedColumns(2, hiddenCols);
953     MappingUtils.mapColumnSelection(proteinSelection, hiddenCols,
954             proteinView, dnaView, dnaSelection, dnaHidden);
955     assertEquals(0, dnaHidden.getNumberOfRegions());
956
957     /*
958      * Column 3 in protein picks up Seq1/P, Seq2/Q, Seq3/S which map to columns
959      * 6-9, 6-10, 5-8 respectively, overall to 5-10
960      */
961     dnaSelection = new ColumnSelection();
962     dnaHidden = new HiddenColumns();
963     hiddenCols.revealAllHiddenColumns(proteinSelection);
964     proteinSelection.clear();
965     proteinSelection.hideSelectedColumns(3, hiddenCols); // 5-10 hidden in dna
966     proteinSelection.addElement(1); // 0-3 selected in dna
967     MappingUtils.mapColumnSelection(proteinSelection, hiddenCols,
968             proteinView, dnaView, dnaSelection, dnaHidden);
969     assertEquals("[0, 1, 2, 3]", dnaSelection.getSelected().toString());
970     regions = dnaHidden.iterator();
971     assertEquals(1, dnaHidden.getNumberOfRegions());
972     assertEquals("[5, 10]", Arrays.toString(regions.next()));
973
974     /*
975      * Combine hiding columns 1 and 3 to get discontiguous hidden columns
976      */
977     dnaSelection = new ColumnSelection();
978     dnaHidden = new HiddenColumns();
979     hiddenCols.revealAllHiddenColumns(proteinSelection);
980     proteinSelection.clear();
981     proteinSelection.hideSelectedColumns(1, hiddenCols);
982     proteinSelection.hideSelectedColumns(3, hiddenCols);
983     MappingUtils.mapColumnSelection(proteinSelection, hiddenCols,
984             proteinView, dnaView, dnaSelection, dnaHidden);
985     regions = dnaHidden.iterator();
986     assertEquals(2, dnaHidden.getNumberOfRegions());
987     assertEquals("[0, 3]", Arrays.toString(regions.next()));
988     assertEquals("[5, 10]", Arrays.toString(regions.next()));
989   }
990
991   @Test(groups = { "Functional" })
992   public void testGetLength()
993   {
994     assertEquals(0, MappingUtils.getLength(null));
995
996     /*
997      * [start, end] ranges
998      */
999     List<int[]> ranges = new ArrayList<>();
1000     assertEquals(0, MappingUtils.getLength(ranges));
1001     ranges.add(new int[] { 1, 1 });
1002     assertEquals(1, MappingUtils.getLength(ranges));
1003     ranges.add(new int[] { 2, 10 });
1004     assertEquals(10, MappingUtils.getLength(ranges));
1005     ranges.add(new int[] { 20, 10 });
1006     assertEquals(21, MappingUtils.getLength(ranges));
1007
1008     /*
1009      * [start, end, start, end...] ranges
1010      */
1011     ranges.clear();
1012     ranges.add(new int[] { 1, 5, 8, 4 });
1013     ranges.add(new int[] { 8, 2 });
1014     ranges.add(new int[] { 12, 12 });
1015     assertEquals(18, MappingUtils.getLength(ranges));
1016   }
1017
1018   @Test(groups = { "Functional" })
1019   public void testContains()
1020   {
1021     assertFalse(MappingUtils.contains(null, 1));
1022     List<int[]> ranges = new ArrayList<>();
1023     assertFalse(MappingUtils.contains(ranges, 1));
1024
1025     ranges.add(new int[] { 1, 4 });
1026     ranges.add(new int[] { 6, 6 });
1027     ranges.add(new int[] { 8, 10 });
1028     ranges.add(new int[] { 30, 20 });
1029     ranges.add(new int[] { -16, -44 });
1030
1031     assertFalse(MappingUtils.contains(ranges, 0));
1032     assertTrue(MappingUtils.contains(ranges, 1));
1033     assertTrue(MappingUtils.contains(ranges, 2));
1034     assertTrue(MappingUtils.contains(ranges, 3));
1035     assertTrue(MappingUtils.contains(ranges, 4));
1036     assertFalse(MappingUtils.contains(ranges, 5));
1037
1038     assertTrue(MappingUtils.contains(ranges, 6));
1039     assertFalse(MappingUtils.contains(ranges, 7));
1040
1041     assertTrue(MappingUtils.contains(ranges, 8));
1042     assertTrue(MappingUtils.contains(ranges, 9));
1043     assertTrue(MappingUtils.contains(ranges, 10));
1044
1045     assertFalse(MappingUtils.contains(ranges, 31));
1046     assertTrue(MappingUtils.contains(ranges, 30));
1047     assertTrue(MappingUtils.contains(ranges, 29));
1048     assertTrue(MappingUtils.contains(ranges, 20));
1049     assertFalse(MappingUtils.contains(ranges, 19));
1050
1051     assertFalse(MappingUtils.contains(ranges, -15));
1052     assertTrue(MappingUtils.contains(ranges, -16));
1053     assertTrue(MappingUtils.contains(ranges, -44));
1054     assertFalse(MappingUtils.contains(ranges, -45));
1055   }
1056
1057   /**
1058    * Test the method that drops positions from the start of a mapped range
1059    */
1060   @Test(groups = "Functional")
1061   public void testRemoveStartPositions()
1062   {
1063     int[] ranges = new int[] { 1, 10 };
1064     int[] adjusted = MappingUtils.removeStartPositions(0, ranges);
1065     assertEquals("[1, 10]", Arrays.toString(adjusted));
1066
1067     adjusted = MappingUtils.removeStartPositions(1, ranges);
1068     assertEquals("[2, 10]", Arrays.toString(adjusted));
1069     assertEquals("[1, 10]", Arrays.toString(ranges));
1070
1071     ranges = adjusted;
1072     adjusted = MappingUtils.removeStartPositions(1, ranges);
1073     assertEquals("[3, 10]", Arrays.toString(adjusted));
1074     assertEquals("[2, 10]", Arrays.toString(ranges));
1075
1076     ranges = new int[] { 2, 3, 10, 12 };
1077     adjusted = MappingUtils.removeStartPositions(1, ranges);
1078     assertEquals("[3, 3, 10, 12]", Arrays.toString(adjusted));
1079     assertEquals("[2, 3, 10, 12]", Arrays.toString(ranges));
1080
1081     ranges = new int[] { 2, 2, 8, 12 };
1082     adjusted = MappingUtils.removeStartPositions(1, ranges);
1083     assertEquals("[8, 12]", Arrays.toString(adjusted));
1084     assertEquals("[2, 2, 8, 12]", Arrays.toString(ranges));
1085
1086     ranges = new int[] { 2, 2, 8, 12 };
1087     adjusted = MappingUtils.removeStartPositions(2, ranges);
1088     assertEquals("[9, 12]", Arrays.toString(adjusted));
1089     assertEquals("[2, 2, 8, 12]", Arrays.toString(ranges));
1090
1091     ranges = new int[] { 2, 2, 4, 4, 9, 12 };
1092     adjusted = MappingUtils.removeStartPositions(1, ranges);
1093     assertEquals("[4, 4, 9, 12]", Arrays.toString(adjusted));
1094     assertEquals("[2, 2, 4, 4, 9, 12]", Arrays.toString(ranges));
1095
1096     ranges = new int[] { 2, 2, 4, 4, 9, 12 };
1097     adjusted = MappingUtils.removeStartPositions(2, ranges);
1098     assertEquals("[9, 12]", Arrays.toString(adjusted));
1099     assertEquals("[2, 2, 4, 4, 9, 12]", Arrays.toString(ranges));
1100
1101     ranges = new int[] { 2, 3, 9, 12 };
1102     adjusted = MappingUtils.removeStartPositions(3, ranges);
1103     assertEquals("[10, 12]", Arrays.toString(adjusted));
1104     assertEquals("[2, 3, 9, 12]", Arrays.toString(ranges));
1105   }
1106
1107   /**
1108    * Test the method that drops positions from the start of a mapped range, on
1109    * the reverse strand
1110    */
1111   @Test(groups = "Functional")
1112   public void testRemoveStartPositions_reverseStrand()
1113   {
1114     int[] ranges = new int[] { 10, 1 };
1115     int[] adjusted = MappingUtils.removeStartPositions(0, ranges);
1116     assertEquals("[10, 1]", Arrays.toString(adjusted));
1117     assertEquals("[10, 1]", Arrays.toString(ranges));
1118
1119     ranges = adjusted;
1120     adjusted = MappingUtils.removeStartPositions(1, ranges);
1121     assertEquals("[9, 1]", Arrays.toString(adjusted));
1122     assertEquals("[10, 1]", Arrays.toString(ranges));
1123
1124     ranges = adjusted;
1125     adjusted = MappingUtils.removeStartPositions(1, ranges);
1126     assertEquals("[8, 1]", Arrays.toString(adjusted));
1127     assertEquals("[9, 1]", Arrays.toString(ranges));
1128
1129     ranges = new int[] { 12, 11, 9, 6 };
1130     adjusted = MappingUtils.removeStartPositions(1, ranges);
1131     assertEquals("[11, 11, 9, 6]", Arrays.toString(adjusted));
1132     assertEquals("[12, 11, 9, 6]", Arrays.toString(ranges));
1133
1134     ranges = new int[] { 12, 12, 8, 4 };
1135     adjusted = MappingUtils.removeStartPositions(1, ranges);
1136     assertEquals("[8, 4]", Arrays.toString(adjusted));
1137     assertEquals("[12, 12, 8, 4]", Arrays.toString(ranges));
1138
1139     ranges = new int[] { 12, 12, 8, 4 };
1140     adjusted = MappingUtils.removeStartPositions(2, ranges);
1141     assertEquals("[7, 4]", Arrays.toString(adjusted));
1142     assertEquals("[12, 12, 8, 4]", Arrays.toString(ranges));
1143
1144     ranges = new int[] { 12, 12, 10, 10, 8, 4 };
1145     adjusted = MappingUtils.removeStartPositions(1, ranges);
1146     assertEquals("[10, 10, 8, 4]", Arrays.toString(adjusted));
1147     assertEquals("[12, 12, 10, 10, 8, 4]", Arrays.toString(ranges));
1148
1149     ranges = new int[] { 12, 12, 10, 10, 8, 4 };
1150     adjusted = MappingUtils.removeStartPositions(2, ranges);
1151     assertEquals("[8, 4]", Arrays.toString(adjusted));
1152     assertEquals("[12, 12, 10, 10, 8, 4]", Arrays.toString(ranges));
1153
1154     ranges = new int[] { 12, 11, 8, 4 };
1155     adjusted = MappingUtils.removeStartPositions(3, ranges);
1156     assertEquals("[7, 4]", Arrays.toString(adjusted));
1157     assertEquals("[12, 11, 8, 4]", Arrays.toString(ranges));
1158   }
1159
1160   @Test(groups = { "Functional" })
1161   public void testRangeContains()
1162   {
1163     /*
1164      * both forward ranges
1165      */
1166     assertTrue(MappingUtils.rangeContains(new int[] { 1, 10 }, new int[] {
1167         1, 10 }));
1168     assertTrue(MappingUtils.rangeContains(new int[] { 1, 10 }, new int[] {
1169         2, 10 }));
1170     assertTrue(MappingUtils.rangeContains(new int[] { 1, 10 }, new int[] {
1171         1, 9 }));
1172     assertTrue(MappingUtils.rangeContains(new int[] { 1, 10 }, new int[] {
1173         4, 5 }));
1174     assertFalse(MappingUtils.rangeContains(new int[] { 1, 10 }, new int[] {
1175         0, 9 }));
1176     assertFalse(MappingUtils.rangeContains(new int[] { 1, 10 }, new int[] {
1177         -10, -9 }));
1178     assertFalse(MappingUtils.rangeContains(new int[] { 1, 10 }, new int[] {
1179         1, 11 }));
1180     assertFalse(MappingUtils.rangeContains(new int[] { 1, 10 }, new int[] {
1181         11, 12 }));
1182
1183     /*
1184      * forward range, reverse query
1185      */
1186     assertTrue(MappingUtils.rangeContains(new int[] { 1, 10 }, new int[] {
1187         10, 1 }));
1188     assertTrue(MappingUtils.rangeContains(new int[] { 1, 10 }, new int[] {
1189         9, 1 }));
1190     assertTrue(MappingUtils.rangeContains(new int[] { 1, 10 }, new int[] {
1191         10, 2 }));
1192     assertTrue(MappingUtils.rangeContains(new int[] { 1, 10 }, new int[] {
1193         5, 5 }));
1194     assertFalse(MappingUtils.rangeContains(new int[] { 1, 10 }, new int[] {
1195         11, 1 }));
1196     assertFalse(MappingUtils.rangeContains(new int[] { 1, 10 }, new int[] {
1197         10, 0 }));
1198
1199     /*
1200      * reverse range, forward query
1201      */
1202     assertTrue(MappingUtils.rangeContains(new int[] { 10, 1 }, new int[] {
1203         1, 10 }));
1204     assertTrue(MappingUtils.rangeContains(new int[] { 10, 1 }, new int[] {
1205         1, 9 }));
1206     assertTrue(MappingUtils.rangeContains(new int[] { 10, 1 }, new int[] {
1207         2, 10 }));
1208     assertTrue(MappingUtils.rangeContains(new int[] { 10, 1 }, new int[] {
1209         6, 6 }));
1210     assertFalse(MappingUtils.rangeContains(new int[] { 10, 1 }, new int[] {
1211         6, 11 }));
1212     assertFalse(MappingUtils.rangeContains(new int[] { 10, 1 }, new int[] {
1213         11, 20 }));
1214     assertFalse(MappingUtils.rangeContains(new int[] { 10, 1 }, new int[] {
1215         -3, -2 }));
1216
1217     /*
1218      * both reverse
1219      */
1220     assertTrue(MappingUtils.rangeContains(new int[] { 10, 1 }, new int[] {
1221         10, 1 }));
1222     assertTrue(MappingUtils.rangeContains(new int[] { 10, 1 }, new int[] {
1223         9, 1 }));
1224     assertTrue(MappingUtils.rangeContains(new int[] { 10, 1 }, new int[] {
1225         10, 2 }));
1226     assertTrue(MappingUtils.rangeContains(new int[] { 10, 1 }, new int[] {
1227         3, 3 }));
1228     assertFalse(MappingUtils.rangeContains(new int[] { 10, 1 }, new int[] {
1229         11, 1 }));
1230     assertFalse(MappingUtils.rangeContains(new int[] { 10, 1 }, new int[] {
1231         10, 0 }));
1232     assertFalse(MappingUtils.rangeContains(new int[] { 10, 1 }, new int[] {
1233         12, 11 }));
1234     assertFalse(MappingUtils.rangeContains(new int[] { 10, 1 }, new int[] {
1235         -5, -8 }));
1236
1237     /*
1238      * bad arguments
1239      */
1240     assertFalse(MappingUtils.rangeContains(new int[] { 1, 10, 12 },
1241             new int[] {
1242         1, 10 }));
1243     assertFalse(MappingUtils.rangeContains(new int[] { 1, 10 },
1244             new int[] { 1 }));
1245     assertFalse(MappingUtils.rangeContains(new int[] { 1, 10 }, null));
1246     assertFalse(MappingUtils.rangeContains(null, new int[] { 1, 10 }));
1247   }
1248
1249   @Test(groups = "Functional")
1250   public void testRemoveEndPositions()
1251   {
1252     List<int[]> ranges = new ArrayList<>();
1253
1254     /*
1255      * case 1: truncate last range
1256      */
1257     ranges.add(new int[] { 1, 10 });
1258     ranges.add(new int[] { 20, 30 });
1259     MappingUtils.removeEndPositions(5, ranges);
1260     assertEquals(2, ranges.size());
1261     assertEquals(25, ranges.get(1)[1]);
1262
1263     /*
1264      * case 2: remove last range
1265      */
1266     ranges.clear();
1267     ranges.add(new int[] { 1, 10 });
1268     ranges.add(new int[] { 20, 22 });
1269     MappingUtils.removeEndPositions(3, ranges);
1270     assertEquals(1, ranges.size());
1271     assertEquals(10, ranges.get(0)[1]);
1272
1273     /*
1274      * case 3: truncate penultimate range
1275      */
1276     ranges.clear();
1277     ranges.add(new int[] { 1, 10 });
1278     ranges.add(new int[] { 20, 21 });
1279     MappingUtils.removeEndPositions(3, ranges);
1280     assertEquals(1, ranges.size());
1281     assertEquals(9, ranges.get(0)[1]);
1282
1283     /*
1284      * case 4: remove last two ranges
1285      */
1286     ranges.clear();
1287     ranges.add(new int[] { 1, 10 });
1288     ranges.add(new int[] { 20, 20 });
1289     ranges.add(new int[] { 30, 30 });
1290     MappingUtils.removeEndPositions(3, ranges);
1291     assertEquals(1, ranges.size());
1292     assertEquals(9, ranges.get(0)[1]);
1293   }
1294 }