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