Merge branch 'develop' into trialMerge
[jalview.git] / test / jalview / datamodel / AlignmentTest.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.datamodel;
22
23 import static org.testng.AssertJUnit.assertEquals;
24 import static org.testng.AssertJUnit.assertFalse;
25 import static org.testng.AssertJUnit.assertNotNull;
26 import static org.testng.AssertJUnit.assertNull;
27 import static org.testng.AssertJUnit.assertSame;
28 import static org.testng.AssertJUnit.assertTrue;
29
30 import jalview.datamodel.AlignedCodonFrame.SequenceToSequenceMapping;
31 import jalview.io.DataSourceType;
32 import jalview.io.FileFormat;
33 import jalview.io.FileFormatI;
34 import jalview.io.FormatAdapter;
35 import jalview.util.MapList;
36
37 import java.io.IOException;
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 import java.util.Iterator;
41 import java.util.List;
42
43 import org.testng.Assert;
44 import org.testng.annotations.BeforeMethod;
45 import org.testng.annotations.Test;
46
47 /**
48  * Unit tests for Alignment datamodel.
49  * 
50  * @author gmcarstairs
51  *
52  */
53 public class AlignmentTest
54 {
55   // @formatter:off
56   private static final String TEST_DATA = 
57           "# STOCKHOLM 1.0\n" +
58           "#=GS D.melanogaster.1 AC AY119185.1/838-902\n" +
59           "#=GS D.melanogaster.2 AC AC092237.1/57223-57161\n" +
60           "#=GS D.melanogaster.3 AC AY060611.1/560-627\n" +
61           "D.melanogaster.1          G.AGCC.CU...AUGAUCGA\n" +
62           "#=GR D.melanogaster.1 SS  ................((((\n" +
63           "D.melanogaster.2          C.AUUCAACU.UAUGAGGAU\n" +
64           "#=GR D.melanogaster.2 SS  ................((((\n" +
65           "D.melanogaster.3          G.UGGCGCU..UAUGACGCA\n" +
66           "#=GR D.melanogaster.3 SS  (.(((...(....(((((((\n" +
67           "//";
68
69   private static final String AA_SEQS_1 = 
70           ">Seq1Name/5-8\n" +
71           "K-QY--L\n" +
72           ">Seq2Name/12-15\n" +
73           "-R-FP-W-\n";
74
75   private static final String CDNA_SEQS_1 = 
76           ">Seq1Name/100-111\n" +
77           "AC-GG--CUC-CAA-CT\n" +
78           ">Seq2Name/200-211\n" +
79           "-CG-TTA--ACG---AAGT\n";
80
81   private static final String CDNA_SEQS_2 = 
82           ">Seq1Name/50-61\n" +
83           "GCTCGUCGTACT\n" +
84           ">Seq2Name/60-71\n" +
85           "GGGTCAGGCAGT\n";
86   // @formatter:on
87
88   private AlignmentI al;
89
90   /**
91    * Helper method to load an alignment and ensure dataset sequences are set up.
92    * 
93    * @param data
94    * @param format
95    *          TODO
96    * @return
97    * @throws IOException
98    */
99   protected AlignmentI loadAlignment(final String data, FileFormatI format)
100           throws IOException
101   {
102     AlignmentI a = new FormatAdapter().readFile(data, DataSourceType.PASTE,
103             format);
104     a.setDataset(null);
105     return a;
106   }
107
108   /**
109    * assert wrapper: tests all references in the given alignment are consistent
110    * 
111    * @param alignment
112    */
113   public static void assertAlignmentDatasetRefs(AlignmentI alignment)
114   {
115     verifyAlignmentDatasetRefs(alignment, true, null);
116   }
117
118   /**
119    * assert wrapper: tests all references in the given alignment are consistent
120    * 
121    * @param alignment
122    * @param message
123    *          - prefixed to any assert failed messages
124    */
125   public static void assertAlignmentDatasetRefs(AlignmentI alignment,
126           String message)
127   {
128     verifyAlignmentDatasetRefs(alignment, true, message);
129   }
130
131   /**
132    * verify sequence and dataset references are properly contained within
133    * dataset
134    * 
135    * @param alignment
136    *          - the alignmentI object to verify (either alignment or dataset)
137    * @param raiseAssert
138    *          - when set, testng assertions are raised.
139    * @param message
140    *          - null or a string message to prepend to the assert failed
141    *          messages.
142    * @return true if alignment references were in order, otherwise false.
143    */
144   public static boolean verifyAlignmentDatasetRefs(AlignmentI alignment,
145           boolean raiseAssert, String message)
146   {
147     if (message == null)
148     {
149       message = "";
150     }
151     if (alignment == null)
152     {
153       if (raiseAssert)
154       {
155         Assert.fail(message + "Alignment for verification was null.");
156       }
157       return false;
158     }
159     if (alignment.getDataset() != null)
160     {
161       AlignmentI dataset = alignment.getDataset();
162       // check all alignment sequences have their dataset within the dataset
163       for (SequenceI seq : alignment.getSequences())
164       {
165         SequenceI seqds = seq.getDatasetSequence();
166         if (seqds.getDatasetSequence() != null)
167         {
168           if (raiseAssert)
169           {
170             Assert.fail(message
171                     + " Alignment contained a sequence who's dataset sequence has a second dataset reference.");
172           }
173           return false;
174         }
175         if (dataset.findIndex(seqds) == -1)
176         {
177           if (raiseAssert)
178           {
179             Assert.fail(message
180                     + " Alignment contained a sequence who's dataset sequence was not in the dataset.");
181           }
182           return false;
183         }
184       }
185       return verifyAlignmentDatasetRefs(alignment.getDataset(),
186               raiseAssert, message);
187     }
188     else
189     {
190       int dsp = -1;
191       // verify all dataset sequences
192       for (SequenceI seqds : alignment.getSequences())
193       {
194         dsp++;
195         if (seqds.getDatasetSequence() != null)
196         {
197           if (raiseAssert)
198           {
199             Assert.fail(message
200                     + " Dataset contained a sequence with non-null dataset reference (ie not a dataset sequence!)");
201           }
202           return false;
203         }
204         int foundp = alignment.findIndex(seqds);
205         if (foundp != dsp)
206         {
207           if (raiseAssert)
208           {
209             Assert.fail(message
210                     + " Dataset sequence array contains a reference at "
211                     + dsp + " to a sequence first seen at " + foundp + " ("
212                     + seqds.toString() + ")");
213           }
214           return false;
215         }
216         if (seqds.getDBRefs() != null)
217         {
218           for (DBRefEntry dbr : seqds.getDBRefs())
219           {
220             if (dbr.getMap() != null)
221             {
222               SequenceI seqdbrmapto = dbr.getMap().getTo();
223               if (seqdbrmapto != null)
224               {
225                 if (seqdbrmapto.getDatasetSequence() != null)
226                 {
227                   if (raiseAssert)
228                   {
229                     Assert.fail(message
230                             + " DBRefEntry for sequence in alignment had map to sequence which was not a dataset sequence");
231                   }
232                   return false;
233
234                 }
235                 if (alignment.findIndex(dbr.getMap().getTo()) == -1)
236                 {
237                   if (raiseAssert)
238                   {
239                     Assert.fail(message
240                             + " DBRefEntry for sequence in alignment had map to sequence not in dataset");
241                   }
242                   return false;
243                 }
244               }
245             }
246           }
247         }
248       }
249       // finally, verify codonmappings involve only dataset sequences.
250       if (alignment.getCodonFrames() != null)
251       {
252         for (AlignedCodonFrame alc : alignment.getCodonFrames())
253         {
254           for (SequenceToSequenceMapping ssm : alc.getMappings())
255           {
256             if (ssm.getFromSeq().getDatasetSequence() != null)
257             {
258               if (raiseAssert)
259               {
260                 Assert.fail(message
261                         + " CodonFrame-SSM-FromSeq is not a dataset sequence");
262               }
263               return false;
264             }
265             if (alignment.findIndex(ssm.getFromSeq()) == -1)
266             {
267
268               if (raiseAssert)
269               {
270                 Assert.fail(message
271                         + " CodonFrame-SSM-FromSeq is not contained in dataset");
272               }
273               return false;
274             }
275             if (ssm.getMapping().getTo().getDatasetSequence() != null)
276             {
277               if (raiseAssert)
278               {
279                 Assert.fail(message
280                         + " CodonFrame-SSM-Mapping-ToSeq is not a dataset sequence");
281               }
282               return false;
283             }
284             if (alignment.findIndex(ssm.getMapping().getTo()) == -1)
285             {
286
287               if (raiseAssert)
288               {
289                 Assert.fail(message
290                         + " CodonFrame-SSM-Mapping-ToSeq is not contained in dataset");
291               }
292               return false;
293             }
294           }
295         }
296       }
297     }
298     return true; // all relationships verified!
299   }
300
301   /**
302    * call verifyAlignmentDatasetRefs with and without assertion raising enabled,
303    * to check expected pass/fail actually occurs in both conditions
304    * 
305    * @param al
306    * @param expected
307    * @param msg
308    */
309   private void assertVerifyAlignment(AlignmentI al, boolean expected,
310           String msg)
311   {
312     if (expected)
313     {
314       try
315       {
316
317         Assert.assertTrue(verifyAlignmentDatasetRefs(al, true, null),
318                 "Valid test alignment failed when raiseAsserts enabled:"
319                         + msg);
320       } catch (AssertionError ae)
321       {
322         ae.printStackTrace();
323         Assert.fail(
324                 "Valid test alignment raised assertion errors when raiseAsserts enabled: "
325                         + msg, ae);
326       }
327       // also check validation passes with asserts disabled
328       Assert.assertTrue(verifyAlignmentDatasetRefs(al, false, null),
329               "Valid test alignment tested false when raiseAsserts disabled:"
330                       + msg);
331     }
332     else
333     {
334       boolean assertRaised = false;
335       try
336       {
337         verifyAlignmentDatasetRefs(al, true, null);
338       } catch (AssertionError ae)
339       {
340         // expected behaviour
341         assertRaised = true;
342       }
343       if (!assertRaised)
344       {
345         Assert.fail("Invalid test alignment passed when raiseAsserts enabled:"
346                 + msg);
347       }
348       // also check validation passes with asserts disabled
349       Assert.assertFalse(verifyAlignmentDatasetRefs(al, false, null),
350               "Invalid test alignment tested true when raiseAsserts disabled:"
351                       + msg);
352     }
353   }
354
355   @Test(groups = { "Functional" })
356   public void testVerifyAlignmentDatasetRefs()
357   {
358     SequenceI sq1 = new Sequence("sq1", "ASFDD"), sq2 = new Sequence("sq2",
359             "TTTTTT");
360
361     // construct simple valid alignment dataset
362     Alignment al = new Alignment(new SequenceI[] { sq1, sq2 });
363     // expect this to pass
364     assertVerifyAlignment(al, true, "Simple valid alignment didn't verify");
365
366     // check test for sequence->datasetSequence validity
367     sq1.setDatasetSequence(sq2);
368     assertVerifyAlignment(al, false,
369             "didn't detect dataset sequence with a dataset sequence reference.");
370
371     sq1.setDatasetSequence(null);
372     assertVerifyAlignment(
373             al,
374             true,
375             "didn't reinstate validity after nulling dataset sequence dataset reference");
376
377     // now create dataset and check again
378     al.createDatasetAlignment();
379     assertNotNull(al.getDataset());
380
381     assertVerifyAlignment(al, true,
382             "verify failed after createDatasetAlignment");
383
384     // create a dbref on sq1 with a sequence ref to sq2
385     DBRefEntry dbrs1tos2 = new DBRefEntry("UNIPROT", "1", "Q111111");
386     dbrs1tos2.setMap(new Mapping(sq2.getDatasetSequence(),
387             new int[] { 1, 5 }, new int[] { 2, 6 }, 1, 1));
388     sq1.getDatasetSequence().addDBRef(dbrs1tos2);
389     assertVerifyAlignment(al, true,
390             "verify failed after addition of valid DBRefEntry/map");
391     // now create a dbref on a new sequence which maps to another sequence
392     // outside of the dataset
393     SequenceI sqout = new Sequence("sqout", "ututututucagcagcag"), sqnew = new Sequence(
394             "sqnew", "EEERRR");
395     DBRefEntry sqnewsqout = new DBRefEntry("ENAFOO", "1", "R000001");
396     sqnewsqout.setMap(new Mapping(sqout, new int[] { 1, 6 }, new int[] { 1,
397         18 }, 1, 3));
398     al.getDataset().addSequence(sqnew);
399
400     assertVerifyAlignment(al, true,
401             "verify failed after addition of new sequence to dataset");
402     // now start checking exception conditions
403     sqnew.addDBRef(sqnewsqout);
404     assertVerifyAlignment(
405             al,
406             false,
407             "verify passed when a dbref with map to sequence outside of dataset was added");
408     // make the verify pass by adding the outsider back in
409     al.getDataset().addSequence(sqout);
410     assertVerifyAlignment(al, true,
411             "verify should have passed after adding dbref->to sequence in to dataset");
412     // and now the same for a codon mapping...
413     SequenceI sqanotherout = new Sequence("sqanotherout",
414             "aggtutaggcagcagcag");
415
416     AlignedCodonFrame alc = new AlignedCodonFrame();
417     alc.addMap(sqanotherout, sqnew, new MapList(new int[] { 1, 6 },
418             new int[] { 1, 18 }, 3, 1));
419
420     al.addCodonFrame(alc);
421     Assert.assertEquals(al.getDataset().getCodonFrames().size(), 1);
422
423     assertVerifyAlignment(
424             al,
425             false,
426             "verify passed when alCodonFrame mapping to sequence outside of dataset was added");
427     // make the verify pass by adding the outsider back in
428     al.getDataset().addSequence(sqanotherout);
429     assertVerifyAlignment(
430             al,
431             true,
432             "verify should have passed once all sequences involved in alCodonFrame were added to dataset");
433     al.getDataset().addSequence(sqanotherout);
434     assertVerifyAlignment(al, false,
435             "verify should have failed when a sequence was added twice to the dataset");
436     al.getDataset().deleteSequence(sqanotherout);
437     assertVerifyAlignment(al, true,
438             "verify should have passed after duplicate entry for sequence was removed");
439   }
440
441   /**
442    * checks that the sequence data for an alignment's dataset is non-redundant.
443    * Fails if there are sequences with same id, sequence, start, and.
444    */
445
446   public static void assertDatasetIsNormalised(AlignmentI al)
447   {
448     assertDatasetIsNormalised(al, null);
449   }
450
451   /**
452    * checks that the sequence data for an alignment's dataset is non-redundant.
453    * Fails if there are sequences with same id, sequence, start, and.
454    * 
455    * @param al
456    *          - alignment to verify
457    * @param message
458    *          - null or message prepended to exception message.
459    */
460   public static void assertDatasetIsNormalised(AlignmentI al, String message)
461   {
462     if (al.getDataset() != null)
463     {
464       assertDatasetIsNormalised(al.getDataset(), message);
465       return;
466     }
467     /*
468      * look for pairs of sequences with same ID, start, end, and sequence
469      */
470     List<SequenceI> seqSet = al.getSequences();
471     for (int p = 0; p < seqSet.size(); p++)
472     {
473       SequenceI pSeq = seqSet.get(p);
474       for (int q = p + 1; q < seqSet.size(); q++)
475       {
476         SequenceI qSeq = seqSet.get(q);
477         if (pSeq.getStart() != qSeq.getStart())
478         {
479           continue;
480         }
481         if (pSeq.getEnd() != qSeq.getEnd())
482         {
483           continue;
484         }
485         if (!pSeq.getName().equals(qSeq.getName()))
486         {
487           continue;
488         }
489         if (!Arrays.equals(pSeq.getSequence(), qSeq.getSequence()))
490         {
491           continue;
492         }
493         Assert.fail((message == null ? "" : message + " :")
494                 + "Found similar sequences at position " + p + " and " + q
495                 + "\n" + pSeq.toString());
496       }
497     }
498   }
499
500   @Test(groups = { "Functional", "Asserts" })
501   public void testAssertDatasetIsNormalised()
502   {
503     Sequence sq1 = new Sequence("s1/1-4", "asdf");
504     Sequence sq1shift = new Sequence("s1/2-5", "asdf");
505     Sequence sq1seqd = new Sequence("s1/1-4", "asdt");
506     Sequence sq2 = new Sequence("s2/1-4", "asdf");
507     Sequence sq1dup = new Sequence("s1/1-4", "asdf");
508
509     Alignment al = new Alignment(new SequenceI[] { sq1 });
510     al.setDataset(null);
511
512     try
513     {
514       assertDatasetIsNormalised(al);
515     } catch (AssertionError ae)
516     {
517       Assert.fail("Single sequence should be valid normalised dataset.");
518     }
519     al.addSequence(sq2);
520     try
521     {
522       assertDatasetIsNormalised(al);
523     } catch (AssertionError ae)
524     {
525       Assert.fail("Two different sequences should be valid normalised dataset.");
526     }
527     /*
528      * now change sq2's name in the alignment. should still be valid
529      */
530     al.findName(sq2.getName()).setName("sq1");
531     try
532     {
533       assertDatasetIsNormalised(al);
534     } catch (AssertionError ae)
535     {
536       Assert.fail("Two different sequences in dataset, but same name in alignment, should be valid normalised dataset.");
537     }
538
539     al.addSequence(sq1seqd);
540     try
541     {
542       assertDatasetIsNormalised(al);
543     } catch (AssertionError ae)
544     {
545       Assert.fail("sq1 and sq1 with different sequence should be distinct.");
546     }
547
548     al.addSequence(sq1shift);
549     try
550     {
551       assertDatasetIsNormalised(al);
552     } catch (AssertionError ae)
553     {
554       Assert.fail("sq1 and sq1 with different start/end should be distinct.");
555     }
556     /*
557      * finally, the failure case
558      */
559     al.addSequence(sq1dup);
560     boolean ssertRaised = false;
561     try
562     {
563       assertDatasetIsNormalised(al);
564
565     } catch (AssertionError ae)
566     {
567       ssertRaised = true;
568     }
569     if (!ssertRaised)
570     {
571       Assert.fail("Expected identical sequence to raise exception.");
572     }
573   }
574
575   /*
576    * Read in Stockholm format test data including secondary structure
577    * annotations.
578    */
579   @BeforeMethod(alwaysRun = true)
580   public void setUp() throws IOException
581   {
582     al = loadAlignment(TEST_DATA, FileFormat.Stockholm);
583     int i = 0;
584     for (AlignmentAnnotation ann : al.getAlignmentAnnotation())
585     {
586       ann.setCalcId("CalcIdFor" + al.getSequenceAt(i).getName());
587       i++;
588     }
589   }
590
591   /**
592    * Test method that returns annotations that match on calcId.
593    */
594   @Test(groups = { "Functional" })
595   public void testFindAnnotation_byCalcId()
596   {
597     Iterable<AlignmentAnnotation> anns = al
598             .findAnnotation("CalcIdForD.melanogaster.2");
599     Iterator<AlignmentAnnotation> iter = anns.iterator();
600     assertTrue(iter.hasNext());
601     AlignmentAnnotation ann = iter.next();
602     assertEquals("D.melanogaster.2", ann.sequenceRef.getName());
603     assertFalse(iter.hasNext());
604   }
605
606   @Test(groups = { "Functional" })
607   public void testDeleteAllAnnotations_includingAutocalculated()
608   {
609     AlignmentAnnotation aa = new AlignmentAnnotation("Consensus",
610             "Consensus", 0.5);
611     aa.autoCalculated = true;
612     al.addAnnotation(aa);
613     AlignmentAnnotation[] anns = al.getAlignmentAnnotation();
614     assertEquals("Wrong number of annotations before deleting", 4,
615             anns.length);
616     al.deleteAllAnnotations(true);
617     assertEquals("Not all deleted", 0, al.getAlignmentAnnotation().length);
618   }
619
620   @Test(groups = { "Functional" })
621   public void testDeleteAllAnnotations_excludingAutocalculated()
622   {
623     AlignmentAnnotation aa = new AlignmentAnnotation("Consensus",
624             "Consensus", 0.5);
625     aa.autoCalculated = true;
626     al.addAnnotation(aa);
627     AlignmentAnnotation[] anns = al.getAlignmentAnnotation();
628     assertEquals("Wrong number of annotations before deleting", 4,
629             anns.length);
630     al.deleteAllAnnotations(false);
631     assertEquals("Not just one annotation left", 1,
632             al.getAlignmentAnnotation().length);
633   }
634
635   /**
636    * Tests for realigning as per a supplied alignment: Dna as Dna.
637    * 
638    * Note: AlignedCodonFrame's state variables are named for protein-to-cDNA
639    * mapping, but can be exploited for a general 'sequence-to-sequence' mapping
640    * as here.
641    * 
642    * @throws IOException
643    */
644   @Test(groups = { "Functional" })
645   public void testAlignAs_dnaAsDna() throws IOException
646   {
647     // aligned cDNA:
648     AlignmentI al1 = loadAlignment(CDNA_SEQS_1, FileFormat.Fasta);
649     // unaligned cDNA:
650     AlignmentI al2 = loadAlignment(CDNA_SEQS_2, FileFormat.Fasta);
651
652     /*
653      * Make mappings between sequences. The 'aligned cDNA' is playing the role
654      * of what would normally be protein here.
655      */
656     makeMappings(al1, al2);
657
658     ((Alignment) al2).alignAs(al1, false, true);
659     assertEquals("GC-TC--GUC-GTACT", al2.getSequenceAt(0)
660             .getSequenceAsString());
661     assertEquals("-GG-GTC--AGG--CAGT", al2.getSequenceAt(1)
662             .getSequenceAsString());
663   }
664
665   /**
666    * Aligning protein from cDNA.
667    * 
668    * @throws IOException
669    */
670   @Test(groups = { "Functional" })
671   public void testAlignAs_proteinAsCdna() throws IOException
672   {
673     // see also AlignmentUtilsTests
674     AlignmentI al1 = loadAlignment(CDNA_SEQS_1, FileFormat.Fasta);
675     AlignmentI al2 = loadAlignment(AA_SEQS_1, FileFormat.Fasta);
676     makeMappings(al1, al2);
677
678     // Fudge - alignProteinAsCdna expects mappings to be on protein
679     al2.getCodonFrames().addAll(al1.getCodonFrames());
680
681     ((Alignment) al2).alignAs(al1, false, true);
682     assertEquals("K-Q-Y-L-", al2.getSequenceAt(0).getSequenceAsString());
683     assertEquals("-R-F-P-W", al2.getSequenceAt(1).getSequenceAsString());
684   }
685
686   /**
687    * Test aligning cdna as per protein alignment.
688    * 
689    * @throws IOException
690    */
691   @Test(groups = { "Functional" }, enabled = true)
692   // TODO review / update this test after redesign of alignAs method
693   public void testAlignAs_cdnaAsProtein() throws IOException
694   {
695     /*
696      * Load alignments and add mappings for cDNA to protein
697      */
698     AlignmentI al1 = loadAlignment(CDNA_SEQS_1, FileFormat.Fasta);
699     AlignmentI al2 = loadAlignment(AA_SEQS_1, FileFormat.Fasta);
700     makeMappings(al1, al2);
701
702     /*
703      * Realign DNA; currently keeping existing gaps in introns only
704      */
705     ((Alignment) al1).alignAs(al2, false, true);
706     assertEquals("ACG---GCUCCA------ACT---", al1.getSequenceAt(0)
707             .getSequenceAsString());
708     assertEquals("---CGT---TAACGA---AGT---", al1.getSequenceAt(1)
709             .getSequenceAsString());
710   }
711
712   /**
713    * Test aligning cdna as per protein - single sequences
714    * 
715    * @throws IOException
716    */
717   @Test(groups = { "Functional" }, enabled = true)
718   // TODO review / update this test after redesign of alignAs method
719   public void testAlignAs_cdnaAsProtein_singleSequence() throws IOException
720   {
721     /*
722      * simple case insert one gap
723      */
724     verifyAlignAs(">dna\nCAAaaa\n", ">protein\nQ-K\n", "CAA---aaa");
725
726     /*
727      * simple case but with sequence offsets
728      */
729     verifyAlignAs(">dna/5-10\nCAAaaa\n", ">protein/20-21\nQ-K\n",
730             "CAA---aaa");
731
732     /*
733      * insert gaps as per protein, drop gaps within codons
734      */
735     verifyAlignAs(">dna/10-18\nCA-Aa-aa--AGA\n", ">aa/6-8\n-Q-K--R\n",
736             "---CAA---aaa------AGA");
737   }
738
739   /**
740    * Helper method that makes mappings and then aligns the first alignment as
741    * the second
742    * 
743    * @param fromSeqs
744    * @param toSeqs
745    * @param expected
746    * @throws IOException
747    */
748   public void verifyAlignAs(String fromSeqs, String toSeqs, String expected)
749           throws IOException
750   {
751     /*
752      * Load alignments and add mappings from nucleotide to protein (or from
753      * first to second if both the same type)
754      */
755     AlignmentI al1 = loadAlignment(fromSeqs, FileFormat.Fasta);
756     AlignmentI al2 = loadAlignment(toSeqs, FileFormat.Fasta);
757     makeMappings(al1, al2);
758
759     /*
760      * Realign DNA; currently keeping existing gaps in introns only
761      */
762     ((Alignment) al1).alignAs(al2, false, true);
763     assertEquals(expected, al1.getSequenceAt(0).getSequenceAsString());
764   }
765
766   /**
767    * Helper method to make mappings between sequences, and add the mappings to
768    * the 'mapped from' alignment
769    * 
770    * @param alFrom
771    * @param alTo
772    */
773   public void makeMappings(AlignmentI alFrom, AlignmentI alTo)
774   {
775     int ratio = (alFrom.isNucleotide() == alTo.isNucleotide() ? 1 : 3);
776
777     AlignedCodonFrame acf = new AlignedCodonFrame();
778
779     for (int i = 0; i < alFrom.getHeight(); i++)
780     {
781       SequenceI seqFrom = alFrom.getSequenceAt(i);
782       SequenceI seqTo = alTo.getSequenceAt(i);
783       MapList ml = new MapList(new int[] { seqFrom.getStart(),
784           seqFrom.getEnd() },
785               new int[] { seqTo.getStart(), seqTo.getEnd() }, ratio, 1);
786       acf.addMap(seqFrom, seqTo, ml);
787     }
788
789     /*
790      * not sure whether mappings 'belong' or protein or nucleotide
791      * alignment, so adding to both ;~)
792      */
793     alFrom.addCodonFrame(acf);
794     alTo.addCodonFrame(acf);
795   }
796
797   /**
798    * Test aligning dna as per protein alignment, for the case where there are
799    * introns (i.e. some dna sites have no mapping from a peptide).
800    * 
801    * @throws IOException
802    */
803   @Test(groups = { "Functional" }, enabled = false)
804   // TODO review / update this test after redesign of alignAs method
805   public void testAlignAs_dnaAsProtein_withIntrons() throws IOException
806   {
807     /*
808      * Load alignments and add mappings for cDNA to protein
809      */
810     String dna1 = "A-Aa-gG-GCC-cT-TT";
811     String dna2 = "c--CCGgg-TT--T-AA-A";
812     AlignmentI al1 = loadAlignment(">Dna1/6-17\n" + dna1
813             + "\n>Dna2/20-31\n" + dna2 + "\n", FileFormat.Fasta);
814     AlignmentI al2 = loadAlignment(
815             ">Pep1/7-9\n-P--YK\n>Pep2/11-13\nG-T--F\n", FileFormat.Fasta);
816     AlignedCodonFrame acf = new AlignedCodonFrame();
817     // Seq1 has intron at dna positions 3,4,9 so splice is AAG GCC TTT
818     // Seq2 has intron at dna positions 1,5,6 so splice is CCG TTT AAA
819     MapList ml1 = new MapList(new int[] { 6, 7, 10, 13, 15, 17 }, new int[]
820     { 7, 9 }, 3, 1);
821     acf.addMap(al1.getSequenceAt(0), al2.getSequenceAt(0), ml1);
822     MapList ml2 = new MapList(new int[] { 21, 23, 26, 31 }, new int[] { 11,
823         13 }, 3, 1);
824     acf.addMap(al1.getSequenceAt(1), al2.getSequenceAt(1), ml2);
825     al2.addCodonFrame(acf);
826
827     /*
828      * Align ignoring gaps in dna introns and exons
829      */
830     ((Alignment) al1).alignAs(al2, false, false);
831     assertEquals("---AAagG------GCCcTTT", al1.getSequenceAt(0)
832             .getSequenceAsString());
833     // note 1 gap in protein corresponds to 'gg-' in DNA (3 positions)
834     assertEquals("cCCGgg-TTT------AAA", al1.getSequenceAt(1)
835             .getSequenceAsString());
836
837     /*
838      * Reset and realign, preserving gaps in dna introns and exons
839      */
840     al1.getSequenceAt(0).setSequence(dna1);
841     al1.getSequenceAt(1).setSequence(dna2);
842     ((Alignment) al1).alignAs(al2, true, true);
843     // String dna1 = "A-Aa-gG-GCC-cT-TT";
844     // String dna2 = "c--CCGgg-TT--T-AA-A";
845     // assumption: we include 'the greater of' protein/dna gap lengths, not both
846     assertEquals("---A-Aa-gG------GCC-cT-TT", al1.getSequenceAt(0)
847             .getSequenceAsString());
848     assertEquals("c--CCGgg-TT--T------AA-A", al1.getSequenceAt(1)
849             .getSequenceAsString());
850   }
851
852   @Test(groups = "Functional")
853   public void testCopyConstructor() throws IOException
854   {
855     AlignmentI protein = loadAlignment(AA_SEQS_1, FileFormat.Fasta);
856     // create sequence and alignment datasets
857     protein.setDataset(null);
858     AlignedCodonFrame acf = new AlignedCodonFrame();
859     List<AlignedCodonFrame> acfList = Arrays.asList(new AlignedCodonFrame[]
860     { acf });
861     protein.getDataset().setCodonFrames(acfList);
862     AlignmentI copy = new Alignment(protein);
863
864     /*
865      * copy has different aligned sequences but the same dataset sequences
866      */
867     assertFalse(copy.getSequenceAt(0) == protein.getSequenceAt(0));
868     assertFalse(copy.getSequenceAt(1) == protein.getSequenceAt(1));
869     assertSame(copy.getSequenceAt(0).getDatasetSequence(), protein
870             .getSequenceAt(0).getDatasetSequence());
871     assertSame(copy.getSequenceAt(1).getDatasetSequence(), protein
872             .getSequenceAt(1).getDatasetSequence());
873
874     // TODO should the copy constructor copy the dataset?
875     // or make a new one referring to the same dataset sequences??
876     assertNull(copy.getDataset());
877     // TODO test metadata is copied when AlignmentI is a dataset
878
879     // assertArrayEquals(copy.getDataset().getSequencesArray(), protein
880     // .getDataset().getSequencesArray());
881   }
882
883   /**
884    * Test behaviour of createDataset
885    * 
886    * @throws IOException
887    */
888   @Test(groups = "Functional")
889   public void testCreateDatasetAlignment() throws IOException
890   {
891     AlignmentI protein = new FormatAdapter().readFile(AA_SEQS_1,
892             DataSourceType.PASTE, FileFormat.Fasta);
893     /*
894      * create a dataset sequence on first sequence
895      * leave the second without one
896      */
897     protein.getSequenceAt(0).createDatasetSequence();
898     assertNotNull(protein.getSequenceAt(0).getDatasetSequence());
899     assertNull(protein.getSequenceAt(1).getDatasetSequence());
900
901     /*
902      * add a mapping to the alignment
903      */
904     AlignedCodonFrame acf = new AlignedCodonFrame();
905     protein.addCodonFrame(acf);
906     assertNull(protein.getDataset());
907     assertTrue(protein.getCodonFrames().contains(acf));
908
909     /*
910      * create the alignment dataset
911      * note this creates sequence datasets where missing
912      * as a side-effect (in this case, on seq2
913      */
914     // TODO promote this method to AlignmentI
915     ((Alignment) protein).createDatasetAlignment();
916
917     AlignmentI ds = protein.getDataset();
918
919     // side-effect: dataset created on second sequence
920     assertNotNull(protein.getSequenceAt(1).getDatasetSequence());
921     // dataset alignment has references to dataset sequences
922     assertEquals(ds.getSequenceAt(0), protein.getSequenceAt(0)
923             .getDatasetSequence());
924     assertEquals(ds.getSequenceAt(1), protein.getSequenceAt(1)
925             .getDatasetSequence());
926
927     // codon frames should have been moved to the dataset
928     // getCodonFrames() should delegate to the dataset:
929     assertTrue(protein.getCodonFrames().contains(acf));
930     // prove the codon frames are indeed on the dataset:
931     assertTrue(ds.getCodonFrames().contains(acf));
932   }
933
934   /**
935    * tests the addition of *all* sequences referred to by a sequence being added
936    * to the dataset
937    */
938   @Test(groups = "Functional")
939   public void testCreateDatasetAlignmentWithMappedToSeqs()
940   {
941     // Alignment with two sequences, gapped.
942     SequenceI sq1 = new Sequence("sq1", "A--SDF");
943     SequenceI sq2 = new Sequence("sq2", "G--TRQ");
944
945     // cross-references to two more sequences.
946     DBRefEntry dbr = new DBRefEntry("SQ1", "", "sq3");
947     SequenceI sq3 = new Sequence("sq3", "VWANG");
948     dbr.setMap(new Mapping(sq3, new MapList(new int[] { 1, 4 }, new int[] {
949         2, 5 }, 1, 1)));
950     sq1.addDBRef(dbr);
951
952     SequenceI sq4 = new Sequence("sq4", "ERKWI");
953     DBRefEntry dbr2 = new DBRefEntry("SQ2", "", "sq4");
954     dbr2.setMap(new Mapping(sq4, new MapList(new int[] { 1, 4 }, new int[] {
955         2, 5 }, 1, 1)));
956     sq2.addDBRef(dbr2);
957     // and a 1:1 codonframe mapping between them.
958     AlignedCodonFrame alc = new AlignedCodonFrame();
959     alc.addMap(sq1, sq2, new MapList(new int[] { 1, 4 },
960             new int[] { 1, 4 }, 1, 1));
961
962     AlignmentI protein = new Alignment(new SequenceI[] { sq1, sq2 });
963
964     /*
965      * create the alignment dataset
966      * note this creates sequence datasets where missing
967      * as a side-effect (in this case, on seq2
968      */
969
970     // TODO promote this method to AlignmentI
971     ((Alignment) protein).createDatasetAlignment();
972
973     AlignmentI ds = protein.getDataset();
974
975     // should be 4 sequences in dataset - two materialised, and two propagated
976     // from dbref
977     assertEquals(4, ds.getHeight());
978     assertTrue(ds.getSequences().contains(sq1.getDatasetSequence()));
979     assertTrue(ds.getSequences().contains(sq2.getDatasetSequence()));
980     assertTrue(ds.getSequences().contains(sq3));
981     assertTrue(ds.getSequences().contains(sq4));
982     // Should have one codon frame mapping between sq1 and sq2 via dataset
983     // sequences
984     assertEquals(ds.getCodonFrame(sq1.getDatasetSequence()),
985             ds.getCodonFrame(sq2.getDatasetSequence()));
986   }
987
988   @Test(groups = "Functional")
989   public void testAddCodonFrame()
990   {
991     AlignmentI align = new Alignment(new SequenceI[] {});
992     AlignedCodonFrame acf = new AlignedCodonFrame();
993     align.addCodonFrame(acf);
994     assertEquals(1, align.getCodonFrames().size());
995     assertTrue(align.getCodonFrames().contains(acf));
996     // can't add the same object twice:
997     align.addCodonFrame(acf);
998     assertEquals(1, align.getCodonFrames().size());
999
1000     // create dataset alignment - mappings move to dataset
1001     ((Alignment) align).createDatasetAlignment();
1002     assertSame(align.getCodonFrames(), align.getDataset().getCodonFrames());
1003     assertEquals(1, align.getCodonFrames().size());
1004
1005     AlignedCodonFrame acf2 = new AlignedCodonFrame();
1006     align.addCodonFrame(acf2);
1007     assertTrue(align.getDataset().getCodonFrames().contains(acf));
1008   }
1009
1010   @Test(groups = "Functional")
1011   public void testAddSequencePreserveDatasetIntegrity()
1012   {
1013     Sequence seq = new Sequence("testSeq", "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
1014     Alignment align = new Alignment(new SequenceI[] { seq });
1015     align.createDatasetAlignment();
1016     AlignmentI ds = align.getDataset();
1017     SequenceI copy = new Sequence(seq);
1018     copy.insertCharAt(3, 5, '-');
1019     align.addSequence(copy);
1020     Assert.assertEquals(align.getDataset().getHeight(), 1,
1021             "Dataset shouldn't have more than one sequence.");
1022
1023     Sequence seq2 = new Sequence("newtestSeq", "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
1024     align.addSequence(seq2);
1025     Assert.assertEquals(align.getDataset().getHeight(), 2,
1026             "Dataset should now have two sequences.");
1027
1028     assertAlignmentDatasetRefs(align,
1029             "addSequence broke dataset reference integrity");
1030   }
1031
1032   @Test(groups = "Functional")
1033   public void getVisibleStartAndEndIndexTest()
1034   {
1035     Sequence seq = new Sequence("testSeq", "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
1036     AlignmentI align = new Alignment(new SequenceI[] { seq });
1037     ArrayList<int[]> hiddenCols = new ArrayList<int[]>();
1038
1039     int[] startEnd = align.getVisibleStartAndEndIndex(hiddenCols);
1040     assertEquals(0, startEnd[0]);
1041     assertEquals(25, startEnd[1]);
1042
1043     hiddenCols.add(new int[] { 0, 0 });
1044     startEnd = align.getVisibleStartAndEndIndex(hiddenCols);
1045     assertEquals(1, startEnd[0]);
1046     assertEquals(25, startEnd[1]);
1047
1048     hiddenCols.add(new int[] { 6, 9 });
1049     hiddenCols.add(new int[] { 11, 12 });
1050     startEnd = align.getVisibleStartAndEndIndex(hiddenCols);
1051     assertEquals(1, startEnd[0]);
1052     assertEquals(25, startEnd[1]);
1053
1054     hiddenCols.add(new int[] { 24, 25 });
1055     startEnd = align.getVisibleStartAndEndIndex(hiddenCols);
1056     System.out.println(startEnd[0] + " : " + startEnd[1]);
1057     assertEquals(1, startEnd[0]);
1058     assertEquals(23, startEnd[1]);
1059   }
1060
1061   /**
1062    * Tests that dbrefs with mappings to sequence get updated if the sequence
1063    * acquires a dataset sequence
1064    */
1065   @Test(groups = "Functional")
1066   public void testCreateDataset_updateDbrefMappings()
1067   {
1068     SequenceI pep = new Sequence("pep", "ASD");
1069     SequenceI dna = new Sequence("dna", "aaaGCCTCGGATggg");
1070     SequenceI cds = new Sequence("cds", "GCCTCGGAT");
1071
1072     // add dbref from dna to peptide
1073     DBRefEntry dbr = new DBRefEntry("UNIPROT", "", "pep");
1074     dbr.setMap(new Mapping(pep, new MapList(new int[] { 4, 15 }, new int[] {
1075         1, 4 }, 3, 1)));
1076     dna.addDBRef(dbr);
1077
1078     // add dbref from dna to peptide
1079     DBRefEntry dbr2 = new DBRefEntry("UNIPROT", "", "pep");
1080     dbr2.setMap(new Mapping(pep, new MapList(new int[] { 1, 12 }, new int[]
1081     { 1, 4 }, 3, 1)));
1082     cds.addDBRef(dbr2);
1083
1084     // add dbref from peptide to dna
1085     DBRefEntry dbr3 = new DBRefEntry("EMBL", "", "dna");
1086     dbr3.setMap(new Mapping(dna, new MapList(new int[] { 1, 4 }, new int[] {
1087         4, 15 }, 1, 3)));
1088     pep.addDBRef(dbr3);
1089
1090     // add dbref from peptide to cds
1091     DBRefEntry dbr4 = new DBRefEntry("EMBLCDS", "", "cds");
1092     dbr4.setMap(new Mapping(cds, new MapList(new int[] { 1, 4 }, new int[] {
1093         1, 12 }, 1, 3)));
1094     pep.addDBRef(dbr4);
1095
1096     AlignmentI protein = new Alignment(new SequenceI[] { pep });
1097
1098     /*
1099      * create the alignment dataset
1100      */
1101     ((Alignment) protein).createDatasetAlignment();
1102
1103     AlignmentI ds = protein.getDataset();
1104
1105     // should be 3 sequences in dataset
1106     assertEquals(3, ds.getHeight());
1107     assertTrue(ds.getSequences().contains(pep.getDatasetSequence()));
1108     assertTrue(ds.getSequences().contains(dna));
1109     assertTrue(ds.getSequences().contains(cds));
1110
1111     /*
1112      * verify peptide.cdsdbref.peptidedbref is now mapped to peptide dataset
1113      */
1114     DBRefEntry[] dbRefs = pep.getDBRefs();
1115     assertEquals(2, dbRefs.length);
1116     assertSame(dna, dbRefs[0].map.to);
1117     assertSame(cds, dbRefs[1].map.to);
1118     assertEquals(1, dna.getDBRefs().length);
1119     assertSame(pep.getDatasetSequence(), dna.getDBRefs()[0].map.to);
1120     assertEquals(1, cds.getDBRefs().length);
1121     assertSame(pep.getDatasetSequence(), cds.getDBRefs()[0].map.to);
1122   }
1123
1124 }