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