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