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