Merge branch 'feature/JAL-3187linkedFeatures' into feature/JAL-3251biotypedMappings
[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.analysis.AlignmentGenerator;
31 import jalview.gui.JvOptionPane;
32 import jalview.io.DataSourceType;
33 import jalview.io.FileFormat;
34 import jalview.io.FileFormatI;
35 import jalview.io.FormatAdapter;
36 import jalview.util.Comparison;
37 import jalview.util.MapList;
38
39 import java.io.IOException;
40 import java.util.Arrays;
41 import java.util.Iterator;
42 import java.util.List;
43
44 import org.testng.Assert;
45 import org.testng.annotations.BeforeClass;
46 import org.testng.annotations.BeforeMethod;
47 import org.testng.annotations.Test;
48
49 /**
50  * Unit tests for Alignment datamodel.
51  * 
52  * @author gmcarstairs
53  *
54  */
55 public class AlignmentTest
56 {
57
58   @BeforeClass(alwaysRun = true)
59   public void setUpJvOptionPane()
60   {
61     JvOptionPane.setInteractiveMode(false);
62     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
63   }
64
65   // @formatter:off
66   private static final String TEST_DATA = 
67           "# STOCKHOLM 1.0\n" +
68           "#=GS D.melanogaster.1 AC AY119185.1/838-902\n" +
69           "#=GS D.melanogaster.2 AC AC092237.1/57223-57161\n" +
70           "#=GS D.melanogaster.3 AC AY060611.1/560-627\n" +
71           "D.melanogaster.1          G.AGCC.CU...AUGAUCGA\n" +
72           "#=GR D.melanogaster.1 SS  ................((((\n" +
73           "D.melanogaster.2          C.AUUCAACU.UAUGAGGAU\n" +
74           "#=GR D.melanogaster.2 SS  ................((((\n" +
75           "D.melanogaster.3          G.UGGCGCU..UAUGACGCA\n" +
76           "#=GR D.melanogaster.3 SS  (.(((...(....(((((((\n" +
77           "//";
78
79   private static final String AA_SEQS_1 = 
80           ">Seq1Name/5-8\n" +
81           "K-QY--L\n" +
82           ">Seq2Name/12-15\n" +
83           "-R-FP-W-\n";
84
85   private static final String CDNA_SEQS_1 = 
86           ">Seq1Name/100-111\n" +
87           "AC-GG--CUC-CAA-CT\n" +
88           ">Seq2Name/200-211\n" +
89           "-CG-TTA--ACG---AAGT\n";
90
91   private static final String CDNA_SEQS_2 = 
92           ">Seq1Name/50-61\n" +
93           "GCTCGUCGTACT\n" +
94           ">Seq2Name/60-71\n" +
95           "GGGTCAGGCAGT\n";
96   // @formatter:on
97
98   private AlignmentI al;
99
100   /**
101    * Helper method to load an alignment and ensure dataset sequences are set up.
102    * 
103    * @param data
104    * @param format
105    *          TODO
106    * @return
107    * @throws IOException
108    */
109   protected AlignmentI loadAlignment(final String data, FileFormatI format)
110           throws IOException
111   {
112     AlignmentI a = new FormatAdapter().readFile(data, DataSourceType.PASTE,
113             format);
114     a.setDataset(null);
115     return a;
116   }
117
118   /**
119    * assert wrapper: tests all references in the given alignment are consistent
120    * 
121    * @param alignment
122    */
123   public static void assertAlignmentDatasetRefs(AlignmentI alignment)
124   {
125     verifyAlignmentDatasetRefs(alignment, true, null);
126   }
127
128   /**
129    * assert wrapper: tests all references in the given alignment are consistent
130    * 
131    * @param alignment
132    * @param message
133    *          - prefixed to any assert failed messages
134    */
135   public static void assertAlignmentDatasetRefs(AlignmentI alignment,
136           String message)
137   {
138     verifyAlignmentDatasetRefs(alignment, true, message);
139   }
140
141   /**
142    * verify sequence and dataset references are properly contained within
143    * dataset
144    * 
145    * @param alignment
146    *          - the alignmentI object to verify (either alignment or dataset)
147    * @param raiseAssert
148    *          - when set, testng assertions are raised.
149    * @param message
150    *          - null or a string message to prepend to the assert failed
151    *          messages.
152    * @return true if alignment references were in order, otherwise false.
153    */
154   public static boolean verifyAlignmentDatasetRefs(AlignmentI alignment,
155           boolean raiseAssert, String message)
156   {
157     if (message == null)
158     {
159       message = "";
160     }
161     if (alignment == null)
162     {
163       if (raiseAssert)
164       {
165         Assert.fail(message + "Alignment for verification was null.");
166       }
167       return false;
168     }
169     if (alignment.getDataset() != null)
170     {
171       AlignmentI dataset = alignment.getDataset();
172       // check all alignment sequences have their dataset within the dataset
173       for (SequenceI seq : alignment.getSequences())
174       {
175         SequenceI seqds = seq.getDatasetSequence();
176         if (seqds.getDatasetSequence() != null)
177         {
178           if (raiseAssert)
179           {
180             Assert.fail(message
181                     + " Alignment contained a sequence who's dataset sequence has a second dataset reference.");
182           }
183           return false;
184         }
185         if (dataset.findIndex(seqds) == -1)
186         {
187           if (raiseAssert)
188           {
189             Assert.fail(message
190                     + " Alignment contained a sequence who's dataset sequence was not in the dataset.");
191           }
192           return false;
193         }
194       }
195       return verifyAlignmentDatasetRefs(alignment.getDataset(),
196               raiseAssert, message);
197     }
198     else
199     {
200       int dsp = -1;
201       // verify all dataset sequences
202       for (SequenceI seqds : alignment.getSequences())
203       {
204         dsp++;
205         if (seqds.getDatasetSequence() != null)
206         {
207           if (raiseAssert)
208           {
209             Assert.fail(message
210                     + " Dataset contained a sequence with non-null dataset reference (ie not a dataset sequence!)");
211           }
212           return false;
213         }
214         int foundp = alignment.findIndex(seqds);
215         if (foundp != dsp)
216         {
217           if (raiseAssert)
218           {
219             Assert.fail(message
220                     + " Dataset sequence array contains a reference at "
221                     + dsp + " to a sequence first seen at " + foundp + " ("
222                     + seqds.toString() + ")");
223           }
224           return false;
225         }
226         if (seqds.getDBRefs() != null)
227         {
228           for (DBRefEntry dbr : seqds.getDBRefs())
229           {
230             if (dbr.getMap() != null)
231             {
232               SequenceI seqdbrmapto = dbr.getMap().getTo();
233               if (seqdbrmapto != null)
234               {
235                 if (seqdbrmapto.getDatasetSequence() != null)
236                 {
237                   if (raiseAssert)
238                   {
239                     Assert.fail(message
240                             + " DBRefEntry for sequence in alignment had map to sequence which was not a dataset sequence");
241                   }
242                   return false;
243
244                 }
245                 if (alignment.findIndex(dbr.getMap().getTo()) == -1)
246                 {
247                   if (raiseAssert)
248                   {
249                     Assert.fail(message
250                             + " DBRefEntry " + dbr + " for sequence "
251                             + seqds
252                             + " in alignment has map to sequence not in dataset");
253                   }
254                   return false;
255                 }
256               }
257             }
258           }
259         }
260       }
261       // finally, verify codonmappings involve only dataset sequences.
262       if (alignment.getCodonFrames() != null)
263       {
264         for (AlignedCodonFrame alc : alignment.getCodonFrames())
265         {
266           for (SequenceMapping ssm : alc.getMappings())
267           {
268             if (ssm.getFromSeq().getDatasetSequence() != null)
269             {
270               if (raiseAssert)
271               {
272                 Assert.fail(message
273                         + " CodonFrame-SSM-FromSeq is not a dataset sequence");
274               }
275               return false;
276             }
277             if (alignment.findIndex(ssm.getFromSeq()) == -1)
278             {
279
280               if (raiseAssert)
281               {
282                 Assert.fail(message
283                         + " CodonFrame-SSM-FromSeq is not contained in dataset");
284               }
285               return false;
286             }
287             if (ssm.getMapping().getTo().getDatasetSequence() != null)
288             {
289               if (raiseAssert)
290               {
291                 Assert.fail(message
292                         + " CodonFrame-SSM-Mapping-ToSeq is not a dataset sequence");
293               }
294               return false;
295             }
296             if (alignment.findIndex(ssm.getMapping().getTo()) == -1)
297             {
298
299               if (raiseAssert)
300               {
301                 Assert.fail(message
302                         + " CodonFrame-SSM-Mapping-ToSeq is not contained in dataset");
303               }
304               return false;
305             }
306           }
307         }
308       }
309     }
310     return true; // all relationships verified!
311   }
312
313   /**
314    * call verifyAlignmentDatasetRefs with and without assertion raising enabled,
315    * to check expected pass/fail actually occurs in both conditions
316    * 
317    * @param al
318    * @param expected
319    * @param msg
320    */
321   private void assertVerifyAlignment(AlignmentI al, boolean expected,
322           String msg)
323   {
324     if (expected)
325     {
326       try
327       {
328
329         Assert.assertTrue(verifyAlignmentDatasetRefs(al, true, null),
330                 "Valid test alignment failed when raiseAsserts enabled:"
331                         + msg);
332       } catch (AssertionError ae)
333       {
334         ae.printStackTrace();
335         Assert.fail(
336                 "Valid test alignment raised assertion errors when raiseAsserts enabled: "
337                         + msg, ae);
338       }
339       // also check validation passes with asserts disabled
340       Assert.assertTrue(verifyAlignmentDatasetRefs(al, false, null),
341               "Valid test alignment tested false when raiseAsserts disabled:"
342                       + msg);
343     }
344     else
345     {
346       boolean assertRaised = false;
347       try
348       {
349         verifyAlignmentDatasetRefs(al, true, null);
350       } catch (AssertionError ae)
351       {
352         // expected behaviour
353         assertRaised = true;
354       }
355       if (!assertRaised)
356       {
357         Assert.fail("Invalid test alignment passed when raiseAsserts enabled:"
358                 + msg);
359       }
360       // also check validation passes with asserts disabled
361       Assert.assertFalse(verifyAlignmentDatasetRefs(al, false, null),
362               "Invalid test alignment tested true when raiseAsserts disabled:"
363                       + msg);
364     }
365   }
366
367   @Test(groups = { "Functional" })
368   public void testVerifyAlignmentDatasetRefs()
369   {
370     SequenceI sq1 = new Sequence("sq1", "ASFDD"), sq2 = new Sequence("sq2",
371             "TTTTTT");
372
373     // construct simple valid alignment dataset
374     Alignment al = new Alignment(new SequenceI[] { sq1, sq2 });
375     // expect this to pass
376     assertVerifyAlignment(al, true, "Simple valid alignment didn't verify");
377
378     // check test for sequence->datasetSequence validity
379     sq1.setDatasetSequence(sq2);
380     assertVerifyAlignment(al, false,
381             "didn't detect dataset sequence with a dataset sequence reference.");
382
383     sq1.setDatasetSequence(null);
384     assertVerifyAlignment(
385             al,
386             true,
387             "didn't reinstate validity after nulling dataset sequence dataset reference");
388
389     // now create dataset and check again
390     al.createDatasetAlignment();
391     assertNotNull(al.getDataset());
392
393     assertVerifyAlignment(al, true,
394             "verify failed after createDatasetAlignment");
395
396     // create a dbref on sq1 with a sequence ref to sq2
397     DBRefEntry dbrs1tos2 = new DBRefEntry("UNIPROT", "1", "Q111111");
398     dbrs1tos2.setMap(new Mapping(sq2.getDatasetSequence(),
399             new int[] { 1, 5 }, new int[] { 2, 6 }, 1, 1));
400     sq1.getDatasetSequence().addDBRef(dbrs1tos2);
401     assertVerifyAlignment(al, true,
402             "verify failed after addition of valid DBRefEntry/map");
403     // now create a dbref on a new sequence which maps to another sequence
404     // outside of the dataset
405     SequenceI sqout = new Sequence("sqout", "ututututucagcagcag"), sqnew = new Sequence(
406             "sqnew", "EEERRR");
407     DBRefEntry sqnewsqout = new DBRefEntry("ENAFOO", "1", "R000001");
408     sqnewsqout.setMap(new Mapping(sqout, new int[] { 1, 6 }, new int[] { 1,
409         18 }, 1, 3));
410     al.getDataset().addSequence(sqnew);
411
412     assertVerifyAlignment(al, true,
413             "verify failed after addition of new sequence to dataset");
414     // now start checking exception conditions
415     sqnew.addDBRef(sqnewsqout);
416     assertVerifyAlignment(
417             al,
418             false,
419             "verify passed when a dbref with map to sequence outside of dataset was added");
420     // make the verify pass by adding the outsider back in
421     al.getDataset().addSequence(sqout);
422     assertVerifyAlignment(al, true,
423             "verify should have passed after adding dbref->to sequence in to dataset");
424     // and now the same for a codon mapping...
425     SequenceI sqanotherout = new Sequence("sqanotherout",
426             "aggtutaggcagcagcag");
427
428     AlignedCodonFrame alc = new AlignedCodonFrame();
429     alc.addMap(sqanotherout, sqnew, new MapList(new int[] { 1, 6 },
430             new int[] { 1, 18 }, 3, 1));
431
432     al.addCodonFrame(alc);
433     Assert.assertEquals(al.getDataset().getCodonFrames().size(), 1);
434
435     assertVerifyAlignment(
436             al,
437             false,
438             "verify passed when alCodonFrame mapping to sequence outside of dataset was added");
439     // make the verify pass by adding the outsider back in
440     al.getDataset().addSequence(sqanotherout);
441     assertVerifyAlignment(
442             al,
443             true,
444             "verify should have passed once all sequences involved in alCodonFrame were added to dataset");
445     al.getDataset().addSequence(sqanotherout);
446     assertVerifyAlignment(al, false,
447             "verify should have failed when a sequence was added twice to the dataset");
448     al.getDataset().deleteSequence(sqanotherout);
449     assertVerifyAlignment(al, true,
450             "verify should have passed after duplicate entry for sequence was removed");
451   }
452
453   /**
454    * checks that the sequence data for an alignment's dataset is non-redundant.
455    * Fails if there are sequences with same id, sequence, start, and.
456    */
457
458   public static void assertDatasetIsNormalised(AlignmentI al)
459   {
460     assertDatasetIsNormalised(al, null);
461   }
462
463   /**
464    * checks that the sequence data for an alignment's dataset is non-redundant.
465    * Fails if there are sequences with same id, sequence, start, and.
466    * 
467    * @param al
468    *          - alignment to verify
469    * @param message
470    *          - null or message prepended to exception message.
471    */
472   public static void assertDatasetIsNormalised(AlignmentI al, String message)
473   {
474     if (al.getDataset() != null)
475     {
476       assertDatasetIsNormalised(al.getDataset(), message);
477       return;
478     }
479     /*
480      * look for pairs of sequences with same ID, start, end, and sequence
481      */
482     List<SequenceI> seqSet = al.getSequences();
483     for (int p = 0; p < seqSet.size(); p++)
484     {
485       SequenceI pSeq = seqSet.get(p);
486       for (int q = p + 1; q < seqSet.size(); q++)
487       {
488         SequenceI qSeq = seqSet.get(q);
489         if (pSeq.getStart() != qSeq.getStart())
490         {
491           continue;
492         }
493         if (pSeq.getEnd() != qSeq.getEnd())
494         {
495           continue;
496         }
497         if (!pSeq.getName().equals(qSeq.getName()))
498         {
499           continue;
500         }
501         if (!Arrays.equals(pSeq.getSequence(), qSeq.getSequence()))
502         {
503           continue;
504         }
505         Assert.fail((message == null ? "" : message + " :")
506                 + "Found similar sequences at position " + p + " and " + q
507                 + "\n" + pSeq.toString());
508       }
509     }
510   }
511
512   @Test(groups = { "Functional", "Asserts" })
513   public void testAssertDatasetIsNormalised()
514   {
515     Sequence sq1 = new Sequence("s1/1-4", "asdf");
516     Sequence sq1shift = new Sequence("s1/2-5", "asdf");
517     Sequence sq1seqd = new Sequence("s1/1-4", "asdt");
518     Sequence sq2 = new Sequence("s2/1-4", "asdf");
519     Sequence sq1dup = new Sequence("s1/1-4", "asdf");
520
521     Alignment al = new Alignment(new SequenceI[] { sq1 });
522     al.setDataset(null);
523
524     try
525     {
526       assertDatasetIsNormalised(al);
527     } catch (AssertionError ae)
528     {
529       Assert.fail("Single sequence should be valid normalised dataset.");
530     }
531     al.addSequence(sq2);
532     try
533     {
534       assertDatasetIsNormalised(al);
535     } catch (AssertionError ae)
536     {
537       Assert.fail("Two different sequences should be valid normalised dataset.");
538     }
539     /*
540      * now change sq2's name in the alignment. should still be valid
541      */
542     al.findName(sq2.getName()).setName("sq1");
543     try
544     {
545       assertDatasetIsNormalised(al);
546     } catch (AssertionError ae)
547     {
548       Assert.fail("Two different sequences in dataset, but same name in alignment, should be valid normalised dataset.");
549     }
550
551     al.addSequence(sq1seqd);
552     try
553     {
554       assertDatasetIsNormalised(al);
555     } catch (AssertionError ae)
556     {
557       Assert.fail("sq1 and sq1 with different sequence should be distinct.");
558     }
559
560     al.addSequence(sq1shift);
561     try
562     {
563       assertDatasetIsNormalised(al);
564     } catch (AssertionError ae)
565     {
566       Assert.fail("sq1 and sq1 with different start/end should be distinct.");
567     }
568     /*
569      * finally, the failure case
570      */
571     al.addSequence(sq1dup);
572     boolean ssertRaised = false;
573     try
574     {
575       assertDatasetIsNormalised(al);
576
577     } catch (AssertionError ae)
578     {
579       ssertRaised = true;
580     }
581     if (!ssertRaised)
582     {
583       Assert.fail("Expected identical sequence to raise exception.");
584     }
585   }
586
587   /*
588    * Read in Stockholm format test data including secondary structure
589    * annotations.
590    */
591   @BeforeMethod(alwaysRun = true)
592   public void setUp() throws IOException
593   {
594     al = loadAlignment(TEST_DATA, FileFormat.Stockholm);
595     int i = 0;
596     for (AlignmentAnnotation ann : al.getAlignmentAnnotation())
597     {
598       ann.setCalcId("CalcIdFor" + al.getSequenceAt(i).getName());
599       i++;
600     }
601   }
602
603   /**
604    * Test method that returns annotations that match on calcId.
605    */
606   @Test(groups = { "Functional" })
607   public void testFindAnnotation_byCalcId()
608   {
609     Iterable<AlignmentAnnotation> anns = al
610             .findAnnotation("CalcIdForD.melanogaster.2");
611     Iterator<AlignmentAnnotation> iter = anns.iterator();
612     assertTrue(iter.hasNext());
613     AlignmentAnnotation ann = iter.next();
614     assertEquals("D.melanogaster.2", ann.sequenceRef.getName());
615     assertFalse(iter.hasNext());
616
617     // invalid id
618     anns = al.findAnnotation("CalcIdForD.melanogaster.?");
619     assertFalse(iter.hasNext());
620     anns = al.findAnnotation(null);
621     assertFalse(iter.hasNext());
622   }
623
624   /**
625    * Test method that returns annotations that match on reference sequence,
626    * label, or calcId.
627    */
628   @Test(groups = { "Functional" })
629   public void testFindAnnotations_bySeqLabelandorCalcId()
630   {
631     // TODO: finish testFindAnnotations_bySeqLabelandorCalcId test
632     /* Note - this is an incomplete test - need to check null or
633      * non-null [ matches, not matches ] behaviour for each of the three
634      * parameters..*/
635
636     // search for a single, unique calcId with wildcards on other params
637     Iterable<AlignmentAnnotation> anns = al.findAnnotations(null,
638             "CalcIdForD.melanogaster.2", null);
639     Iterator<AlignmentAnnotation> iter = anns.iterator();
640     assertTrue(iter.hasNext());
641     AlignmentAnnotation ann = iter.next();
642     assertEquals("D.melanogaster.2", ann.sequenceRef.getName());
643     assertFalse(iter.hasNext());
644
645     // save reference to test sequence reference parameter
646     SequenceI rseq = ann.sequenceRef;
647
648     // search for annotation associated with a single sequence
649     anns = al.findAnnotations(rseq, null, null);
650     iter = anns.iterator();
651     assertTrue(iter.hasNext());
652     ann = iter.next();
653     assertEquals("D.melanogaster.2", ann.sequenceRef.getName());
654     assertFalse(iter.hasNext());
655
656     // search for annotation with a non-existant calcId
657     anns = al.findAnnotations(null, "CalcIdForD.melanogaster.?", null);
658     iter = anns.iterator();
659     assertFalse(iter.hasNext());
660
661     // search for annotation with a particular label - expect three
662     anns = al.findAnnotations(null, null, "Secondary Structure");
663     iter = anns.iterator();
664     assertTrue(iter.hasNext());
665     iter.next();
666     assertTrue(iter.hasNext());
667     iter.next();
668     assertTrue(iter.hasNext());
669     iter.next();
670     // third found.. so
671     assertFalse(iter.hasNext());
672
673     // search for annotation on one sequence with a particular label - expect
674     // one
675     SequenceI sqfound;
676     anns = al.findAnnotations(sqfound = al.getSequenceAt(1), null,
677             "Secondary Structure");
678     iter = anns.iterator();
679     assertTrue(iter.hasNext());
680     // expect reference to sequence 1 in the alignment
681     assertTrue(sqfound == iter.next().sequenceRef);
682     assertFalse(iter.hasNext());
683
684     // null on all parameters == find all annotations
685     anns = al.findAnnotations(null, null, null);
686     iter = anns.iterator();
687     int n = al.getAlignmentAnnotation().length;
688     while (iter.hasNext())
689     {
690       n--;
691       iter.next();
692     }
693     assertTrue("Found " + n + " fewer annotations from search.", n == 0);
694   }
695
696   @Test(groups = { "Functional" })
697   public void testDeleteAllAnnotations_includingAutocalculated()
698   {
699     AlignmentAnnotation aa = new AlignmentAnnotation("Consensus",
700             "Consensus", 0.5);
701     aa.autoCalculated = true;
702     al.addAnnotation(aa);
703     AlignmentAnnotation[] anns = al.getAlignmentAnnotation();
704     assertEquals("Wrong number of annotations before deleting", 4,
705             anns.length);
706     al.deleteAllAnnotations(true);
707     assertEquals("Not all deleted", 0, al.getAlignmentAnnotation().length);
708   }
709
710   @Test(groups = { "Functional" })
711   public void testDeleteAllAnnotations_excludingAutocalculated()
712   {
713     AlignmentAnnotation aa = new AlignmentAnnotation("Consensus",
714             "Consensus", 0.5);
715     aa.autoCalculated = true;
716     al.addAnnotation(aa);
717     AlignmentAnnotation[] anns = al.getAlignmentAnnotation();
718     assertEquals("Wrong number of annotations before deleting", 4,
719             anns.length);
720     al.deleteAllAnnotations(false);
721     assertEquals("Not just one annotation left", 1,
722             al.getAlignmentAnnotation().length);
723   }
724
725   /**
726    * Tests for realigning as per a supplied alignment: Dna as Dna.
727    * 
728    * Note: AlignedCodonFrame's state variables are named for protein-to-cDNA
729    * mapping, but can be exploited for a general 'sequence-to-sequence' mapping
730    * as here.
731    * 
732    * @throws IOException
733    */
734   @Test(groups = { "Functional" })
735   public void testAlignAs_dnaAsDna() throws IOException
736   {
737     // aligned cDNA:
738     AlignmentI al1 = loadAlignment(CDNA_SEQS_1, FileFormat.Fasta);
739     // unaligned cDNA:
740     AlignmentI al2 = loadAlignment(CDNA_SEQS_2, FileFormat.Fasta);
741
742     /*
743      * Make mappings between sequences. The 'aligned cDNA' is playing the role
744      * of what would normally be protein here.
745      */
746     makeMappings(al1, al2);
747
748     ((Alignment) al2).alignAs(al1, false, true);
749     assertEquals("GC-TC--GUC-GTACT", al2.getSequenceAt(0)
750             .getSequenceAsString());
751     assertEquals("-GG-GTC--AGG--CAGT", al2.getSequenceAt(1)
752             .getSequenceAsString());
753   }
754
755   /**
756    * Aligning protein from cDNA.
757    * 
758    * @throws IOException
759    */
760   @Test(groups = { "Functional" })
761   public void testAlignAs_proteinAsCdna() throws IOException
762   {
763     // see also AlignmentUtilsTests
764     AlignmentI al1 = loadAlignment(CDNA_SEQS_1, FileFormat.Fasta);
765     AlignmentI al2 = loadAlignment(AA_SEQS_1, FileFormat.Fasta);
766     makeMappings(al1, al2);
767
768     // Fudge - alignProteinAsCdna expects mappings to be on protein
769     al2.getCodonFrames().addAll(al1.getCodonFrames());
770
771     ((Alignment) al2).alignAs(al1, false, true);
772     assertEquals("K-Q-Y-L-", al2.getSequenceAt(0).getSequenceAsString());
773     assertEquals("-R-F-P-W", al2.getSequenceAt(1).getSequenceAsString());
774   }
775
776   /**
777    * Test aligning cdna as per protein alignment.
778    * 
779    * @throws IOException
780    */
781   @Test(groups = { "Functional" }, enabled = true)
782   // TODO review / update this test after redesign of alignAs method
783   public void testAlignAs_cdnaAsProtein() throws IOException
784   {
785     /*
786      * Load alignments and add mappings for cDNA to protein
787      */
788     AlignmentI al1 = loadAlignment(CDNA_SEQS_1, FileFormat.Fasta);
789     AlignmentI al2 = loadAlignment(AA_SEQS_1, FileFormat.Fasta);
790     makeMappings(al1, al2);
791
792     /*
793      * Realign DNA; currently keeping existing gaps in introns only
794      */
795     ((Alignment) al1).alignAs(al2, false, true);
796     assertEquals("ACG---GCUCCA------ACT---", al1.getSequenceAt(0)
797             .getSequenceAsString());
798     assertEquals("---CGT---TAACGA---AGT---", al1.getSequenceAt(1)
799             .getSequenceAsString());
800   }
801
802   /**
803    * Test aligning cdna as per protein - single sequences
804    * 
805    * @throws IOException
806    */
807   @Test(groups = { "Functional" }, enabled = true)
808   // TODO review / update this test after redesign of alignAs method
809   public void testAlignAs_cdnaAsProtein_singleSequence() throws IOException
810   {
811     /*
812      * simple case insert one gap
813      */
814     verifyAlignAs(">dna\nCAAaaa\n", ">protein\nQ-K\n", "CAA---aaa");
815
816     /*
817      * simple case but with sequence offsets
818      */
819     verifyAlignAs(">dna/5-10\nCAAaaa\n", ">protein/20-21\nQ-K\n",
820             "CAA---aaa");
821
822     /*
823      * insert gaps as per protein, drop gaps within codons
824      */
825     verifyAlignAs(">dna/10-18\nCA-Aa-aa--AGA\n", ">aa/6-8\n-Q-K--R\n",
826             "---CAA---aaa------AGA");
827   }
828
829   /**
830    * Helper method that makes mappings and then aligns the first alignment as
831    * the second
832    * 
833    * @param fromSeqs
834    * @param toSeqs
835    * @param expected
836    * @throws IOException
837    */
838   public void verifyAlignAs(String fromSeqs, String toSeqs, String expected)
839           throws IOException
840   {
841     /*
842      * Load alignments and add mappings from nucleotide to protein (or from
843      * first to second if both the same type)
844      */
845     AlignmentI al1 = loadAlignment(fromSeqs, FileFormat.Fasta);
846     AlignmentI al2 = loadAlignment(toSeqs, FileFormat.Fasta);
847     makeMappings(al1, al2);
848
849     /*
850      * Realign DNA; currently keeping existing gaps in introns only
851      */
852     ((Alignment) al1).alignAs(al2, false, true);
853     assertEquals(expected, al1.getSequenceAt(0).getSequenceAsString());
854   }
855
856   /**
857    * Helper method to make mappings between sequences, and add the mappings to
858    * the 'mapped from' alignment
859    * 
860    * @param alFrom
861    * @param alTo
862    */
863   public void makeMappings(AlignmentI alFrom, AlignmentI alTo)
864   {
865     int ratio = (alFrom.isNucleotide() == alTo.isNucleotide() ? 1 : 3);
866
867     AlignedCodonFrame acf = new AlignedCodonFrame();
868
869     for (int i = 0; i < alFrom.getHeight(); i++)
870     {
871       SequenceI seqFrom = alFrom.getSequenceAt(i);
872       SequenceI seqTo = alTo.getSequenceAt(i);
873       MapList ml = new MapList(new int[] { seqFrom.getStart(),
874           seqFrom.getEnd() },
875               new int[] { seqTo.getStart(), seqTo.getEnd() }, ratio, 1);
876       acf.addMap(seqFrom, seqTo, ml);
877     }
878
879     /*
880      * not sure whether mappings 'belong' or protein or nucleotide
881      * alignment, so adding to both ;~)
882      */
883     alFrom.addCodonFrame(acf);
884     alTo.addCodonFrame(acf);
885   }
886
887   /**
888    * Test aligning dna as per protein alignment, for the case where there are
889    * introns (i.e. some dna sites have no mapping from a peptide).
890    * 
891    * @throws IOException
892    */
893   @Test(groups = { "Functional" }, enabled = false)
894   // TODO review / update this test after redesign of alignAs method
895   public void testAlignAs_dnaAsProtein_withIntrons() throws IOException
896   {
897     /*
898      * Load alignments and add mappings for cDNA to protein
899      */
900     String dna1 = "A-Aa-gG-GCC-cT-TT";
901     String dna2 = "c--CCGgg-TT--T-AA-A";
902     AlignmentI al1 = loadAlignment(">Dna1/6-17\n" + dna1
903             + "\n>Dna2/20-31\n" + dna2 + "\n", FileFormat.Fasta);
904     AlignmentI al2 = loadAlignment(
905             ">Pep1/7-9\n-P--YK\n>Pep2/11-13\nG-T--F\n", FileFormat.Fasta);
906     AlignedCodonFrame acf = new AlignedCodonFrame();
907     // Seq1 has intron at dna positions 3,4,9 so splice is AAG GCC TTT
908     // Seq2 has intron at dna positions 1,5,6 so splice is CCG TTT AAA
909     MapList ml1 = new MapList(new int[] { 6, 7, 10, 13, 15, 17 }, new int[]
910     { 7, 9 }, 3, 1);
911     acf.addMap(al1.getSequenceAt(0), al2.getSequenceAt(0), ml1);
912     MapList ml2 = new MapList(new int[] { 21, 23, 26, 31 }, new int[] { 11,
913         13 }, 3, 1);
914     acf.addMap(al1.getSequenceAt(1), al2.getSequenceAt(1), ml2);
915     al2.addCodonFrame(acf);
916
917     /*
918      * Align ignoring gaps in dna introns and exons
919      */
920     ((Alignment) al1).alignAs(al2, false, false);
921     assertEquals("---AAagG------GCCcTTT", al1.getSequenceAt(0)
922             .getSequenceAsString());
923     // note 1 gap in protein corresponds to 'gg-' in DNA (3 positions)
924     assertEquals("cCCGgg-TTT------AAA", al1.getSequenceAt(1)
925             .getSequenceAsString());
926
927     /*
928      * Reset and realign, preserving gaps in dna introns and exons
929      */
930     al1.getSequenceAt(0).setSequence(dna1);
931     al1.getSequenceAt(1).setSequence(dna2);
932     ((Alignment) al1).alignAs(al2, true, true);
933     // String dna1 = "A-Aa-gG-GCC-cT-TT";
934     // String dna2 = "c--CCGgg-TT--T-AA-A";
935     // assumption: we include 'the greater of' protein/dna gap lengths, not both
936     assertEquals("---A-Aa-gG------GCC-cT-TT", al1.getSequenceAt(0)
937             .getSequenceAsString());
938     assertEquals("c--CCGgg-TT--T------AA-A", al1.getSequenceAt(1)
939             .getSequenceAsString());
940   }
941
942   @Test(groups = "Functional")
943   public void testCopyConstructor() throws IOException
944   {
945     AlignmentI protein = loadAlignment(AA_SEQS_1, FileFormat.Fasta);
946     // create sequence and alignment datasets
947     protein.setDataset(null);
948     AlignedCodonFrame acf = new AlignedCodonFrame();
949     List<AlignedCodonFrame> acfList = Arrays.asList(new AlignedCodonFrame[]
950     { acf });
951     protein.getDataset().setCodonFrames(acfList);
952     AlignmentI copy = new Alignment(protein);
953
954     /*
955      * copy has different aligned sequences but the same dataset sequences
956      */
957     assertFalse(copy.getSequenceAt(0) == protein.getSequenceAt(0));
958     assertFalse(copy.getSequenceAt(1) == protein.getSequenceAt(1));
959     assertSame(copy.getSequenceAt(0).getDatasetSequence(), protein
960             .getSequenceAt(0).getDatasetSequence());
961     assertSame(copy.getSequenceAt(1).getDatasetSequence(), protein
962             .getSequenceAt(1).getDatasetSequence());
963
964     // TODO should the copy constructor copy the dataset?
965     // or make a new one referring to the same dataset sequences??
966     assertNull(copy.getDataset());
967     // TODO test metadata is copied when AlignmentI is a dataset
968
969     // assertArrayEquals(copy.getDataset().getSequencesArray(), protein
970     // .getDataset().getSequencesArray());
971   }
972
973   /**
974    * Test behaviour of createDataset
975    * 
976    * @throws IOException
977    */
978   @Test(groups = "Functional")
979   public void testCreateDatasetAlignment() throws IOException
980   {
981     AlignmentI protein = new FormatAdapter().readFile(AA_SEQS_1,
982             DataSourceType.PASTE, FileFormat.Fasta);
983     /*
984      * create a dataset sequence on first sequence
985      * leave the second without one
986      */
987     protein.getSequenceAt(0).createDatasetSequence();
988     assertNotNull(protein.getSequenceAt(0).getDatasetSequence());
989     assertNull(protein.getSequenceAt(1).getDatasetSequence());
990
991     /*
992      * add a mapping to the alignment
993      */
994     AlignedCodonFrame acf = new AlignedCodonFrame();
995     protein.addCodonFrame(acf);
996     assertNull(protein.getDataset());
997     assertTrue(protein.getCodonFrames().contains(acf));
998
999     /*
1000      * create the alignment dataset
1001      * note this creates sequence datasets where missing
1002      * as a side-effect (in this case, on seq2
1003      */
1004     // TODO promote this method to AlignmentI
1005     ((Alignment) protein).createDatasetAlignment();
1006
1007     AlignmentI ds = protein.getDataset();
1008
1009     // side-effect: dataset created on second sequence
1010     assertNotNull(protein.getSequenceAt(1).getDatasetSequence());
1011     // dataset alignment has references to dataset sequences
1012     assertEquals(ds.getSequenceAt(0), protein.getSequenceAt(0)
1013             .getDatasetSequence());
1014     assertEquals(ds.getSequenceAt(1), protein.getSequenceAt(1)
1015             .getDatasetSequence());
1016
1017     // codon frames should have been moved to the dataset
1018     // getCodonFrames() should delegate to the dataset:
1019     assertTrue(protein.getCodonFrames().contains(acf));
1020     // prove the codon frames are indeed on the dataset:
1021     assertTrue(ds.getCodonFrames().contains(acf));
1022   }
1023
1024   /**
1025    * tests the addition of *all* sequences referred to by a sequence being added
1026    * to the dataset
1027    */
1028   @Test(groups = "Functional")
1029   public void testCreateDatasetAlignmentWithMappedToSeqs()
1030   {
1031     // Alignment with two sequences, gapped.
1032     SequenceI sq1 = new Sequence("sq1", "A--SDF");
1033     SequenceI sq2 = new Sequence("sq2", "G--TRQ");
1034
1035     // cross-references to two more sequences.
1036     DBRefEntry dbr = new DBRefEntry("SQ1", "", "sq3");
1037     SequenceI sq3 = new Sequence("sq3", "VWANG");
1038     dbr.setMap(new Mapping(sq3, new MapList(new int[] { 1, 4 }, new int[] {
1039         2, 5 }, 1, 1)));
1040     sq1.addDBRef(dbr);
1041
1042     SequenceI sq4 = new Sequence("sq4", "ERKWI");
1043     DBRefEntry dbr2 = new DBRefEntry("SQ2", "", "sq4");
1044     dbr2.setMap(new Mapping(sq4, new MapList(new int[] { 1, 4 }, new int[] {
1045         2, 5 }, 1, 1)));
1046     sq2.addDBRef(dbr2);
1047     // and a 1:1 codonframe mapping between them.
1048     AlignedCodonFrame alc = new AlignedCodonFrame();
1049     alc.addMap(sq1, sq2, new MapList(new int[] { 1, 4 },
1050             new int[] { 1, 4 }, 1, 1));
1051
1052     AlignmentI protein = new Alignment(new SequenceI[] { sq1, sq2 });
1053
1054     /*
1055      * create the alignment dataset
1056      * note this creates sequence datasets where missing
1057      * as a side-effect (in this case, on seq2
1058      */
1059
1060     // TODO promote this method to AlignmentI
1061     ((Alignment) protein).createDatasetAlignment();
1062
1063     AlignmentI ds = protein.getDataset();
1064
1065     // should be 4 sequences in dataset - two materialised, and two propagated
1066     // from dbref
1067     assertEquals(4, ds.getHeight());
1068     assertTrue(ds.getSequences().contains(sq1.getDatasetSequence()));
1069     assertTrue(ds.getSequences().contains(sq2.getDatasetSequence()));
1070     assertTrue(ds.getSequences().contains(sq3));
1071     assertTrue(ds.getSequences().contains(sq4));
1072     // Should have one codon frame mapping between sq1 and sq2 via dataset
1073     // sequences
1074     assertEquals(ds.getCodonFrame(sq1.getDatasetSequence()),
1075             ds.getCodonFrame(sq2.getDatasetSequence()));
1076   }
1077
1078   @Test(groups = "Functional")
1079   public void testAddCodonFrame()
1080   {
1081     AlignmentI align = new Alignment(new SequenceI[] {});
1082     AlignedCodonFrame acf = new AlignedCodonFrame();
1083     align.addCodonFrame(acf);
1084     assertEquals(1, align.getCodonFrames().size());
1085     assertTrue(align.getCodonFrames().contains(acf));
1086     // can't add the same object twice:
1087     align.addCodonFrame(acf);
1088     assertEquals(1, align.getCodonFrames().size());
1089
1090     // create dataset alignment - mappings move to dataset
1091     ((Alignment) align).createDatasetAlignment();
1092     assertSame(align.getCodonFrames(), align.getDataset().getCodonFrames());
1093     assertEquals(1, align.getCodonFrames().size());
1094
1095     AlignedCodonFrame acf2 = new AlignedCodonFrame();
1096     align.addCodonFrame(acf2);
1097     assertTrue(align.getDataset().getCodonFrames().contains(acf));
1098   }
1099
1100   @Test(groups = "Functional")
1101   public void testAddSequencePreserveDatasetIntegrity()
1102   {
1103     Sequence seq = new Sequence("testSeq", "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
1104     Alignment align = new Alignment(new SequenceI[] { seq });
1105     align.createDatasetAlignment();
1106     AlignmentI ds = align.getDataset();
1107     SequenceI copy = new Sequence(seq);
1108     copy.insertCharAt(3, 5, '-');
1109     align.addSequence(copy);
1110     Assert.assertEquals(align.getDataset().getHeight(), 1,
1111             "Dataset shouldn't have more than one sequence.");
1112
1113     Sequence seq2 = new Sequence("newtestSeq", "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
1114     align.addSequence(seq2);
1115     Assert.assertEquals(align.getDataset().getHeight(), 2,
1116             "Dataset should now have two sequences.");
1117
1118     assertAlignmentDatasetRefs(align,
1119             "addSequence broke dataset reference integrity");
1120   }
1121
1122   /**
1123    * Tests that dbrefs with mappings to sequence get updated if the sequence
1124    * acquires a dataset sequence
1125    */
1126   @Test(groups = "Functional")
1127   public void testCreateDataset_updateDbrefMappings()
1128   {
1129     SequenceI pep = new Sequence("pep", "ASD");
1130     SequenceI dna = new Sequence("dna", "aaaGCCTCGGATggg");
1131     SequenceI cds = new Sequence("cds", "GCCTCGGAT");
1132
1133     // add dbref from dna to peptide
1134     DBRefEntry dbr = new DBRefEntry("UNIPROT", "", "pep");
1135     dbr.setMap(new Mapping(pep, new MapList(new int[] { 4, 15 }, new int[] {
1136         1, 4 }, 3, 1)));
1137     dna.addDBRef(dbr);
1138
1139     // add dbref from dna to peptide
1140     DBRefEntry dbr2 = new DBRefEntry("UNIPROT", "", "pep");
1141     dbr2.setMap(new Mapping(pep, new MapList(new int[] { 1, 12 }, new int[]
1142     { 1, 4 }, 3, 1)));
1143     cds.addDBRef(dbr2);
1144
1145     // add dbref from peptide to dna
1146     DBRefEntry dbr3 = new DBRefEntry("EMBL", "", "dna");
1147     dbr3.setMap(new Mapping(dna, new MapList(new int[] { 1, 4 }, new int[] {
1148         4, 15 }, 1, 3)));
1149     pep.addDBRef(dbr3);
1150
1151     // add dbref from peptide to cds
1152     DBRefEntry dbr4 = new DBRefEntry("EMBLCDS", "", "cds");
1153     dbr4.setMap(new Mapping(cds, new MapList(new int[] { 1, 4 }, new int[] {
1154         1, 12 }, 1, 3)));
1155     pep.addDBRef(dbr4);
1156
1157     AlignmentI protein = new Alignment(new SequenceI[] { pep });
1158
1159     /*
1160      * create the alignment dataset
1161      */
1162     ((Alignment) protein).createDatasetAlignment();
1163
1164     AlignmentI ds = protein.getDataset();
1165
1166     // should be 3 sequences in dataset
1167     assertEquals(3, ds.getHeight());
1168     assertTrue(ds.getSequences().contains(pep.getDatasetSequence()));
1169     assertTrue(ds.getSequences().contains(dna));
1170     assertTrue(ds.getSequences().contains(cds));
1171
1172     /*
1173      * verify peptide.cdsdbref.peptidedbref is now mapped to peptide dataset
1174      */
1175     DBRefEntry[] dbRefs = pep.getDBRefs();
1176     assertEquals(2, dbRefs.length);
1177     assertSame(dna, dbRefs[0].map.to);
1178     assertSame(cds, dbRefs[1].map.to);
1179     assertEquals(1, dna.getDBRefs().length);
1180     assertSame(pep.getDatasetSequence(), dna.getDBRefs()[0].map.to);
1181     assertEquals(1, cds.getDBRefs().length);
1182     assertSame(pep.getDatasetSequence(), cds.getDBRefs()[0].map.to);
1183   }
1184
1185   @Test(groups = { "Functional" })
1186   public void testFindGroup()
1187   {
1188     SequenceI seq1 = new Sequence("seq1", "ABCDEF---GHI");
1189     SequenceI seq2 = new Sequence("seq2", "---JKLMNO---");
1190     AlignmentI a = new Alignment(new SequenceI[] { seq1, seq2 });
1191
1192     assertNull(a.findGroup(null, 0));
1193     assertNull(a.findGroup(seq1, 1));
1194     assertNull(a.findGroup(seq1, -1));
1195
1196     /*
1197      * add a group consisting of just "DEF"
1198      */
1199     SequenceGroup sg1 = new SequenceGroup();
1200     sg1.addSequence(seq1, false);
1201     sg1.setStartRes(3);
1202     sg1.setEndRes(5);
1203     a.addGroup(sg1);
1204
1205     assertNull(a.findGroup(seq1, 2)); // position not in group
1206     assertNull(a.findGroup(seq1, 6)); // position not in group
1207     assertNull(a.findGroup(seq2, 5)); // sequence not in group
1208     assertSame(a.findGroup(seq1, 3), sg1); // yes
1209     assertSame(a.findGroup(seq1, 4), sg1);
1210     assertSame(a.findGroup(seq1, 5), sg1);
1211
1212     /*
1213      * add a group consisting of 
1214      * EF--
1215      * KLMN
1216      */
1217     SequenceGroup sg2 = new SequenceGroup();
1218     sg2.addSequence(seq1, false);
1219     sg2.addSequence(seq2, false);
1220     sg2.setStartRes(4);
1221     sg2.setEndRes(7);
1222     a.addGroup(sg2);
1223
1224     assertNull(a.findGroup(seq1, 2)); // unchanged
1225     assertSame(a.findGroup(seq1, 3), sg1); // unchanged
1226     /*
1227      * if a residue is in more than one group, method returns
1228      * the first found (in order groups were added)
1229      */
1230     assertSame(a.findGroup(seq1, 4), sg1);
1231     assertSame(a.findGroup(seq1, 5), sg1);
1232
1233     /*
1234      * seq2 only belongs to the second group
1235      */
1236     assertSame(a.findGroup(seq2, 4), sg2);
1237     assertSame(a.findGroup(seq2, 5), sg2);
1238     assertSame(a.findGroup(seq2, 6), sg2);
1239     assertSame(a.findGroup(seq2, 7), sg2);
1240     assertNull(a.findGroup(seq2, 3));
1241     assertNull(a.findGroup(seq2, 8));
1242   }
1243
1244   @Test(groups = { "Functional" })
1245   public void testDeleteSequenceByIndex()
1246   {
1247     // create random alignment
1248     AlignmentGenerator gen = new AlignmentGenerator(false);
1249     AlignmentI a = gen.generate(20, 15, 123, 5, 5);
1250
1251     // delete sequence 10, alignment reduced by 1
1252     int height = a.getAbsoluteHeight();
1253     a.deleteSequence(10);
1254     assertEquals(a.getAbsoluteHeight(), height - 1);
1255
1256     // try to delete -ve index, nothing happens
1257     a.deleteSequence(-1);
1258     assertEquals(a.getAbsoluteHeight(), height - 1);
1259
1260     // try to delete beyond end of alignment, nothing happens
1261     a.deleteSequence(14);
1262     assertEquals(a.getAbsoluteHeight(), height - 1);
1263   }
1264
1265   @Test(groups = { "Functional" })
1266   public void testDeleteSequenceBySeq()
1267   {
1268     // create random alignment
1269     AlignmentGenerator gen = new AlignmentGenerator(false);
1270     AlignmentI a = gen.generate(20, 15, 123, 5, 5);
1271
1272     // delete sequence 10, alignment reduced by 1
1273     int height = a.getAbsoluteHeight();
1274     SequenceI seq = a.getSequenceAt(10);
1275     a.deleteSequence(seq);
1276     assertEquals(a.getAbsoluteHeight(), height - 1);
1277
1278     // try to delete non-existent sequence, nothing happens
1279     seq = new Sequence("cds", "GCCTCGGAT");
1280     assertEquals(a.getAbsoluteHeight(), height - 1);
1281   }
1282
1283   @Test(groups = { "Functional" })
1284   public void testDeleteHiddenSequence()
1285   {
1286     // create random alignment
1287     AlignmentGenerator gen = new AlignmentGenerator(false);
1288     AlignmentI a = gen.generate(20, 15, 123, 5, 5);
1289
1290     // delete a sequence which is hidden, check it is NOT removed from hidden
1291     // sequences
1292     int height = a.getAbsoluteHeight();
1293     SequenceI seq = a.getSequenceAt(2);
1294     a.getHiddenSequences().hideSequence(seq);
1295     assertEquals(a.getHiddenSequences().getSize(), 1);
1296     a.deleteSequence(2);
1297     assertEquals(a.getAbsoluteHeight(), height - 1);
1298     assertEquals(a.getHiddenSequences().getSize(), 1);
1299
1300     // delete a sequence which is not hidden, check hiddenSequences are not
1301     // affected
1302     a.deleteSequence(10);
1303     assertEquals(a.getAbsoluteHeight(), height - 2);
1304     assertEquals(a.getHiddenSequences().getSize(), 1);
1305   }
1306
1307   @Test(
1308     groups = "Functional",
1309     expectedExceptions = { IllegalArgumentException.class })
1310   public void testSetDataset_selfReference()
1311   {
1312     SequenceI seq = new Sequence("a", "a");
1313     AlignmentI alignment = new Alignment(new SequenceI[] { seq });
1314     alignment.setDataset(alignment);
1315   }
1316
1317   @Test(groups = "Functional")
1318   public void testAppend()
1319   {
1320     SequenceI seq = new Sequence("seq1", "FRMLPSRT-A--L-");
1321     AlignmentI alignment = new Alignment(new SequenceI[] { seq });
1322     alignment.setGapCharacter('-');
1323     SequenceI seq2 = new Sequence("seq1", "KP..L.FQII.");
1324     AlignmentI alignment2 = new Alignment(new SequenceI[] { seq2 });
1325     alignment2.setGapCharacter('.');
1326
1327     alignment.append(alignment2);
1328
1329     assertEquals('-', alignment.getGapCharacter());
1330     assertSame(seq, alignment.getSequenceAt(0));
1331     assertEquals("KP--L-FQII-", alignment.getSequenceAt(1)
1332             .getSequenceAsString());
1333
1334     // todo test coverage for annotations, mappings, groups,
1335     // hidden sequences, properties
1336   }
1337
1338   /**
1339    * test that calcId == null on findOrCreate doesn't raise an NPE, and yields
1340    * an annotation with a null calcId
1341    * 
1342    */
1343   @Test(groups = "Functional")
1344   public void testFindOrCreateForNullCalcId()
1345   {
1346     SequenceI seq = new Sequence("seq1", "FRMLPSRT-A--L-");
1347     AlignmentI alignment = new Alignment(new SequenceI[] { seq });
1348
1349     AlignmentAnnotation ala = alignment.findOrCreateAnnotation(
1350             "Temperature Factor", null, false, seq, null);
1351     assertNotNull(ala);
1352     assertEquals(seq, ala.sequenceRef);
1353     assertEquals("", ala.calcId);
1354   }
1355
1356   @Test(groups = "Functional")
1357   public void testPropagateInsertions()
1358   {
1359     // create an alignment with no gaps - this will be the profile seq and other
1360     // JPRED seqs
1361     AlignmentGenerator gen = new AlignmentGenerator(false);
1362     AlignmentI al = gen.generate(25, 10, 1234, 0, 0);
1363
1364     // get the profileseq
1365     SequenceI profileseq = al.getSequenceAt(0);
1366     SequenceI gappedseq = new Sequence(profileseq);
1367     gappedseq.insertCharAt(5, al.getGapCharacter());
1368     gappedseq.insertCharAt(6, al.getGapCharacter());
1369     gappedseq.insertCharAt(7, al.getGapCharacter());
1370     gappedseq.insertCharAt(8, al.getGapCharacter());
1371
1372     // force different kinds of padding
1373     al.getSequenceAt(3).deleteChars(2, 23);
1374     al.getSequenceAt(4).deleteChars(2, 27);
1375     al.getSequenceAt(5).deleteChars(10, 27);
1376
1377     // create an alignment view with the gapped sequence
1378     SequenceI[] seqs = new SequenceI[1];
1379     seqs[0] = gappedseq;
1380     AlignmentI newal = new Alignment(seqs);
1381     HiddenColumns hidden = new HiddenColumns();
1382     hidden.hideColumns(15, 17);
1383
1384     AlignmentView view = new AlignmentView(newal, hidden, null, true, false,
1385             false);
1386
1387     // confirm that original contigs are as expected
1388     Iterator<int[]> visible = hidden.getVisContigsIterator(0, 25, false);
1389     int[] region = visible.next();
1390     assertEquals("[0, 14]", Arrays.toString(region));
1391     region = visible.next();
1392     assertEquals("[18, 24]", Arrays.toString(region));
1393
1394     // propagate insertions
1395     HiddenColumns result = al.propagateInsertions(profileseq, view);
1396
1397     // confirm that the contigs have changed to account for the gaps
1398     visible = result.getVisContigsIterator(0, 25, false);
1399     region = visible.next();
1400     assertEquals("[0, 10]", Arrays.toString(region));
1401     region = visible.next();
1402     assertEquals("[14, 24]", Arrays.toString(region));
1403
1404     // confirm the alignment has been changed so that the other sequences have
1405     // gaps inserted where the columns are hidden
1406     assertFalse(Comparison.isGap(al.getSequenceAt(1).getSequence()[10]));
1407     assertTrue(Comparison.isGap(al.getSequenceAt(1).getSequence()[11]));
1408     assertTrue(Comparison.isGap(al.getSequenceAt(1).getSequence()[12]));
1409     assertTrue(Comparison.isGap(al.getSequenceAt(1).getSequence()[13]));
1410     assertFalse(Comparison.isGap(al.getSequenceAt(1).getSequence()[14]));
1411
1412   }
1413
1414   @Test(groups = "Functional")
1415   public void testPropagateInsertionsOverlap()
1416   {
1417     // test propagateInsertions where gaps and hiddenColumns overlap
1418
1419     // create an alignment with no gaps - this will be the profile seq and other
1420     // JPRED seqs
1421     AlignmentGenerator gen = new AlignmentGenerator(false);
1422     AlignmentI al = gen.generate(20, 10, 1234, 0, 0);
1423
1424     // get the profileseq
1425     SequenceI profileseq = al.getSequenceAt(0);
1426     SequenceI gappedseq = new Sequence(profileseq);
1427     gappedseq.insertCharAt(5, al.getGapCharacter());
1428     gappedseq.insertCharAt(6, al.getGapCharacter());
1429     gappedseq.insertCharAt(7, al.getGapCharacter());
1430     gappedseq.insertCharAt(8, al.getGapCharacter());
1431
1432     // create an alignment view with the gapped sequence
1433     SequenceI[] seqs = new SequenceI[1];
1434     seqs[0] = gappedseq;
1435     AlignmentI newal = new Alignment(seqs);
1436
1437     // hide columns so that some overlap with the gaps
1438     HiddenColumns hidden = new HiddenColumns();
1439     hidden.hideColumns(7, 10);
1440
1441     AlignmentView view = new AlignmentView(newal, hidden, null, true, false,
1442             false);
1443
1444     // confirm that original contigs are as expected
1445     Iterator<int[]> visible = hidden.getVisContigsIterator(0, 20, false);
1446     int[] region = visible.next();
1447     assertEquals("[0, 6]", Arrays.toString(region));
1448     region = visible.next();
1449     assertEquals("[11, 19]", Arrays.toString(region));
1450     assertFalse(visible.hasNext());
1451
1452     // propagate insertions
1453     HiddenColumns result = al.propagateInsertions(profileseq, view);
1454
1455     // confirm that the contigs have changed to account for the gaps
1456     visible = result.getVisContigsIterator(0, 20, false);
1457     region = visible.next();
1458     assertEquals("[0, 4]", Arrays.toString(region));
1459     region = visible.next();
1460     assertEquals("[7, 19]", Arrays.toString(region));
1461     assertFalse(visible.hasNext());
1462
1463     // confirm the alignment has been changed so that the other sequences have
1464     // gaps inserted where the columns are hidden
1465     assertFalse(Comparison.isGap(al.getSequenceAt(1).getSequence()[4]));
1466     assertTrue(Comparison.isGap(al.getSequenceAt(1).getSequence()[5]));
1467     assertTrue(Comparison.isGap(al.getSequenceAt(1).getSequence()[6]));
1468     assertFalse(Comparison.isGap(al.getSequenceAt(1).getSequence()[7]));
1469   }
1470
1471   @Test(groups = { "Functional" })
1472   public void testPadGaps()
1473   {
1474     SequenceI seq1 = new Sequence("seq1", "ABCDEF--");
1475     SequenceI seq2 = new Sequence("seq2", "-JKLMNO--");
1476     SequenceI seq3 = new Sequence("seq2", "-PQR");
1477     AlignmentI a = new Alignment(new SequenceI[] { seq1, seq2, seq3 });
1478     a.setGapCharacter('.'); // this replaces existing gaps
1479     assertEquals("ABCDEF..", seq1.getSequenceAsString());
1480     a.padGaps();
1481     // trailing gaps are pruned, short sequences padded with gap character
1482     assertEquals("ABCDEF.", seq1.getSequenceAsString());
1483     assertEquals(".JKLMNO", seq2.getSequenceAsString());
1484     assertEquals(".PQR...", seq3.getSequenceAsString());
1485   }
1486
1487   /**
1488    * Test for setHiddenColumns, to check it returns true if the hidden columns
1489    * have changed, else false
1490    */
1491   @Test(groups = { "Functional" })
1492   public void testSetHiddenColumns()
1493   {
1494     AlignmentI al = new Alignment(new SequenceI[] {});
1495     assertFalse(al.getHiddenColumns().hasHiddenColumns());
1496
1497     HiddenColumns hc = new HiddenColumns();
1498     assertFalse(al.setHiddenColumns(hc)); // no change
1499     assertSame(hc, al.getHiddenColumns());
1500
1501     hc.hideColumns(2, 4);
1502     assertTrue(al.getHiddenColumns().hasHiddenColumns());
1503
1504     /*
1505      * set a different object but with the same columns hidden
1506      */
1507     HiddenColumns hc2 = new HiddenColumns();
1508     hc2.hideColumns(2, 4);
1509     assertFalse(al.setHiddenColumns(hc2)); // no change
1510     assertSame(hc2, al.getHiddenColumns());
1511
1512     assertTrue(al.setHiddenColumns(null));
1513     assertNull(al.getHiddenColumns());
1514     assertTrue(al.setHiddenColumns(hc));
1515     assertSame(hc, al.getHiddenColumns());
1516
1517     al.getHiddenColumns().hideColumns(10, 12);
1518     hc2.hideColumns(10, 12);
1519     assertFalse(al.setHiddenColumns(hc2)); // no change
1520
1521     /*
1522      * hide columns 15-16 then 17-18 in hc
1523      * hide columns 15-18 in hc2
1524      * these are not now 'equal' objects even though they
1525      * represent the same set of columns
1526      */
1527     assertSame(hc2, al.getHiddenColumns());
1528     hc.hideColumns(15, 16);
1529     hc.hideColumns(17, 18);
1530     hc2.hideColumns(15, 18);
1531     assertFalse(hc.equals(hc2));
1532     assertTrue(al.setHiddenColumns(hc)); // 'changed'
1533   }
1534
1535   @Test(groups = { "Functional" })
1536   public void testGetWidth()
1537   {
1538     SequenceI seq1 = new Sequence("seq1", "ABCDEF--");
1539     SequenceI seq2 = new Sequence("seq2", "-JKLMNO--");
1540     SequenceI seq3 = new Sequence("seq2", "-PQR");
1541     AlignmentI a = new Alignment(new SequenceI[] { seq1, seq2, seq3 });
1542
1543     assertEquals(9, a.getWidth());
1544
1545     // width includes hidden columns
1546     a.getHiddenColumns().hideColumns(2, 5);
1547     assertEquals(9, a.getWidth());
1548   }
1549
1550   @Test(groups = { "Functional" })
1551   public void testGetVisibleWidth()
1552   {
1553     SequenceI seq1 = new Sequence("seq1", "ABCDEF--");
1554     SequenceI seq2 = new Sequence("seq2", "-JKLMNO--");
1555     SequenceI seq3 = new Sequence("seq2", "-PQR");
1556     AlignmentI a = new Alignment(new SequenceI[] { seq1, seq2, seq3 });
1557
1558     assertEquals(9, a.getVisibleWidth());
1559
1560     // width excludes hidden columns
1561     a.getHiddenColumns().hideColumns(2, 5);
1562     assertEquals(5, a.getVisibleWidth());
1563   }
1564 }