d4df8c70ed72cf14c370fbc904ada7e23220a355
[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.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
63   @BeforeClass(alwaysRun = true)
64   public void setUpJvOptionPane()
65   {
66     JvOptionPane.setInteractiveMode(false);
67     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
68   }
69
70   private AlignViewportI dnaView;
71
72   private AlignViewportI proteinView;
73
74   /**
75    * Simple test of mapping with no intron involved.
76    */
77   @Test(groups = { "Functional" })
78   public void testBuildSearchResults()
79   {
80     final Sequence seq1 = new Sequence("Seq1/5-10", "C-G-TA-GC");
81     seq1.createDatasetSequence();
82
83     final Sequence aseq1 = new Sequence("Seq1/12-13", "-P-R");
84     aseq1.createDatasetSequence();
85
86     /*
87      * Map dna bases 5-10 to protein residues 12-13
88      */
89     AlignedCodonFrame acf = new AlignedCodonFrame();
90     MapList map = new MapList(new int[] { 5, 10 }, new int[] { 12, 13 }, 3,
91             1);
92     acf.addMap(seq1.getDatasetSequence(), aseq1.getDatasetSequence(), map);
93     List<AlignedCodonFrame> acfList = Arrays
94             .asList(new AlignedCodonFrame[]
95             { acf });
96
97     /*
98      * Check protein residue 12 maps to codon 5-7, 13 to codon 8-10
99      */
100     SearchResultsI sr = MappingUtils.buildSearchResults(aseq1, 12, acfList);
101     assertEquals(1, sr.getResults().size());
102     SearchResultMatchI m = sr.getResults().get(0);
103     assertEquals(seq1.getDatasetSequence(), m.getSequence());
104     assertEquals(5, m.getStart());
105     assertEquals(7, m.getEnd());
106     sr = MappingUtils.buildSearchResults(aseq1, 13, acfList);
107     assertEquals(1, sr.getResults().size());
108     m = sr.getResults().get(0);
109     assertEquals(seq1.getDatasetSequence(), m.getSequence());
110     assertEquals(8, m.getStart());
111     assertEquals(10, m.getEnd());
112
113     /*
114      * Check inverse mappings, from codons 5-7, 8-10 to protein 12, 13
115      */
116     for (int i = 5; i < 11; i++)
117     {
118       sr = MappingUtils.buildSearchResults(seq1, i, acfList);
119       assertEquals(1, sr.getResults().size());
120       m = sr.getResults().get(0);
121       assertEquals(aseq1.getDatasetSequence(), m.getSequence());
122       int residue = i > 7 ? 13 : 12;
123       assertEquals(residue, m.getStart());
124       assertEquals(residue, m.getEnd());
125     }
126   }
127
128   /**
129    * Simple test of mapping with introns involved.
130    */
131   @Test(groups = { "Functional" })
132   public void testBuildSearchResults_withIntron()
133   {
134     final Sequence seq1 = new Sequence("Seq1/5-17", "c-G-tAGa-GcAgCtt");
135     seq1.createDatasetSequence();
136
137     final Sequence aseq1 = new Sequence("Seq1/8-9", "-E-D");
138     aseq1.createDatasetSequence();
139
140     /*
141      * Map dna bases [6, 8, 9], [11, 13, 115] to protein residues 8 and 9
142      */
143     AlignedCodonFrame acf = new AlignedCodonFrame();
144     MapList map = new MapList(
145             new int[]
146             { 6, 6, 8, 9, 11, 11, 13, 13, 15, 15 }, new int[] { 8, 9 }, 3,
147             1);
148     acf.addMap(seq1.getDatasetSequence(), aseq1.getDatasetSequence(), map);
149     List<AlignedCodonFrame> acfList = Arrays
150             .asList(new AlignedCodonFrame[]
151             { acf });
152
153     /*
154      * Check protein residue 8 maps to [6, 8, 9]
155      */
156     SearchResultsI sr = MappingUtils.buildSearchResults(aseq1, 8, acfList);
157     assertEquals(2, sr.getResults().size());
158     SearchResultMatchI m = sr.getResults().get(0);
159     assertEquals(seq1.getDatasetSequence(), m.getSequence());
160     assertEquals(6, m.getStart());
161     assertEquals(6, m.getEnd());
162     m = sr.getResults().get(1);
163     assertEquals(seq1.getDatasetSequence(), m.getSequence());
164     assertEquals(8, m.getStart());
165     assertEquals(9, m.getEnd());
166
167     /*
168      * Check protein residue 9 maps to [11, 13, 15]
169      */
170     sr = MappingUtils.buildSearchResults(aseq1, 9, acfList);
171     assertEquals(3, sr.getResults().size());
172     m = sr.getResults().get(0);
173     assertEquals(seq1.getDatasetSequence(), m.getSequence());
174     assertEquals(11, m.getStart());
175     assertEquals(11, m.getEnd());
176     m = sr.getResults().get(1);
177     assertEquals(seq1.getDatasetSequence(), m.getSequence());
178     assertEquals(13, m.getStart());
179     assertEquals(13, m.getEnd());
180     m = sr.getResults().get(2);
181     assertEquals(seq1.getDatasetSequence(), m.getSequence());
182     assertEquals(15, m.getStart());
183     assertEquals(15, m.getEnd());
184
185     /*
186      * Check inverse mappings, from codons to protein
187      */
188     for (int i = 5; i < 18; i++)
189     {
190       sr = MappingUtils.buildSearchResults(seq1, i, acfList);
191       int residue = (i == 6 || i == 8 || i == 9) ? 8
192               : (i == 11 || i == 13 || i == 15 ? 9 : 0);
193       if (residue == 0)
194       {
195         assertEquals(0, sr.getResults().size());
196         continue;
197       }
198       assertEquals(1, sr.getResults().size());
199       m = sr.getResults().get(0);
200       assertEquals(aseq1.getDatasetSequence(), m.getSequence());
201       assertEquals(residue, m.getStart());
202       assertEquals(residue, m.getEnd());
203     }
204   }
205
206   /**
207    * Test mapping a sequence group made of entire sequences.
208    * 
209    * @throws IOException
210    */
211   @Test(groups = { "Functional" })
212   public void testMapSequenceGroup_sequences() throws IOException
213   {
214     /*
215      * Set up dna and protein Seq1/2/3 with mappings (held on the protein
216      * viewport).
217      */
218     AlignmentI cdna = loadAlignment(">Seq1\nACG\n>Seq2\nTGA\n>Seq3\nTAC\n",
219             FileFormat.Fasta);
220     cdna.setDataset(null);
221     AlignmentI protein = loadAlignment(">Seq1\nK\n>Seq2\nL\n>Seq3\nQ\n",
222             FileFormat.Fasta);
223     protein.setDataset(null);
224     AlignedCodonFrame acf = new AlignedCodonFrame();
225     MapList map = new MapList(new int[] { 1, 3 }, new int[] { 1, 1 }, 3, 1);
226     for (int seq = 0; seq < 3; seq++)
227     {
228       acf.addMap(cdna.getSequenceAt(seq).getDatasetSequence(),
229               protein.getSequenceAt(seq).getDatasetSequence(), map);
230     }
231     List<AlignedCodonFrame> acfList = Arrays
232             .asList(new AlignedCodonFrame[]
233             { acf });
234
235     AlignViewportI dnaView = new AlignViewport(cdna);
236     AlignViewportI proteinView = new AlignViewport(protein);
237     protein.setCodonFrames(acfList);
238
239     /*
240      * Select Seq1 and Seq3 in the protein
241      */
242     SequenceGroup sg = new SequenceGroup();
243     sg.setColourText(true);
244     sg.setIdColour(Color.GREEN);
245     sg.setOutlineColour(Color.LIGHT_GRAY);
246     sg.addSequence(protein.getSequenceAt(0), false);
247     sg.addSequence(protein.getSequenceAt(2), false);
248     sg.setEndRes(protein.getWidth() - 1);
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()); // 3 columns (1 codon)
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, DataSourceType.PASTE,
296             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, dnaView,
342             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, dnaView,
353             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, dnaView,
365             cs, hs);
366     assertEquals("[0, 1, 2, 3, 5, 6, 7, 8, 9, 10]",
367             cs.getSelected().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[]
397             { 40, 41 }, 3, 1);
398     acf.addMap(cdna.getSequenceAt(0).getDatasetSequence(),
399             protein.getSequenceAt(0).getDatasetSequence(), map);
400
401     // map second dna to second protein seq
402     map = new MapList(new int[] { 20, 20, 22, 23, 24, 26 },
403             new int[]
404             { 50, 51 }, 3, 1);
405     acf.addMap(cdna.getSequenceAt(1).getDatasetSequence(),
406             protein.getSequenceAt(1).getDatasetSequence(), map);
407
408     // map third dna to third protein seq
409     map = new MapList(new int[] { 30, 30, 32, 34, 36, 37 },
410             new int[]
411             { 60, 61 }, 3, 1);
412     acf.addMap(cdna.getSequenceAt(2).getDatasetSequence(),
413             protein.getSequenceAt(2).getDatasetSequence(), map);
414     List<AlignedCodonFrame> acfList = Arrays
415             .asList(new AlignedCodonFrame[]
416             { acf });
417
418     dnaView = new AlignViewport(cdna);
419     proteinView = new AlignViewport(protein);
420     protein.setCodonFrames(acfList);
421   }
422
423   /**
424    * Test mapping a column selection in dna to its protein equivalent
425    * 
426    * @throws IOException
427    */
428   @Test(groups = { "Functional" })
429   public void testMapColumnSelection_dnaToProtein() throws IOException
430   {
431     setupMappedAlignments();
432
433     ColumnSelection colsel = new ColumnSelection();
434     HiddenColumns hidden = new HiddenColumns();
435
436     /*
437      * Column 0 in dna picks up first bases which map to residue 1, columns 0-1
438      * in protein.
439      */
440     ColumnSelection cs = new ColumnSelection();
441     HiddenColumns hs = new HiddenColumns();
442     colsel.addElement(0);
443     MappingUtils.mapColumnSelection(colsel, hidden, dnaView, proteinView,
444             cs, hs);
445     assertEquals("[0, 1]", cs.getSelected().toString());
446
447     /*
448      * Columns 3-5 in dna map to the first residues in protein Seq1, Seq2, and
449      * the first two in Seq3. Overall to columns 0, 1, 3 (col2 is all gaps).
450      */
451     colsel.addElement(3);
452     colsel.addElement(4);
453     colsel.addElement(5);
454     cs.clear();
455     MappingUtils.mapColumnSelection(colsel, hidden, dnaView, proteinView,
456             cs, hs);
457     assertEquals("[0, 1, 3]", cs.getSelected().toString());
458   }
459
460   @Test(groups = { "Functional" })
461   public void testMapColumnSelection_null() throws IOException
462   {
463     setupMappedAlignments();
464     ColumnSelection cs = new ColumnSelection();
465     HiddenColumns hs = new HiddenColumns();
466     MappingUtils.mapColumnSelection(null, null, dnaView, proteinView, cs,
467             hs);
468     assertTrue("mapped selection not empty", cs.getSelected().isEmpty());
469   }
470
471   /**
472    * Tests for the method that converts a series of [start, end] ranges to
473    * single positions
474    */
475   @Test(groups = { "Functional" })
476   public void testFlattenRanges()
477   {
478     assertEquals("[1, 2, 3, 4]",
479             Arrays.toString(MappingUtils.flattenRanges(new int[]
480             { 1, 4 })));
481     assertEquals("[1, 2, 3, 4]",
482             Arrays.toString(MappingUtils.flattenRanges(new int[]
483             { 1, 2, 3, 4 })));
484     assertEquals("[1, 2, 3, 4]",
485             Arrays.toString(MappingUtils.flattenRanges(new int[]
486             { 1, 1, 2, 2, 3, 3, 4, 4 })));
487     assertEquals("[1, 2, 3, 4, 7, 8, 9, 12]",
488             Arrays.toString(MappingUtils.flattenRanges(new int[]
489             { 1, 4, 7, 9, 12, 12 })));
490     // trailing unpaired start position is ignored:
491     assertEquals("[1, 2, 3, 4, 7, 8, 9, 12]",
492             Arrays.toString(MappingUtils.flattenRanges(new int[]
493             { 1, 4, 7, 9, 12, 12, 15 })));
494   }
495
496   /**
497    * Test mapping a sequence group made of entire columns.
498    * 
499    * @throws IOException
500    */
501   @Test(groups = { "Functional" })
502   public void testMapSequenceGroup_columns() throws IOException
503   {
504     /*
505      * Set up dna and protein Seq1/2/3 with mappings (held on the protein
506      * viewport).
507      */
508     AlignmentI cdna = loadAlignment(
509             ">Seq1\nACGGCA\n>Seq2\nTGACAG\n>Seq3\nTACGTA\n",
510             FileFormat.Fasta);
511     cdna.setDataset(null);
512     AlignmentI protein = loadAlignment(">Seq1\nKA\n>Seq2\nLQ\n>Seq3\nQV\n",
513             FileFormat.Fasta);
514     protein.setDataset(null);
515     AlignedCodonFrame acf = new AlignedCodonFrame();
516     MapList map = new MapList(new int[] { 1, 6 }, new int[] { 1, 2 }, 3, 1);
517     for (int seq = 0; seq < 3; seq++)
518     {
519       acf.addMap(cdna.getSequenceAt(seq).getDatasetSequence(),
520               protein.getSequenceAt(seq).getDatasetSequence(), map);
521     }
522     List<AlignedCodonFrame> acfList = Arrays
523             .asList(new AlignedCodonFrame[]
524             { acf });
525
526     AlignViewportI dnaView = new AlignViewport(cdna);
527     AlignViewportI proteinView = new AlignViewport(protein);
528     protein.setCodonFrames(acfList);
529
530     /*
531      * Select all sequences, column 2 in the protein
532      */
533     SequenceGroup sg = new SequenceGroup();
534     sg.setColourText(true);
535     sg.setIdColour(Color.GREEN);
536     sg.setOutlineColour(Color.LIGHT_GRAY);
537     sg.addSequence(protein.getSequenceAt(0), false);
538     sg.addSequence(protein.getSequenceAt(1), false);
539     sg.addSequence(protein.getSequenceAt(2), false);
540     sg.setStartRes(1);
541     sg.setEndRes(1);
542
543     /*
544      * Verify the mapped sequence group in dna
545      */
546     SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(sg,
547             proteinView, dnaView);
548     assertTrue(mappedGroup.getColourText());
549     assertSame(sg.getIdColour(), mappedGroup.getIdColour());
550     assertSame(sg.getOutlineColour(), mappedGroup.getOutlineColour());
551     assertEquals(3, mappedGroup.getSequences().size());
552     assertSame(cdna.getSequenceAt(0), mappedGroup.getSequences().get(0));
553     assertSame(cdna.getSequenceAt(1), mappedGroup.getSequences().get(1));
554     assertSame(cdna.getSequenceAt(2), mappedGroup.getSequences().get(2));
555     assertEquals(3, mappedGroup.getStartRes());
556     assertEquals(5, mappedGroup.getEndRes());
557
558     /*
559      * Verify mapping sequence group from dna to protein
560      */
561     sg.clear();
562     sg.addSequence(cdna.getSequenceAt(0), false);
563     sg.addSequence(cdna.getSequenceAt(1), false);
564     sg.addSequence(cdna.getSequenceAt(2), false);
565     // select columns 2 and 3 in DNA which span protein columns 0 and 1
566     sg.setStartRes(2);
567     sg.setEndRes(3);
568     mappedGroup = MappingUtils.mapSequenceGroup(sg, dnaView, proteinView);
569     assertTrue(mappedGroup.getColourText());
570     assertSame(sg.getIdColour(), mappedGroup.getIdColour());
571     assertSame(sg.getOutlineColour(), mappedGroup.getOutlineColour());
572     assertEquals(3, mappedGroup.getSequences().size());
573     assertSame(protein.getSequenceAt(0), mappedGroup.getSequences().get(0));
574     assertSame(protein.getSequenceAt(1), mappedGroup.getSequences().get(1));
575     assertSame(protein.getSequenceAt(2), mappedGroup.getSequences().get(2));
576     assertEquals(0, mappedGroup.getStartRes());
577     assertEquals(1, mappedGroup.getEndRes());
578   }
579
580   /**
581    * Test mapping a sequence group made of a sequences/columns region.
582    * 
583    * @throws IOException
584    */
585   @Test(groups = { "Functional" })
586   public void testMapSequenceGroup_region() throws IOException
587   {
588     /*
589      * Set up gapped dna and protein Seq1/2/3 with mappings (held on the protein
590      * viewport).
591      */
592     AlignmentI cdna = loadAlignment(
593             ">Seq1\nA-CG-GC--AT-CA\n>Seq2\n-TG-AC-AG-T-AT\n>Seq3\n-T--ACG-TAAT-G\n",
594             FileFormat.Fasta);
595     cdna.setDataset(null);
596     AlignmentI protein = loadAlignment(
597             ">Seq1\n-KA-S\n>Seq2\n--L-QY\n>Seq3\nQ-V-M\n",
598             FileFormat.Fasta);
599     protein.setDataset(null);
600     AlignedCodonFrame acf = new AlignedCodonFrame();
601     MapList map = new MapList(new int[] { 1, 9 }, new int[] { 1, 3 }, 3, 1);
602     for (int seq = 0; seq < 3; seq++)
603     {
604       acf.addMap(cdna.getSequenceAt(seq).getDatasetSequence(),
605               protein.getSequenceAt(seq).getDatasetSequence(), map);
606     }
607     List<AlignedCodonFrame> acfList = Arrays
608             .asList(new AlignedCodonFrame[]
609             { acf });
610
611     AlignViewportI dnaView = new AlignViewport(cdna);
612     AlignViewportI proteinView = new AlignViewport(protein);
613     protein.setCodonFrames(acfList);
614
615     /*
616      * Select Seq1 and Seq2 in the protein, column 1 (K/-). Expect mapped
617      * sequence group to cover Seq1, columns 0-3 (ACG). Because the selection
618      * only includes a gap in Seq2 there is no mappable selection region in the
619      * corresponding DNA.
620      */
621     SequenceGroup sg = new SequenceGroup();
622     sg.setColourText(true);
623     sg.setIdColour(Color.GREEN);
624     sg.setOutlineColour(Color.LIGHT_GRAY);
625     sg.addSequence(protein.getSequenceAt(0), false);
626     sg.addSequence(protein.getSequenceAt(1), false);
627     sg.setStartRes(1);
628     sg.setEndRes(1);
629
630     /*
631      * Verify the mapped sequence group in dna
632      */
633     SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(sg,
634             proteinView, dnaView);
635     assertTrue(mappedGroup.getColourText());
636     assertSame(sg.getIdColour(), mappedGroup.getIdColour());
637     assertSame(sg.getOutlineColour(), mappedGroup.getOutlineColour());
638     assertEquals(1, mappedGroup.getSequences().size());
639     assertSame(cdna.getSequenceAt(0), mappedGroup.getSequences().get(0));
640     // Seq2 in protein has a gap in column 1 - ignored
641     // Seq1 has K which should map to columns 0-3 in Seq1
642     assertEquals(0, mappedGroup.getStartRes());
643     assertEquals(3, mappedGroup.getEndRes());
644
645     /*
646      * Now select cols 2-4 in protein. These cover Seq1:AS Seq2:LQ Seq3:VM which
647      * extend over DNA columns 3-12, 1-7, 6-13 respectively, or 1-13 overall.
648      */
649     sg.setStartRes(2);
650     sg.setEndRes(4);
651     mappedGroup = MappingUtils.mapSequenceGroup(sg, proteinView, dnaView);
652     assertEquals(1, mappedGroup.getStartRes());
653     assertEquals(13, mappedGroup.getEndRes());
654
655     /*
656      * Verify mapping sequence group from dna to protein
657      */
658     sg.clear();
659     sg.addSequence(cdna.getSequenceAt(0), false);
660
661     // select columns 4,5 - includes Seq1:codon2 (A) only
662     sg.setStartRes(4);
663     sg.setEndRes(5);
664     mappedGroup = MappingUtils.mapSequenceGroup(sg, dnaView, proteinView);
665     assertEquals(2, mappedGroup.getStartRes());
666     assertEquals(2, mappedGroup.getEndRes());
667
668     // add Seq2 to dna selection cols 4-5 include codons 1 and 2 (LQ)
669     sg.addSequence(cdna.getSequenceAt(1), false);
670     mappedGroup = MappingUtils.mapSequenceGroup(sg, dnaView, proteinView);
671     assertEquals(2, mappedGroup.getStartRes());
672     assertEquals(4, mappedGroup.getEndRes());
673
674     // add Seq3 to dna selection cols 4-5 include codon 1 (Q)
675     sg.addSequence(cdna.getSequenceAt(2), false);
676     mappedGroup = MappingUtils.mapSequenceGroup(sg, dnaView, proteinView);
677     assertEquals(0, mappedGroup.getStartRes());
678     assertEquals(4, mappedGroup.getEndRes());
679   }
680
681   @Test(groups = { "Functional" })
682   public void testFindMappingsForSequence()
683   {
684     SequenceI seq1 = new Sequence("Seq1", "ABC");
685     SequenceI seq2 = new Sequence("Seq2", "ABC");
686     SequenceI seq3 = new Sequence("Seq3", "ABC");
687     SequenceI seq4 = new Sequence("Seq4", "ABC");
688     seq1.createDatasetSequence();
689     seq2.createDatasetSequence();
690     seq3.createDatasetSequence();
691     seq4.createDatasetSequence();
692
693     /*
694      * Create mappings from seq1 to seq2, seq2 to seq1, seq3 to seq1
695      */
696     AlignedCodonFrame acf1 = new AlignedCodonFrame();
697     MapList map = new MapList(new int[] { 1, 3 }, new int[] { 1, 3 }, 1, 1);
698     acf1.addMap(seq1.getDatasetSequence(), seq2.getDatasetSequence(), map);
699     AlignedCodonFrame acf2 = new AlignedCodonFrame();
700     acf2.addMap(seq2.getDatasetSequence(), seq1.getDatasetSequence(), map);
701     AlignedCodonFrame acf3 = new AlignedCodonFrame();
702     acf3.addMap(seq3.getDatasetSequence(), seq1.getDatasetSequence(), map);
703
704     List<AlignedCodonFrame> mappings = new ArrayList<>();
705     mappings.add(acf1);
706     mappings.add(acf2);
707     mappings.add(acf3);
708
709     /*
710      * Seq1 has three mappings
711      */
712     List<AlignedCodonFrame> result = MappingUtils
713             .findMappingsForSequence(seq1, mappings);
714     assertEquals(3, result.size());
715     assertTrue(result.contains(acf1));
716     assertTrue(result.contains(acf2));
717     assertTrue(result.contains(acf3));
718
719     /*
720      * Seq2 has two mappings
721      */
722     result = MappingUtils.findMappingsForSequence(seq2, mappings);
723     assertEquals(2, result.size());
724     assertTrue(result.contains(acf1));
725     assertTrue(result.contains(acf2));
726
727     /*
728      * Seq3 has one mapping
729      */
730     result = MappingUtils.findMappingsForSequence(seq3, mappings);
731     assertEquals(1, result.size());
732     assertTrue(result.contains(acf3));
733
734     /*
735      * Seq4 has no mappings
736      */
737     result = MappingUtils.findMappingsForSequence(seq4, mappings);
738     assertEquals(0, result.size());
739
740     result = MappingUtils.findMappingsForSequence(null, mappings);
741     assertEquals(0, result.size());
742
743     result = MappingUtils.findMappingsForSequence(seq1, null);
744     assertEquals(0, result.size());
745
746     result = MappingUtils.findMappingsForSequence(null, null);
747     assertEquals(0, result.size());
748   }
749
750   /**
751    * just like the one above, but this time, we provide a set of sequences to
752    * subselect the mapping search
753    */
754   @Test(groups = { "Functional" })
755   public void testFindMappingsForSequenceAndOthers()
756   {
757     SequenceI seq1 = new Sequence("Seq1", "ABC");
758     SequenceI seq2 = new Sequence("Seq2", "ABC");
759     SequenceI seq3 = new Sequence("Seq3", "ABC");
760     SequenceI seq4 = new Sequence("Seq4", "ABC");
761     seq1.createDatasetSequence();
762     seq2.createDatasetSequence();
763     seq3.createDatasetSequence();
764     seq4.createDatasetSequence();
765
766     /*
767      * Create mappings from seq1 to seq2, seq2 to seq1, seq3 to seq1, seq3 to seq4
768      */
769     AlignedCodonFrame acf1 = new AlignedCodonFrame();
770     MapList map = new MapList(new int[] { 1, 3 }, new int[] { 1, 3 }, 1, 1);
771     acf1.addMap(seq1.getDatasetSequence(), seq2.getDatasetSequence(), map);
772     AlignedCodonFrame acf2 = new AlignedCodonFrame();
773     acf2.addMap(seq2.getDatasetSequence(), seq1.getDatasetSequence(), map);
774     AlignedCodonFrame acf3 = new AlignedCodonFrame();
775     acf3.addMap(seq3.getDatasetSequence(), seq1.getDatasetSequence(), map);
776     AlignedCodonFrame acf4 = new AlignedCodonFrame();
777     acf4.addMap(seq3.getDatasetSequence(), seq4.getDatasetSequence(), map);
778
779     List<AlignedCodonFrame> mappings = new ArrayList<>();
780     mappings.add(acf1);
781     mappings.add(acf2);
782     mappings.add(acf3);
783     mappings.add(acf4);
784
785     /*
786      * test for null args
787      */
788     List<AlignedCodonFrame> result = MappingUtils
789             .findMappingsForSequenceAndOthers(null, mappings,
790                     Arrays.asList(new SequenceI[]
791                     { seq1, seq2 }));
792     assertTrue(result.isEmpty());
793
794     result = MappingUtils.findMappingsForSequenceAndOthers(seq1, null,
795             Arrays.asList(new SequenceI[]
796             { seq1, seq2 }));
797     assertTrue(result.isEmpty());
798
799     /*
800      * Seq1 has three mappings, but filter argument will only accept
801      * those to seq2
802      */
803     result = MappingUtils.findMappingsForSequenceAndOthers(seq1, mappings,
804             Arrays.asList(new SequenceI[]
805             { seq1, seq2, seq1.getDatasetSequence() }));
806     assertEquals(2, result.size());
807     assertTrue(result.contains(acf1));
808     assertTrue(result.contains(acf2));
809     assertFalse("Did not expect to find mapping acf3 - subselect failed",
810             result.contains(acf3));
811     assertFalse(
812             "Did not expect to find mapping acf4 - doesn't involve sequence",
813             result.contains(acf4));
814
815     /*
816      * and verify the no filter case
817      */
818     result = MappingUtils.findMappingsForSequenceAndOthers(seq1, mappings,
819             null);
820     assertEquals(3, result.size());
821     assertTrue(result.contains(acf1));
822     assertTrue(result.contains(acf2));
823     assertTrue(result.contains(acf3));
824   }
825
826   @Test(groups = { "Functional" })
827   public void testMapEditCommand()
828   {
829     SequenceI dna = new Sequence("Seq1", "---ACG---GCATCA", 8, 16);
830     SequenceI protein = new Sequence("Seq2", "-T-AS", 5, 7);
831     dna.createDatasetSequence();
832     protein.createDatasetSequence();
833     AlignedCodonFrame acf = new AlignedCodonFrame();
834     MapList map = new MapList(new int[] { 8, 16 }, new int[] { 5, 7 }, 3,
835             1);
836     acf.addMap(dna.getDatasetSequence(), protein.getDatasetSequence(), map);
837     List<AlignedCodonFrame> mappings = new ArrayList<>();
838     mappings.add(acf);
839
840     AlignmentI prot = new Alignment(new SequenceI[] { protein });
841     prot.setCodonFrames(mappings);
842     AlignmentI nuc = new Alignment(new SequenceI[] { dna });
843
844     /*
845      * construct and perform the edit command to turn "-T-AS" in to "-T-A--S"
846      * i.e. insert two gaps at column 4
847      */
848     EditCommand ec = new EditCommand();
849     final Edit edit = ec.new Edit(Action.INSERT_GAP,
850             new SequenceI[]
851             { protein }, 4, 2, '-');
852     ec.appendEdit(edit, prot, true, null);
853
854     /*
855      * the mapped edit command should be to insert 6 gaps before base 4 in the
856      * nucleotide sequence, which corresponds to aligned column 12 in the dna
857      */
858     EditCommand mappedEdit = MappingUtils.mapEditCommand(ec, false, nuc,
859             '-', mappings);
860     assertEquals(1, mappedEdit.getEdits().size());
861     Edit e = mappedEdit.getEdits().get(0);
862     assertEquals(1, e.getSequences().length);
863     assertEquals(dna, e.getSequences()[0]);
864     assertEquals(12, e.getPosition());
865     assertEquals(6, e.getNumber());
866   }
867
868   /**
869    * Tests for the method that converts a series of [start, end] ranges to
870    * single positions, where the mapping is to a reverse strand i.e. start is
871    * greater than end point mapped to
872    */
873   @Test(groups = { "Functional" })
874   public void testFlattenRanges_reverseStrand()
875   {
876     assertEquals("[4, 3, 2, 1]",
877             Arrays.toString(MappingUtils.flattenRanges(new int[]
878             { 4, 1 })));
879     assertEquals("[4, 3, 2, 1]",
880             Arrays.toString(MappingUtils.flattenRanges(new int[]
881             { 4, 3, 2, 1 })));
882     assertEquals("[4, 3, 2, 1]",
883             Arrays.toString(MappingUtils.flattenRanges(new int[]
884             { 4, 4, 3, 3, 2, 2, 1, 1 })));
885     assertEquals("[12, 9, 8, 7, 4, 3, 2, 1]",
886             Arrays.toString(MappingUtils.flattenRanges(new int[]
887             { 12, 12, 9, 7, 4, 1 })));
888     // forwards and backwards anyone?
889     assertEquals("[4, 5, 6, 3, 2, 1]",
890             Arrays.toString(MappingUtils.flattenRanges(new int[]
891             { 4, 6, 3, 1 })));
892     // backwards and forwards
893     assertEquals("[3, 2, 1, 4, 5, 6]",
894             Arrays.toString(MappingUtils.flattenRanges(new int[]
895             { 3, 1, 4, 6 })));
896     // trailing unpaired start position is ignored:
897     assertEquals("[12, 9, 8, 7, 4, 3, 2]",
898             Arrays.toString(MappingUtils.flattenRanges(new int[]
899             { 12, 12, 9, 7, 4, 2, 1 })));
900   }
901
902   /**
903    * Test mapping a column selection including hidden columns
904    * 
905    * @throws IOException
906    */
907   @Test(groups = { "Functional" })
908   public void testMapColumnSelection_hiddenColumns() throws IOException
909   {
910     setupMappedAlignments();
911
912     ColumnSelection proteinSelection = new ColumnSelection();
913     HiddenColumns hiddenCols = new HiddenColumns();
914
915     /*
916      * Column 0 in protein picks up Seq2/L, Seq3/G which map to cols 0-4 and 0-3
917      * in dna respectively, overall 0-4
918      */
919     proteinSelection.hideSelectedColumns(0, hiddenCols);
920     ColumnSelection dnaSelection = new ColumnSelection();
921     HiddenColumns dnaHidden = new HiddenColumns();
922     MappingUtils.mapColumnSelection(proteinSelection, hiddenCols,
923             proteinView, dnaView, dnaSelection, dnaHidden);
924     assertEquals("[]", dnaSelection.getSelected().toString());
925     Iterator<int[]> regions = dnaHidden.iterator();
926     assertEquals(1, dnaHidden.getNumberOfRegions());
927     assertEquals("[0, 4]", Arrays.toString(regions.next()));
928
929     /*
930      * Column 1 in protein picks up Seq1/K which maps to cols 0-3 in dna
931      */
932     dnaSelection = new ColumnSelection();
933     dnaHidden = new HiddenColumns();
934     hiddenCols.revealAllHiddenColumns(proteinSelection);
935     // the unhidden columns are now marked selected!
936     assertEquals("[0]", proteinSelection.getSelected().toString());
937     // deselect these or hideColumns will be expanded to include 0
938     proteinSelection.clear();
939     proteinSelection.hideSelectedColumns(1, hiddenCols);
940     MappingUtils.mapColumnSelection(proteinSelection, hiddenCols,
941             proteinView, dnaView, dnaSelection, dnaHidden);
942     regions = dnaHidden.iterator();
943     assertEquals(1, dnaHidden.getNumberOfRegions());
944     assertEquals("[0, 3]", Arrays.toString(regions.next()));
945
946     /*
947      * Column 2 in protein picks up gaps only - no mapping
948      */
949     dnaSelection = new ColumnSelection();
950     dnaHidden = new HiddenColumns();
951     hiddenCols.revealAllHiddenColumns(proteinSelection);
952     proteinSelection.clear();
953     proteinSelection.hideSelectedColumns(2, hiddenCols);
954     MappingUtils.mapColumnSelection(proteinSelection, hiddenCols,
955             proteinView, dnaView, dnaSelection, dnaHidden);
956     assertEquals(0, dnaHidden.getNumberOfRegions());
957
958     /*
959      * Column 3 in protein picks up Seq1/P, Seq2/Q, Seq3/S which map to columns
960      * 6-9, 6-10, 5-8 respectively, overall to 5-10
961      */
962     dnaSelection = new ColumnSelection();
963     dnaHidden = new HiddenColumns();
964     hiddenCols.revealAllHiddenColumns(proteinSelection);
965     proteinSelection.clear();
966     proteinSelection.hideSelectedColumns(3, hiddenCols); // 5-10 hidden in dna
967     proteinSelection.addElement(1); // 0-3 selected in dna
968     MappingUtils.mapColumnSelection(proteinSelection, hiddenCols,
969             proteinView, dnaView, dnaSelection, dnaHidden);
970     assertEquals("[0, 1, 2, 3]", dnaSelection.getSelected().toString());
971     regions = dnaHidden.iterator();
972     assertEquals(1, dnaHidden.getNumberOfRegions());
973     assertEquals("[5, 10]", Arrays.toString(regions.next()));
974
975     /*
976      * Combine hiding columns 1 and 3 to get discontiguous hidden columns
977      */
978     dnaSelection = new ColumnSelection();
979     dnaHidden = new HiddenColumns();
980     hiddenCols.revealAllHiddenColumns(proteinSelection);
981     proteinSelection.clear();
982     proteinSelection.hideSelectedColumns(1, hiddenCols);
983     proteinSelection.hideSelectedColumns(3, hiddenCols);
984     MappingUtils.mapColumnSelection(proteinSelection, hiddenCols,
985             proteinView, dnaView, dnaSelection, dnaHidden);
986     regions = dnaHidden.iterator();
987     assertEquals(2, dnaHidden.getNumberOfRegions());
988     assertEquals("[0, 3]", Arrays.toString(regions.next()));
989     assertEquals("[5, 10]", Arrays.toString(regions.next()));
990   }
991
992   @Test(groups = { "Functional" })
993   public void testGetLength()
994   {
995     assertEquals(0, MappingUtils.getLength(null));
996
997     /*
998      * [start, end] ranges
999      */
1000     List<int[]> ranges = new ArrayList<>();
1001     assertEquals(0, MappingUtils.getLength(ranges));
1002     ranges.add(new int[] { 1, 1 });
1003     assertEquals(1, MappingUtils.getLength(ranges));
1004     ranges.add(new int[] { 2, 10 });
1005     assertEquals(10, MappingUtils.getLength(ranges));
1006     ranges.add(new int[] { 20, 10 });
1007     assertEquals(21, MappingUtils.getLength(ranges));
1008
1009     /*
1010      * [start, end, start, end...] ranges
1011      */
1012     ranges.clear();
1013     ranges.add(new int[] { 1, 5, 8, 4 });
1014     ranges.add(new int[] { 8, 2 });
1015     ranges.add(new int[] { 12, 12 });
1016     assertEquals(18, MappingUtils.getLength(ranges));
1017   }
1018
1019   @Test(groups = { "Functional" })
1020   public void testContains()
1021   {
1022     assertFalse(MappingUtils.contains(null, 1));
1023     List<int[]> ranges = new ArrayList<>();
1024     assertFalse(MappingUtils.contains(ranges, 1));
1025
1026     ranges.add(new int[] { 1, 4 });
1027     ranges.add(new int[] { 6, 6 });
1028     ranges.add(new int[] { 8, 10 });
1029     ranges.add(new int[] { 30, 20 });
1030     ranges.add(new int[] { -16, -44 });
1031
1032     assertFalse(MappingUtils.contains(ranges, 0));
1033     assertTrue(MappingUtils.contains(ranges, 1));
1034     assertTrue(MappingUtils.contains(ranges, 2));
1035     assertTrue(MappingUtils.contains(ranges, 3));
1036     assertTrue(MappingUtils.contains(ranges, 4));
1037     assertFalse(MappingUtils.contains(ranges, 5));
1038
1039     assertTrue(MappingUtils.contains(ranges, 6));
1040     assertFalse(MappingUtils.contains(ranges, 7));
1041
1042     assertTrue(MappingUtils.contains(ranges, 8));
1043     assertTrue(MappingUtils.contains(ranges, 9));
1044     assertTrue(MappingUtils.contains(ranges, 10));
1045
1046     assertFalse(MappingUtils.contains(ranges, 31));
1047     assertTrue(MappingUtils.contains(ranges, 30));
1048     assertTrue(MappingUtils.contains(ranges, 29));
1049     assertTrue(MappingUtils.contains(ranges, 20));
1050     assertFalse(MappingUtils.contains(ranges, 19));
1051
1052     assertFalse(MappingUtils.contains(ranges, -15));
1053     assertTrue(MappingUtils.contains(ranges, -16));
1054     assertTrue(MappingUtils.contains(ranges, -44));
1055     assertFalse(MappingUtils.contains(ranges, -45));
1056   }
1057
1058   /**
1059    * Test the method that drops positions from the start of a mapped range
1060    */
1061   @Test(groups = "Functional")
1062   public void testRemoveStartPositions()
1063   {
1064     int[] ranges = new int[] { 1, 10 };
1065     int[] adjusted = MappingUtils.removeStartPositions(0, ranges);
1066     assertEquals("[1, 10]", Arrays.toString(adjusted));
1067
1068     adjusted = MappingUtils.removeStartPositions(1, ranges);
1069     assertEquals("[2, 10]", Arrays.toString(adjusted));
1070     assertEquals("[1, 10]", Arrays.toString(ranges));
1071
1072     ranges = adjusted;
1073     adjusted = MappingUtils.removeStartPositions(1, ranges);
1074     assertEquals("[3, 10]", Arrays.toString(adjusted));
1075     assertEquals("[2, 10]", Arrays.toString(ranges));
1076
1077     ranges = new int[] { 2, 3, 10, 12 };
1078     adjusted = MappingUtils.removeStartPositions(1, ranges);
1079     assertEquals("[3, 3, 10, 12]", Arrays.toString(adjusted));
1080     assertEquals("[2, 3, 10, 12]", Arrays.toString(ranges));
1081
1082     ranges = new int[] { 2, 2, 8, 12 };
1083     adjusted = MappingUtils.removeStartPositions(1, ranges);
1084     assertEquals("[8, 12]", Arrays.toString(adjusted));
1085     assertEquals("[2, 2, 8, 12]", Arrays.toString(ranges));
1086
1087     ranges = new int[] { 2, 2, 8, 12 };
1088     adjusted = MappingUtils.removeStartPositions(2, ranges);
1089     assertEquals("[9, 12]", Arrays.toString(adjusted));
1090     assertEquals("[2, 2, 8, 12]", Arrays.toString(ranges));
1091
1092     ranges = new int[] { 2, 2, 4, 4, 9, 12 };
1093     adjusted = MappingUtils.removeStartPositions(1, ranges);
1094     assertEquals("[4, 4, 9, 12]", Arrays.toString(adjusted));
1095     assertEquals("[2, 2, 4, 4, 9, 12]", Arrays.toString(ranges));
1096
1097     ranges = new int[] { 2, 2, 4, 4, 9, 12 };
1098     adjusted = MappingUtils.removeStartPositions(2, ranges);
1099     assertEquals("[9, 12]", Arrays.toString(adjusted));
1100     assertEquals("[2, 2, 4, 4, 9, 12]", Arrays.toString(ranges));
1101
1102     ranges = new int[] { 2, 3, 9, 12 };
1103     adjusted = MappingUtils.removeStartPositions(3, ranges);
1104     assertEquals("[10, 12]", Arrays.toString(adjusted));
1105     assertEquals("[2, 3, 9, 12]", Arrays.toString(ranges));
1106   }
1107
1108   /**
1109    * Test the method that drops positions from the start of a mapped range, on
1110    * the reverse strand
1111    */
1112   @Test(groups = "Functional")
1113   public void testRemoveStartPositions_reverseStrand()
1114   {
1115     int[] ranges = new int[] { 10, 1 };
1116     int[] adjusted = MappingUtils.removeStartPositions(0, ranges);
1117     assertEquals("[10, 1]", Arrays.toString(adjusted));
1118     assertEquals("[10, 1]", Arrays.toString(ranges));
1119
1120     ranges = adjusted;
1121     adjusted = MappingUtils.removeStartPositions(1, ranges);
1122     assertEquals("[9, 1]", Arrays.toString(adjusted));
1123     assertEquals("[10, 1]", Arrays.toString(ranges));
1124
1125     ranges = adjusted;
1126     adjusted = MappingUtils.removeStartPositions(1, ranges);
1127     assertEquals("[8, 1]", Arrays.toString(adjusted));
1128     assertEquals("[9, 1]", Arrays.toString(ranges));
1129
1130     ranges = new int[] { 12, 11, 9, 6 };
1131     adjusted = MappingUtils.removeStartPositions(1, ranges);
1132     assertEquals("[11, 11, 9, 6]", Arrays.toString(adjusted));
1133     assertEquals("[12, 11, 9, 6]", Arrays.toString(ranges));
1134
1135     ranges = new int[] { 12, 12, 8, 4 };
1136     adjusted = MappingUtils.removeStartPositions(1, ranges);
1137     assertEquals("[8, 4]", Arrays.toString(adjusted));
1138     assertEquals("[12, 12, 8, 4]", Arrays.toString(ranges));
1139
1140     ranges = new int[] { 12, 12, 8, 4 };
1141     adjusted = MappingUtils.removeStartPositions(2, ranges);
1142     assertEquals("[7, 4]", Arrays.toString(adjusted));
1143     assertEquals("[12, 12, 8, 4]", Arrays.toString(ranges));
1144
1145     ranges = new int[] { 12, 12, 10, 10, 8, 4 };
1146     adjusted = MappingUtils.removeStartPositions(1, ranges);
1147     assertEquals("[10, 10, 8, 4]", Arrays.toString(adjusted));
1148     assertEquals("[12, 12, 10, 10, 8, 4]", Arrays.toString(ranges));
1149
1150     ranges = new int[] { 12, 12, 10, 10, 8, 4 };
1151     adjusted = MappingUtils.removeStartPositions(2, ranges);
1152     assertEquals("[8, 4]", Arrays.toString(adjusted));
1153     assertEquals("[12, 12, 10, 10, 8, 4]", Arrays.toString(ranges));
1154
1155     ranges = new int[] { 12, 11, 8, 4 };
1156     adjusted = MappingUtils.removeStartPositions(3, ranges);
1157     assertEquals("[7, 4]", Arrays.toString(adjusted));
1158     assertEquals("[12, 11, 8, 4]", Arrays.toString(ranges));
1159   }
1160
1161   @Test(groups = { "Functional" })
1162   public void testRangeContains()
1163   {
1164     /*
1165      * both forward ranges
1166      */
1167     assertTrue(
1168             MappingUtils.rangeContains(new int[]
1169             { 1, 10 }, new int[] { 1, 10 }));
1170     assertTrue(
1171             MappingUtils.rangeContains(new int[]
1172             { 1, 10 }, new int[] { 2, 10 }));
1173     assertTrue(
1174             MappingUtils.rangeContains(new int[]
1175             { 1, 10 }, new int[] { 1, 9 }));
1176     assertTrue(
1177             MappingUtils.rangeContains(new int[]
1178             { 1, 10 }, new int[] { 4, 5 }));
1179     assertFalse(
1180             MappingUtils.rangeContains(new int[]
1181             { 1, 10 }, new int[] { 0, 9 }));
1182     assertFalse(
1183             MappingUtils.rangeContains(new int[]
1184             { 1, 10 }, new int[] { -10, -9 }));
1185     assertFalse(
1186             MappingUtils.rangeContains(new int[]
1187             { 1, 10 }, new int[] { 1, 11 }));
1188     assertFalse(
1189             MappingUtils.rangeContains(new int[]
1190             { 1, 10 }, new int[] { 11, 12 }));
1191
1192     /*
1193      * forward range, reverse query
1194      */
1195     assertTrue(
1196             MappingUtils.rangeContains(new int[]
1197             { 1, 10 }, new int[] { 10, 1 }));
1198     assertTrue(
1199             MappingUtils.rangeContains(new int[]
1200             { 1, 10 }, new int[] { 9, 1 }));
1201     assertTrue(
1202             MappingUtils.rangeContains(new int[]
1203             { 1, 10 }, new int[] { 10, 2 }));
1204     assertTrue(
1205             MappingUtils.rangeContains(new int[]
1206             { 1, 10 }, new int[] { 5, 5 }));
1207     assertFalse(
1208             MappingUtils.rangeContains(new int[]
1209             { 1, 10 }, new int[] { 11, 1 }));
1210     assertFalse(
1211             MappingUtils.rangeContains(new int[]
1212             { 1, 10 }, new int[] { 10, 0 }));
1213
1214     /*
1215      * reverse range, forward query
1216      */
1217     assertTrue(
1218             MappingUtils.rangeContains(new int[]
1219             { 10, 1 }, new int[] { 1, 10 }));
1220     assertTrue(
1221             MappingUtils.rangeContains(new int[]
1222             { 10, 1 }, new int[] { 1, 9 }));
1223     assertTrue(
1224             MappingUtils.rangeContains(new int[]
1225             { 10, 1 }, new int[] { 2, 10 }));
1226     assertTrue(
1227             MappingUtils.rangeContains(new int[]
1228             { 10, 1 }, new int[] { 6, 6 }));
1229     assertFalse(
1230             MappingUtils.rangeContains(new int[]
1231             { 10, 1 }, new int[] { 6, 11 }));
1232     assertFalse(
1233             MappingUtils.rangeContains(new int[]
1234             { 10, 1 }, new int[] { 11, 20 }));
1235     assertFalse(
1236             MappingUtils.rangeContains(new int[]
1237             { 10, 1 }, new int[] { -3, -2 }));
1238
1239     /*
1240      * both reverse
1241      */
1242     assertTrue(
1243             MappingUtils.rangeContains(new int[]
1244             { 10, 1 }, new int[] { 10, 1 }));
1245     assertTrue(
1246             MappingUtils.rangeContains(new int[]
1247             { 10, 1 }, new int[] { 9, 1 }));
1248     assertTrue(
1249             MappingUtils.rangeContains(new int[]
1250             { 10, 1 }, new int[] { 10, 2 }));
1251     assertTrue(
1252             MappingUtils.rangeContains(new int[]
1253             { 10, 1 }, new int[] { 3, 3 }));
1254     assertFalse(
1255             MappingUtils.rangeContains(new int[]
1256             { 10, 1 }, new int[] { 11, 1 }));
1257     assertFalse(
1258             MappingUtils.rangeContains(new int[]
1259             { 10, 1 }, new int[] { 10, 0 }));
1260     assertFalse(
1261             MappingUtils.rangeContains(new int[]
1262             { 10, 1 }, new int[] { 12, 11 }));
1263     assertFalse(
1264             MappingUtils.rangeContains(new int[]
1265             { 10, 1 }, new int[] { -5, -8 }));
1266
1267     /*
1268      * bad arguments
1269      */
1270     assertFalse(
1271             MappingUtils.rangeContains(new int[]
1272             { 1, 10, 12 }, new int[] { 1, 10 }));
1273     assertFalse(
1274             MappingUtils.rangeContains(new int[]
1275             { 1, 10 }, new int[] { 1 }));
1276     assertFalse(MappingUtils.rangeContains(new int[] { 1, 10 }, null));
1277     assertFalse(MappingUtils.rangeContains(null, new int[] { 1, 10 }));
1278   }
1279
1280   @Test(groups = "Functional")
1281   public void testRemoveEndPositions()
1282   {
1283     List<int[]> ranges = new ArrayList<>();
1284
1285     /*
1286      * case 1: truncate last range
1287      */
1288     ranges.add(new int[] { 1, 10 });
1289     ranges.add(new int[] { 20, 30 });
1290     MappingUtils.removeEndPositions(5, ranges);
1291     assertEquals(2, ranges.size());
1292     assertEquals(25, ranges.get(1)[1]);
1293
1294     /*
1295      * case 2: remove last range
1296      */
1297     ranges.clear();
1298     ranges.add(new int[] { 1, 10 });
1299     ranges.add(new int[] { 20, 22 });
1300     MappingUtils.removeEndPositions(3, ranges);
1301     assertEquals(1, ranges.size());
1302     assertEquals(10, ranges.get(0)[1]);
1303
1304     /*
1305      * case 3: truncate penultimate range
1306      */
1307     ranges.clear();
1308     ranges.add(new int[] { 1, 10 });
1309     ranges.add(new int[] { 20, 21 });
1310     MappingUtils.removeEndPositions(3, ranges);
1311     assertEquals(1, ranges.size());
1312     assertEquals(9, ranges.get(0)[1]);
1313
1314     /*
1315      * case 4: remove last two ranges
1316      */
1317     ranges.clear();
1318     ranges.add(new int[] { 1, 10 });
1319     ranges.add(new int[] { 20, 20 });
1320     ranges.add(new int[] { 30, 30 });
1321     MappingUtils.removeEndPositions(3, ranges);
1322     assertEquals(1, ranges.size());
1323     assertEquals(9, ranges.get(0)[1]);
1324   }
1325   
1326   @Test(groups = "Functional")
1327   public void testListToArray()
1328   {
1329     List<int[]> ranges = new ArrayList<>();
1330     
1331     int[] result = MappingUtils.listToArray(ranges);
1332     assertEquals(result.length, 0);
1333     ranges.add(new int[] {24, 12});
1334     result = MappingUtils.listToArray(ranges);
1335     assertEquals(result.length, 2);
1336     assertEquals(result[0], 24);
1337     assertEquals(result[1], 12);
1338     ranges.add(new int[] {-7, 30});
1339     result = MappingUtils.listToArray(ranges);
1340     assertEquals(result.length, 4);
1341     assertEquals(result[0], 24);
1342     assertEquals(result[1], 12);
1343     assertEquals(result[2], -7);
1344     assertEquals(result[3], 30);
1345     try
1346     {
1347       MappingUtils.listToArray(null);
1348       fail("Expected exception");
1349     } catch (NullPointerException e)
1350     {
1351       // expected
1352     }
1353   }
1354
1355   /**
1356    * Test mapping a sequence group where sequences in and outside the group
1357    * share a dataset sequence (e.g. alternative CDS for the same gene)
1358    * <p>
1359    * This scenario doesn't arise after JAL-3763 changes, but test left as still valid
1360    * @throws IOException
1361    */
1362   @Test(groups = { "Functional" })
1363   public void testMapSequenceGroup_sharedDataset() throws IOException
1364   {
1365     /*
1366      * Set up dna and protein Seq1/2/3 with mappings (held on the protein
1367      * viewport). CDS sequences share the same 'gene' dataset sequence.
1368      */
1369     SequenceI dna = new Sequence("dna", "aaatttgggcccaaatttgggccc");
1370     SequenceI cds1 = new Sequence("cds1/1-6", "aaattt");
1371     SequenceI cds2 = new Sequence("cds1/4-9", "tttggg");
1372     SequenceI cds3 = new Sequence("cds1/19-24", "gggccc");
1373
1374     cds1.setDatasetSequence(dna);
1375     cds2.setDatasetSequence(dna);
1376     cds3.setDatasetSequence(dna);
1377
1378     SequenceI pep1 = new Sequence("pep1", "KF");
1379     SequenceI pep2 = new Sequence("pep2", "FG");
1380     SequenceI pep3 = new Sequence("pep3", "GP");
1381     pep1.createDatasetSequence();
1382     pep2.createDatasetSequence();
1383     pep3.createDatasetSequence();
1384
1385     /*
1386      * add mappings from coding positions of dna to respective peptides
1387      */
1388     AlignedCodonFrame acf = new AlignedCodonFrame();
1389     acf.addMap(dna, pep1,
1390             new MapList(new int[]
1391             { 1, 6 }, new int[] { 1, 2 }, 3, 1));
1392     acf.addMap(dna, pep2,
1393             new MapList(new int[]
1394             { 4, 9 }, new int[] { 1, 2 }, 3, 1));
1395     acf.addMap(dna, pep3,
1396             new MapList(new int[]
1397             { 19, 24 }, new int[] { 1, 2 }, 3, 1));
1398
1399     List<AlignedCodonFrame> acfList = Arrays
1400             .asList(new AlignedCodonFrame[]
1401             { acf });
1402
1403     AlignmentI cdna = new Alignment(new SequenceI[] { cds1, cds2, cds3 });
1404     AlignmentI protein = new Alignment(
1405             new SequenceI[]
1406             { pep1, pep2, pep3 });
1407     AlignViewportI cdnaView = new AlignViewport(cdna);
1408     AlignViewportI peptideView = new AlignViewport(protein);
1409     protein.setCodonFrames(acfList);
1410
1411     /*
1412      * Select pep1 and pep3 in the protein alignment
1413      */
1414     SequenceGroup sg = new SequenceGroup();
1415     sg.setColourText(true);
1416     sg.setIdColour(Color.GREEN);
1417     sg.setOutlineColour(Color.LIGHT_GRAY);
1418     sg.addSequence(pep1, false);
1419     sg.addSequence(pep3, false);
1420     sg.setEndRes(protein.getWidth() - 1);
1421
1422     /*
1423      * Verify the mapped sequence group in dna is cds1 and cds3
1424      */
1425     SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(sg,
1426             peptideView, cdnaView);
1427     assertTrue(mappedGroup.getColourText());
1428     assertSame(sg.getIdColour(), mappedGroup.getIdColour());
1429     assertSame(sg.getOutlineColour(), mappedGroup.getOutlineColour());
1430     assertEquals(2, mappedGroup.getSequences().size());
1431     assertSame(cds1, mappedGroup.getSequences().get(0));
1432     assertSame(cds3, mappedGroup.getSequences().get(1));
1433     // columns 1-6 selected (0-5 base zero)
1434     assertEquals(0, mappedGroup.getStartRes());
1435     assertEquals(5, mappedGroup.getEndRes());
1436
1437     /*
1438      * Select mapping sequence group from dna to protein
1439      */
1440     sg.clear();
1441     sg.addSequence(cds2, false);
1442     sg.addSequence(cds1, false);
1443     sg.setStartRes(0);
1444     sg.setEndRes(cdna.getWidth() - 1);
1445     mappedGroup = MappingUtils.mapSequenceGroup(sg, cdnaView, peptideView);
1446     assertTrue(mappedGroup.getColourText());
1447     assertSame(sg.getIdColour(), mappedGroup.getIdColour());
1448     assertSame(sg.getOutlineColour(), mappedGroup.getOutlineColour());
1449     assertEquals(2, mappedGroup.getSequences().size());
1450     assertSame(protein.getSequenceAt(1), mappedGroup.getSequences().get(0));
1451     assertSame(protein.getSequenceAt(0), mappedGroup.getSequences().get(1));
1452     assertEquals(0, mappedGroup.getStartRes());
1453     assertEquals(1, mappedGroup.getEndRes()); // two columns
1454   }
1455 }