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