JAL-2089 patch broken merge to master for Release 2.10.0b1
[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.internal.junit.ArrayAsserts.assertArrayEquals;
29
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.List;
33
34 import org.testng.annotations.Test;
35
36 public class MapListTest
37 {
38
39   @Test(groups = { "Functional" })
40   public void testSomething()
41   {
42     MapList ml = new MapList(new int[] { 1, 5, 10, 15, 25, 20 }, new int[] {
43         51, 1 }, 1, 3);
44     MapList ml1 = new MapList(new int[] { 1, 3, 17, 4 },
45             new int[] { 51, 1 }, 1, 3);
46     MapList ml2 = new MapList(new int[] { 1, 60 }, new int[] { 1, 20 }, 3,
47             1);
48     // test internal consistency
49     int to[] = new int[51];
50     testMap(ml, 1, 60);
51     MapList mldna = new MapList(new int[] { 2, 2, 6, 8, 12, 16 }, new int[]
52     { 1, 3 }, 3, 1);
53     int[] frm = mldna.locateInFrom(1, 1);
54     testLocateFrom(mldna, 1, 1, new int[] { 2, 2, 6, 7 });
55     testMap(mldna, 1, 3);
56     /*
57      * for (int from=1; from<=51; from++) { int[] too=ml.shiftTo(from); int[]
58      * toofrom=ml.shiftFrom(too[0]);
59      * System.out.println("ShiftFrom("+from+")=="+too[0]+" %
60      * "+too[1]+"\t+-+\tShiftTo("+too[0]+")=="+toofrom[0]+" % "+toofrom[1]); }
61      */
62   }
63
64   private static void testLocateFrom(MapList mldna, int i, int j, int[] ks)
65   {
66     int[] frm = mldna.locateInFrom(i, j);
67     assertEquals("Failed test locate from " + i + " to " + j,
68             Arrays.toString(frm), Arrays.toString(ks));
69   }
70
71   /**
72    * test routine. not incremental.
73    * 
74    * @param ml
75    * @param fromS
76    * @param fromE
77    */
78   private void testMap(MapList ml, int fromS, int fromE)
79   {
80     // todo convert to JUnit style tests
81     for (int from = 1; from <= 25; from++)
82     {
83       int[] too = ml.shiftFrom(from);
84       System.out.print("ShiftFrom(" + from + ")==");
85       if (too == null)
86       {
87         System.out.print("NaN\n");
88       }
89       else
90       {
91         System.out.print(too[0] + " % " + too[1] + " (" + too[2] + ")");
92         System.out.print("\t+--+\t");
93         int[] toofrom = ml.shiftTo(too[0]);
94         if (toofrom != null)
95         {
96           if (toofrom[0] != from)
97           {
98             System.err.println("Mapping not reflexive:" + from + " "
99                     + too[0] + "->" + toofrom[0]);
100           }
101           System.out.println("ShiftTo(" + too[0] + ")==" + toofrom[0]
102                   + " % " + toofrom[1] + " (" + toofrom[2] + ")");
103         }
104         else
105         {
106           System.out.println("ShiftTo(" + too[0] + ")=="
107                   + "NaN! - not Bijective Mapping!");
108         }
109       }
110     }
111     int mmap[][] = ml.makeFromMap();
112     System.out.println("FromMap : (" + mmap[0][0] + " " + mmap[0][1] + " "
113             + mmap[0][2] + " " + mmap[0][3] + " ");
114     for (int i = 1; i <= mmap[1].length; i++)
115     {
116       if (mmap[1][i - 1] == -1)
117       {
118         System.out.print(i + "=XXX");
119
120       }
121       else
122       {
123         System.out.print(i + "=" + (mmap[0][2] + mmap[1][i - 1]));
124       }
125       if (i % 20 == 0)
126       {
127         System.out.print("\n");
128       }
129       else
130       {
131         System.out.print(",");
132       }
133     }
134     // test range function
135     System.out.print("\nTest locateInFrom\n");
136     {
137       int f = mmap[0][2], t = mmap[0][3];
138       while (f <= t)
139       {
140         System.out.println("Range " + f + " to " + t);
141         int rng[] = ml.locateInFrom(f, t);
142         if (rng != null)
143         {
144           for (int i = 0; i < rng.length; i++)
145           {
146             System.out.print(rng[i] + ((i % 2 == 0) ? "," : ";"));
147           }
148         }
149         else
150         {
151           System.out.println("No range!");
152         }
153         System.out.print("\nReversed\n");
154         rng = ml.locateInFrom(t, f);
155         if (rng != null)
156         {
157           for (int i = 0; i < rng.length; i++)
158           {
159             System.out.print(rng[i] + ((i % 2 == 0) ? "," : ";"));
160           }
161         }
162         else
163         {
164           System.out.println("No range!");
165         }
166         System.out.print("\n");
167         f++;
168         t--;
169       }
170     }
171     System.out.print("\n");
172     mmap = ml.makeToMap();
173     System.out.println("ToMap : (" + mmap[0][0] + " " + mmap[0][1] + " "
174             + mmap[0][2] + " " + mmap[0][3] + " ");
175     for (int i = 1; i <= mmap[1].length; i++)
176     {
177       if (mmap[1][i - 1] == -1)
178       {
179         System.out.print(i + "=XXX");
180
181       }
182       else
183       {
184         System.out.print(i + "=" + (mmap[0][2] + mmap[1][i - 1]));
185       }
186       if (i % 20 == 0)
187       {
188         System.out.print("\n");
189       }
190       else
191       {
192         System.out.print(",");
193       }
194     }
195     System.out.print("\n");
196     // test range function
197     System.out.print("\nTest locateInTo\n");
198     {
199       int f = mmap[0][2], t = mmap[0][3];
200       while (f <= t)
201       {
202         System.out.println("Range " + f + " to " + t);
203         int rng[] = ml.locateInTo(f, t);
204         if (rng != null)
205         {
206           for (int i = 0; i < rng.length; i++)
207           {
208             System.out.print(rng[i] + ((i % 2 == 0) ? "," : ";"));
209           }
210         }
211         else
212         {
213           System.out.println("No range!");
214         }
215         System.out.print("\nReversed\n");
216         rng = ml.locateInTo(t, f);
217         if (rng != null)
218         {
219           for (int i = 0; i < rng.length; i++)
220           {
221             System.out.print(rng[i] + ((i % 2 == 0) ? "," : ";"));
222           }
223         }
224         else
225         {
226           System.out.println("No range!");
227         }
228         f++;
229         t--;
230         System.out.print("\n");
231       }
232     }
233   }
234
235   /**
236    * Tests for method that locates ranges in the 'from' map for given range in
237    * the 'to' map.
238    */
239   @Test(groups = { "Functional" })
240   public void testLocateInFrom_noIntrons()
241   {
242     /*
243      * Simple mapping with no introns
244      */
245     int[] codons = new int[] { 1, 12 };
246     int[] protein = new int[] { 1, 4 };
247     MapList ml = new MapList(codons, protein, 3, 1);
248     assertEquals("[1, 3]", Arrays.toString(ml.locateInFrom(1, 1)));
249     assertEquals("[4, 6]", Arrays.toString(ml.locateInFrom(2, 2)));
250     assertEquals("[7, 9]", Arrays.toString(ml.locateInFrom(3, 3)));
251     assertEquals("[10, 12]", Arrays.toString(ml.locateInFrom(4, 4)));
252     assertEquals("[1, 6]", Arrays.toString(ml.locateInFrom(1, 2)));
253     assertEquals("[1, 9]", Arrays.toString(ml.locateInFrom(1, 3)));
254     assertEquals("[1, 12]", Arrays.toString(ml.locateInFrom(1, 4)));
255     assertEquals("[4, 9]", Arrays.toString(ml.locateInFrom(2, 3)));
256     assertEquals("[4, 12]", Arrays.toString(ml.locateInFrom(2, 4)));
257     assertEquals("[7, 12]", Arrays.toString(ml.locateInFrom(3, 4)));
258     assertEquals("[10, 12]", Arrays.toString(ml.locateInFrom(4, 4)));
259
260     assertNull(ml.locateInFrom(0, 0));
261     assertNull(ml.locateInFrom(1, 5));
262     assertNull(ml.locateInFrom(-1, 1));
263   }
264
265   /**
266    * Tests for method that locates ranges in the 'from' map for given range in
267    * the 'to' map.
268    */
269   @Test(groups = { "Functional" })
270   public void testLocateInFrom_withIntrons()
271   {
272     /*
273      * Exons at positions [2, 3, 5] [6, 7, 9] [10, 12, 14] [16, 17, 18] i.e.
274      * 2-3, 5-7, 9-10, 12-12, 14-14, 16-18
275      */
276     int[] codons = { 2, 3, 5, 7, 9, 10, 12, 12, 14, 14, 16, 18 };
277     int[] protein = { 1, 4 };
278     MapList ml = new MapList(codons, protein, 3, 1);
279     assertEquals("[2, 3, 5, 5]", Arrays.toString(ml.locateInFrom(1, 1)));
280     assertEquals("[6, 7, 9, 9]", Arrays.toString(ml.locateInFrom(2, 2)));
281     assertEquals("[10, 10, 12, 12, 14, 14]",
282             Arrays.toString(ml.locateInFrom(3, 3)));
283     assertEquals("[16, 18]", Arrays.toString(ml.locateInFrom(4, 4)));
284   }
285
286   /**
287    * Tests for method that locates ranges in the 'to' map for given range in the
288    * 'from' map.
289    */
290   @Test(groups = { "Functional" })
291   public void testLocateInTo_noIntrons()
292   {
293     /*
294      * Simple mapping with no introns
295      */
296     int[] codons = new int[] { 1, 12 };
297     int[] protein = new int[] { 1, 4 };
298     MapList ml = new MapList(codons, protein, 3, 1);
299     assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(1, 3)));
300     assertEquals("[2, 2]", Arrays.toString(ml.locateInTo(4, 6)));
301     assertEquals("[3, 3]", Arrays.toString(ml.locateInTo(7, 9)));
302     assertEquals("[4, 4]", Arrays.toString(ml.locateInTo(10, 12)));
303     assertEquals("[1, 2]", Arrays.toString(ml.locateInTo(1, 6)));
304     assertEquals("[1, 3]", Arrays.toString(ml.locateInTo(1, 9)));
305     assertEquals("[1, 4]", Arrays.toString(ml.locateInTo(1, 12)));
306     assertEquals("[2, 2]", Arrays.toString(ml.locateInTo(4, 6)));
307     assertEquals("[2, 4]", Arrays.toString(ml.locateInTo(4, 12)));
308
309     /*
310      * A part codon is treated as if a whole one.
311      */
312     assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(1, 1)));
313     assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(1, 2)));
314     assertEquals("[1, 2]", Arrays.toString(ml.locateInTo(1, 4)));
315     assertEquals("[1, 3]", Arrays.toString(ml.locateInTo(2, 8)));
316     assertEquals("[1, 4]", Arrays.toString(ml.locateInTo(3, 11)));
317     assertEquals("[2, 4]", Arrays.toString(ml.locateInTo(5, 11)));
318
319     assertNull(ml.locateInTo(0, 0));
320     assertNull(ml.locateInTo(1, 13));
321     assertNull(ml.locateInTo(-1, 1));
322   }
323
324   /**
325    * Tests for method that locates ranges in the 'to' map for given range in the
326    * 'from' map.
327    */
328   @Test(groups = { "Functional" })
329   public void testLocateInTo_withIntrons()
330   {
331     /*
332      * Exons at positions [2, 3, 5] [6, 7, 9] [10, 12, 14] [16, 17, 18] i.e.
333      * 2-3, 5-7, 9-10, 12-12, 14-14, 16-18
334      */
335     int[] codons = { 2, 3, 5, 7, 9, 10, 12, 12, 14, 14, 16, 18 };
336     /*
337      * Mapped proteins at positions 1, 3, 4, 6 in the sequence
338      */
339     int[] protein = { 1, 1, 3, 4, 6, 6 };
340     MapList ml = new MapList(codons, protein, 3, 1);
341
342     /*
343      * Can't map from an unmapped position
344      */
345     assertNull(ml.locateInTo(1, 2));
346     assertNull(ml.locateInTo(2, 4));
347     assertNull(ml.locateInTo(4, 4));
348
349     /*
350      * Valid range or subrange of codon1 maps to protein1.
351      */
352     assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(2, 2)));
353     assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(3, 3)));
354     assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(3, 5)));
355     assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(2, 3)));
356     assertEquals("[1, 1]", Arrays.toString(ml.locateInTo(2, 5)));
357
358     // codon position 6 starts the next protein:
359     assertEquals("[1, 1, 3, 3]", Arrays.toString(ml.locateInTo(3, 6)));
360
361     // codon positions 7 to 17 (part) cover proteins 2/3/4 at positions 3/4/6
362     assertEquals("[3, 4, 6, 6]", Arrays.toString(ml.locateInTo(7, 17)));
363
364   }
365
366   /**
367    * Test equals method.
368    */
369   @Test(groups = { "Functional" })
370   public void testEquals()
371   {
372     int[] codons = new int[] { 2, 3, 5, 7, 9, 10, 12, 12, 14, 14, 16, 18 };
373     int[] protein = new int[] { 1, 4 };
374     MapList ml = new MapList(codons, protein, 3, 1);
375     MapList ml1 = new MapList(codons, protein, 3, 1); // same values
376     MapList ml2 = new MapList(codons, protein, 2, 1); // fromRatio differs
377     MapList ml3 = new MapList(codons, protein, 3, 2); // toRatio differs
378     codons[2] = 4;
379     MapList ml6 = new MapList(codons, protein, 3, 1); // fromShifts differ
380     protein[1] = 3;
381     MapList ml7 = new MapList(codons, protein, 3, 1); // toShifts differ
382
383     assertTrue(ml.equals(ml));
384     assertTrue(ml.equals(ml1));
385     assertTrue(ml1.equals(ml));
386
387     assertFalse(ml.equals(null));
388     assertFalse(ml.equals("hello"));
389     assertFalse(ml.equals(ml2));
390     assertFalse(ml.equals(ml3));
391     assertFalse(ml.equals(ml6));
392     assertFalse(ml.equals(ml7));
393     assertFalse(ml6.equals(ml7));
394
395     try
396     {
397       MapList ml4 = new MapList(codons, null, 3, 1); // toShifts null
398       assertFalse(ml.equals(ml4));
399     } catch (NullPointerException e)
400     {
401       // actually thrown by constructor before equals can be called
402     }
403     try
404     {
405       MapList ml5 = new MapList(null, protein, 3, 1); // fromShifts null
406       assertFalse(ml.equals(ml5));
407     } catch (NullPointerException e)
408     {
409       // actually thrown by constructor before equals can be called
410     }
411   }
412
413   /**
414    * Test for the method that flattens a list of ranges into a single array.
415    */
416   @Test(groups = { "Functional" })
417   public void testGetRanges()
418   {
419     List<int[]> ranges = new ArrayList<int[]>();
420     ranges.add(new int[] { 2, 3 });
421     ranges.add(new int[] { 5, 6 });
422     assertEquals("[2, 3, 5, 6]", Arrays.toString(MapList.getRanges(ranges)));
423   }
424
425   /**
426    * Check state after construction
427    */
428   @Test(groups = { "Functional" })
429   public void testConstructor()
430   {
431     int[] codons = { 2, 3, 5, 7, 9, 10, 12, 12, 14, 14, 16, 18 };
432     int[] protein = { 1, 1, 3, 4, 6, 6 };
433     MapList ml = new MapList(codons, protein, 3, 1);
434     assertEquals(3, ml.getFromRatio());
435     assertEquals(2, ml.getFromLowest());
436     assertEquals(18, ml.getFromHighest());
437     assertEquals(1, ml.getToLowest());
438     assertEquals(6, ml.getToHighest());
439     assertEquals("{[2, 3], [5, 7], [9, 10], [12, 12], [14, 14], [16, 18]}",
440             prettyPrint(ml.getFromRanges()));
441     assertEquals("{[1, 1], [3, 4], [6, 6]}", prettyPrint(ml.getToRanges()));
442
443     /*
444      * Also copy constructor
445      */
446     MapList ml2 = new MapList(ml);
447     assertEquals(3, ml2.getFromRatio());
448     assertEquals(2, ml2.getFromLowest());
449     assertEquals(18, ml2.getFromHighest());
450     assertEquals(1, ml2.getToLowest());
451     assertEquals(6, ml2.getToHighest());
452     assertEquals("{[2, 3], [5, 7], [9, 10], [12, 12], [14, 14], [16, 18]}",
453             prettyPrint(ml2.getFromRanges()));
454     assertEquals("{[1, 1], [3, 4], [6, 6]}", prettyPrint(ml2.getToRanges()));
455
456     /*
457      * reverse direction
458      */
459     codons = new int[] { 9, 6 };
460     protein = new int[] { 100, 91, 80, 79 };
461     ml = new MapList(codons, protein, 3, 1);
462     assertEquals(6, ml.getFromLowest());
463     assertEquals(9, ml.getFromHighest());
464     assertEquals(79, ml.getToLowest());
465     assertEquals(100, ml.getToHighest());
466   }
467
468   /**
469    * Test constructor can merge consecutive ranges
470    */
471   @Test(groups = { "Functional" })
472   public void testConstructor_mergeRanges()
473   {
474     int[] codons = { 2, 3, 3, 7, 9, 10, 12, 12, 14, 14, 16, 17 };
475     int[] protein = { 1, 1, 1, 3, 6, 6 };
476     MapList ml = new MapList(codons, protein, 3, 1);
477     assertEquals(3, ml.getFromRatio());
478     assertEquals(2, ml.getFromLowest());
479     assertEquals(17, ml.getFromHighest());
480     assertEquals(1, ml.getToLowest());
481     assertEquals(6, ml.getToHighest());
482     assertEquals("{[2, 7], [9, 10], [12, 12], [14, 14], [16, 17]}",
483             prettyPrint(ml.getFromRanges()));
484     assertEquals("{[1, 3], [6, 6]}", prettyPrint(ml.getToRanges()));
485   }
486
487   /**
488    * Convert a List of {[i, j], [k, l], ...} to "[[i, j], [k, l], ...]"
489    * 
490    * @param ranges
491    * @return
492    */
493   private String prettyPrint(List<int[]> ranges)
494   {
495     StringBuilder sb = new StringBuilder(ranges.size() * 5);
496     boolean first = true;
497     sb.append("{");
498     for (int[] range : ranges)
499     {
500       if (!first)
501       {
502         sb.append(", ");
503       }
504       sb.append(Arrays.toString(range));
505       first = false;
506     }
507     sb.append("}");
508     return sb.toString();
509   }
510
511   /**
512    * Test the method that creates an inverse mapping
513    */
514   @Test(groups = { "Functional" })
515   public void testGetInverse()
516   {
517     int[] codons = { 2, 3, 5, 7, 9, 10, 12, 12, 14, 14, 16, 18 };
518     int[] protein = { 1, 1, 3, 4, 6, 6 };
519
520     MapList ml = new MapList(codons, protein, 3, 1);
521     MapList ml2 = ml.getInverse();
522     assertEquals(ml.getFromRatio(), ml2.getToRatio());
523     assertEquals(ml.getFromRatio(), ml2.getToRatio());
524     assertEquals(ml.getToHighest(), ml2.getFromHighest());
525     assertEquals(ml.getFromHighest(), ml2.getToHighest());
526     assertEquals(prettyPrint(ml.getFromRanges()),
527             prettyPrint(ml2.getToRanges()));
528     assertEquals(prettyPrint(ml.getToRanges()),
529             prettyPrint(ml2.getFromRanges()));
530   }
531
532   @Test(groups = { "Functional" })
533   public void testToString()
534   {
535     MapList ml = new MapList(new int[] { 1, 5, 10, 15, 25, 20 }, new int[] {
536         51, 1 }, 1, 3);
537     String s = ml.toString();
538     assertEquals("[ [1, 5] [10, 15] [25, 20] ] 1:3 to [ [51, 1] ]", s);
539   }
540
541   @Test(groups = { "Functional" })
542   public void testAddMapList()
543   {
544     MapList ml = new MapList(new int[] { 11, 15, 20, 25, 35, 30 },
545             new int[] { 72, 22 }, 1, 3);
546     assertEquals(11, ml.getFromLowest());
547     assertEquals(35, ml.getFromHighest());
548     assertEquals(22, ml.getToLowest());
549     assertEquals(72, ml.getToHighest());
550
551     MapList ml2 = new MapList(new int[] { 2, 4, 37, 40 }, new int[] { 12,
552         17, 78, 83, 88, 96 }, 1, 3);
553     ml.addMapList(ml2);
554     assertEquals(2, ml.getFromLowest());
555     assertEquals(40, ml.getFromHighest());
556     assertEquals(12, ml.getToLowest());
557     assertEquals(96, ml.getToHighest());
558
559     String s = ml.toString();
560     assertEquals(
561             "[ [11, 15] [20, 25] [35, 30] [2, 4] [37, 40] ] 1:3 to [ [72, 22] [12, 17] [78, 83] [88, 96] ]",
562             s);
563   }
564
565   /**
566    * Test that confirms adding a map twice does nothing
567    */
568   @Test(groups = { "Functional" })
569   public void testAddMapList_sameMap()
570   {
571     MapList ml = new MapList(new int[] { 11, 15, 20, 25, 35, 30 },
572             new int[] { 72, 22 }, 1, 3);
573     String before = ml.toString();
574     ml.addMapList(ml);
575     assertEquals(before, ml.toString());
576     ml.addMapList(new MapList(ml));
577     assertEquals(before, ml.toString());
578   }
579
580   @Test(groups = { "Functional" })
581   public void testAddMapList_contiguous()
582   {
583     MapList ml = new MapList(new int[] { 11, 15 }, new int[] { 72, 58 }, 1,
584             3);
585
586     MapList ml2 = new MapList(new int[] { 15, 16 }, new int[] { 58, 53 },
587             1, 3);
588     ml.addMapList(ml2);
589     assertEquals("[ [11, 16] ] 1:3 to [ [72, 53] ]", ml.toString());
590   }
591
592   @Test(groups = "Functional")
593   public void testAddRange()
594   {
595     int[] range = { 1, 5 };
596     List<int[]> ranges = new ArrayList<int[]>();
597
598     // add to empty list:
599     MapList.addRange(range, ranges);
600     assertEquals(1, ranges.size());
601     assertSame(range, ranges.get(0));
602
603     // extend contiguous (same position):
604     MapList.addRange(new int[] { 5, 10 }, ranges);
605     assertEquals(1, ranges.size());
606     assertEquals(1, ranges.get(0)[0]);
607     assertEquals(10, ranges.get(0)[1]);
608
609     // extend contiguous (next position):
610     MapList.addRange(new int[] { 11, 15 }, ranges);
611     assertEquals(1, ranges.size());
612     assertEquals(1, ranges.get(0)[0]);
613     assertEquals(15, ranges.get(0)[1]);
614
615     // change direction: range is not merged:
616     MapList.addRange(new int[] { 16, 10 }, ranges);
617     assertEquals(2, ranges.size());
618     assertEquals(16, ranges.get(1)[0]);
619     assertEquals(10, ranges.get(1)[1]);
620
621     // extend reverse contiguous (same position):
622     MapList.addRange(new int[] { 10, 8 }, ranges);
623     assertEquals(2, ranges.size());
624     assertEquals(16, ranges.get(1)[0]);
625     assertEquals(8, ranges.get(1)[1]);
626
627     // extend reverse contiguous (next position):
628     MapList.addRange(new int[] { 7, 6 }, ranges);
629     assertEquals(2, ranges.size());
630     assertEquals(16, ranges.get(1)[0]);
631     assertEquals(6, ranges.get(1)[1]);
632
633     // change direction: range is not merged:
634     MapList.addRange(new int[] { 6, 9 }, ranges);
635     assertEquals(3, ranges.size());
636     assertEquals(6, ranges.get(2)[0]);
637     assertEquals(9, ranges.get(2)[1]);
638
639     // not contiguous: not merged
640     MapList.addRange(new int[] { 11, 12 }, ranges);
641     assertEquals(4, ranges.size());
642     assertEquals(11, ranges.get(3)[0]);
643     assertEquals(12, ranges.get(3)[1]);
644   }
645
646   /**
647    * Check state after construction
648    */
649   @Test(groups = { "Functional" })
650   public void testConstructor_withLists()
651   {
652     /*
653      * reverse direction
654      */
655     int[][] codons = new int[][] { { 9, 6 } };
656     int[][] protein = new int[][] { { 100, 91 }, { 80, 79 } };
657     MapList ml = new MapList(Arrays.asList(codons), Arrays.asList(protein),
658             3, 1);
659     assertEquals(6, ml.getFromLowest());
660     assertEquals(9, ml.getFromHighest());
661     assertEquals(79, ml.getToLowest());
662     assertEquals(100, ml.getToHighest());
663   }
664
665   /**
666    * Test that method that inspects for the (first) forward or reverse from
667    * range. Single position ranges are ignored.
668    */
669   @Test(groups = { "Functional" })
670   public void testIsFromForwardStrand()
671   {
672     // [3-9] declares forward strand
673     MapList ml = new MapList(new int[] { 2, 2, 3, 9, 12, 11 }, new int[] {
674         20, 11 }, 1, 1);
675     assertTrue(ml.isFromForwardStrand());
676
677     // [11-5] declares reverse strand ([13-14] is ignored)
678     ml = new MapList(new int[] { 2, 2, 11, 5, 13, 14 },
679             new int[] { 20, 11 }, 1, 1);
680     assertFalse(ml.isFromForwardStrand());
681
682     // all single position ranges - defaults to forward strand
683     ml = new MapList(new int[] { 2, 2, 4, 4, 6, 6 }, new int[] { 3, 1 }, 1,
684             1);
685     assertTrue(ml.isFromForwardStrand());
686   }
687
688   /**
689    * Test the method that merges a list of ranges where possible
690    */
691   @Test(groups = { "Functional" })
692   public void testCoalesceRanges()
693   {
694     assertNull(MapList.coalesceRanges(null));
695     List<int[]> ranges = new ArrayList<int[]>();
696     assertSame(ranges, MapList.coalesceRanges(ranges));
697     ranges.add(new int[] { 1, 3 });
698     assertSame(ranges, MapList.coalesceRanges(ranges));
699
700     // add non-contiguous range:
701     ranges.add(new int[] { 5, 6 });
702     assertSame(ranges, MapList.coalesceRanges(ranges));
703
704     // 'contiguous' range in opposite direction is not merged:
705     ranges.add(new int[] { 7, 6 });
706     assertSame(ranges, MapList.coalesceRanges(ranges));
707
708     // merging in forward direction:
709     ranges.clear();
710     ranges.add(new int[] { 1, 3 });
711     ranges.add(new int[] { 4, 5 });
712     ranges.add(new int[] { 5, 5 });
713     ranges.add(new int[] { 5, 7 });
714     List<int[]> merged = MapList.coalesceRanges(ranges);
715     assertEquals(1, merged.size());
716     assertArrayEquals(new int[] { 1, 7 }, merged.get(0));
717     // verify input list is unchanged
718     assertEquals(4, ranges.size());
719     assertArrayEquals(new int[] { 1, 3 }, ranges.get(0));
720     assertArrayEquals(new int[] { 4, 5 }, ranges.get(1));
721     assertArrayEquals(new int[] { 5, 5 }, ranges.get(2));
722     assertArrayEquals(new int[] { 5, 7 }, ranges.get(3));
723
724     // merging in reverse direction:
725     ranges.clear();
726     ranges.add(new int[] { 7, 5 });
727     ranges.add(new int[] { 5, 4 });
728     ranges.add(new int[] { 4, 4 });
729     ranges.add(new int[] { 3, 1 });
730     merged = MapList.coalesceRanges(ranges);
731     assertEquals(1, merged.size());
732     assertArrayEquals(new int[] { 7, 1 }, merged.get(0));
733
734     // merging with switches of direction:
735     ranges.clear();
736     ranges.add(new int[] { 1, 3 });
737     ranges.add(new int[] { 4, 5 });
738     ranges.add(new int[] { 5, 5 });
739     ranges.add(new int[] { 6, 6 });
740     ranges.add(new int[] { 12, 10 });
741     ranges.add(new int[] { 9, 8 });
742     ranges.add(new int[] { 8, 8 });
743     ranges.add(new int[] { 7, 7 });
744     merged = MapList.coalesceRanges(ranges);
745     assertEquals(2, merged.size());
746     assertArrayEquals(new int[] { 1, 6 }, merged.get(0));
747     assertArrayEquals(new int[] { 12, 7 }, merged.get(1));
748   }
749
750   /**
751    * Test the method that merges a list of ranges where possible
752    */
753   @Test(groups = { "Functional" })
754   public void testCoalesceRanges_withOverlap()
755   {
756     List<int[]> ranges = new ArrayList<int[]>();
757     ranges.add(new int[] { 1, 3 });
758     ranges.add(new int[] { 2, 5 });
759
760     /*
761      * [2, 5] should extend [1, 3]
762      */
763     List<int[]> merged = MapList.coalesceRanges(ranges);
764     assertEquals(1, merged.size());
765     assertArrayEquals(new int[] { 1, 5 }, merged.get(0));
766
767     /*
768      * a subsumed interval should be dropped
769      */
770     ranges.clear();
771     ranges.add(new int[] { 1, 6 });
772     ranges.add(new int[] { 2, 4 });
773     merged = MapList.coalesceRanges(ranges);
774     assertEquals(1, merged.size());
775     assertArrayEquals(new int[] { 1, 6 }, merged.get(0));
776
777     ranges.clear();
778     ranges.add(new int[] { 1, 5 });
779     ranges.add(new int[] { 1, 6 });
780     merged = MapList.coalesceRanges(ranges);
781     assertEquals(1, merged.size());
782     assertArrayEquals(new int[] { 1, 6 }, merged.get(0));
783
784     /*
785      * merge duplicate ranges
786      */
787     ranges.clear();
788     ranges.add(new int[] { 1, 3 });
789     ranges.add(new int[] { 1, 3 });
790     merged = MapList.coalesceRanges(ranges);
791     assertEquals(1, merged.size());
792     assertArrayEquals(new int[] { 1, 3 }, merged.get(0));
793
794     /*
795      * reverse direction
796      */
797     ranges.clear();
798     ranges.add(new int[] { 9, 5 });
799     ranges.add(new int[] { 9, 4 });
800     ranges.add(new int[] { 8, 3 });
801     ranges.add(new int[] { 3, 2 });
802     ranges.add(new int[] { 1, 0 });
803     merged = MapList.coalesceRanges(ranges);
804     assertEquals(1, merged.size());
805     assertArrayEquals(new int[] { 9, 0 }, merged.get(0));
806   }
807 }