JAL-3761 locateInFrom2 work in progress commit
[jalview.git] / test / jalview / util / MapListTest.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.util;
22
23 import static org.testng.AssertJUnit.assertEquals;
24 import static org.testng.AssertJUnit.assertFalse;
25 import static org.testng.AssertJUnit.assertNull;
26 import static org.testng.AssertJUnit.assertSame;
27 import static org.testng.AssertJUnit.assertTrue;
28 import static org.testng.AssertJUnit.fail;
29 import static org.testng.internal.junit.ArrayAsserts.assertArrayEquals;
30
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.List;
34
35 import org.testng.annotations.BeforeClass;
36 import org.testng.annotations.Test;
37
38 import jalview.bin.Cache;
39 import jalview.gui.JvOptionPane;
40
41 public class MapListTest
42 {
43   @BeforeClass(alwaysRun = true)
44   public void setUp()
45   {
46     Cache.initLogger();
47   }
48
49   @BeforeClass(alwaysRun = true)
50   public void setUpJvOptionPane()
51   {
52     JvOptionPane.setInteractiveMode(false);
53     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
54   }
55
56   @Test(groups = { "Functional" })
57   public void testSomething()
58   {
59     MapList ml = new MapList(new int[] { 1, 5, 10, 15, 25, 20 },
60             new int[]
61             { 51, 1 }, 1, 3);
62     MapList ml1 = new MapList(new int[] { 1, 3, 17, 4 },
63             new int[]
64             { 51, 1 }, 1, 3);
65     MapList ml2 = new MapList(new int[] { 1, 60 }, new int[] { 1, 20 }, 3,
66             1);
67     // test internal consistency
68     int to[] = new int[51];
69     testMap(ml, 1, 60);
70     MapList mldna = new MapList(new int[] { 2, 2, 6, 8, 12, 16 },
71             new int[]
72             { 1, 3 }, 3, 1);
73     int[] frm = mldna.locateInFrom(1, 1);
74     testLocateFrom(mldna, 1, 1, new int[] { 2, 2, 6, 7 });
75     testMap(mldna, 1, 3);
76     /*
77      * for (int from=1; from<=51; from++) { int[] too=ml.shiftTo(from); int[]
78      * toofrom=ml.shiftFrom(too[0]);
79      * System.out.println("ShiftFrom("+from+")=="+too[0]+" %
80      * "+too[1]+"\t+-+\tShiftTo("+too[0]+")=="+toofrom[0]+" % "+toofrom[1]); }
81      */
82   }
83
84   private static void testLocateFrom(MapList mldna, int i, int j, int[] ks)
85   {
86     int[] frm = mldna.locateInFrom(i, j);
87     assertEquals("Failed test locate from " + i + " to " + j,
88             Arrays.toString(frm), Arrays.toString(ks));
89   }
90
91   /**
92    * test routine. not incremental.
93    * 
94    * @param ml
95    * @param fromS
96    * @param fromE
97    */
98   private void testMap(MapList ml, int fromS, int fromE)
99   {
100     // todo convert to JUnit style tests
101     for (int from = 1; from <= 25; from++)
102     {
103       int[] too = ml.shiftFrom(from);
104       System.out.print("ShiftFrom(" + from + ")==");
105       if (too == null)
106       {
107         System.out.print("NaN\n");
108       }
109       else
110       {
111         System.out.print(too[0] + " % " + too[1] + " (" + too[2] + ")");
112         System.out.print("\t+--+\t");
113         int[] toofrom = ml.shiftTo(too[0]);
114         if (toofrom != null)
115         {
116           if (toofrom[0] != from)
117           {
118             System.err.println("Mapping not reflexive:" + from + " "
119                     + too[0] + "->" + toofrom[0]);
120           }
121           System.out.println("ShiftTo(" + too[0] + ")==" + toofrom[0]
122                   + " % " + toofrom[1] + " (" + toofrom[2] + ")");
123         }
124         else
125         {
126           System.out.println("ShiftTo(" + too[0] + ")=="
127                   + "NaN! - not Bijective Mapping!");
128         }
129       }
130     }
131     int mmap[][] = ml.makeFromMap();
132     System.out.println("FromMap : (" + mmap[0][0] + " " + mmap[0][1] + " "
133             + mmap[0][2] + " " + mmap[0][3] + " ");
134     for (int i = 1; i <= mmap[1].length; i++)
135     {
136       if (mmap[1][i - 1] == -1)
137       {
138         System.out.print(i + "=XXX");
139
140       }
141       else
142       {
143         System.out.print(i + "=" + (mmap[0][2] + mmap[1][i - 1]));
144       }
145       if (i % 20 == 0)
146       {
147         System.out.print("\n");
148       }
149       else
150       {
151         System.out.print(",");
152       }
153     }
154     // test range function
155     System.out.print("\nTest locateInFrom\n");
156     {
157       int f = mmap[0][2], t = mmap[0][3];
158       while (f <= t)
159       {
160         System.out.println("Range " + f + " to " + t);
161         int rng[] = ml.locateInFrom(f, t);
162         if (rng != null)
163         {
164           for (int i = 0; i < rng.length; i++)
165           {
166             System.out.print(rng[i] + ((i % 2 == 0) ? "," : ";"));
167           }
168         }
169         else
170         {
171           System.out.println("No range!");
172         }
173         System.out.print("\nReversed\n");
174         rng = ml.locateInFrom(t, f);
175         if (rng != null)
176         {
177           for (int i = 0; i < rng.length; i++)
178           {
179             System.out.print(rng[i] + ((i % 2 == 0) ? "," : ";"));
180           }
181         }
182         else
183         {
184           System.out.println("No range!");
185         }
186         System.out.print("\n");
187         f++;
188         t--;
189       }
190     }
191     System.out.print("\n");
192     mmap = ml.makeToMap();
193     System.out.println("ToMap : (" + mmap[0][0] + " " + mmap[0][1] + " "
194             + mmap[0][2] + " " + mmap[0][3] + " ");
195     for (int i = 1; i <= mmap[1].length; i++)
196     {
197       if (mmap[1][i - 1] == -1)
198       {
199         System.out.print(i + "=XXX");
200
201       }
202       else
203       {
204         System.out.print(i + "=" + (mmap[0][2] + mmap[1][i - 1]));
205       }
206       if (i % 20 == 0)
207       {
208         System.out.print("\n");
209       }
210       else
211       {
212         System.out.print(",");
213       }
214     }
215     System.out.print("\n");
216     // test range function
217     System.out.print("\nTest locateInTo\n");
218     {
219       int f = mmap[0][2], t = mmap[0][3];
220       while (f <= t)
221       {
222         System.out.println("Range " + f + " to " + t);
223         int rng[] = ml.locateInTo(f, t);
224         if (rng != null)
225         {
226           for (int i = 0; i < rng.length; i++)
227           {
228             System.out.print(rng[i] + ((i % 2 == 0) ? "," : ";"));
229           }
230         }
231         else
232         {
233           System.out.println("No range!");
234         }
235         System.out.print("\nReversed\n");
236         rng = ml.locateInTo(t, f);
237         if (rng != null)
238         {
239           for (int i = 0; i < rng.length; i++)
240           {
241             System.out.print(rng[i] + ((i % 2 == 0) ? "," : ";"));
242           }
243         }
244         else
245         {
246           System.out.println("No range!");
247         }
248         f++;
249         t--;
250         System.out.print("\n");
251       }
252     }
253   }
254
255   /**
256    * Tests for method that locates ranges in the 'from' map for given range in
257    * the 'to' map.
258    */
259   @Test(groups = { "Functional" })
260   public void testLocateInFrom_noIntrons()
261   {
262     /*
263      * Simple mapping with no introns
264      */
265     int[] codons = new int[] { 1, 12 };
266     int[] protein = new int[] { 1, 4 };
267     MapList ml = new MapList(codons, protein, 3, 1);
268     assertEquals("[1, 3]", Arrays.toString(ml.locateInFrom(1, 1)));
269     assertEquals("[4, 6]", Arrays.toString(ml.locateInFrom(2, 2)));
270     assertEquals("[7, 9]", Arrays.toString(ml.locateInFrom(3, 3)));
271     assertEquals("[10, 12]", Arrays.toString(ml.locateInFrom(4, 4)));
272     assertEquals("[1, 6]", Arrays.toString(ml.locateInFrom(1, 2)));
273     assertEquals("[1, 9]", Arrays.toString(ml.locateInFrom(1, 3)));
274     assertEquals("[1, 12]", Arrays.toString(ml.locateInFrom(1, 4)));
275     assertEquals("[4, 9]", Arrays.toString(ml.locateInFrom(2, 3)));
276     assertEquals("[4, 12]", Arrays.toString(ml.locateInFrom(2, 4)));
277     assertEquals("[7, 12]", Arrays.toString(ml.locateInFrom(3, 4)));
278     assertEquals("[10, 12]", Arrays.toString(ml.locateInFrom(4, 4)));
279     // reversing the range reverses the result:
280     assertEquals("[12, 7]", Arrays.toString(ml.locateInFrom(4, 3))); // fails with [10, 9] !
281
282     assertNull(ml.locateInFrom(0, 0));
283     assertNull(ml.locateInFrom(1, 5));
284     assertNull(ml.locateInFrom(-1, 1));
285   }
286
287   /**
288    * Tests for method that locates ranges in the 'from' map for given range in
289    * the 'to' map.
290    */
291   @Test(groups = { "Functional" })
292   public void testLocateInFrom_withIntrons()
293   {
294     /*
295      * Exons at positions [2, 3, 5] [6, 7, 9] [10, 12, 14] [16, 17, 18] i.e.
296      * 2-3, 5-7, 9-10, 12-12, 14-14, 16-18
297      */
298     int[] codons = { 2, 3, 5, 7, 9, 10, 12, 12, 14, 14, 16, 18 };
299     int[] protein = { 1, 4 };
300     MapList ml = new MapList(codons, protein, 3, 1);
301     assertEquals("[2, 3, 5, 5]", Arrays.toString(ml.locateInFrom(1, 1)));
302     assertEquals("[6, 7, 9, 9]", Arrays.toString(ml.locateInFrom(2, 2)));
303     assertEquals("[10, 10, 12, 12, 14, 14]",
304             Arrays.toString(ml.locateInFrom(3, 3)));
305     assertEquals("[16, 18]", Arrays.toString(ml.locateInFrom(4, 4)));
306   }
307
308   @Test(groups = { "Functional" })
309   public void testLocateInFrom_reverseStrand()
310   {
311     int[] codons = new int[] { 12, 1 };
312     int[] protein = new int[] { 1, 4 };
313     MapList ml = new MapList(codons, protein, 3, 1);
314     assertEquals("[12, 10]", Arrays.toString(ml.locateInFrom(1, 1)));
315     assertEquals("[9, 4]", Arrays.toString(ml.locateInFrom(2, 3)));
316   }
317   
318    /**
319    * Tests for method that locates ranges in the 'to' map for given range in the
320    * 'from' map.
321    */
322   @Test(groups = { "Functional" })
323   public void testLocateInTo_noIntrons()
324   {
325     /*
326      * Simple mapping with no introns
327      */
328     int[] codons = new int[] { 1, 12 };
329     int[] protein = new int[] { 1, 4 };
330     MapList ml = new MapList(codons, protein, 3, 1);
331     assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(1, 3)));
332     assertEquals("[2, 2]", Arrays.toString(ml.locateInTo(4, 6)));
333     assertEquals("[3, 3]", Arrays.toString(ml.locateInTo(7, 9)));
334     assertEquals("[4, 4]", Arrays.toString(ml.locateInTo(10, 12)));
335     assertEquals("[1, 2]", Arrays.toString(ml.locateInTo(1, 6)));
336     assertEquals("[1, 3]", Arrays.toString(ml.locateInTo(1, 9)));
337     assertEquals("[1, 4]", Arrays.toString(ml.locateInTo(1, 12)));
338     assertEquals("[2, 2]", Arrays.toString(ml.locateInTo(4, 6)));
339     assertEquals("[2, 4]", Arrays.toString(ml.locateInTo(4, 12)));
340     // reversing the 'from' range reverses the result
341     assertEquals("[4, 2]", Arrays.toString(ml.locateInTo(12, 4)));
342
343     /*
344      * A part codon is treated as if a whole one.
345      */
346     assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(1, 1)));
347     assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(1, 2)));
348     assertEquals("[1, 2]", Arrays.toString(ml.locateInTo(1, 4)));
349     assertEquals("[1, 3]", Arrays.toString(ml.locateInTo(2, 8)));
350     assertEquals("[1, 4]", Arrays.toString(ml.locateInTo(3, 11)));
351     assertEquals("[2, 4]", Arrays.toString(ml.locateInTo(5, 11)));
352
353     assertNull(ml.locateInTo(0, 0));
354     assertNull(ml.locateInTo(1, 13));
355     assertNull(ml.locateInTo(-1, 1));
356   }
357
358   /**
359    * Tests for method that locates ranges in the 'to' map for given range in the
360    * 'from' map.
361    */
362   @Test(groups = { "Functional" })
363   public void testLocateInTo_withIntrons()
364   {
365     /*
366      * Exons at positions [2, 3, 5] [6, 7, 9] [10, 12, 14] [16, 17, 18] i.e.
367      * 2-3, 5-7, 9-10, 12-12, 14-14, 16-18
368      */
369     int[] codons = { 2, 3, 5, 7, 9, 10, 12, 12, 14, 14, 16, 18 };
370     /*
371      * Mapped proteins at positions 1, 3, 4, 6 in the sequence
372      */
373     int[] protein = { 1, 1, 3, 4, 6, 6 };
374     MapList ml = new MapList(codons, protein, 3, 1);
375
376     /*
377      * Can't map from an unmapped position
378      */
379     assertNull(ml.locateInTo(1, 2));
380     assertNull(ml.locateInTo(1, 4));
381     assertNull(ml.locateInTo(2, 4));
382     assertNull(ml.locateInTo(4, 4));
383
384     /*
385      * Valid range or subrange of codon1 maps to protein1
386      */
387     assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(2, 2)));
388     assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(3, 3)));
389     assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(3, 5)));
390     assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(2, 3)));
391     assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(2, 5)));
392
393     // codon position 6 starts the next protein:
394     assertEquals("[1, 1, 3, 3]", Arrays.toString(ml.locateInTo(3, 6)));
395
396     // codon positions 7 to 17 (part) cover proteins 2/3/4 at positions 3/4/6
397     assertEquals("[3, 4, 6, 6]", Arrays.toString(ml.locateInTo(7, 17)));
398   }
399
400   /**
401    * Test equals method.
402    */
403   @Test(groups = { "Functional" })
404   public void testEquals()
405   {
406     int[] codons = new int[] { 2, 3, 5, 7, 9, 10, 12, 12, 14, 14, 16, 18 };
407     int[] protein = new int[] { 1, 4 };
408     MapList ml = new MapList(codons, protein, 3, 1);
409     MapList ml1 = new MapList(codons, protein, 3, 1); // same values
410     MapList ml2 = new MapList(codons, protein, 2, 1); // fromRatio differs
411     MapList ml3 = new MapList(codons, protein, 3, 2); // toRatio differs
412     codons[2] = 4;
413     MapList ml6 = new MapList(codons, protein, 3, 1); // fromShifts differ
414     protein[1] = 3;
415     MapList ml7 = new MapList(codons, protein, 3, 1); // toShifts differ
416
417     assertTrue(ml.equals(ml));
418     assertEquals(ml.hashCode(), ml.hashCode());
419     assertTrue(ml.equals(ml1));
420     assertEquals(ml.hashCode(), ml1.hashCode());
421     assertTrue(ml1.equals(ml));
422
423     assertFalse(ml.equals(null));
424     assertFalse(ml.equals("hello"));
425     assertFalse(ml.equals(ml2));
426     assertFalse(ml.equals(ml3));
427     assertFalse(ml.equals(ml6));
428     assertFalse(ml.equals(ml7));
429     assertFalse(ml6.equals(ml7));
430
431     try
432     {
433       MapList ml4 = new MapList(codons, null, 3, 1); // toShifts null
434       assertFalse(ml.equals(ml4));
435     } catch (NullPointerException e)
436     {
437       // actually thrown by constructor before equals can be called
438     }
439     try
440     {
441       MapList ml5 = new MapList(null, protein, 3, 1); // fromShifts null
442       assertFalse(ml.equals(ml5));
443     } catch (NullPointerException e)
444     {
445       // actually thrown by constructor before equals can be called
446     }
447   }
448
449   /**
450    * Test for the method that flattens a list of ranges into a single array.
451    */
452   @Test(groups = { "Functional" })
453   public void testGetRanges()
454   {
455     List<int[]> ranges = new ArrayList<>();
456     ranges.add(new int[] { 2, 3 });
457     ranges.add(new int[] { 5, 6 });
458     assertEquals("[2, 3, 5, 6]",
459             Arrays.toString(MapList.getRanges(ranges)));
460   }
461
462   /**
463    * Check state after construction
464    */
465   @Test(groups = { "Functional" })
466   public void testConstructor()
467   {
468     int[] codons = { 2, 3, 5, 7, 9, 10, 12, 12, 14, 14, 16, 18 };
469     int[] protein = { 1, 1, 3, 4, 6, 6 };
470     MapList ml = new MapList(codons, protein, 3, 1);
471     assertEquals(3, ml.getFromRatio());
472     assertEquals(2, ml.getFromLowest());
473     assertEquals(18, ml.getFromHighest());
474     assertEquals(1, ml.getToLowest());
475     assertEquals(6, ml.getToHighest());
476     assertEquals("{[2, 3], [5, 7], [9, 10], [12, 12], [14, 14], [16, 18]}",
477             prettyPrint(ml.getFromRanges()));
478     assertEquals("{[1, 1], [3, 4], [6, 6]}", prettyPrint(ml.getToRanges()));
479
480     /*
481      * Also copy constructor
482      */
483     MapList ml2 = new MapList(ml);
484     assertEquals(3, ml2.getFromRatio());
485     assertEquals(2, ml2.getFromLowest());
486     assertEquals(18, ml2.getFromHighest());
487     assertEquals(1, ml2.getToLowest());
488     assertEquals(6, ml2.getToHighest());
489     assertEquals("{[2, 3], [5, 7], [9, 10], [12, 12], [14, 14], [16, 18]}",
490             prettyPrint(ml2.getFromRanges()));
491     assertEquals("{[1, 1], [3, 4], [6, 6]}",
492             prettyPrint(ml2.getToRanges()));
493
494     /*
495      * reverse direction
496      */
497     codons = new int[] { 9, 6 };
498     protein = new int[] { 100, 91, 80, 79 };
499     ml = new MapList(codons, protein, 3, 1);
500     assertEquals(6, ml.getFromLowest());
501     assertEquals(9, ml.getFromHighest());
502     assertEquals(79, ml.getToLowest());
503     assertEquals(100, ml.getToHighest());
504   }
505
506   /**
507    * Test constructor used to merge consecutive ranges but now just leaves them
508    * as supplied (JAL-3751)
509    */
510   @Test(groups = { "Functional" })
511   public void testConstructor_mergeRanges()
512   {
513     int[] codons = { 2, 3, 3, 7, 9, 10, 12, 12, 13, 14, 16, 17 };
514     int[] protein = { 1, 1, 2, 3, 6, 6 };
515     MapList ml = new MapList(codons, protein, 3, 1);
516     assertEquals(3, ml.getFromRatio());
517     assertEquals(2, ml.getFromLowest());
518     assertEquals(17, ml.getFromHighest());
519     assertEquals(1, ml.getToLowest());
520     assertEquals(6, ml.getToHighest());
521     assertEquals("{[2, 3], [3, 7], [9, 10], [12, 12], [13, 14], [16, 17]}",
522             prettyPrint(ml.getFromRanges()));
523     assertEquals("{[1, 1], [2, 3], [6, 6]}", prettyPrint(ml.getToRanges()));
524   }
525
526   /**
527    * Convert a List of {[i, j], [k, l], ...} to "[[i, j], [k, l], ...]"
528    * 
529    * @param ranges
530    * @return
531    */
532   private String prettyPrint(List<int[]> ranges)
533   {
534     StringBuilder sb = new StringBuilder(ranges.size() * 5);
535     boolean first = true;
536     sb.append("{");
537     for (int[] range : ranges)
538     {
539       if (!first)
540       {
541         sb.append(", ");
542       }
543       sb.append(Arrays.toString(range));
544       first = false;
545     }
546     sb.append("}");
547     return sb.toString();
548   }
549
550   /**
551    * Test the method that creates an inverse mapping
552    */
553   @Test(groups = { "Functional" })
554   public void testGetInverse()
555   {
556     int[] codons = { 2, 3, 5, 7, 9, 10, 12, 12, 14, 14, 16, 18 };
557     int[] protein = { 1, 1, 3, 4, 6, 6 };
558
559     MapList ml = new MapList(codons, protein, 3, 1);
560     MapList ml2 = ml.getInverse();
561     assertEquals(ml.getFromRatio(), ml2.getToRatio());
562     assertEquals(ml.getFromRatio(), ml2.getToRatio());
563     assertEquals(ml.getToHighest(), ml2.getFromHighest());
564     assertEquals(ml.getFromHighest(), ml2.getToHighest());
565     assertEquals(prettyPrint(ml.getFromRanges()),
566             prettyPrint(ml2.getToRanges()));
567     assertEquals(prettyPrint(ml.getToRanges()),
568             prettyPrint(ml2.getFromRanges()));
569   }
570
571   @Test(groups = { "Functional" })
572   public void testToString()
573   {
574     MapList ml = new MapList(new int[] { 1, 5, 10, 15, 25, 20 },
575             new int[]
576             { 51, 1 }, 1, 3);
577     String s = ml.toString();
578     assertEquals("[ [1, 5] [10, 15] [25, 20] ] 1:3 to [ [51, 1] ]", s);
579   }
580
581   @Test(groups = { "Functional" })
582   public void testAddMapList()
583   {
584     MapList ml = new MapList(new int[] { 11, 15, 20, 25, 35, 30 },
585             new int[]
586             { 72, 22 }, 1, 3);
587     assertEquals(11, ml.getFromLowest());
588     assertEquals(35, ml.getFromHighest());
589     assertEquals(22, ml.getToLowest());
590     assertEquals(72, ml.getToHighest());
591
592     MapList ml2 = new MapList(new int[] { 2, 4, 37, 40 },
593             new int[]
594             { 12, 17, 78, 83, 88, 96 }, 1, 3);
595     ml.addMapList(ml2);
596     assertEquals(2, ml.getFromLowest());
597     assertEquals(40, ml.getFromHighest());
598     assertEquals(12, ml.getToLowest());
599     assertEquals(96, ml.getToHighest());
600
601     String s = ml.toString();
602     assertEquals(
603             "[ [11, 15] [20, 25] [35, 30] [2, 4] [37, 40] ] 1:3 to [ [72, 22] [12, 17] [78, 83] [88, 96] ]",
604             s);
605   }
606
607   /**
608    * Test that confirms adding a map twice does nothing
609    */
610   @Test(groups = { "Functional" })
611   public void testAddMapList_sameMap()
612   {
613     MapList ml = new MapList(new int[] { 11, 15, 20, 25, 35, 30 },
614             new int[]
615             { 72, 22 }, 1, 3);
616     String before = ml.toString();
617     ml.addMapList(ml);
618     assertEquals(before, ml.toString());
619     ml.addMapList(new MapList(ml));
620     assertEquals(before, ml.toString());
621   }
622
623   @Test(groups = { "Functional" })
624   public void testAddMapList_contiguous()
625   {
626     MapList ml = new MapList(new int[] { 11, 15 }, new int[] { 72, 58 }, 1,
627             3);
628
629     MapList ml2 = new MapList(new int[] { 15, 16 }, new int[] { 58, 53 }, 1,
630             3);
631     ml.addMapList(ml2);
632     assertEquals("[ [11, 16] ] 1:3 to [ [72, 53] ]", ml.toString());
633   }
634
635   @Test(groups = "Functional")
636   public void testAddRange()
637   {
638     int[] range = { 1, 5 };
639     List<int[]> ranges = new ArrayList<>();
640
641     // add to empty list:
642     MapList.addRange(range, ranges);
643     assertEquals(1, ranges.size());
644     assertSame(range, ranges.get(0));
645
646     // extend contiguous (same position):
647     MapList.addRange(new int[] { 5, 10 }, ranges);
648     assertEquals(1, ranges.size());
649     assertEquals(1, ranges.get(0)[0]);
650     assertEquals(10, ranges.get(0)[1]);
651
652     // extend contiguous (next position):
653     MapList.addRange(new int[] { 11, 15 }, ranges);
654     assertEquals(1, ranges.size());
655     assertEquals(1, ranges.get(0)[0]);
656     assertEquals(15, ranges.get(0)[1]);
657
658     // change direction: range is not merged:
659     MapList.addRange(new int[] { 16, 10 }, ranges);
660     assertEquals(2, ranges.size());
661     assertEquals(16, ranges.get(1)[0]);
662     assertEquals(10, ranges.get(1)[1]);
663
664     // extend reverse contiguous (same position):
665     MapList.addRange(new int[] { 10, 8 }, ranges);
666     assertEquals(2, ranges.size());
667     assertEquals(16, ranges.get(1)[0]);
668     assertEquals(8, ranges.get(1)[1]);
669
670     // extend reverse contiguous (next position):
671     MapList.addRange(new int[] { 7, 6 }, ranges);
672     assertEquals(2, ranges.size());
673     assertEquals(16, ranges.get(1)[0]);
674     assertEquals(6, ranges.get(1)[1]);
675
676     // change direction: range is not merged:
677     MapList.addRange(new int[] { 6, 9 }, ranges);
678     assertEquals(3, ranges.size());
679     assertEquals(6, ranges.get(2)[0]);
680     assertEquals(9, ranges.get(2)[1]);
681
682     // not contiguous: not merged
683     MapList.addRange(new int[] { 11, 12 }, ranges);
684     assertEquals(4, ranges.size());
685     assertEquals(11, ranges.get(3)[0]);
686     assertEquals(12, ranges.get(3)[1]);
687   }
688
689   /**
690    * Check state after construction
691    */
692   @Test(groups = { "Functional" })
693   public void testConstructor_withLists()
694   {
695     /*
696      * reverse direction
697      */
698     int[][] codons = new int[][] { { 9, 6 } };
699     int[][] protein = new int[][] { { 100, 91 }, { 80, 79 } };
700     MapList ml = new MapList(Arrays.asList(codons), Arrays.asList(protein),
701             3, 1);
702     assertEquals(6, ml.getFromLowest());
703     assertEquals(9, ml.getFromHighest());
704     assertEquals(79, ml.getToLowest());
705     assertEquals(100, ml.getToHighest());
706   }
707
708   /**
709    * Test that method that inspects for the (first) forward or reverse from
710    * range. Single position ranges are ignored.
711    */
712   @Test(groups = { "Functional" })
713   public void testIsFromForwardStrand()
714   {
715     // [3-9] declares forward strand
716     MapList ml = new MapList(new int[] { 2, 2, 3, 9, 12, 11 },
717             new int[]
718             { 20, 11 }, 1, 1);
719     assertTrue(ml.isFromForwardStrand());
720
721     // [11-5] declares reverse strand ([13-14] is ignored)
722     ml = new MapList(new int[] { 2, 2, 11, 5, 13, 14 },
723             new int[]
724             { 20, 11 }, 1, 1);
725     assertFalse(ml.isFromForwardStrand());
726
727     // all single position ranges - defaults to forward strand
728     ml = new MapList(new int[] { 2, 2, 4, 4, 6, 6 }, new int[] { 3, 1 }, 1,
729             1);
730     assertTrue(ml.isFromForwardStrand());
731   }
732
733   /**
734    * Test the method that merges contiguous ranges
735    */
736   @Test(groups = { "Functional" })
737   public void testCoalesceRanges()
738   {
739     assertNull(MapList.coalesceRanges(null));
740     List<int[]> ranges = new ArrayList<>();
741     assertSame(ranges, MapList.coalesceRanges(ranges));
742     ranges.add(new int[] { 1, 3 });
743     assertSame(ranges, MapList.coalesceRanges(ranges));
744
745     // add non-contiguous range:
746     ranges.add(new int[] { 5, 6 });
747     assertSame(ranges, MapList.coalesceRanges(ranges));
748
749     // 'contiguous' range in opposite direction is not merged:
750     ranges.add(new int[] { 7, 6 });
751     assertSame(ranges, MapList.coalesceRanges(ranges));
752
753     // merging in forward direction:
754     ranges.clear();
755     ranges.add(new int[] { 1, 3 });
756     ranges.add(new int[] { 4, 5 }); // contiguous
757     ranges.add(new int[] { 5, 5 }); // overlap!
758     ranges.add(new int[] { 6, 7 }); // contiguous
759     List<int[]> merged = MapList.coalesceRanges(ranges);
760     assertEquals(2, merged.size());
761     assertArrayEquals(new int[] { 1, 5 }, merged.get(0));
762     assertArrayEquals(new int[] { 5, 7 }, merged.get(1));
763     // verify input list is unchanged
764     assertEquals(4, ranges.size());
765     assertArrayEquals(new int[] { 1, 3 }, ranges.get(0));
766     assertArrayEquals(new int[] { 4, 5 }, ranges.get(1));
767     assertArrayEquals(new int[] { 5, 5 }, ranges.get(2));
768     assertArrayEquals(new int[] { 6, 7 }, ranges.get(3));
769
770     // merging in reverse direction:
771     ranges.clear();
772     ranges.add(new int[] { 7, 5 });
773     ranges.add(new int[] { 5, 4 }); // overlap
774     ranges.add(new int[] { 4, 4 }); // overlap
775     ranges.add(new int[] { 3, 1 }); // contiguous
776     merged = MapList.coalesceRanges(ranges);
777     assertEquals(3, merged.size());
778     assertArrayEquals(new int[] { 7, 5 }, merged.get(0));
779     assertArrayEquals(new int[] { 5, 4 }, merged.get(1));
780     assertArrayEquals(new int[] { 4, 1 }, merged.get(2));
781
782     // merging with switches of direction:
783     ranges.clear();
784     ranges.add(new int[] { 1, 3 });
785     ranges.add(new int[] { 4, 5 }); // contiguous
786     ranges.add(new int[] { 5, 5 }); // overlap
787     ranges.add(new int[] { 6, 6 }); // contiguous
788     ranges.add(new int[] { 12, 10 });
789     ranges.add(new int[] { 9, 8 }); // contiguous
790     ranges.add(new int[] { 8, 8 }); // overlap
791     ranges.add(new int[] { 7, 7 }); // contiguous
792     merged = MapList.coalesceRanges(ranges);
793     assertEquals(4, merged.size());
794     assertArrayEquals(new int[] { 1, 5 }, merged.get(0));
795     assertArrayEquals(new int[] { 5, 6 }, merged.get(1));
796     assertArrayEquals(new int[] { 12, 8 }, merged.get(2));
797     assertArrayEquals(new int[] { 8, 7 }, merged.get(3));
798
799     // 'subsumed' ranges are preserved
800     ranges.clear();
801     ranges.add(new int[] { 10, 30 });
802     ranges.add(new int[] { 15, 25 });
803     merged = MapList.coalesceRanges(ranges);
804     assertEquals(2, merged.size());
805     assertArrayEquals(new int[] { 10, 30 }, merged.get(0));
806     assertArrayEquals(new int[] { 15, 25 }, merged.get(1));
807   }
808
809   /**
810    * Test the method that compounds ('traverses') two mappings
811    */
812   @Test(groups = "Functional")
813   public void testTraverse()
814   {
815     /*
816      * simple 1:1 plus 1:1 forwards
817      */
818     MapList ml1 = new MapList(new int[] { 3, 4, 8, 12 },
819             new int[]
820             { 5, 8, 11, 13 }, 1, 1);
821     assertEquals("{[3, 4], [8, 12]}", prettyPrint(ml1.getFromRanges()));
822     assertEquals("{[5, 8], [11, 13]}", prettyPrint(ml1.getToRanges()));
823
824     MapList ml2 = new MapList(new int[] { 1, 50 },
825             new int[]
826             { 40, 45, 70, 75, 90, 127 }, 1, 1);
827     assertEquals("{[1, 50]}", prettyPrint(ml2.getFromRanges()));
828     assertEquals("{[40, 45], [70, 75], [90, 127]}",
829             prettyPrint(ml2.getToRanges()));
830
831     MapList compound = ml1.traverse(ml2);
832
833     assertEquals(1, compound.getFromRatio());
834     assertEquals(1, compound.getToRatio());
835     List<int[]> fromRanges = compound.getFromRanges();
836     assertEquals(2, fromRanges.size());
837     assertArrayEquals(new int[] { 3, 4 }, fromRanges.get(0));
838     assertArrayEquals(new int[] { 8, 12 }, fromRanges.get(1));
839     List<int[]> toRanges = compound.getToRanges();
840     assertEquals(4, toRanges.size());
841     // 5-8 maps to 44-45,70-71
842     // 11-13 maps to 74-75,90
843     assertArrayEquals(new int[] { 44, 45 }, toRanges.get(0));
844     assertArrayEquals(new int[] { 70, 71 }, toRanges.get(1));
845     assertArrayEquals(new int[] { 74, 75 }, toRanges.get(2));
846     assertArrayEquals(new int[] { 90, 90 }, toRanges.get(3));
847
848     /*
849      * 1:1 over 1:1 backwards ('reverse strand')
850      */
851     ml1 = new MapList(new int[] { 1, 50 }, new int[] { 70, 119 }, 1, 1);
852     ml2 = new MapList(new int[] { 1, 500 },
853             new int[]
854             { 1000, 901, 600, 201 }, 1, 1);
855     compound = ml1.traverse(ml2);
856
857     assertEquals(1, compound.getFromRatio());
858     assertEquals(1, compound.getToRatio());
859     fromRanges = compound.getFromRanges();
860     assertEquals(1, fromRanges.size());
861     assertArrayEquals(new int[] { 1, 50 }, fromRanges.get(0));
862     toRanges = compound.getToRanges();
863     assertEquals(2, toRanges.size());
864     assertArrayEquals(new int[] { 931, 901 }, toRanges.get(0));
865     assertArrayEquals(new int[] { 600, 582 }, toRanges.get(1));
866
867     /*
868      * 1:1 plus 1:3 should result in 1:3
869      */
870     ml1 = new MapList(new int[] { 1, 30 }, new int[] { 11, 40 }, 1, 1);
871     ml2 = new MapList(new int[] { 1, 100 }, new int[] { 1, 50, 91, 340 }, 1,
872             3);
873     compound = ml1.traverse(ml2);
874
875     assertEquals(1, compound.getFromRatio());
876     assertEquals(3, compound.getToRatio());
877     fromRanges = compound.getFromRanges();
878     assertEquals(1, fromRanges.size());
879     assertArrayEquals(new int[] { 1, 30 }, fromRanges.get(0));
880     // 11-40 maps to 31-50,91-160
881     toRanges = compound.getToRanges();
882     assertEquals(2, toRanges.size());
883     assertArrayEquals(new int[] { 31, 50 }, toRanges.get(0));
884     assertArrayEquals(new int[] { 91, 160 }, toRanges.get(1));
885
886     /*
887      * 3:1 plus 1:1 should result in 3:1
888      */
889     ml1 = new MapList(new int[] { 1, 30 }, new int[] { 11, 20 }, 3, 1);
890     ml2 = new MapList(new int[] { 1, 100 }, new int[] { 1, 15, 91, 175 }, 1,
891             1);
892     compound = ml1.traverse(ml2);
893
894     assertEquals(3, compound.getFromRatio());
895     assertEquals(1, compound.getToRatio());
896     fromRanges = compound.getFromRanges();
897     assertEquals(1, fromRanges.size());
898     assertArrayEquals(new int[] { 1, 30 }, fromRanges.get(0));
899     // 11-20 maps to 11-15, 91-95
900     toRanges = compound.getToRanges();
901     assertEquals(2, toRanges.size());
902     assertArrayEquals(new int[] { 11, 15 }, toRanges.get(0));
903     assertArrayEquals(new int[] { 91, 95 }, toRanges.get(1));
904
905     /*
906      * 1:3 plus 3:1 should result in 1:1
907      */
908     ml1 = new MapList(new int[] { 21, 40 }, new int[] { 13, 72 }, 1, 3);
909     ml2 = new MapList(new int[] { 1, 300 }, new int[] { 51, 70, 121, 200 },
910             3, 1);
911     compound = ml1.traverse(ml2);
912
913     assertEquals(1, compound.getFromRatio());
914     assertEquals(1, compound.getToRatio());
915     fromRanges = compound.getFromRanges();
916     assertEquals(1, fromRanges.size());
917     assertArrayEquals(new int[] { 21, 40 }, fromRanges.get(0));
918     // 13-72 maps 3:1 to 55-70, 121-124
919     toRanges = compound.getToRanges();
920     assertEquals(2, toRanges.size());
921     assertArrayEquals(new int[] { 55, 70 }, toRanges.get(0));
922     assertArrayEquals(new int[] { 121, 124 }, toRanges.get(1));
923
924     /*
925      * 3:1 plus 1:3 should result in 1:1
926      */
927     ml1 = new MapList(new int[] { 31, 90 }, new int[] { 13, 32 }, 3, 1);
928     ml2 = new MapList(new int[] { 11, 40 }, new int[] { 41, 50, 71, 150 },
929             1, 3);
930     compound = ml1.traverse(ml2);
931
932     assertEquals(1, compound.getFromRatio());
933     assertEquals(1, compound.getToRatio());
934     fromRanges = compound.getFromRanges();
935     assertEquals(1, fromRanges.size());
936     assertArrayEquals(new int[] { 31, 90 }, fromRanges.get(0));
937     // 13-32 maps to 47-50,71-126
938     toRanges = compound.getToRanges();
939     assertEquals(2, toRanges.size());
940     assertArrayEquals(new int[] { 47, 50 }, toRanges.get(0));
941     assertArrayEquals(new int[] { 71, 126 }, toRanges.get(1));
942
943     /*
944      * method returns null if not all regions are mapped through
945      */
946     ml1 = new MapList(new int[] { 1, 50 }, new int[] { 101, 150 }, 1, 1);
947     ml2 = new MapList(new int[] { 131, 180 }, new int[] { 201, 250 }, 1, 3);
948     compound = ml1.traverse(ml2);
949     assertNull(compound);
950   }
951
952   /**
953    * Test that method that inspects for the (first) forward or reverse 'to'
954    * range. Single position ranges are ignored.
955    */
956   @Test(groups = { "Functional" })
957   public void testIsToForwardsStrand()
958   {
959     // [3-9] declares forward strand
960     MapList ml = new MapList(new int[] { 20, 11 },
961             new int[]
962             { 2, 2, 3, 9, 12, 11 }, 1, 1);
963     assertTrue(ml.isToForwardStrand());
964
965     // [11-5] declares reverse strand ([13-14] is ignored)
966     ml = new MapList(new int[] { 20, 11 },
967             new int[]
968             { 2, 2, 11, 5, 13, 14 }, 1, 1);
969     assertFalse(ml.isToForwardStrand());
970
971     // all single position ranges - defaults to forward strand
972     ml = new MapList(new int[] { 3, 1 }, new int[] { 2, 2, 4, 4, 6, 6 }, 1,
973             1);
974     assertTrue(ml.isToForwardStrand());
975   }
976
977   /**
978    * Test for mapping with overlapping ranges
979    */
980   @Test(groups = { "Functional" })
981   public void testLocateInFrom_withOverlap()
982   {
983     /*
984      * gene to protein...
985      */
986     int[] codons = new int[] { 1, 12, 12, 17 };
987     int[] protein = new int[] { 1, 6 };
988     MapList ml = new MapList(codons, protein, 3, 1);
989     assertEquals("[1, 3]", Arrays.toString(ml.locateInFrom(1, 1)));
990     assertEquals("[4, 6]", Arrays.toString(ml.locateInFrom(2, 2)));
991     assertEquals("[7, 9]", Arrays.toString(ml.locateInFrom(3, 3)));
992     assertEquals("[10, 12]", Arrays.toString(ml.locateInFrom(4, 4)));
993     assertEquals("[12, 14]", Arrays.toString(ml.locateInFrom(5, 5)));
994     assertEquals("[15, 17]", Arrays.toString(ml.locateInFrom(6, 6)));
995     assertEquals("[1, 6]", Arrays.toString(ml.locateInFrom(1, 2)));
996     assertEquals("[1, 9]", Arrays.toString(ml.locateInFrom(1, 3)));
997     assertEquals("[1, 12]", Arrays.toString(ml.locateInFrom(1, 4)));
998     assertEquals("[1, 12, 12, 14]", Arrays.toString(ml.locateInFrom(1, 5)));
999     assertEquals("[1, 12, 12, 17]", Arrays.toString(ml.locateInFrom(1, 6)));
1000     assertEquals("[4, 9]", Arrays.toString(ml.locateInFrom(2, 3)));
1001     assertEquals("[7, 12, 12, 17]", Arrays.toString(ml.locateInFrom(3, 6)));
1002
1003     assertNull(ml.locateInFrom(0, 0));
1004     assertNull(ml.locateInFrom(1, 7));
1005     assertNull(ml.locateInFrom(-1, 1));
1006
1007     /*
1008      * gene to CDS...from EMBL:MN908947
1009      */
1010     int [] gene = new int[] { 266, 13468, 13468, 21555 };
1011     int [] cds = new int[] { 1, 21291 };
1012     ml = new MapList(gene, cds, 1, 1);
1013     assertEquals("[13468, 13468]", Arrays.toString(ml.locateInFrom(13203, 13203)));
1014     assertEquals("[13468, 13468]", Arrays.toString(ml.locateInFrom(13204, 13204)));
1015     assertEquals("[13468, 13468]", Arrays.toString(ml.locateInFrom(13203, 13204)));
1016   }
1017
1018   /**
1019    * Test for mapping with overlapping ranges
1020    */
1021   @Test(groups = { "Functional" })
1022   public void testLocateInTo_withOverlap()
1023   {
1024     /*
1025      * gene to protein...
1026      */
1027     int[] codons = new int[] { 1, 12, 12, 17 };
1028     int[] protein = new int[] { 1, 6 };
1029     MapList ml = new MapList(codons, protein, 3, 1);
1030     assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(1, 1)));
1031     assertEquals("[1, 3]", Arrays.toString(ml.locateInTo(3, 8)));
1032     assertEquals("[1, 4]", Arrays.toString(ml.locateInTo(2, 11)));
1033     assertEquals("[1, 4]", Arrays.toString(ml.locateInTo(3, 11)));
1034
1035     // we want base 12 to map to both of the amino acids it codes for
1036     assertEquals("[4, 5]", Arrays.toString(ml.locateInTo(12, 12)));
1037     assertEquals("[4, 5]", Arrays.toString(ml.locateInTo(11, 12)));
1038     assertEquals("[4, 6]", Arrays.toString(ml.locateInTo(11, 15)));
1039     assertEquals("[6, 6]", Arrays.toString(ml.locateInTo(15, 17)));
1040
1041     assertNull(ml.locateInTo(0, 0));
1042     assertNull(ml.locateInTo(1, 18));
1043     assertNull(ml.locateInTo(-1, 1));
1044
1045     /*
1046      * gene to CDS...from EMBL:MN908947
1047      */
1048     int [] gene = new int[] { 266, 13468, 13468, 21555 };
1049     int [] cds = new int[] { 1, 21291 };
1050     ml = new MapList(gene, cds, 1, 1);
1051     assertEquals("[13203, 13204]", Arrays.toString(ml.locateInTo(13468, 13468)));
1052   }
1053
1054   @Test(groups = { "Functional" })
1055   public void testTraverseToPosition()
1056   {
1057     List<int[]> ranges = new ArrayList<>();
1058     assertNull(MapList.traverseToPosition(ranges, 0));
1059
1060     ranges.add(new int[] { 3, 6 });
1061     assertNull(MapList.traverseToPosition(ranges, 0));
1062   }
1063
1064   @Test(groups = { "Functional" })
1065   public void testCountPositions()
1066   {
1067     try
1068     {
1069       MapList.countPositions(null, 1);
1070       fail("expected exception");
1071     } catch (NullPointerException e)
1072     {
1073       // expected
1074     }
1075
1076     List<int[]> intervals = new ArrayList<>();
1077     assertNull(MapList.countPositions(intervals,  1));
1078     
1079     /*
1080      * forward strand
1081      */
1082     intervals.add(new int[] {10, 20});
1083     assertNull(MapList.countPositions(intervals,  9));
1084     assertNull(MapList.countPositions(intervals,  21));
1085     assertArrayEquals(new int[] {1, 1}, MapList.countPositions(intervals,  10));
1086     assertArrayEquals(new int[] {6, 1}, MapList.countPositions(intervals,  15));
1087     assertArrayEquals(new int[] {11, 1}, MapList.countPositions(intervals,  20));
1088
1089     intervals.add(new int[] {25, 25});
1090     assertArrayEquals(new int[] {12, 1}, MapList.countPositions(intervals,  25));
1091
1092     // next interval repeats position 25 - which should be counted twice if traversed
1093     intervals.add(new int[] {25, 26});
1094     assertArrayEquals(new int[] {12, 1}, MapList.countPositions(intervals,  25));
1095     assertArrayEquals(new int[] {14, 1}, MapList.countPositions(intervals,  26));
1096     
1097     /*
1098      * reverse strand
1099      */
1100     intervals.clear();
1101     intervals.add(new int[] {5, -5});
1102     assertNull(MapList.countPositions(intervals,  6));
1103     assertNull(MapList.countPositions(intervals,  -6));
1104     assertArrayEquals(new int[] {1, -1}, MapList.countPositions(intervals,  5));
1105     assertArrayEquals(new int[] {7, -1}, MapList.countPositions(intervals,  -1));
1106     assertArrayEquals(new int[] {11, -1}, MapList.countPositions(intervals,  -5));
1107     
1108     /*
1109      * reverse then forward
1110      */
1111     intervals.add(new int[] {5, 10});
1112     assertArrayEquals(new int[] {13, 1}, MapList.countPositions(intervals,  6));
1113     
1114     /*
1115      * reverse then forward then reverse
1116      */
1117     intervals.add(new int[] {-10, -20});
1118     assertArrayEquals(new int[] {20, -1}, MapList.countPositions(intervals,  -12));
1119     
1120     /*
1121      * an interval [x, x] is treated as forward
1122      */
1123     intervals.add(new int[] {30, 30});
1124     assertArrayEquals(new int[] {29, 1}, MapList.countPositions(intervals,  30));
1125     
1126     /*
1127      * it is the first matched occurrence that is returned
1128      */
1129     intervals.clear();
1130     intervals.add(new int[] {1, 2});
1131     intervals.add(new int[] {2, 3});
1132     assertArrayEquals(new int[] {2, 1}, MapList.countPositions(intervals,  2));
1133     intervals.add(new int[] {-1, -2});
1134     intervals.add(new int[] {-2, -3});
1135     assertArrayEquals(new int[] {6, -1}, MapList.countPositions(intervals,  -2));
1136   }
1137
1138   @Test(groups = { "Functional" })
1139   public void testFindOverlapPositions()
1140   {
1141     List<int[]> ranges = new ArrayList<>();
1142     List<int[]> overlaps = MapList.findOverlapPositions(ranges,  20,  30);
1143     assertTrue(overlaps.isEmpty());
1144     ranges.add(new int[] {15, 25});
1145     overlaps = MapList.findOverlapPositions(ranges,  20,  30);
1146     assertEquals(1, overlaps.size());
1147     assertArrayEquals(new int[] {6, 11}, overlaps.get(0));
1148   }
1149
1150   @Test(groups = { "Functional" })
1151   public void testMapWords()
1152   {
1153     List<int[]> ranges = new ArrayList<>();
1154     
1155     /*
1156      * 1:1 (trivial) case
1157      */
1158     ranges.add(new int[] {2, 4});
1159     ranges.add(new int[] {6, 9});
1160     MapList.mapWords(ranges,  1, 1);
1161     assertEquals(ranges.size(), 2);
1162     assertArrayEquals(new int[] {2, 4}, ranges.get(0));
1163     assertArrayEquals(new int[] {6, 9}, ranges.get(1));
1164     
1165     /*
1166      * 1:3 case (peptide to codon ranges)
1167      */
1168     MapList.mapWords(ranges,  1, 3);
1169     assertEquals(ranges.size(), 2);
1170     assertArrayEquals(new int[] {6, 14}, ranges.get(0));
1171     assertArrayEquals(new int[] {18, 29}, ranges.get(1));
1172     
1173     /*
1174      * 3:1 case (codon or part codon to peptide)
1175      */
1176     ranges.clear();
1177     ranges.add(new int[] {0, 5}); // 2 whole codons
1178     ranges.add(new int[] {7, 11}); // part + whole codon
1179     ranges.add(new int[] {15, 19}); // whole + part codon
1180     ranges.add(new int[] {23, 27}); // part + part codon
1181     ranges.add(new int[] {30, 30}); // first base of codon
1182     ranges.add(new int[] {31, 31}); // second base of codon
1183     ranges.add(new int[] {32, 32}); // third base of codon
1184     MapList.mapWords(ranges,  3, 1);
1185     assertEquals(ranges.size(), 7);
1186     assertArrayEquals(new int[] {0, 1}, ranges.get(0));
1187     assertArrayEquals(new int[] {2, 3}, ranges.get(1));
1188     assertArrayEquals(new int[] {5, 6}, ranges.get(2));
1189     assertArrayEquals(new int[] {7, 9}, ranges.get(3));
1190     assertArrayEquals(new int[] {10, 10}, ranges.get(4));
1191     assertArrayEquals(new int[] {10, 10}, ranges.get(5));
1192     assertArrayEquals(new int[] {10, 10}, ranges.get(6));
1193   }
1194 }