JAL-1705 align CDS and peptide products to transcripts
[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.internal.junit.ArrayAsserts.assertArrayEquals;
28
29 import jalview.api.AlignViewportI;
30 import jalview.commands.EditCommand;
31 import jalview.commands.EditCommand.Action;
32 import jalview.commands.EditCommand.Edit;
33 import jalview.datamodel.AlignedCodonFrame;
34 import jalview.datamodel.Alignment;
35 import jalview.datamodel.AlignmentI;
36 import jalview.datamodel.ColumnSelection;
37 import jalview.datamodel.SearchResults;
38 import jalview.datamodel.SearchResults.Match;
39 import jalview.datamodel.Sequence;
40 import jalview.datamodel.SequenceGroup;
41 import jalview.datamodel.SequenceI;
42 import jalview.gui.AlignViewport;
43 import jalview.io.AppletFormatAdapter;
44 import jalview.io.FormatAdapter;
45
46 import java.awt.Color;
47 import java.io.IOException;
48 import java.util.ArrayList;
49 import java.util.Arrays;
50 import java.util.List;
51
52 import org.testng.annotations.Test;
53
54 public class MappingUtilsTest
55 {
56   private AlignViewportI dnaView;
57
58   private AlignViewportI proteinView;
59
60   /**
61    * Simple test of mapping with no intron involved.
62    */
63   @Test(groups = { "Functional" })
64   public void testBuildSearchResults()
65   {
66     final Sequence seq1 = new Sequence("Seq1/5-10", "C-G-TA-GC");
67     seq1.createDatasetSequence();
68
69     final Sequence aseq1 = new Sequence("Seq1/12-13", "-P-R");
70     aseq1.createDatasetSequence();
71
72     /*
73      * Map dna bases 5-10 to protein residues 12-13
74      */
75     AlignedCodonFrame acf = new AlignedCodonFrame();
76     MapList map = new MapList(new int[] { 5, 10 }, new int[] { 12, 13 }, 3,
77             1);
78     acf.addMap(seq1.getDatasetSequence(), aseq1.getDatasetSequence(), map);
79     List<AlignedCodonFrame> acfList = Arrays.asList(new AlignedCodonFrame[]
80     { acf });
81
82     /*
83      * Check protein residue 12 maps to codon 5-7, 13 to codon 8-10
84      */
85     SearchResults sr = MappingUtils.buildSearchResults(aseq1, 12, acfList);
86     assertEquals(1, sr.getResults().size());
87     Match m = sr.getResults().get(0);
88     assertEquals(seq1.getDatasetSequence(), m.getSequence());
89     assertEquals(5, m.getStart());
90     assertEquals(7, m.getEnd());
91     sr = MappingUtils.buildSearchResults(aseq1, 13, acfList);
92     assertEquals(1, sr.getResults().size());
93     m = sr.getResults().get(0);
94     assertEquals(seq1.getDatasetSequence(), m.getSequence());
95     assertEquals(8, m.getStart());
96     assertEquals(10, m.getEnd());
97
98     /*
99      * Check inverse mappings, from codons 5-7, 8-10 to protein 12, 13
100      */
101     for (int i = 5; i < 11; i++)
102     {
103       sr = MappingUtils.buildSearchResults(seq1, i, acfList);
104       assertEquals(1, sr.getResults().size());
105       m = sr.getResults().get(0);
106       assertEquals(aseq1.getDatasetSequence(), m.getSequence());
107       int residue = i > 7 ? 13 : 12;
108       assertEquals(residue, m.getStart());
109       assertEquals(residue, m.getEnd());
110     }
111   }
112
113   /**
114    * Simple test of mapping with introns involved.
115    */
116   @Test(groups = { "Functional" })
117   public void testBuildSearchResults_withIntron()
118   {
119     final Sequence seq1 = new Sequence("Seq1/5-17", "c-G-tAGa-GcAgCtt");
120     seq1.createDatasetSequence();
121
122     final Sequence aseq1 = new Sequence("Seq1/8-9", "-E-D");
123     aseq1.createDatasetSequence();
124
125     /*
126      * Map dna bases [6, 8, 9], [11, 13, 115] to protein residues 8 and 9
127      */
128     AlignedCodonFrame acf = new AlignedCodonFrame();
129     MapList map = new MapList(new int[] { 6, 6, 8, 9, 11, 11, 13, 13, 15,
130         15 }, new int[] { 8, 9 }, 3, 1);
131     acf.addMap(seq1.getDatasetSequence(), aseq1.getDatasetSequence(), map);
132     List<AlignedCodonFrame> acfList = Arrays.asList(new AlignedCodonFrame[]
133     { acf });
134
135     /*
136      * Check protein residue 8 maps to [6, 8, 9]
137      */
138     SearchResults sr = MappingUtils.buildSearchResults(aseq1, 8, acfList);
139     assertEquals(2, sr.getResults().size());
140     Match m = sr.getResults().get(0);
141     assertEquals(seq1.getDatasetSequence(), m.getSequence());
142     assertEquals(6, m.getStart());
143     assertEquals(6, m.getEnd());
144     m = sr.getResults().get(1);
145     assertEquals(seq1.getDatasetSequence(), m.getSequence());
146     assertEquals(8, m.getStart());
147     assertEquals(9, m.getEnd());
148
149     /*
150      * Check protein residue 9 maps to [11, 13, 15]
151      */
152     sr = MappingUtils.buildSearchResults(aseq1, 9, acfList);
153     assertEquals(3, sr.getResults().size());
154     m = sr.getResults().get(0);
155     assertEquals(seq1.getDatasetSequence(), m.getSequence());
156     assertEquals(11, m.getStart());
157     assertEquals(11, m.getEnd());
158     m = sr.getResults().get(1);
159     assertEquals(seq1.getDatasetSequence(), m.getSequence());
160     assertEquals(13, m.getStart());
161     assertEquals(13, m.getEnd());
162     m = sr.getResults().get(2);
163     assertEquals(seq1.getDatasetSequence(), m.getSequence());
164     assertEquals(15, m.getStart());
165     assertEquals(15, m.getEnd());
166
167     /*
168      * Check inverse mappings, from codons to protein
169      */
170     for (int i = 5; i < 18; i++)
171     {
172       sr = MappingUtils.buildSearchResults(seq1, i, acfList);
173       int residue = (i == 6 || i == 8 || i == 9) ? 8 : (i == 11 || i == 13
174               || i == 15 ? 9 : 0);
175       if (residue == 0)
176       {
177         assertEquals(0, sr.getResults().size());
178         continue;
179       }
180       assertEquals(1, sr.getResults().size());
181       m = sr.getResults().get(0);
182       assertEquals(aseq1.getDatasetSequence(), m.getSequence());
183       assertEquals(residue, m.getStart());
184       assertEquals(residue, m.getEnd());
185     }
186   }
187
188   /**
189    * Test mapping a sequence group made of entire sequences.
190    * 
191    * @throws IOException
192    */
193   @Test(groups = { "Functional" })
194   public void testMapSequenceGroup_sequences() throws IOException
195   {
196     /*
197      * Set up dna and protein Seq1/2/3 with mappings (held on the protein
198      * viewport).
199      */
200     AlignmentI cdna = loadAlignment(">Seq1\nACG\n>Seq2\nTGA\n>Seq3\nTAC\n",
201             "FASTA");
202     cdna.setDataset(null);
203     AlignmentI protein = loadAlignment(">Seq1\nK\n>Seq2\nL\n>Seq3\nQ\n",
204             "FASTA");
205     protein.setDataset(null);
206     AlignedCodonFrame acf = new AlignedCodonFrame();
207     MapList map = new MapList(new int[] { 1, 3 }, new int[] { 1, 1 }, 3, 1);
208     for (int seq = 0; seq < 3; seq++)
209     {
210       acf.addMap(cdna.getSequenceAt(seq).getDatasetSequence(), protein
211               .getSequenceAt(seq).getDatasetSequence(), map);
212     }
213     List<AlignedCodonFrame> acfList = Arrays.asList(new AlignedCodonFrame[]
214     { acf });
215
216     AlignViewportI dnaView = new AlignViewport(cdna);
217     AlignViewportI proteinView = new AlignViewport(protein);
218     protein.setCodonFrames(acfList);
219
220     /*
221      * Select Seq1 and Seq3 in the protein (startRes=endRes=0)
222      */
223     SequenceGroup sg = new SequenceGroup();
224     sg.setColourText(true);
225     sg.setIdColour(Color.GREEN);
226     sg.setOutlineColour(Color.LIGHT_GRAY);
227     sg.addSequence(protein.getSequenceAt(0), false);
228     sg.addSequence(protein.getSequenceAt(2), false);
229
230     /*
231      * Verify the mapped sequence group in dna
232      */
233     SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(sg,
234             proteinView, dnaView);
235     assertTrue(mappedGroup.getColourText());
236     assertSame(sg.getIdColour(), mappedGroup.getIdColour());
237     assertSame(sg.getOutlineColour(), mappedGroup.getOutlineColour());
238     assertEquals(2, mappedGroup.getSequences().size());
239     assertSame(cdna.getSequenceAt(0), mappedGroup.getSequences().get(0));
240     assertSame(cdna.getSequenceAt(2), mappedGroup.getSequences().get(1));
241     assertEquals(0, mappedGroup.getStartRes());
242     assertEquals(2, mappedGroup.getEndRes());
243
244     /*
245      * Verify mapping sequence group from dna to protein
246      */
247     sg.clear();
248     sg.addSequence(cdna.getSequenceAt(1), false);
249     sg.addSequence(cdna.getSequenceAt(0), false);
250     sg.setStartRes(0);
251     sg.setEndRes(2);
252     mappedGroup = MappingUtils.mapSequenceGroup(sg, dnaView, proteinView);
253     assertTrue(mappedGroup.getColourText());
254     assertSame(sg.getIdColour(), mappedGroup.getIdColour());
255     assertSame(sg.getOutlineColour(), mappedGroup.getOutlineColour());
256     assertEquals(2, mappedGroup.getSequences().size());
257     assertSame(protein.getSequenceAt(1), mappedGroup.getSequences().get(0));
258     assertSame(protein.getSequenceAt(0), mappedGroup.getSequences().get(1));
259     assertEquals(0, mappedGroup.getStartRes());
260     assertEquals(0, mappedGroup.getEndRes());
261   }
262
263   /**
264    * Helper method to load an alignment and ensure dataset sequences are set up.
265    * 
266    * @param data
267    * @param format
268    *          TODO
269    * @return
270    * @throws IOException
271    */
272   protected AlignmentI loadAlignment(final String data, String format)
273           throws IOException
274   {
275     AlignmentI a = new FormatAdapter().readFile(data,
276             AppletFormatAdapter.PASTE, format);
277     a.setDataset(null);
278     return a;
279   }
280
281   /**
282    * Test mapping a column selection in protein to its dna equivalent
283    * 
284    * @throws IOException
285    */
286   @Test(groups = { "Functional" })
287   public void testMapColumnSelection_proteinToDna() throws IOException
288   {
289     setupMappedAlignments();
290
291     ColumnSelection colsel = new ColumnSelection();
292
293     /*
294      * Column 0 in protein picks up Seq2/L, Seq3/G which map to cols 0-4 and 0-3
295      * in dna respectively, overall 0-4
296      */
297     colsel.addElement(0);
298     ColumnSelection cs = MappingUtils.mapColumnSelection(colsel,
299             proteinView, dnaView);
300     assertEquals("[0, 1, 2, 3, 4]", cs.getSelected().toString());
301
302     /*
303      * Column 1 in protein picks up Seq1/K which maps to cols 0-3 in dna
304      */
305     colsel.clear();
306     colsel.addElement(1);
307     cs = MappingUtils.mapColumnSelection(colsel, proteinView, dnaView);
308     assertEquals("[0, 1, 2, 3]", cs.getSelected().toString());
309
310     /*
311      * Column 2 in protein picks up gaps only - no mapping
312      */
313     colsel.clear();
314     colsel.addElement(2);
315     cs = MappingUtils.mapColumnSelection(colsel, proteinView, dnaView);
316     assertEquals("[]", cs.getSelected().toString());
317
318     /*
319      * Column 3 in protein picks up Seq1/P, Seq2/Q, Seq3/S which map to columns
320      * 6-9, 6-10, 5-8 respectively, overall to 5-10
321      */
322     colsel.clear();
323     colsel.addElement(3);
324     cs = MappingUtils.mapColumnSelection(colsel, proteinView, dnaView);
325     assertEquals("[5, 6, 7, 8, 9, 10]", cs.getSelected().toString());
326
327     /*
328      * Combine selection of columns 1 and 3 to get a discontiguous mapped
329      * selection
330      */
331     colsel.clear();
332     colsel.addElement(1);
333     colsel.addElement(3);
334     cs = MappingUtils.mapColumnSelection(colsel, proteinView, dnaView);
335     assertEquals("[0, 1, 2, 3, 5, 6, 7, 8, 9, 10]", cs.getSelected()
336             .toString());
337   }
338
339   /**
340    * Set up mappings for tests from 3 dna to 3 protein sequences. Sequences have
341    * offset start positions for a more general test case.
342    * 
343    * @throws IOException
344    */
345   protected void setupMappedAlignments() throws IOException
346   {
347     /*
348      * Map (upper-case = coding):
349      * Seq1/10-18 AC-GctGtC-T to Seq1/40 -K-P
350      * Seq2/20-27 Tc-GA-G-T-T to Seq2/20-27 L--Q
351      * Seq3/30-38 TtTT-AaCGg- to Seq3/60-61\nG--S
352      */
353     AlignmentI cdna = loadAlignment(">Seq1/10-18\nAC-GctGtC-T\n"
354             + ">Seq2/20-27\nTc-GA-G-T-Tc\n" + ">Seq3/30-38\nTtTT-AaCGg-\n",
355             "FASTA");
356     cdna.setDataset(null);
357     AlignmentI protein = loadAlignment(
358             ">Seq1/40-41\n-K-P\n>Seq2/50-51\nL--Q\n>Seq3/60-61\nG--S\n",
359             "FASTA");
360     protein.setDataset(null);
361
362     // map first dna to first protein seq
363     AlignedCodonFrame acf = new AlignedCodonFrame();
364     MapList map = new MapList(new int[] { 10, 12, 15, 15, 17, 18 },
365             new int[] { 40, 41 }, 3, 1);
366     acf.addMap(cdna.getSequenceAt(0).getDatasetSequence(), protein
367             .getSequenceAt(0).getDatasetSequence(), map);
368
369     // map second dna to second protein seq
370     map = new MapList(new int[] { 20, 20, 22, 23, 24, 26 }, new int[] { 50,
371         51 }, 3, 1);
372     acf.addMap(cdna.getSequenceAt(1).getDatasetSequence(), protein
373             .getSequenceAt(1).getDatasetSequence(), map);
374
375     // map third dna to third protein seq
376     map = new MapList(new int[] { 30, 30, 32, 34, 36, 37 }, new int[] { 60,
377         61 }, 3, 1);
378     acf.addMap(cdna.getSequenceAt(2).getDatasetSequence(), protein
379             .getSequenceAt(2).getDatasetSequence(), map);
380     List<AlignedCodonFrame> acfList = Arrays.asList(new AlignedCodonFrame[]
381     { acf });
382
383     dnaView = new AlignViewport(cdna);
384     proteinView = new AlignViewport(protein);
385     protein.setCodonFrames(acfList);
386   }
387
388   /**
389    * Test mapping a column selection in dna to its protein equivalent
390    * 
391    * @throws IOException
392    */
393   @Test(groups = { "Functional" })
394   public void testMapColumnSelection_dnaToProtein() throws IOException
395   {
396     setupMappedAlignments();
397
398     ColumnSelection colsel = new ColumnSelection();
399
400     /*
401      * Column 0 in dna picks up first bases which map to residue 1, columns 0-1
402      * in protein.
403      */
404     colsel.addElement(0);
405     ColumnSelection cs = MappingUtils.mapColumnSelection(colsel, dnaView,
406             proteinView);
407     assertEquals("[0, 1]", cs.getSelected().toString());
408
409     /*
410      * Columns 3-5 in dna map to the first residues in protein Seq1, Seq2, and
411      * the first two in Seq3. Overall to columns 0, 1, 3 (col2 is all gaps).
412      */
413     colsel.addElement(3);
414     colsel.addElement(4);
415     colsel.addElement(5);
416     cs = MappingUtils.mapColumnSelection(colsel, dnaView, proteinView);
417     assertEquals("[0, 1, 3]", cs.getSelected().toString());
418   }
419
420   @Test(groups = { "Functional" })
421   public void testMapColumnSelection_null() throws IOException
422   {
423     setupMappedAlignments();
424     ColumnSelection cs = MappingUtils.mapColumnSelection(null, dnaView,
425             proteinView);
426     assertTrue("mapped selection not empty", cs.getSelected().isEmpty());
427   }
428
429   /**
430    * Tests for the method that converts a series of [start, end] ranges to
431    * single positions
432    */
433   @Test(groups = { "Functional" })
434   public void testFlattenRanges()
435   {
436     assertEquals("[1, 2, 3, 4]",
437             Arrays.toString(MappingUtils.flattenRanges(new int[] { 1, 4 })));
438     assertEquals(
439             "[1, 2, 3, 4]",
440             Arrays.toString(MappingUtils.flattenRanges(new int[] { 1, 2, 3,
441                 4 })));
442     assertEquals(
443             "[1, 2, 3, 4]",
444             Arrays.toString(MappingUtils.flattenRanges(new int[] { 1, 1, 2,
445                 2, 3, 3, 4, 4 })));
446     assertEquals(
447             "[1, 2, 3, 4, 7, 8, 9, 12]",
448             Arrays.toString(MappingUtils.flattenRanges(new int[] { 1, 4, 7,
449                 9, 12, 12 })));
450     // trailing unpaired start position is ignored:
451     assertEquals(
452             "[1, 2, 3, 4, 7, 8, 9, 12]",
453             Arrays.toString(MappingUtils.flattenRanges(new int[] { 1, 4, 7,
454                 9, 12, 12, 15 })));
455   }
456
457   /**
458    * Test mapping a sequence group made of entire columns.
459    * 
460    * @throws IOException
461    */
462   @Test(groups = { "Functional" })
463   public void testMapSequenceGroup_columns() throws IOException
464   {
465     /*
466      * Set up dna and protein Seq1/2/3 with mappings (held on the protein
467      * viewport).
468      */
469     AlignmentI cdna = loadAlignment(
470             ">Seq1\nACGGCA\n>Seq2\nTGACAG\n>Seq3\nTACGTA\n", "FASTA");
471     cdna.setDataset(null);
472     AlignmentI protein = loadAlignment(">Seq1\nKA\n>Seq2\nLQ\n>Seq3\nQV\n",
473             "FASTA");
474     protein.setDataset(null);
475     AlignedCodonFrame acf = new AlignedCodonFrame();
476     MapList map = new MapList(new int[] { 1, 6 }, new int[] { 1, 2 }, 3, 1);
477     for (int seq = 0; seq < 3; seq++)
478     {
479       acf.addMap(cdna.getSequenceAt(seq).getDatasetSequence(), protein
480               .getSequenceAt(seq).getDatasetSequence(), map);
481     }
482     List<AlignedCodonFrame> acfList = Arrays.asList(new AlignedCodonFrame[]
483     { acf });
484
485     AlignViewportI dnaView = new AlignViewport(cdna);
486     AlignViewportI proteinView = new AlignViewport(protein);
487     protein.setCodonFrames(acfList);
488
489     /*
490      * Select all sequences, column 2 in the protein
491      */
492     SequenceGroup sg = new SequenceGroup();
493     sg.setColourText(true);
494     sg.setIdColour(Color.GREEN);
495     sg.setOutlineColour(Color.LIGHT_GRAY);
496     sg.addSequence(protein.getSequenceAt(0), false);
497     sg.addSequence(protein.getSequenceAt(1), false);
498     sg.addSequence(protein.getSequenceAt(2), false);
499     sg.setStartRes(1);
500     sg.setEndRes(1);
501
502     /*
503      * Verify the mapped sequence group in dna
504      */
505     SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(sg,
506             proteinView, dnaView);
507     assertTrue(mappedGroup.getColourText());
508     assertSame(sg.getIdColour(), mappedGroup.getIdColour());
509     assertSame(sg.getOutlineColour(), mappedGroup.getOutlineColour());
510     assertEquals(3, mappedGroup.getSequences().size());
511     assertSame(cdna.getSequenceAt(0), mappedGroup.getSequences().get(0));
512     assertSame(cdna.getSequenceAt(1), mappedGroup.getSequences().get(1));
513     assertSame(cdna.getSequenceAt(2), mappedGroup.getSequences().get(2));
514     assertEquals(3, mappedGroup.getStartRes());
515     assertEquals(5, mappedGroup.getEndRes());
516
517     /*
518      * Verify mapping sequence group from dna to protein
519      */
520     sg.clear();
521     sg.addSequence(cdna.getSequenceAt(0), false);
522     sg.addSequence(cdna.getSequenceAt(1), false);
523     sg.addSequence(cdna.getSequenceAt(2), false);
524     // select columns 2 and 3 in DNA which span protein columns 0 and 1
525     sg.setStartRes(2);
526     sg.setEndRes(3);
527     mappedGroup = MappingUtils.mapSequenceGroup(sg, dnaView, proteinView);
528     assertTrue(mappedGroup.getColourText());
529     assertSame(sg.getIdColour(), mappedGroup.getIdColour());
530     assertSame(sg.getOutlineColour(), mappedGroup.getOutlineColour());
531     assertEquals(3, mappedGroup.getSequences().size());
532     assertSame(protein.getSequenceAt(0), mappedGroup.getSequences().get(0));
533     assertSame(protein.getSequenceAt(1), mappedGroup.getSequences().get(1));
534     assertSame(protein.getSequenceAt(2), mappedGroup.getSequences().get(2));
535     assertEquals(0, mappedGroup.getStartRes());
536     assertEquals(1, mappedGroup.getEndRes());
537   }
538
539   /**
540    * Test mapping a sequence group made of a sequences/columns region.
541    * 
542    * @throws IOException
543    */
544   @Test(groups = { "Functional" })
545   public void testMapSequenceGroup_region() throws IOException
546   {
547     /*
548      * Set up gapped dna and protein Seq1/2/3 with mappings (held on the protein
549      * viewport).
550      */
551     AlignmentI cdna = loadAlignment(
552             ">Seq1\nA-CG-GC--AT-CA\n>Seq2\n-TG-AC-AG-T-AT\n>Seq3\n-T--ACG-TAAT-G\n",
553             "FASTA");
554     cdna.setDataset(null);
555     AlignmentI protein = loadAlignment(
556             ">Seq1\n-KA-S\n>Seq2\n--L-QY\n>Seq3\nQ-V-M\n", "FASTA");
557     protein.setDataset(null);
558     AlignedCodonFrame acf = new AlignedCodonFrame();
559     MapList map = new MapList(new int[] { 1, 9 }, new int[] { 1, 3 }, 3, 1);
560     for (int seq = 0; seq < 3; seq++)
561     {
562       acf.addMap(cdna.getSequenceAt(seq).getDatasetSequence(), protein
563               .getSequenceAt(seq).getDatasetSequence(), map);
564     }
565     List<AlignedCodonFrame> acfList = Arrays.asList(new AlignedCodonFrame[]
566     { acf });
567
568     AlignViewportI dnaView = new AlignViewport(cdna);
569     AlignViewportI proteinView = new AlignViewport(protein);
570     protein.setCodonFrames(acfList);
571
572     /*
573      * Select Seq1 and Seq2 in the protein, column 1 (K/-). Expect mapped
574      * sequence group to cover Seq1, columns 0-3 (ACG). Because the selection
575      * only includes a gap in Seq2 there is no mappable selection region in the
576      * corresponding DNA.
577      */
578     SequenceGroup sg = new SequenceGroup();
579     sg.setColourText(true);
580     sg.setIdColour(Color.GREEN);
581     sg.setOutlineColour(Color.LIGHT_GRAY);
582     sg.addSequence(protein.getSequenceAt(0), false);
583     sg.addSequence(protein.getSequenceAt(1), false);
584     sg.setStartRes(1);
585     sg.setEndRes(1);
586
587     /*
588      * Verify the mapped sequence group in dna
589      */
590     SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(sg,
591             proteinView, dnaView);
592     assertTrue(mappedGroup.getColourText());
593     assertSame(sg.getIdColour(), mappedGroup.getIdColour());
594     assertSame(sg.getOutlineColour(), mappedGroup.getOutlineColour());
595     assertEquals(1, mappedGroup.getSequences().size());
596     assertSame(cdna.getSequenceAt(0), mappedGroup.getSequences().get(0));
597     // Seq2 in protein has a gap in column 1 - ignored
598     // Seq1 has K which should map to columns 0-3 in Seq1
599     assertEquals(0, mappedGroup.getStartRes());
600     assertEquals(3, mappedGroup.getEndRes());
601
602     /*
603      * Now select cols 2-4 in protein. These cover Seq1:AS Seq2:LQ Seq3:VM which
604      * extend over DNA columns 3-12, 1-7, 6-13 respectively, or 1-13 overall.
605      */
606     sg.setStartRes(2);
607     sg.setEndRes(4);
608     mappedGroup = MappingUtils.mapSequenceGroup(sg, proteinView, dnaView);
609     assertEquals(1, mappedGroup.getStartRes());
610     assertEquals(13, mappedGroup.getEndRes());
611
612     /*
613      * Verify mapping sequence group from dna to protein
614      */
615     sg.clear();
616     sg.addSequence(cdna.getSequenceAt(0), false);
617
618     // select columns 4,5 - includes Seq1:codon2 (A) only
619     sg.setStartRes(4);
620     sg.setEndRes(5);
621     mappedGroup = MappingUtils.mapSequenceGroup(sg, dnaView, proteinView);
622     assertEquals(2, mappedGroup.getStartRes());
623     assertEquals(2, mappedGroup.getEndRes());
624
625     // add Seq2 to dna selection cols 4-5 include codons 1 and 2 (LQ)
626     sg.addSequence(cdna.getSequenceAt(1), false);
627     mappedGroup = MappingUtils.mapSequenceGroup(sg, dnaView, proteinView);
628     assertEquals(2, mappedGroup.getStartRes());
629     assertEquals(4, mappedGroup.getEndRes());
630
631     // add Seq3 to dna selection cols 4-5 include codon 1 (Q)
632     sg.addSequence(cdna.getSequenceAt(2), false);
633     mappedGroup = MappingUtils.mapSequenceGroup(sg, dnaView, proteinView);
634     assertEquals(0, mappedGroup.getStartRes());
635     assertEquals(4, mappedGroup.getEndRes());
636   }
637
638   @Test(groups = { "Functional" })
639   public void testFindMappingsForSequence()
640   {
641     SequenceI seq1 = new Sequence("Seq1", "ABC");
642     SequenceI seq2 = new Sequence("Seq2", "ABC");
643     SequenceI seq3 = new Sequence("Seq3", "ABC");
644     SequenceI seq4 = new Sequence("Seq4", "ABC");
645     seq1.createDatasetSequence();
646     seq2.createDatasetSequence();
647     seq3.createDatasetSequence();
648     seq4.createDatasetSequence();
649
650     /*
651      * Create mappings from seq1 to seq2, seq2 to seq1, seq3 to seq1
652      */
653     AlignedCodonFrame acf1 = new AlignedCodonFrame();
654     MapList map = new MapList(new int[] { 1, 3 }, new int[] { 1, 3 }, 1, 1);
655     acf1.addMap(seq1.getDatasetSequence(), seq2.getDatasetSequence(), map);
656     AlignedCodonFrame acf2 = new AlignedCodonFrame();
657     acf2.addMap(seq2.getDatasetSequence(), seq1.getDatasetSequence(), map);
658     AlignedCodonFrame acf3 = new AlignedCodonFrame();
659     acf3.addMap(seq3.getDatasetSequence(), seq1.getDatasetSequence(), map);
660
661     List<AlignedCodonFrame> mappings = new ArrayList<AlignedCodonFrame>();
662     mappings.add(acf1);
663     mappings.add(acf2);
664     mappings.add(acf3);
665
666     /*
667      * Seq1 has three mappings
668      */
669     List<AlignedCodonFrame> result = MappingUtils.findMappingsForSequence(
670             seq1, mappings);
671     assertEquals(3, result.size());
672     assertTrue(result.contains(acf1));
673     assertTrue(result.contains(acf2));
674     assertTrue(result.contains(acf3));
675
676     /*
677      * Seq2 has two mappings
678      */
679     result = MappingUtils.findMappingsForSequence(seq2, mappings);
680     assertEquals(2, result.size());
681     assertTrue(result.contains(acf1));
682     assertTrue(result.contains(acf2));
683
684     /*
685      * Seq3 has one mapping
686      */
687     result = MappingUtils.findMappingsForSequence(seq3, mappings);
688     assertEquals(1, result.size());
689     assertTrue(result.contains(acf3));
690
691     /*
692      * Seq4 has no mappings
693      */
694     result = MappingUtils.findMappingsForSequence(seq4, mappings);
695     assertEquals(0, result.size());
696
697     result = MappingUtils.findMappingsForSequence(null, mappings);
698     assertEquals(0, result.size());
699
700     result = MappingUtils.findMappingsForSequence(seq1, null);
701     assertEquals(0, result.size());
702
703     result = MappingUtils.findMappingsForSequence(null, null);
704     assertEquals(0, result.size());
705   }
706
707   @Test(groups = { "Functional" })
708   public void testMapEditCommand()
709   {
710     SequenceI dna = new Sequence("Seq1", "---ACG---GCATCA", 8, 16);
711     SequenceI protein = new Sequence("Seq2", "-T-AS", 5, 7);
712     dna.createDatasetSequence();
713     protein.createDatasetSequence();
714     AlignedCodonFrame acf = new AlignedCodonFrame();
715     MapList map = new MapList(new int[] { 8, 16 }, new int[] { 5, 7 }, 3, 1);
716     acf.addMap(dna.getDatasetSequence(), protein.getDatasetSequence(), map);
717     List<AlignedCodonFrame> mappings = new ArrayList<AlignedCodonFrame>();
718     mappings.add(acf);
719
720     AlignmentI prot = new Alignment(new SequenceI[] { protein });
721     prot.setCodonFrames(mappings);
722     AlignmentI nuc = new Alignment(new SequenceI[] { dna });
723
724     /*
725      * construct and perform the edit command to turn "-T-AS" in to "-T-A--S"
726      * i.e. insert two gaps at column 4
727      */
728     EditCommand ec = new EditCommand();
729     final Edit edit = ec.new Edit(Action.INSERT_GAP,
730             new SequenceI[] { protein }, 4, 2, '-');
731     ec.appendEdit(edit, prot, true, null);
732
733     /*
734      * the mapped edit command should be to insert 6 gaps before base 4 in the
735      * nucleotide sequence, which corresponds to aligned column 12 in the dna
736      */
737     EditCommand mappedEdit = MappingUtils.mapEditCommand(ec, false, nuc,
738             '-', mappings);
739     assertEquals(1, mappedEdit.getEdits().size());
740     Edit e = mappedEdit.getEdits().get(0);
741     assertEquals(1, e.getSequences().length);
742     assertEquals(dna, e.getSequences()[0]);
743     assertEquals(12, e.getPosition());
744     assertEquals(6, e.getNumber());
745   }
746
747   /**
748    * Tests for the method that converts a series of [start, end] ranges to
749    * single positions, where the mapping is to a reverse strand i.e. start is
750    * greater than end point mapped to
751    */
752   @Test(groups = { "Functional" })
753   public void testFlattenRanges_reverseStrand()
754   {
755     assertEquals("[4, 3, 2, 1]",
756             Arrays.toString(MappingUtils.flattenRanges(new int[] { 4, 1 })));
757     assertEquals(
758             "[4, 3, 2, 1]",
759             Arrays.toString(MappingUtils.flattenRanges(new int[] { 4, 3, 2,
760                 1 })));
761     assertEquals(
762             "[4, 3, 2, 1]",
763             Arrays.toString(MappingUtils.flattenRanges(new int[] { 4, 4, 3,
764                 3, 2, 2, 1, 1 })));
765     assertEquals(
766             "[12, 9, 8, 7, 4, 3, 2, 1]",
767             Arrays.toString(MappingUtils.flattenRanges(new int[] { 12, 12,
768                 9, 7, 4, 1 })));
769     // forwards and backwards anyone?
770     assertEquals(
771             "[4, 5, 6, 3, 2, 1]",
772             Arrays.toString(MappingUtils.flattenRanges(new int[] { 4, 6, 3,
773                 1 })));
774     // backwards and forwards
775     assertEquals(
776             "[3, 2, 1, 4, 5, 6]",
777             Arrays.toString(MappingUtils.flattenRanges(new int[] { 3, 1, 4,
778                 6 })));
779     // trailing unpaired start position is ignored:
780     assertEquals(
781             "[12, 9, 8, 7, 4, 3, 2]",
782             Arrays.toString(MappingUtils.flattenRanges(new int[] { 12, 12,
783                 9, 7, 4, 2, 1 })));
784   }
785
786   /**
787    * Test mapping a column selection including hidden columns
788    * 
789    * @throws IOException
790    */
791   @Test(groups = { "Functional" })
792   public void testMapColumnSelection_hiddenColumns() throws IOException
793   {
794     setupMappedAlignments();
795   
796     ColumnSelection proteinSelection = new ColumnSelection();
797
798     /*
799      * Column 0 in protein picks up Seq2/L, Seq3/G which map to cols 0-4 and 0-3
800      * in dna respectively, overall 0-4
801      */
802     proteinSelection.hideColumns(0);
803     ColumnSelection dnaSelection = MappingUtils.mapColumnSelection(proteinSelection,
804             proteinView, dnaView);
805     assertEquals("[]", dnaSelection.getSelected().toString());
806     List<int[]> hidden = dnaSelection.getHiddenColumns();
807     assertEquals(1, hidden.size());
808     assertEquals("[0, 4]", Arrays.toString(hidden.get(0)));
809
810     /*
811      * Column 1 in protein picks up Seq1/K which maps to cols 0-3 in dna
812      */
813     proteinSelection.revealAllHiddenColumns();
814     // the unhidden columns are now marked selected!
815     assertEquals("[0]", proteinSelection.getSelected().toString());
816     // deselect these or hideColumns will be expanded to include 0
817     proteinSelection.clear();
818     proteinSelection.hideColumns(1);
819     dnaSelection = MappingUtils.mapColumnSelection(proteinSelection, proteinView, dnaView);
820     hidden = dnaSelection.getHiddenColumns();
821     assertEquals(1, hidden.size());
822     assertEquals("[0, 3]", Arrays.toString(hidden.get(0)));
823
824     /*
825      * Column 2 in protein picks up gaps only - no mapping
826      */
827     proteinSelection.revealAllHiddenColumns();
828     proteinSelection.clear();
829     proteinSelection.hideColumns(2);
830     dnaSelection = MappingUtils.mapColumnSelection(proteinSelection, proteinView, dnaView);
831     assertTrue(dnaSelection.getHiddenColumns().isEmpty());
832
833     /*
834      * Column 3 in protein picks up Seq1/P, Seq2/Q, Seq3/S which map to columns
835      * 6-9, 6-10, 5-8 respectively, overall to 5-10
836      */
837     proteinSelection.revealAllHiddenColumns();
838     proteinSelection.clear();
839     proteinSelection.hideColumns(3); // 5-10 hidden in dna
840     proteinSelection.addElement(1); // 0-3 selected in dna
841     dnaSelection = MappingUtils.mapColumnSelection(proteinSelection, proteinView, dnaView);
842     assertEquals("[0, 1, 2, 3]", dnaSelection.getSelected().toString());
843     hidden = dnaSelection.getHiddenColumns();
844     assertEquals(1, hidden.size());
845     assertEquals("[5, 10]", Arrays.toString(hidden.get(0)));
846
847     /*
848      * Combine hiding columns 1 and 3 to get discontiguous hidden columns
849      */
850     proteinSelection.revealAllHiddenColumns();
851     proteinSelection.clear();
852     proteinSelection.hideColumns(1);
853     proteinSelection.hideColumns(3);
854     dnaSelection = MappingUtils.mapColumnSelection(proteinSelection, proteinView, dnaView);
855     hidden = dnaSelection.getHiddenColumns();
856     assertEquals(2, hidden.size());
857     assertEquals("[0, 3]", Arrays.toString(hidden.get(0)));
858     assertEquals("[5, 10]", Arrays.toString(hidden.get(1)));
859   }
860
861   /**
862    * Tests for the method that removes the trailing stop codon from a mapping
863    * range i.e. the last 3 positions (whether split or not)
864    */
865   @Test(groups = { "Functional" })
866   public void testUnmapStopCodon()
867   {
868     List<int[]> ranges = new ArrayList<int[]>();
869
870     // simple case, forward strand:
871     ranges.add(new int[] { 1, 3 });
872     ranges.add(new int[] { 9, 14 });
873     MappingUtils.unmapStopCodon(ranges, 9);
874     assertEquals(2, ranges.size());
875     assertArrayEquals(new int[] { 1, 3 }, ranges.get(0));
876     assertArrayEquals(new int[] { 9, 11 }, ranges.get(1));
877
878     // split stop codon, forward strand:
879     ranges.clear();
880     ranges.add(new int[] { 1, 8 });
881     ranges.add(new int[] { 10, 10 });
882     MappingUtils.unmapStopCodon(ranges, 9);
883     assertEquals(1, ranges.size());
884     assertArrayEquals(new int[] { 1, 6 }, ranges.get(0));
885
886     // very split stop codon, forward strand:
887     ranges.clear();
888     ranges.add(new int[] { 1, 1 });
889     ranges.add(new int[] { 3, 4 });
890     ranges.add(new int[] { 6, 6 });
891     ranges.add(new int[] { 8, 8 });
892     ranges.add(new int[] { 10, 10 });
893     MappingUtils.unmapStopCodon(ranges, 6);
894     assertEquals(2, ranges.size());
895     assertArrayEquals(new int[] { 1, 1 }, ranges.get(0));
896     assertArrayEquals(new int[] { 3, 4 }, ranges.get(1));
897
898     // simple case, reverse strand:
899     ranges.clear();
900     ranges.add(new int[] { 12, 10 });
901     ranges.add(new int[] { 6, 1 });
902     MappingUtils.unmapStopCodon(ranges, 9);
903     assertEquals(2, ranges.size());
904     assertArrayEquals(new int[] { 12, 10 }, ranges.get(0));
905     assertArrayEquals(new int[] { 6, 4 }, ranges.get(1));
906
907     // split stop codon, reverse strand:
908     ranges.clear();
909     ranges.add(new int[] { 12, 6 });
910     ranges.add(new int[] { 4, 3 });
911     MappingUtils.unmapStopCodon(ranges, 9);
912     assertEquals(1, ranges.size());
913     assertArrayEquals(new int[] { 12, 7 }, ranges.get(0));
914   }
915
916   @Test(groups = { "Functional" })
917   public void testGetLength()
918   {
919     assertEquals(0, MappingUtils.getLength(null));
920     List<int[]> ranges = new ArrayList<int[]>();
921     assertEquals(0, MappingUtils.getLength(ranges));
922     ranges.add(new int[] { 1, 1 });
923     assertEquals(1, MappingUtils.getLength(ranges));
924     ranges.add(new int[] { 2, 10 });
925     assertEquals(10, MappingUtils.getLength(ranges));
926     ranges.add(new int[] { 20, 10 });
927     assertEquals(21, MappingUtils.getLength(ranges));
928   }
929
930   @Test(groups = { "Functional" })
931   public void testContains()
932   {
933     assertFalse(MappingUtils.contains(null, 1));
934     List<int[]> ranges = new ArrayList<int[]>();
935     assertFalse(MappingUtils.contains(ranges, 1));
936
937     ranges.add(new int[] { 1, 4 });
938     ranges.add(new int[] { 6, 6 });
939     ranges.add(new int[] { 8, 10 });
940     ranges.add(new int[] { 30, 20 });
941     ranges.add(new int[] { -16, -44 });
942
943     assertFalse(MappingUtils.contains(ranges, 0));
944     assertTrue(MappingUtils.contains(ranges, 1));
945     assertTrue(MappingUtils.contains(ranges, 2));
946     assertTrue(MappingUtils.contains(ranges, 3));
947     assertTrue(MappingUtils.contains(ranges, 4));
948     assertFalse(MappingUtils.contains(ranges, 5));
949
950     assertTrue(MappingUtils.contains(ranges, 6));
951     assertFalse(MappingUtils.contains(ranges, 7));
952
953     assertTrue(MappingUtils.contains(ranges, 8));
954     assertTrue(MappingUtils.contains(ranges, 9));
955     assertTrue(MappingUtils.contains(ranges, 10));
956
957     assertFalse(MappingUtils.contains(ranges, 31));
958     assertTrue(MappingUtils.contains(ranges, 30));
959     assertTrue(MappingUtils.contains(ranges, 29));
960     assertTrue(MappingUtils.contains(ranges, 20));
961     assertFalse(MappingUtils.contains(ranges, 19));
962
963     assertFalse(MappingUtils.contains(ranges, -15));
964     assertTrue(MappingUtils.contains(ranges, -16));
965     assertTrue(MappingUtils.contains(ranges, -44));
966     assertFalse(MappingUtils.contains(ranges, -45));
967   }
968
969 }