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