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