JAL-1499 Gene and Domain parsed to AlignmentAnnotation (currently as
[jalview.git] / test / jalview / io / MegaFileTest.java
1 package jalview.io;
2
3 import static org.testng.AssertJUnit.assertEquals;
4 import static org.testng.AssertJUnit.assertFalse;
5 import static org.testng.AssertJUnit.assertNull;
6 import static org.testng.AssertJUnit.assertTrue;
7 import static org.testng.AssertJUnit.fail;
8
9 import jalview.datamodel.AlignmentAnnotation;
10 import jalview.datamodel.AlignmentI;
11 import jalview.datamodel.Sequence;
12 import jalview.datamodel.SequenceFeature;
13 import jalview.datamodel.SequenceI;
14
15 import java.io.IOException;
16 import java.util.List;
17 import java.util.Vector;
18
19 import org.testng.annotations.Test;
20
21 /*
22  * Unit tests for MegaFile - read and write in MEGA format(s).
23  */
24 public class MegaFileTest
25 {
26   private static final String TWENTY_CHARS = "9876543210abcdefghij";
27
28   private static final String THIRTY_CHARS = "0123456789klmnopqrstABCDEFGHIJ";
29
30   //@formatter:off
31   private static final String INTERLEAVED = 
32           "#MEGA\n"+ 
33           "TITLE: Interleaved sequence data\n\n" + 
34           "#U455   ABCDEF\n" + 
35           "#CPZANT  MNOPQR\n\n" + 
36           "#U455   KLMNOP\n" + 
37           "#CPZANT WXYZGC";
38
39   private static final String INTERLEAVED_NOHEADERS = 
40           "#U455   ABCDEF\n" 
41           + "#CPZANT MNOPQR\n\n" 
42           + "#U455   KLMNOP\n"
43           + "#CPZANT WXYZGC\n";
44
45   // interleaved sequences, with 50 residues
46   private static final String INTERLEAVED_50RESIDUES = 
47           "#MEGA\n"
48           + "!TITLE Interleaved sequence data\n\n"
49           + "#U455 " + THIRTY_CHARS + TWENTY_CHARS + "\n" 
50           + "#CPZANT " + TWENTY_CHARS + THIRTY_CHARS + "\n";
51
52   private static final String NONINTERLEAVED = 
53           "#MEGA\n"
54           + "!TITLE Noninterleaved sequence data\n\n" 
55           + "#U455  \n"
56           + "ABCFEDHIJ\n" 
57           + "MNOPQR\n\n" 
58           + "#CPZANT \n" 
59           + "KLMNOPWXYZ\n" 
60           + "CGATC\n";
61   
62   // this one starts interleaved then switches to non-interleaved
63   private static final String MIXED = 
64           "#MEGA\n"
65           + "!TITLE This is a mess\n\n" 
66           + "#CPZANT KLMNOPWXYZCGATC\n\n"
67           + "#U455\n  "
68           + "ABCFEDHIJ\n";
69
70   // interleaved with a new sequence appearing in the second block :-O
71   private static final String INTERLEAVED_SEQUENCE_ERROR = 
72           "#MEGA" + "\n"
73           + "!TITLE Interleaved sequence data\n\n"
74           + "#U455   ABCDEF\n" 
75           + "#CPZANT  MNOPQR\n\n"
76           + "#U456   KLMNOP\n";
77
78   // interleaved with description, bases/gaps in triplet groups
79   private static final String INTERLEAVED_WITH_DESCRIPTION = 
80           "#MEGA\n"
81           + "!Title Data with description;\n"
82           + "!Format DataType=DNA  indel=-\tCodeTable=Standard Missing=? MatchChar=.;\n\n"
83           + "!Description\n" 
84           + "    Line one of description\n"
85           + "    Line two of description;\n\n"
86           + "#U455   C-- GTA\n" 
87           + "#CPZANT ATC -G-\n\n"
88           + "#U455   CGA --T\n" 
89           + "#CPZANT CA- -GC\n";
90
91   //@formatter:on
92
93   /**
94    * Test parse of interleaved mega format data.
95    * 
96    * @throws IOException
97    */
98   @Test(groups = { "Functional" })
99   public void testParse_interleaved() throws IOException
100   {
101     MegaFile testee = new MegaFile(INTERLEAVED, AppletFormatAdapter.PASTE);
102     assertEquals("Title not as expected", "Interleaved sequence data",
103             testee.getAlignmentProperty(MegaFile.PROP_TITLE));
104     Vector<SequenceI> seqs = testee.getSeqs();
105     // should be 2 sequences
106     assertEquals("Expected two sequences", 2, seqs.size());
107     // check sequence names correct and order preserved
108     assertEquals("First sequence id wrong", "U455", seqs.get(0).getName());
109     assertEquals("Second sequence id wrong", "CPZANT", seqs.get(1)
110             .getName());
111     // check sequence data
112     assertEquals("First sequence data wrong", "ABCDEFKLMNOP", seqs.get(0)
113             .getSequenceAsString());
114     assertEquals("Second sequence data wrong", "MNOPQRWXYZGC", seqs.get(1)
115             .getSequenceAsString());
116     assertTrue("File format is not flagged as interleaved",
117             testee.isInterleaved());
118   }
119
120   /**
121    * Test parse of noninterleaved mega format data.
122    * 
123    * @throws IOException
124    */
125   @Test(groups = { "Functional" })
126   public void testParse_nonInterleaved() throws IOException
127   {
128     MegaFile testee = new MegaFile(NONINTERLEAVED,
129             AppletFormatAdapter.PASTE);
130     assertEquals("Title not as expected", "Noninterleaved sequence data",
131             testee.getAlignmentProperty(MegaFile.PROP_TITLE));
132     Vector<SequenceI> seqs = testee.getSeqs();
133     // should be 2 sequences
134     assertEquals("Expected two sequences", 2, seqs.size());
135     // check sequence names correct and order preserved
136     assertEquals("First sequence id wrong", "U455", seqs.get(0).getName());
137     assertEquals("Second sequence id wrong", "CPZANT", seqs.get(1)
138             .getName());
139     // check sequence data
140     assertEquals("First sequence data wrong", "ABCFEDHIJMNOPQR", seqs
141             .get(0).getSequenceAsString());
142     assertEquals("Second sequence data wrong", "KLMNOPWXYZCGATC",
143             seqs.get(1).getSequenceAsString());
144     assertFalse("File format is not flagged as noninterleaved",
145             testee.isInterleaved());
146   }
147
148   /**
149    * Test parsing an interleaved file with an extra sequence appearing after the
150    * first block - should fail.
151    */
152   @Test(groups = { "Functional" })
153   public void testParse_interleavedExtraSequenceError()
154   {
155     try
156     {
157       new MegaFile(INTERLEAVED_SEQUENCE_ERROR, AppletFormatAdapter.PASTE);
158       fail("Expected extra sequence IOException");
159     } catch (IOException e)
160     {
161       assertEquals(
162               "Unexpected exception message",
163               "Parse error: misplaced new sequence starting at #U456   KLMNOP",
164               e.getMessage());
165     }
166   }
167
168   /**
169    * Test a mixed up file.
170    */
171   @Test(groups = { "Functional" })
172   public void testParse_mixedInterleavedNonInterleaved()
173   {
174     try
175     {
176       new MegaFile(MIXED, AppletFormatAdapter.PASTE);
177       fail("Expected mixed content exception");
178     } catch (IOException e)
179     {
180       assertEquals(
181               "Unexpected exception message",
182               "Parse error: interleaved was true but now seems to be false, at line: ABCFEDHIJ",
183               e.getMessage());
184     }
185
186   }
187
188   @Test(groups = { "Functional" })
189   public void testGetSequenceId()
190   {
191     assertEquals("AB123", MegaFile.getSequenceId("#AB123 CGATC"));
192     assertEquals("AB123", MegaFile.getSequenceId("#AB123    CGATC"));
193     assertEquals("AB123", MegaFile.getSequenceId("#AB123 CGC TAC"));
194     assertEquals("AB123", MegaFile.getSequenceId("#AB123"));
195     assertNull(MegaFile.getSequenceId("AB123 CTAG"));
196     assertNull(MegaFile.getSequenceId("AB123"));
197     assertNull(MegaFile.getSequenceId(""));
198     assertNull(MegaFile.getSequenceId(null));
199   }
200
201   @Test(groups = { "Functional" })
202   public void testGetMaxIdLength()
203   {
204     SequenceI[] seqs = new Sequence[2];
205     seqs[0] = new Sequence("Something", "GCATAC");
206     seqs[1] = new Sequence("SomethingElse", "GCATAC");
207     assertEquals(13, MegaFile.getMaxIdLength(seqs));
208     seqs[1] = new Sequence("DNA", "GCATAC");
209     assertEquals(9, MegaFile.getMaxIdLength(seqs));
210   }
211
212   @Test(groups = { "Functional" })
213   public void testGetMaxSequenceLength()
214   {
215     SequenceI[] seqs = new Sequence[2];
216     seqs[0] = new Sequence("Seq1", "GCATAC");
217     seqs[1] = new Sequence("Seq2", "GCATACTAG");
218     assertEquals(9, MegaFile.getMaxSequenceLength(seqs));
219     seqs[1] = new Sequence("Seq2", "GCA");
220     assertEquals(6, MegaFile.getMaxSequenceLength(seqs));
221   }
222
223   /**
224    * Test (parse and) print of interleaved mega format data.
225    * 
226    * @throws IOException
227    */
228   @Test(groups = { "Functional" })
229   public void testPrint_interleaved() throws IOException
230   {
231     MegaFile testee = new MegaFile(INTERLEAVED, AppletFormatAdapter.PASTE);
232     String printed = testee.print();
233     System.out.println(printed);
234     // normally output should match input
235     // we cheated here with a number of short input lines
236     // nb don't get Title in output if not calling print(AlignmentI)
237     String expected = "#MEGA\n\n" + "#U455   ABCDEF [6]\n"
238             + "#CPZANT MNOPQR [6]\n\n" + "#U455   KLMNOP [12]\n"
239             + "#CPZANT WXYZGC [12]"
240             + "\n";
241     assertEquals("Print format wrong", expected, printed);
242   }
243
244   /**
245    * Test (parse and) print of interleaved data with no headers (acceptable).
246    * 
247    * @throws IOException
248    */
249   @Test(groups = { "Functional" })
250   public void testPrint_interleavedNoHeaders() throws IOException
251   {
252     MegaFile testee = new MegaFile(INTERLEAVED_NOHEADERS,
253             AppletFormatAdapter.PASTE);
254     String printed = testee.print();
255     System.out.println(printed);
256
257     //@formatter:off
258     assertEquals("Print format wrong", 
259     "#MEGA\n\n" + "#U455   ABCDEF [6]\n" 
260     + "#CPZANT MNOPQR [6]\n\n" 
261     + "#U455   KLMNOP [12]\n"
262     + "#CPZANT WXYZGC [12]\n",
263             printed);
264     //@formatter:on
265   }
266
267   /**
268    * Test (parse and) print of noninterleaved mega format data.
269    * 
270    * @throws IOException
271    */
272   @Test(groups = { "Functional" })
273   public void testPrint_noninterleaved() throws IOException
274   {
275     MegaFile testee = new MegaFile(NONINTERLEAVED,
276             AppletFormatAdapter.PASTE);
277     assertEquals(10, testee.getPositionsPerLine());
278     String printed = testee.print();
279     System.out.println(printed);
280     // normally output should match input
281     // we cheated here with a number of short input lines
282     String expected = "#MEGA\n\n" + "#U455\n"
283             + "ABCFEDHIJM [10]\nNOPQR [15]\n\n" + "#CPZANT\n"
284             + "KLMNOPWXYZ [10]\nCGATC [15]\n";
285     assertEquals("Print format wrong", expected, printed);
286   }
287
288   /**
289    * Test (parse and) print of interleaved mega format data extending to more
290    * than one line of output.
291    * 
292    * @throws IOException
293    */
294   @Test(groups = { "Functional" })
295   public void testPrint_interleavedMultiLine() throws IOException
296   {
297     MegaFile testee = new MegaFile(INTERLEAVED_50RESIDUES,
298             AppletFormatAdapter.PASTE);
299     assertEquals(50, testee.getPositionsPerLine());
300     /*
301      * now simulate choosing 20 residues per line on output
302      */
303     testee.setPositionsPerLine(20);
304     String printed = testee.print();
305     System.out.println(printed);
306     //@formatter:off
307     String expected = 
308             "#MEGA\n\n" + 
309             "#U455   0123456789 klmnopqrst [20]\n" + // first 20
310             "#CPZANT 9876543210 abcdefghij [20]\n\n" +
311             "#U455   ABCDEFGHIJ 9876543210 [40]\n" + // next 20
312             "#CPZANT 0123456789 klmnopqrst [40]\n\n" +
313             "#U455   abcdefghij [50]\n" + // last 10
314             "#CPZANT ABCDEFGHIJ [50]\n";
315     //@formatter:on
316     assertEquals("Print format wrong", expected, printed);
317   }
318
319   /**
320    * Test (parse and) print of noninterleaved mega format data extending to more
321    * than one line of output.
322    * 
323    * @throws IOException
324    */
325   @Test(groups = { "Functional" })
326   public void testPrint_noninterleavedMultiLine() throws IOException
327   {
328     final String NONINTERLEAVED_LONGERTHAN50 = "#SIXTY\n" + THIRTY_CHARS
329             + "\n" + TWENTY_CHARS + "9993332221\n";
330     MegaFile testee = new MegaFile(NONINTERLEAVED_LONGERTHAN50,
331             AppletFormatAdapter.PASTE);
332     assertEquals(30, testee.getPositionsPerLine());
333     testee.setPositionsPerLine(25);
334     String printed = testee.print();
335
336     /*
337      * 25 positions per line is rounded down to 20 (two blocks of 10)
338      */
339     String expected = "#MEGA\n\n" + "#SIXTY\n"
340             + "0123456789 klmnopqrst [20]\n"
341             + "ABCDEFGHIJ 9876543210 [40]\n"
342             + "abcdefghij 9993332221 [60]\n";
343     assertEquals("Print format wrong", expected, printed);
344   }
345
346   /**
347    * Test parse of data including description
348    * 
349    * @throws IOException
350    */
351   @Test(groups = { "Functional" })
352   public void testParse_withDescription() throws IOException
353   {
354     MegaFile testee = new MegaFile(INTERLEAVED_WITH_DESCRIPTION,
355             AppletFormatAdapter.PASTE);
356     assertEquals("Title not as expected", "Data with description",
357             testee.getAlignmentProperty(MegaFile.PROP_TITLE));
358
359     Vector<SequenceI> seqs = testee.getSeqs();
360     // should be 2 sequences
361     assertEquals("Expected two sequences", 2, seqs.size());
362     // check sequence names correct and order preserved
363     assertEquals("First sequence id wrong", "U455", seqs.get(0).getName());
364     assertEquals("Second sequence id wrong", "CPZANT", seqs.get(1)
365             .getName());
366     // check sequence data
367     assertEquals("First sequence data wrong", "C--GTACGA--T", seqs.get(0)
368             .getSequenceAsString());
369     assertEquals("Second sequence data wrong", "ATC-G-CA--GC", seqs.get(1)
370             .getSequenceAsString());
371     assertTrue("File format is not flagged as interleaved",
372             testee.isInterleaved());
373
374     assertEquals(
375             "Description property not parsed",
376             "    Line one of description\n" + "    Line two of description",
377             testee.getAlignmentProperty(MegaFile.PROP_DESCRIPTION));
378   }
379
380   @Test(groups = { "Functional" })
381   public void testGetNonCommentContent() throws FileFormatException
382   {
383     assertEquals("abcde", MegaFile.getNonCommentContent("abcde", 0));
384     assertEquals("CGT ACG GAC ",
385             MegaFile.getNonCommentContent("CGT ACG GAC [9]", 0));
386     assertEquals("", MegaFile.getNonCommentContent("abcde", 1));
387     assertEquals(" abcde",
388             MegaFile.getNonCommentContent("and others ] abcde", 1));
389     assertEquals(" abcde", MegaFile.getNonCommentContent(
390             "and others [including refs] ] abcde", 1));
391     assertEquals(" x ] abcde",
392             MegaFile.getNonCommentContent("and others ] x ] abcde", 1));
393   }
394
395   @Test(groups = { "Functional" })
396   public void testCommentDepth() throws FileFormatException
397   {
398     assertEquals(0, MegaFile.commentDepth("abcde", 0));
399     assertEquals(1, MegaFile.commentDepth("abc[de", 0));
400     assertEquals(3, MegaFile.commentDepth("ab[c[de", 1));
401     assertEquals(1, MegaFile.commentDepth("ab]c[d]e[f", 1));
402     assertEquals(0, MegaFile.commentDepth("a]b[c]d]e", 1));
403   }
404
405   @Test(groups = { "Functional" })
406   public void testGetValue()
407   {
408     assertEquals("Mega", MegaFile.getValue("Name=Mega"));
409     assertEquals("Mega", MegaFile.getValue("Name =Mega"));
410     assertEquals("Mega", MegaFile.getValue(" Name = Mega "));
411     assertEquals("Mega", MegaFile.getValue("Name = Mega; "));
412     assertEquals("Mega", MegaFile.getValue(" Name = Mega ; "));
413     assertEquals("Mega", MegaFile.getValue("\t!Name \t= \tMega ; "));
414     assertEquals("Mega", MegaFile.getValue("!Name \t\t Mega; "));
415     assertEquals("", MegaFile.getValue("Name"));
416   }
417
418   /**
419    * Test reading a MEGA file to an alignment then writing it out in MEGA
420    * format. Verify the output is (functionally) the same as the input.
421    * 
422    * @throws IOException
423    */
424   @Test(groups = "Functional")
425   public void testRoundTrip_Interleaved() throws IOException
426   {
427     AppletFormatAdapter fa = new AppletFormatAdapter();
428     AlignmentI al = fa.readFile(INTERLEAVED_WITH_DESCRIPTION,
429             AppletFormatAdapter.PASTE, "MEGA");
430     MegaFile output = new MegaFile();
431     String formatted = output.print(al);
432     //@formatter:off
433     String expected = 
434          "#MEGA\n!Title Data with description;\n" +
435          "!Description\n" +
436          "    Line one of description\n" +
437          "    Line two of description;\n" +
438          "!Format\n" +
439          "    DataType=DNA CodeTable=Standard\n" +
440          "    NSeqs=2 NSites=12\n" + // NSites includes gaps
441          "    Indel=- Identical=. Missing=?;\n\n" +
442          "#U455   C-- GTA [6]\n" +
443          "#CPZANT ATC -G- [6]\n\n" +
444          "#U455   CGA --T [12]\n" +
445          "#CPZANT CA- -GC [12]\n";
446     //@formatter:on
447     assertEquals("Roundtrip didn't match", expected,
448             formatted);
449   }
450
451   /**
452    * Test reading a MEGA file to an alignment then writing it out in MEGA
453    * format. Verify the output is (functionally) the same as the input.
454    * 
455    * @throws IOException
456    */
457   @Test(groups = "Functional")
458   public void testRoundTrip_multilineFormatWithComments()
459           throws IOException
460   {
461     AppletFormatAdapter fa = new AppletFormatAdapter();
462     //@formatter:off
463     AlignmentI al = fa.readFile("#MEGA\n"
464     + "!Title Data with description;\n"
465     + "[ this comment should be ignored\n"
466     + "including [this nested comment]\n"
467     + "]\n"
468     + "!Format \n"
469     + "DataType=DNA CodeTable=Standard\n"
470     + "indel=- Missing=? MatchChar=.;\n\n"
471     + "!Description\n" 
472     + "    Line one of description\n"
473     + "    Line two of description;\n\n"
474     + "#U455   CGC GTA\n" 
475     + "#CPZANT ATC GGG\n\n"
476     + "#U455   CGA TTT\n" 
477     + "#CPZANT CAA TGC\n",
478             AppletFormatAdapter.PASTE, "MEGA");
479     //@formatter:on
480     MegaFile output = new MegaFile();
481     String formatted = output.print(al);
482     //@formatter:off
483     String expected = 
484          "#MEGA\n!Title Data with description;\n" +
485          "!Description\n" +
486          "    Line one of description\n" +
487          "    Line two of description;\n" +
488          "!Format\n" +
489          "    DataType=DNA CodeTable=Standard\n" +
490          "    NSeqs=2 NSites=12\n" +
491          "    Indel=- Identical=. Missing=?;\n\n" +
492          "#U455   CGC GTA [6]\n" +
493          "#CPZANT ATC GGG [6]\n\n" +
494          "#U455   CGA TTT [12]\n" +
495          "#CPZANT CAA TGC [12]\n";
496     //@formatter:on
497     assertEquals("Roundtrip didn't match", expected,
498             formatted);
499   }
500
501   //@formatter:on
502   
503   /**
504    * Test parse of interleaved mega format data where the identity character is
505    * used in sequences after the first
506    * 
507    * @throws IOException
508    */
509   @Test(groups = { "Functional" })
510   public void testParse_interleavedWithIdentityAndTabs() throws IOException
511   {
512     //@formatter:off
513     // uses tab instead of space separators to check robustness
514     MegaFile testee = new MegaFile("#MEGA\n"+ 
515     "!TITLE\tInterleaved sequence data;\n" +
516     "!Format\tIdentical=.;\n\n" +
517     "#U455\tABCDEF\n" + 
518     "#CPZANT\tM..P.R\n\n" + 
519     "#U455\t\tKLMNOP\n" +
520     "#CPZANT\t..YZ..", AppletFormatAdapter.PASTE);
521     //@formatter:on
522     assertEquals("Title not as expected", "Interleaved sequence data",
523             testee.getAlignmentProperty(MegaFile.PROP_TITLE));
524     Vector<SequenceI> seqs = testee.getSeqs();
525     // should be 2 sequences
526     assertEquals("Expected two sequences", 2, seqs.size());
527     // check sequence names correct and order preserved
528     assertEquals("First sequence id wrong", "U455", seqs.get(0).getName());
529     assertEquals("Second sequence id wrong", "CPZANT", seqs.get(1)
530             .getName());
531     // check sequence data
532     assertEquals("First sequence data wrong", "ABCDEFKLMNOP", seqs.get(0)
533             .getSequenceAsString());
534     assertEquals("Second sequence data wrong", "MBCPERKLYZOP", seqs.get(1)
535             .getSequenceAsString());
536     assertTrue("File format is not flagged as interleaved",
537             testee.isInterleaved());
538   }
539
540   /**
541    * Test parse of noninterleaved format data including identity symbol
542    * 
543    * @throws IOException
544    */
545   @Test(groups = { "Functional" })
546   public void testParse_nonInterleavedWithIdentity() throws IOException
547   {
548     //@formatter:off
549     MegaFile testee = new MegaFile("#MEGA\n"
550     + "!TITLE Noninterleaved sequence data;\n"
551     + "!Format MatchChar=.;\n"
552     + "#U455  \n"
553     + "ABCFEDHIJ\n" 
554     + "MNOPQR\n\n" 
555     + "#CPZANT \n" 
556     + "KL..O..XYZ\n" 
557     + "CG..C\n",
558             AppletFormatAdapter.PASTE);
559     //@formatter:on
560     assertEquals("Title not as expected", "Noninterleaved sequence data",
561             testee.getAlignmentProperty(MegaFile.PROP_TITLE));
562     Vector<SequenceI> seqs = testee.getSeqs();
563     // should be 2 sequences
564     assertEquals("Expected two sequences", 2, seqs.size());
565     // check sequence names correct and order preserved
566     assertEquals("First sequence id wrong", "U455", seqs.get(0).getName());
567     assertEquals("Second sequence id wrong", "CPZANT", seqs.get(1)
568             .getName());
569     // check sequence data
570     assertEquals("First sequence data wrong", "ABCFEDHIJMNOPQR", seqs
571             .get(0).getSequenceAsString());
572     assertEquals("Second sequence data wrong", "KLCFODHXYZCGPQC",
573             seqs.get(1).getSequenceAsString());
574     assertFalse("File format is not flagged as noninterleaved",
575             testee.isInterleaved());
576   }
577
578   //@formatter:on
579   
580   /**
581    * Test parse of interleaved format data including position number comments.
582    * 
583    * @throws IOException
584    */
585   @Test(groups = { "Functional" })
586   public void testParse_interleavedWithPositionNumber() throws IOException
587   {
588     //@formatter:off
589     MegaFile testee = new MegaFile("#MEGA\n"+ 
590     "TITLE: Interleaved sequence data\n\n" + 
591     "#U455   ABCDEF [6]\n" + 
592     "#CPZANT  MNOPQR [6]\n\n" + 
593     "#U455   KLMNOP [12]\n" + 
594     "#CPZANT WXYZGC [12]\n", AppletFormatAdapter.PASTE);
595     //@formatter:on
596     assertEquals("Title not as expected", "Interleaved sequence data",
597             testee.getAlignmentProperty(MegaFile.PROP_TITLE));
598     Vector<SequenceI> seqs = testee.getSeqs();
599     // should be 2 sequences
600     assertEquals("Expected two sequences", 2, seqs.size());
601     // check sequence names correct and order preserved
602     assertEquals("First sequence id wrong", "U455", seqs.get(0).getName());
603     assertEquals("Second sequence id wrong", "CPZANT", seqs.get(1)
604             .getName());
605     // check sequence data
606     assertEquals("First sequence data wrong", "ABCDEFKLMNOP", seqs.get(0)
607             .getSequenceAsString());
608     assertEquals("Second sequence data wrong", "MNOPQRWXYZGC", seqs.get(1)
609             .getSequenceAsString());
610     assertTrue("File format is not flagged as interleaved",
611             testee.isInterleaved());
612   }
613
614   //@formatter:on
615   
616   /**
617    * Test parse of data with !Gene and !Domain statements.
618    * 
619    * @throws IOException
620    */
621   @Test(groups = { "Functional" })
622   public void testParse_geneDomains() throws IOException
623   {
624     //@formatter:off
625     String data = "#MEGA\n"+ 
626     "TITLE: Interleaved sequence data\n\n" + 
627     "#U455   CCCCCC\n" + 
628     "#CPZANT  TTTTTT\n\n" +
629     "!Domain=Exon1 Gene=Adh Property=Coding CodonStart=1;\n" +
630     "#U455   GGGGGG\n" + 
631     "#CPZANT AAAAAA\n\n" +
632     "!domain=Intron1 Property=Intron Gene=Adh;\n" +
633     "#U455   tttttt\n" + 
634     "#CPZANT cccccc\n\n" +
635     "!Domain=Exon2 Gene=Adh Property=Exon CodonStart=1;\n" +
636     "#U455   aaaaaa\n" + 
637     "#CPZANT gggggg\n\n" +
638     // explicit end of Exon2, implicit end of Adh:
639     "!Domain=Exon2 Property=domainend;\n" +
640     "!Domain=Intron1 Gene=Opsin Property=Noncoding;\n" +
641     "#U455   GGGGGG\n" + 
642     "#CPZANT AAAAAA\n\n" +
643     // end Opsin, start MEF2A
644     "!Domain=Exon1 Gene=MEF2A Property=Coding CodonStart=1;\n" +
645     "#U455   tttttt\n" + 
646     "#CPZANT cccccc\n\n" +
647     // end MEF2A
648     "!Domain=BindingSite;\n" +
649     "#U455   CCCCCC\n" + 
650     "#CPZANT TTTTTT\n\n";
651     //@formatter:on
652     MegaFile testee = new MegaFile(data, AppletFormatAdapter.PASTE);
653
654     Vector<SequenceI> seqs = testee.getSeqs();
655     // should be 2 sequences
656     assertEquals("Expected two sequences", 2, seqs.size());
657     // check sequence data
658     assertEquals("First sequence data wrong",
659             "CCCCCCGGGGGGttttttaaaaaaGGGGGGttttttCCCCCC", seqs.get(0)
660             .getSequenceAsString());
661     assertEquals("Second sequence data wrong",
662             "TTTTTTAAAAAAccccccggggggAAAAAAccccccTTTTTT", seqs.get(1)
663             .getSequenceAsString());
664
665     /*
666      * sequences should have features for Gene=Adh 7-24, Exon1 7-12, Intron1
667      * 13-18, Exon2 19-24, BindingSite 25-30
668      */
669     for (SequenceI seq : seqs) {
670       SequenceFeature[] sfs = seq.getSequenceFeatures();
671       // features are added in the order in which their end is found
672       // (Domain before Gene when they end together)
673       assertEquals(9, sfs.length);
674       // TODO settle which way round type/description go!
675       verifySequenceFeature(sfs[0], "Exon1 (Adh Coding)", "Domain", 7, 12);
676       verifySequenceFeature(sfs[1], "Intron1 (Adh Noncoding)", "Domain",
677               13, 18);
678       verifySequenceFeature(sfs[2], "Exon2 (Adh Coding)", "Domain", 19, 24);
679       verifySequenceFeature(sfs[3], "Adh", "Gene", 7, 24);
680       verifySequenceFeature(sfs[4], "Intron1 (Opsin Noncoding)", "Domain",
681               25, 30);
682       verifySequenceFeature(sfs[5], "Opsin", "Gene", 25, 30);
683       verifySequenceFeature(sfs[6], "Exon1 (MEF2A Coding)", "Domain", 31,
684               36);
685       verifySequenceFeature(sfs[7], "MEF2A", "Gene", 31, 36);
686       verifySequenceFeature(sfs[8], "BindingSite", "Domain", 37, 42);
687     }
688
689     /*
690      * verify gene and domain alignment annotations
691      */
692     assertEquals(2, testee.annotations.size());
693     AlignmentAnnotation ann = testee.annotations.get(0);
694     assertEquals("MEGA Gene", ann.label);
695     assertEquals(42, ann.annotations.length);
696     verifyAnnotation(ann, 0, 6, null);
697     verifyAnnotation(ann, 6, 24, "Adh");
698     verifyAnnotation(ann, 24, 30, "Opsin");
699     verifyAnnotation(ann, 30, 36, "MEF2A");
700     verifyAnnotation(ann, 37, 42, null);
701
702     ann = testee.annotations.get(1);
703     assertEquals("MEGA Domain", ann.label);
704     assertEquals(42, ann.annotations.length);
705     verifyAnnotation(ann, 0, 6, null);
706     verifyAnnotation(ann, 6, 12, "Exon1 (Adh Coding)");
707     verifyAnnotation(ann, 12, 18, "Intron1 (Adh Noncoding)");
708     verifyAnnotation(ann, 19, 24, "Exon2 (Adh Coding)");
709     verifyAnnotation(ann, 25, 30, "Intron1 (Opsin Noncoding)");
710     verifyAnnotation(ann, 31, 36, "Exon1 (MEF2A Coding)");
711     verifyAnnotation(ann, 37, 42, "BindingSite");
712
713   }
714
715   /**
716    * Helper method to verify a range of annotation positions all have the given
717    * description
718    * 
719    * @param ann
720    *          array of annotations to check
721    * @param from
722    *          start index to check
723    * @param to
724    *          end index to check (exclusive)
725    * @param description
726    *          value to assert
727    */
728   protected void verifyAnnotation(AlignmentAnnotation ann, int from,
729           int to, String description)
730   {
731     for (int pos = from; pos < to; pos++)
732     {
733       if (description == null)
734       {
735         assertNull(ann.annotations[pos]);
736       }
737       else
738       {
739         assertEquals(description, ann.annotations[pos].description);
740       }
741     }
742   }
743
744   /**
745    * Helper method to assert properties of a SequenceFeature
746    * 
747    * @param sf
748    * @param description
749    * @param type
750    * @param begin
751    * @param end
752    */
753   protected void verifySequenceFeature(SequenceFeature sf,
754           String description, String type, int begin, int end)
755   {
756     assertEquals(description, sf.type);
757     assertEquals(type, sf.description);
758     assertEquals(begin, sf.begin);
759     assertEquals(end, sf.end);
760   }
761
762   //@formatter:on
763   
764   /**
765    * Test parse of data including !Label statements. An underscore means no
766    * label, other characters are treated as alignment annotation.
767    * 
768    * @throws IOException
769    */
770   @Test(groups = { "Functional" })
771   public void testParse_withLabels() throws IOException
772   {
773     //@formatter:off
774     MegaFile testee = new MegaFile("#MEGA\n"+ 
775     "TITLE: Interleaved sequence data\n\n" + 
776     "#U455   ABC DEF\n" + 
777     "#CPZANT MNO PQR\n" +
778     "!Label  +-_ 23_\n\n" +
779     // a row with no labels = null annotation
780     "#U455   abc def\n" + 
781     "#CPZANT mno pqr\n\n" +
782     "#U455   KLM NOP\n" + 
783     "#CPZANT WXY ZGC\n" +
784     "!label  __3 +X_\n", AppletFormatAdapter.PASTE);
785     //@formatter:on
786     Vector<SequenceI> seqs = testee.getSeqs();
787     assertEquals("Expected two sequences", 2, seqs.size());
788     assertEquals("First sequence data wrong", "ABCDEFabcdefKLMNOP", seqs
789             .get(0)
790             .getSequenceAsString());
791     assertEquals("Second sequence data wrong", "MNOPQRmnopqrWXYZGC", seqs
792             .get(1)
793             .getSequenceAsString());
794
795     // check AlignmentAnnotation added with expected values
796     assertEquals(1, testee.annotations.size());
797     AlignmentAnnotation aa = testee.annotations.get(0);
798     assertNull(aa.sequenceRef);
799     assertEquals("MEGA Label", aa.label);
800     assertEquals(18, aa.annotations.length);
801     assertEquals("+, -, , 2, 3, , , , , , , , , , 3, +, X, , ",
802             aa.toString());
803   }
804
805   //@formatter:on
806   
807   /**
808    * Test case where a domain is implicitly terminated by starting a new gene
809    * 
810    * @throws IOException
811    */
812   @Test(groups = { "Functional" })
813   public void testParse_changeOfGeneEndsDomain() throws IOException
814   {
815     //@formatter:off
816     // uses tab instead of space separators to check robustness
817     MegaFile testee = new MegaFile("#MEGA\n"+ 
818     "!TITLE Interleaved sequence data;\n" +
819     "!Format Identical=.;\n\n" +
820     "!Gene=gene1 Domain=Exon1 Property=Coding;\n" +
821     "#U455 ABCDEF\n" + 
822     "#CPZANT M..P.R\n\n" + 
823     "!Gene=gene2;\n" +
824     "#U455 KLMNOP\n" +
825     "#CPZANT ..YZ..", AppletFormatAdapter.PASTE);
826     //@formatter:on
827     Vector<SequenceI> seqs = testee.getSeqs();
828     assertEquals("Expected two sequences", 2, seqs.size());
829     assertEquals("First sequence data wrong", "ABCDEFKLMNOP", seqs.get(0)
830             .getSequenceAsString());
831     assertEquals("Second sequence data wrong", "MBCPERKLYZOP", seqs.get(1)
832             .getSequenceAsString());
833     assertTrue("File format is not flagged as interleaved",
834             testee.isInterleaved());
835
836     for (SequenceI seq : seqs)
837     {
838       SequenceFeature[] sfs = seq.getSequenceFeatures();
839       assertEquals(3, sfs.length);
840       verifySequenceFeature(sfs[0], "Exon1 (gene1 Coding)", "Domain", 1, 6);
841       verifySequenceFeature(sfs[1], "gene1", "Gene", 1, 6);
842       verifySequenceFeature(sfs[2], "gene2", "Gene", 7, 12);
843     }
844   }
845
846   //@formatter:on
847   
848   /**
849    * Test case where the declared gap character is one Jalview does not support;
850    * it should be converted to a '-'
851    * 
852    * @throws IOException
853    */
854   @Test(groups = { "Functional" })
855   public void testParse_weirdGapCharacter() throws IOException
856   {
857     //@formatter:off
858     String data = "#MEGA\n"+ 
859     "!TITLE Interleaved sequence data;\n" +
860     "!Format Identical=. Indel=%;\n\n" +
861     "#U455 %BC%EF\n" + 
862     "#CPZANT M..P.R\n\n" + 
863     "#U455 KLMNOP\n" +
864     "#CPZANT .%%Z..";
865     AppletFormatAdapter fa = new AppletFormatAdapter();
866     AlignmentI al = fa.readFile(data,
867             AppletFormatAdapter.PASTE, "MEGA");
868     //@formatter:on
869     List<SequenceI> seqs = al.getSequences();
870     assertEquals("First sequence data wrong", "-BC-EFKLMNOP", seqs.get(0)
871             .getSequenceAsString());
872     assertEquals("Second sequence data wrong", "MBCPERK--ZOP", seqs.get(1)
873             .getSequenceAsString());
874     assertEquals('-', al.getGapCharacter());
875   }
876
877   /**
878    * Test reading a MEGA file to an alignment then writing it out in MEGA
879    * format. Includes !Label statements which should be converted to
880    * AlignmentAnnotation and back again.
881    * 
882    * @throws IOException
883    */
884   @Test(groups = "Functional")
885   public void testRoundTrip_withLabels() throws IOException
886   {
887     AppletFormatAdapter fa = new AppletFormatAdapter();
888
889     //@formatter:off
890     String data = "#MEGA\n"
891     + "#U455   C-- GTA\n" 
892     + "#CPZANT ATC -G-\n"
893     + "!Label F__E_H\n\n"
894     + "#U455   CGA --T\n" 
895     + "#CPZANT CA- -GC\n"
896     + "!Label FFH__E\n";
897     AlignmentI al = fa.readFile(data,
898             AppletFormatAdapter.PASTE, "MEGA");
899     AlignmentAnnotation aa = al.getAlignmentAnnotation()[0];
900     assertEquals("MEGA Label", aa.label);
901     assertEquals("F, , , E, , H, F, F, H, , , E, ",
902             aa.toString());
903
904     MegaFile output = new MegaFile();
905     String formatted = output.print(al);
906     String expected = 
907         "#MEGA\n" +
908         "!Format\n" +
909         "    DataType=Nucleotide CodeTable=Standard\n" +
910         "    NSeqs=2 NSites=12\n" +
911         "    Indel=-;\n\n" +
912         "#U455   C-- GTA [6]\n" +
913         "#CPZANT ATC -G- [6]\n" +
914         "!Label F__ E_H;\n\n" +  
915         "#U455   CGA --T [12]\n" +
916         "#CPZANT CA- -GC [12]\n" +
917         "!Label FFH __E;\n";
918     //@formatter:on
919     assertEquals("Roundtrip didn't match", expected,
920             formatted);
921   }
922 }