bdae11a71cc37e8cd36e11bccbd38e5d372a997f
[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   /**
691    * Helper method to assert properties of a SequenceFeature
692    * 
693    * @param sf
694    * @param description
695    * @param type
696    * @param begin
697    * @param end
698    */
699   protected void verifySequenceFeature(SequenceFeature sf,
700           String description, String type, int begin, int end)
701   {
702     assertEquals(description, sf.type);
703     assertEquals(type, sf.description);
704     assertEquals(begin, sf.begin);
705     assertEquals(end, sf.end);
706   }
707
708   //@formatter:on
709   
710   /**
711    * Test parse of data including !Label statements. An underscore means no
712    * label, other characters are treated as alignment annotation.
713    * 
714    * @throws IOException
715    */
716   @Test(groups = { "Functional" })
717   public void testParse_withLabels() throws IOException
718   {
719     //@formatter:off
720     MegaFile testee = new MegaFile("#MEGA\n"+ 
721     "TITLE: Interleaved sequence data\n\n" + 
722     "#U455   ABC DEF\n" + 
723     "#CPZANT MNO PQR\n" +
724     "!Label  +-_ 23_\n\n" +
725     // a row with no labels = null annotation
726     "#U455   abc def\n" + 
727     "#CPZANT mno pqr\n\n" +
728     "#U455   KLM NOP\n" + 
729     "#CPZANT WXY ZGC\n" +
730     "!label  __3 +X_\n", AppletFormatAdapter.PASTE);
731     //@formatter:on
732     Vector<SequenceI> seqs = testee.getSeqs();
733     assertEquals("Expected two sequences", 2, seqs.size());
734     assertEquals("First sequence data wrong", "ABCDEFabcdefKLMNOP", seqs
735             .get(0)
736             .getSequenceAsString());
737     assertEquals("Second sequence data wrong", "MNOPQRmnopqrWXYZGC", seqs
738             .get(1)
739             .getSequenceAsString());
740
741     // check AlignmentAnnotation added with expected values
742     assertEquals(1, testee.annotations.size());
743     AlignmentAnnotation aa = testee.annotations.get(0);
744     assertNull(aa.sequenceRef);
745     assertEquals("MEGA Label", aa.label);
746     assertEquals(18, aa.annotations.length);
747     assertEquals("+, -, , 2, 3, , , , , , , , , , 3, +, X, , ",
748             aa.toString());
749   }
750
751   //@formatter:on
752   
753   /**
754    * Test case where a domain is implicitly terminated by starting a new gene
755    * 
756    * @throws IOException
757    */
758   @Test(groups = { "Functional" })
759   public void testParse_changeOfGeneEndsDomain() throws IOException
760   {
761     //@formatter:off
762     // uses tab instead of space separators to check robustness
763     MegaFile testee = new MegaFile("#MEGA\n"+ 
764     "!TITLE Interleaved sequence data;\n" +
765     "!Format Identical=.;\n\n" +
766     "!Gene=gene1 Domain=Exon1 Property=Coding;\n" +
767     "#U455 ABCDEF\n" + 
768     "#CPZANT M..P.R\n\n" + 
769     "!Gene=gene2;\n" +
770     "#U455 KLMNOP\n" +
771     "#CPZANT ..YZ..", AppletFormatAdapter.PASTE);
772     //@formatter:on
773     Vector<SequenceI> seqs = testee.getSeqs();
774     assertEquals("Expected two sequences", 2, seqs.size());
775     assertEquals("First sequence data wrong", "ABCDEFKLMNOP", seqs.get(0)
776             .getSequenceAsString());
777     assertEquals("Second sequence data wrong", "MBCPERKLYZOP", seqs.get(1)
778             .getSequenceAsString());
779     assertTrue("File format is not flagged as interleaved",
780             testee.isInterleaved());
781
782     for (SequenceI seq : seqs)
783     {
784       SequenceFeature[] sfs = seq.getSequenceFeatures();
785       assertEquals(3, sfs.length);
786       verifySequenceFeature(sfs[0], "Exon1 (gene1 Coding)", "Domain", 1, 6);
787       verifySequenceFeature(sfs[1], "gene1", "Gene", 1, 6);
788       verifySequenceFeature(sfs[2], "gene2", "Gene", 7, 12);
789     }
790   }
791
792   //@formatter:on
793   
794   /**
795    * Test case where the declared gap character is one Jalview does not support;
796    * it should be converted to a '-'
797    * 
798    * @throws IOException
799    */
800   @Test(groups = { "Functional" })
801   public void testParse_weirdGapCharacter() throws IOException
802   {
803     //@formatter:off
804     String data = "#MEGA\n"+ 
805     "!TITLE Interleaved sequence data;\n" +
806     "!Format Identical=. Indel=%;\n\n" +
807     "#U455 %BC%EF\n" + 
808     "#CPZANT M..P.R\n\n" + 
809     "#U455 KLMNOP\n" +
810     "#CPZANT .%%Z..";
811     AppletFormatAdapter fa = new AppletFormatAdapter();
812     AlignmentI al = fa.readFile(data,
813             AppletFormatAdapter.PASTE, "MEGA");
814     //@formatter:on
815     List<SequenceI> seqs = al.getSequences();
816     assertEquals("First sequence data wrong", "-BC-EFKLMNOP", seqs.get(0)
817             .getSequenceAsString());
818     assertEquals("Second sequence data wrong", "MBCPERK--ZOP", seqs.get(1)
819             .getSequenceAsString());
820     assertEquals('-', al.getGapCharacter());
821   }
822
823   /**
824    * Test reading a MEGA file to an alignment then writing it out in MEGA
825    * format. Includes !Label statements which should be converted to
826    * AlignmentAnnotation and back again.
827    * 
828    * @throws IOException
829    */
830   @Test(groups = "Functional")
831   public void testRoundTrip_withLabels() throws IOException
832   {
833     AppletFormatAdapter fa = new AppletFormatAdapter();
834
835     //@formatter:off
836     String data = "#MEGA\n"
837     + "#U455   C-- GTA\n" 
838     + "#CPZANT ATC -G-\n"
839     + "!Label F__E_H\n\n"
840     + "#U455   CGA --T\n" 
841     + "#CPZANT CA- -GC\n"
842     + "!Label FFH__E\n";
843     AlignmentI al = fa.readFile(data,
844             AppletFormatAdapter.PASTE, "MEGA");
845     AlignmentAnnotation aa = al.getAlignmentAnnotation()[0];
846     assertEquals("MEGA Label", aa.label);
847     assertEquals("F, , , E, , H, F, F, H, , , E, ",
848             aa.toString());
849
850     MegaFile output = new MegaFile();
851     String formatted = output.print(al);
852     String expected = 
853         "#MEGA\n" +
854         "!Format\n" +
855         "    DataType=Nucleotide CodeTable=Standard\n" +
856         "    NSeqs=2 NSites=12\n" +
857         "    Indel=-;\n\n" +
858         "#U455   C-- GTA [6]\n" +
859         "#CPZANT ATC -G- [6]\n" +
860         "!Label F__ E_H;\n\n" +  
861         "#U455   CGA --T [12]\n" +
862         "#CPZANT CA- -GC [12]\n" +
863         "!Label FFH __E;\n";
864     //@formatter:on
865     assertEquals("Roundtrip didn't match", expected,
866             formatted);
867   }
868 }