JAL-2291 SequenceI.getInsertionsAsBits for convenient logical operations
[jalview.git] / test / jalview / datamodel / SequenceTest.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 import static org.testng.internal.junit.ArrayAsserts.assertArrayEquals;
30
31 import jalview.datamodel.PDBEntry.Type;
32 import jalview.gui.JvOptionPane;
33 import jalview.util.MapList;
34
35 import java.io.File;
36 import java.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.BitSet;
39 import java.util.List;
40 import java.util.Vector;
41
42 import org.testng.Assert;
43 import org.testng.annotations.BeforeClass;
44 import org.testng.annotations.BeforeMethod;
45 import org.testng.annotations.Test;
46
47 public class SequenceTest
48 {
49
50   @BeforeClass(alwaysRun = true)
51   public void setUpJvOptionPane()
52   {
53     JvOptionPane.setInteractiveMode(false);
54     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
55   }
56
57   Sequence seq;
58
59   @BeforeMethod(alwaysRun = true)
60   public void setUp()
61   {
62     seq = new Sequence("FER1", "AKPNGVL");
63   }
64
65   @Test(groups = { "Functional" })
66   public void testInsertGapsAndGapmaps()
67   {
68     SequenceI aseq = seq.deriveSequence();
69     aseq.insertCharAt(2, 3, '-');
70     aseq.insertCharAt(6, 3, '-');
71     assertEquals("Gap insertions not correct", "AK---P---NGVL",
72             aseq.getSequenceAsString());
73     List<int[]> gapInt = aseq.getInsertions();
74     assertEquals("Gap interval 1 start wrong", 2, gapInt.get(0)[0]);
75     assertEquals("Gap interval 1 end wrong", 4, gapInt.get(0)[1]);
76     assertEquals("Gap interval 2 start wrong", 6, gapInt.get(1)[0]);
77     assertEquals("Gap interval 2 end wrong", 8, gapInt.get(1)[1]);
78
79     BitSet gapfield = aseq.getInsertionsAsBits();
80     BitSet expectedgaps = new BitSet();
81     expectedgaps.set(2, 4);
82     expectedgaps.set(6, 8);
83
84     assertEquals("getInsertionsAsBits not correct.", expectedgaps, gapfield);
85   }
86
87   @Test(groups = ("Functional"))
88   public void testIsProtein()
89   {
90     // test Protein
91     assertTrue(new Sequence("prot", "ASDFASDFASDF").isProtein());
92     // test DNA
93     assertFalse(new Sequence("prot", "ACGTACGTACGT").isProtein());
94     // test RNA
95     SequenceI sq = new Sequence("prot", "ACGUACGUACGU");
96     assertFalse(sq.isProtein());
97     // change sequence, should trigger an update of cached result
98     sq.setSequence("ASDFASDFADSF");
99     assertTrue(sq.isProtein());
100     /*
101      * in situ change of sequence doesn't change hashcode :-O
102      * (sequence should not expose internal implementation)
103      */
104     for (int i = 0; i < sq.getSequence().length; i++)
105     {
106       sq.getSequence()[i] = "acgtu".charAt(i % 5);
107     }
108     assertTrue(sq.isProtein()); // but it isn't
109   }
110
111   @Test(groups = { "Functional" })
112   public void testGetAnnotation()
113   {
114     // initial state returns null not an empty array
115     assertNull(seq.getAnnotation());
116     AlignmentAnnotation ann = addAnnotation("label1", "desc1", "calcId1",
117             1f);
118     AlignmentAnnotation[] anns = seq.getAnnotation();
119     assertEquals(1, anns.length);
120     assertSame(ann, anns[0]);
121
122     // removing all annotations reverts array to null
123     seq.removeAlignmentAnnotation(ann);
124     assertNull(seq.getAnnotation());
125   }
126
127   @Test(groups = { "Functional" })
128   public void testGetAnnotation_forLabel()
129   {
130     AlignmentAnnotation ann1 = addAnnotation("label1", "desc1", "calcId1",
131             1f);
132     addAnnotation("label2", "desc2", "calcId2", 1f);
133     AlignmentAnnotation ann3 = addAnnotation("label1", "desc3", "calcId3",
134             1f);
135     AlignmentAnnotation[] anns = seq.getAnnotation("label1");
136     assertEquals(2, anns.length);
137     assertSame(ann1, anns[0]);
138     assertSame(ann3, anns[1]);
139   }
140
141   private AlignmentAnnotation addAnnotation(String label,
142           String description, String calcId, float value)
143   {
144     final AlignmentAnnotation annotation = new AlignmentAnnotation(label,
145             description, value);
146     annotation.setCalcId(calcId);
147     seq.addAlignmentAnnotation(annotation);
148     return annotation;
149   }
150
151   @Test(groups = { "Functional" })
152   public void testGetAlignmentAnnotations_forCalcIdAndLabel()
153   {
154     addAnnotation("label1", "desc1", "calcId1", 1f);
155     AlignmentAnnotation ann2 = addAnnotation("label2", "desc2", "calcId2",
156             1f);
157     addAnnotation("label2", "desc3", "calcId3", 1f);
158     AlignmentAnnotation ann4 = addAnnotation("label2", "desc3", "calcId2",
159             1f);
160     addAnnotation("label5", "desc3", null, 1f);
161     addAnnotation(null, "desc3", "calcId3", 1f);
162
163     List<AlignmentAnnotation> anns = seq.getAlignmentAnnotations("calcId2",
164             "label2");
165     assertEquals(2, anns.size());
166     assertSame(ann2, anns.get(0));
167     assertSame(ann4, anns.get(1));
168
169     assertTrue(seq.getAlignmentAnnotations("calcId2", "label3").isEmpty());
170     assertTrue(seq.getAlignmentAnnotations("calcId3", "label5").isEmpty());
171     assertTrue(seq.getAlignmentAnnotations("calcId2", null).isEmpty());
172     assertTrue(seq.getAlignmentAnnotations(null, "label3").isEmpty());
173     assertTrue(seq.getAlignmentAnnotations(null, null).isEmpty());
174   }
175
176   /**
177    * Tests for addAlignmentAnnotation. Note this method has the side-effect of
178    * setting the sequenceRef on the annotation. Adding the same annotation twice
179    * should be ignored.
180    */
181   @Test(groups = { "Functional" })
182   public void testAddAlignmentAnnotation()
183   {
184     assertNull(seq.getAnnotation());
185     final AlignmentAnnotation annotation = new AlignmentAnnotation("a",
186             "b", 2d);
187     assertNull(annotation.sequenceRef);
188     seq.addAlignmentAnnotation(annotation);
189     assertSame(seq, annotation.sequenceRef);
190     AlignmentAnnotation[] anns = seq.getAnnotation();
191     assertEquals(1, anns.length);
192     assertSame(annotation, anns[0]);
193
194     // re-adding does nothing
195     seq.addAlignmentAnnotation(annotation);
196     anns = seq.getAnnotation();
197     assertEquals(1, anns.length);
198     assertSame(annotation, anns[0]);
199
200     // an identical but different annotation can be added
201     final AlignmentAnnotation annotation2 = new AlignmentAnnotation("a",
202             "b", 2d);
203     seq.addAlignmentAnnotation(annotation2);
204     anns = seq.getAnnotation();
205     assertEquals(2, anns.length);
206     assertSame(annotation, anns[0]);
207     assertSame(annotation2, anns[1]);
208   }
209
210   @Test(groups = { "Functional" })
211   public void testGetStartGetEnd()
212   {
213     SequenceI sq = new Sequence("test", "ABCDEF");
214     assertEquals(1, sq.getStart());
215     assertEquals(6, sq.getEnd());
216
217     sq = new Sequence("test", "--AB-C-DEF--");
218     assertEquals(1, sq.getStart());
219     assertEquals(6, sq.getEnd());
220
221     sq = new Sequence("test", "----");
222     assertEquals(1, sq.getStart());
223     assertEquals(0, sq.getEnd()); // ??
224   }
225
226   /**
227    * Tests for the method that returns an alignment column position (base 1) for
228    * a given sequence position (base 1).
229    */
230   @Test(groups = { "Functional" })
231   public void testFindIndex()
232   {
233     SequenceI sq = new Sequence("test", "ABCDEF");
234     assertEquals(0, sq.findIndex(0));
235     assertEquals(1, sq.findIndex(1));
236     assertEquals(5, sq.findIndex(5));
237     assertEquals(6, sq.findIndex(6));
238     assertEquals(6, sq.findIndex(9));
239
240     sq = new Sequence("test", "-A--B-C-D-E-F--");
241     assertEquals(2, sq.findIndex(1));
242     assertEquals(5, sq.findIndex(2));
243     assertEquals(7, sq.findIndex(3));
244
245     // before start returns 0
246     assertEquals(0, sq.findIndex(0));
247     assertEquals(0, sq.findIndex(-1));
248
249     // beyond end returns last residue column
250     assertEquals(13, sq.findIndex(99));
251
252   }
253
254   /**
255    * Tests for the method that returns a dataset sequence position (base 1) for
256    * an aligned column position (base 0).
257    */
258   @Test(groups = { "Functional" })
259   public void testFindPosition()
260   {
261     SequenceI sq = new Sequence("test", "ABCDEF");
262     assertEquals(1, sq.findPosition(0));
263     assertEquals(6, sq.findPosition(5));
264     // assertEquals(-1, seq.findPosition(6)); // fails
265
266     sq = new Sequence("test", "AB-C-D--");
267     assertEquals(1, sq.findPosition(0));
268     assertEquals(2, sq.findPosition(1));
269     // gap position 'finds' residue to the right (not the left as per javadoc)
270     assertEquals(3, sq.findPosition(2));
271     assertEquals(3, sq.findPosition(3));
272     assertEquals(4, sq.findPosition(4));
273     assertEquals(4, sq.findPosition(5));
274     // returns 1 more than sequence length if off the end ?!?
275     assertEquals(5, sq.findPosition(6));
276     assertEquals(5, sq.findPosition(7));
277
278     sq = new Sequence("test", "--AB-C-DEF--");
279     assertEquals(1, sq.findPosition(0));
280     assertEquals(1, sq.findPosition(1));
281     assertEquals(1, sq.findPosition(2));
282     assertEquals(2, sq.findPosition(3));
283     assertEquals(3, sq.findPosition(4));
284     assertEquals(3, sq.findPosition(5));
285     assertEquals(4, sq.findPosition(6));
286     assertEquals(4, sq.findPosition(7));
287     assertEquals(5, sq.findPosition(8));
288     assertEquals(6, sq.findPosition(9));
289     assertEquals(7, sq.findPosition(10));
290     assertEquals(7, sq.findPosition(11));
291   }
292
293   @Test(groups = { "Functional" })
294   public void testDeleteChars()
295   {
296     SequenceI sq = new Sequence("test", "ABCDEF");
297     assertEquals(1, sq.getStart());
298     assertEquals(6, sq.getEnd());
299     sq.deleteChars(2, 3);
300     assertEquals("ABDEF", sq.getSequenceAsString());
301     assertEquals(1, sq.getStart());
302     assertEquals(5, sq.getEnd());
303
304     sq = new Sequence("test", "ABCDEF");
305     sq.deleteChars(0, 2);
306     assertEquals("CDEF", sq.getSequenceAsString());
307     assertEquals(3, sq.getStart());
308     assertEquals(6, sq.getEnd());
309   }
310
311   @Test(groups = { "Functional" })
312   public void testInsertCharAt()
313   {
314     // non-static methods:
315     SequenceI sq = new Sequence("test", "ABCDEF");
316     sq.insertCharAt(0, 'z');
317     assertEquals("zABCDEF", sq.getSequenceAsString());
318     sq.insertCharAt(2, 2, 'x');
319     assertEquals("zAxxBCDEF", sq.getSequenceAsString());
320
321     // for static method see StringUtilsTest
322   }
323
324   /**
325    * Test the method that returns an array of aligned sequence positions where
326    * the array index is the data sequence position (both base 0).
327    */
328   @Test(groups = { "Functional" })
329   public void testGapMap()
330   {
331     SequenceI sq = new Sequence("test", "-A--B-CD-E--F-");
332     sq.createDatasetSequence();
333     assertEquals("[1, 4, 6, 7, 9, 12]", Arrays.toString(sq.gapMap()));
334   }
335
336   /**
337    * Test the method that gets sequence features, either from the sequence or
338    * its dataset.
339    */
340   @Test(groups = { "Functional" })
341   public void testGetSequenceFeatures()
342   {
343     SequenceI sq = new Sequence("test", "GATCAT");
344     sq.createDatasetSequence();
345
346     assertNull(sq.getSequenceFeatures());
347
348     /*
349      * SequenceFeature on sequence
350      */
351     SequenceFeature sf = new SequenceFeature();
352     sq.addSequenceFeature(sf);
353     SequenceFeature[] sfs = sq.getSequenceFeatures();
354     assertEquals(1, sfs.length);
355     assertSame(sf, sfs[0]);
356
357     /*
358      * SequenceFeature on sequence and dataset sequence; returns that on
359      * sequence
360      * 
361      * Note JAL-2046: spurious: we have no use case for this at the moment.
362      * This test also buggy - as sf2.equals(sf), no new feature is added
363      */
364     SequenceFeature sf2 = new SequenceFeature();
365     sq.getDatasetSequence().addSequenceFeature(sf2);
366     sfs = sq.getSequenceFeatures();
367     assertEquals(1, sfs.length);
368     assertSame(sf, sfs[0]);
369
370     /*
371      * SequenceFeature on dataset sequence only
372      * Note JAL-2046: spurious: we have no use case for setting a non-dataset sequence's feature array to null at the moment.
373      */
374     sq.setSequenceFeatures(null);
375     assertNull(sq.getDatasetSequence().getSequenceFeatures());
376
377     /*
378      * Corrupt case - no SequenceFeature, dataset's dataset is the original
379      * sequence. Test shows no infinite loop results.
380      */
381     sq.getDatasetSequence().setSequenceFeatures(null);
382     /**
383      * is there a usecase for this ? setDatasetSequence should throw an error if
384      * this actually occurs.
385      */
386     try
387     {
388       sq.getDatasetSequence().setDatasetSequence(sq); // loop!
389       Assert.fail("Expected Error to be raised when calling setDatasetSequence with self reference");
390     } catch (IllegalArgumentException e)
391     {
392       // TODO Jalview error/exception class for raising implementation errors
393       assertTrue(e.getMessage().toLowerCase()
394               .contains("implementation error"));
395     }
396     assertNull(sq.getSequenceFeatures());
397   }
398
399   /**
400    * Test the method that returns an array, indexed by sequence position, whose
401    * entries are the residue positions at the sequence position (or to the right
402    * if a gap)
403    */
404   @Test(groups = { "Functional" })
405   public void testFindPositionMap()
406   {
407     /*
408      * Note: Javadoc for findPosition says it returns the residue position to
409      * the left of a gapped position; in fact it returns the position to the
410      * right. Also it returns a non-existent residue position for a gap beyond
411      * the sequence.
412      */
413     Sequence sq = new Sequence("TestSeq", "AB.C-D E.");
414     int[] map = sq.findPositionMap();
415     assertEquals(Arrays.toString(new int[] { 1, 2, 3, 3, 4, 4, 5, 5, 6 }),
416             Arrays.toString(map));
417   }
418
419   /**
420    * Test for getSubsequence
421    */
422   @Test(groups = { "Functional" })
423   public void testGetSubsequence()
424   {
425     SequenceI sq = new Sequence("TestSeq", "ABCDEFG");
426     sq.createDatasetSequence();
427
428     // positions are base 0, end position is exclusive
429     SequenceI subseq = sq.getSubSequence(2, 4);
430
431     assertEquals("CD", subseq.getSequenceAsString());
432     // start/end are base 1 positions
433     assertEquals(3, subseq.getStart());
434     assertEquals(4, subseq.getEnd());
435     // subsequence shares the full dataset sequence
436     assertSame(sq.getDatasetSequence(), subseq.getDatasetSequence());
437   }
438
439   /**
440    * test createDatasetSequence behaves to doc
441    */
442   @Test(groups = { "Functional" })
443   public void testCreateDatasetSequence()
444   {
445     SequenceI sq = new Sequence("my", "ASDASD");
446     assertNull(sq.getDatasetSequence());
447     SequenceI rds = sq.createDatasetSequence();
448     assertNotNull(rds);
449     assertNull(rds.getDatasetSequence());
450     assertEquals(sq.getDatasetSequence(), rds);
451   }
452
453   /**
454    * Test for deriveSequence applied to a sequence with a dataset
455    */
456   @Test(groups = { "Functional" })
457   public void testDeriveSequence_existingDataset()
458   {
459     Sequence sq = new Sequence("Seq1", "CD");
460     sq.setDatasetSequence(new Sequence("Seq1", "ABCDEF"));
461     sq.getDatasetSequence().addSequenceFeature(
462             new SequenceFeature("", "", 1, 2, 0f, null));
463     sq.setStart(3);
464     sq.setEnd(4);
465
466     sq.setDescription("Test sequence description..");
467     sq.setVamsasId("TestVamsasId");
468     sq.addDBRef(new DBRefEntry("PDB", "version0", "1TST"));
469
470     sq.addDBRef(new DBRefEntry("PDB", "version1", "1PDB"));
471     sq.addDBRef(new DBRefEntry("PDB", "version2", "2PDB"));
472     sq.addDBRef(new DBRefEntry("PDB", "version3", "3PDB"));
473     sq.addDBRef(new DBRefEntry("PDB", "version4", "4PDB"));
474
475     sq.addPDBId(new PDBEntry("1PDB", "A", Type.PDB, "filePath/test1"));
476     sq.addPDBId(new PDBEntry("1PDB", "B", Type.PDB, "filePath/test1"));
477     sq.addPDBId(new PDBEntry("2PDB", "A", Type.MMCIF, "filePath/test2"));
478     sq.addPDBId(new PDBEntry("2PDB", "B", Type.MMCIF, "filePath/test2"));
479
480     // these are the same as ones already added
481     DBRefEntry pdb1pdb = new DBRefEntry("PDB", "version1", "1PDB");
482     DBRefEntry pdb2pdb = new DBRefEntry("PDB", "version2", "2PDB");
483
484     List<DBRefEntry> primRefs = Arrays.asList(new DBRefEntry[] { pdb1pdb,
485         pdb2pdb });
486
487     sq.getDatasetSequence().addDBRef(pdb1pdb); // should do nothing
488     sq.getDatasetSequence().addDBRef(pdb2pdb); // should do nothing
489     sq.getDatasetSequence().addDBRef(
490             new DBRefEntry("PDB", "version3", "3PDB")); // should do nothing
491     sq.getDatasetSequence().addDBRef(
492             new DBRefEntry("PDB", "version4", "4PDB")); // should do nothing
493
494     PDBEntry pdbe1a = new PDBEntry("1PDB", "A", Type.PDB, "filePath/test1");
495     PDBEntry pdbe1b = new PDBEntry("1PDB", "B", Type.PDB, "filePath/test1");
496     PDBEntry pdbe2a = new PDBEntry("2PDB", "A", Type.MMCIF,
497             "filePath/test2");
498     PDBEntry pdbe2b = new PDBEntry("2PDB", "B", Type.MMCIF,
499             "filePath/test2");
500     sq.getDatasetSequence().addPDBId(pdbe1a);
501     sq.getDatasetSequence().addPDBId(pdbe1b);
502     sq.getDatasetSequence().addPDBId(pdbe2a);
503     sq.getDatasetSequence().addPDBId(pdbe2b);
504
505     /*
506      * test we added pdb entries to the dataset sequence
507      */
508     Assert.assertEquals(sq.getDatasetSequence().getAllPDBEntries(), Arrays
509             .asList(new PDBEntry[] { pdbe1a, pdbe1b, pdbe2a, pdbe2b }),
510             "PDB Entries were not found on dataset sequence.");
511
512     /*
513      * we should recover a pdb entry that is on the dataset sequence via PDBEntry
514      */
515     Assert.assertEquals(pdbe1a,
516             sq.getDatasetSequence().getPDBEntry("1PDB"),
517             "PDB Entry '1PDB' not found on dataset sequence via getPDBEntry.");
518     ArrayList<Annotation> annotsList = new ArrayList<Annotation>();
519     System.out.println(">>>>>> " + sq.getSequenceAsString().length());
520     annotsList.add(new Annotation("A", "A", 'X', 0.1f));
521     annotsList.add(new Annotation("A", "A", 'X', 0.1f));
522     Annotation[] annots = annotsList.toArray(new Annotation[0]);
523     sq.addAlignmentAnnotation(new AlignmentAnnotation("Test annot",
524             "Test annot description", annots));
525     sq.getDatasetSequence().addAlignmentAnnotation(
526             new AlignmentAnnotation("Test annot", "Test annot description",
527                     annots));
528     Assert.assertEquals(sq.getDescription(), "Test sequence description..");
529     Assert.assertEquals(sq.getDBRefs().length, 5); // DBRefs are on dataset
530                                                    // sequence
531     Assert.assertEquals(sq.getAllPDBEntries().size(), 4);
532     Assert.assertNotNull(sq.getAnnotation());
533     Assert.assertEquals(sq.getAnnotation()[0].annotations.length, 2);
534     Assert.assertEquals(sq.getDatasetSequence().getDBRefs().length, 5); // same
535                                                                         // as
536                                                                         // sq.getDBRefs()
537     Assert.assertEquals(sq.getDatasetSequence().getAllPDBEntries().size(),
538             4);
539     Assert.assertNotNull(sq.getDatasetSequence().getAnnotation());
540
541     Sequence derived = (Sequence) sq.deriveSequence();
542
543     Assert.assertEquals(derived.getDescription(),
544             "Test sequence description..");
545     Assert.assertEquals(derived.getDBRefs().length, 5); // come from dataset
546     Assert.assertEquals(derived.getAllPDBEntries().size(), 4);
547     Assert.assertNotNull(derived.getAnnotation());
548     Assert.assertEquals(derived.getAnnotation()[0].annotations.length, 2);
549     Assert.assertEquals(derived.getDatasetSequence().getDBRefs().length, 5);
550     Assert.assertEquals(derived.getDatasetSequence().getAllPDBEntries()
551             .size(), 4);
552     Assert.assertNotNull(derived.getDatasetSequence().getAnnotation());
553
554     assertEquals("CD", derived.getSequenceAsString());
555     assertSame(sq.getDatasetSequence(), derived.getDatasetSequence());
556
557     assertNull(sq.sequenceFeatures);
558     assertNull(derived.sequenceFeatures);
559     // derived sequence should access dataset sequence features
560     assertNotNull(sq.getSequenceFeatures());
561     assertArrayEquals(sq.getSequenceFeatures(),
562             derived.getSequenceFeatures());
563
564     /*
565      *  verify we have primary db refs *just* for PDB IDs with associated
566      *  PDBEntry objects
567      */
568
569     assertEquals(primRefs, sq.getPrimaryDBRefs());
570     assertEquals(primRefs, sq.getDatasetSequence().getPrimaryDBRefs());
571
572     assertEquals(sq.getPrimaryDBRefs(), derived.getPrimaryDBRefs());
573
574   }
575
576   /**
577    * Test for deriveSequence applied to an ungapped sequence with no dataset
578    */
579   @Test(groups = { "Functional" })
580   public void testDeriveSequence_noDatasetUngapped()
581   {
582     SequenceI sq = new Sequence("Seq1", "ABCDEF");
583     assertEquals(1, sq.getStart());
584     assertEquals(6, sq.getEnd());
585     SequenceI derived = sq.deriveSequence();
586     assertEquals("ABCDEF", derived.getSequenceAsString());
587     assertEquals("ABCDEF", derived.getDatasetSequence()
588             .getSequenceAsString());
589   }
590
591   /**
592    * Test for deriveSequence applied to a gapped sequence with no dataset
593    */
594   @Test(groups = { "Functional" })
595   public void testDeriveSequence_noDatasetGapped()
596   {
597     SequenceI sq = new Sequence("Seq1", "AB-C.D EF");
598     assertEquals(1, sq.getStart());
599     assertEquals(6, sq.getEnd());
600     assertNull(sq.getDatasetSequence());
601     SequenceI derived = sq.deriveSequence();
602     assertEquals("AB-C.D EF", derived.getSequenceAsString());
603     assertEquals("ABCDEF", derived.getDatasetSequence()
604             .getSequenceAsString());
605   }
606
607   @Test(groups = { "Functional" })
608   public void testCopyConstructor_noDataset()
609   {
610     SequenceI seq1 = new Sequence("Seq1", "AB-C.D EF");
611     seq1.setDescription("description");
612     seq1.addAlignmentAnnotation(new AlignmentAnnotation("label", "desc",
613             1.3d));
614     seq1.addSequenceFeature(new SequenceFeature("type", "desc", 22, 33,
615             12.4f, "group"));
616     seq1.addPDBId(new PDBEntry("1A70", "B", Type.PDB, "File"));
617     seq1.addDBRef(new DBRefEntry("EMBL", "1.2", "AZ12345"));
618
619     SequenceI copy = new Sequence(seq1);
620
621     assertNull(copy.getDatasetSequence());
622
623     verifyCopiedSequence(seq1, copy);
624
625     // copy has a copy of the DBRefEntry
626     // this is murky - DBrefs are only copied for dataset sequences
627     // where the test for 'dataset sequence' is 'dataset is null'
628     // but that doesn't distinguish it from an aligned sequence
629     // which has not yet generated a dataset sequence
630     // NB getDBRef looks inside dataset sequence if not null
631     DBRefEntry[] dbrefs = copy.getDBRefs();
632     assertEquals(1, dbrefs.length);
633     assertFalse(dbrefs[0] == seq1.getDBRefs()[0]);
634     assertTrue(dbrefs[0].equals(seq1.getDBRefs()[0]));
635   }
636
637   @Test(groups = { "Functional" })
638   public void testCopyConstructor_withDataset()
639   {
640     SequenceI seq1 = new Sequence("Seq1", "AB-C.D EF");
641     seq1.createDatasetSequence();
642     seq1.setDescription("description");
643     seq1.addAlignmentAnnotation(new AlignmentAnnotation("label", "desc",
644             1.3d));
645     // JAL-2046 - what is the contract for using a derived sequence's
646     // addSequenceFeature ?
647     seq1.addSequenceFeature(new SequenceFeature("type", "desc", 22, 33,
648             12.4f, "group"));
649     seq1.addPDBId(new PDBEntry("1A70", "B", Type.PDB, "File"));
650     // here we add DBRef to the dataset sequence:
651     seq1.getDatasetSequence().addDBRef(
652             new DBRefEntry("EMBL", "1.2", "AZ12345"));
653
654     SequenceI copy = new Sequence(seq1);
655
656     assertNotNull(copy.getDatasetSequence());
657     assertSame(copy.getDatasetSequence(), seq1.getDatasetSequence());
658
659     verifyCopiedSequence(seq1, copy);
660
661     // getDBRef looks inside dataset sequence and this is shared,
662     // so holds the same dbref objects
663     DBRefEntry[] dbrefs = copy.getDBRefs();
664     assertEquals(1, dbrefs.length);
665     assertSame(dbrefs[0], seq1.getDBRefs()[0]);
666   }
667
668   /**
669    * Helper to make assertions about a copied sequence
670    * 
671    * @param seq1
672    * @param copy
673    */
674   protected void verifyCopiedSequence(SequenceI seq1, SequenceI copy)
675   {
676     // verify basic properties:
677     assertEquals(copy.getName(), seq1.getName());
678     assertEquals(copy.getDescription(), seq1.getDescription());
679     assertEquals(copy.getStart(), seq1.getStart());
680     assertEquals(copy.getEnd(), seq1.getEnd());
681     assertEquals(copy.getSequenceAsString(), seq1.getSequenceAsString());
682
683     // copy has a copy of the annotation:
684     AlignmentAnnotation[] anns = copy.getAnnotation();
685     assertEquals(1, anns.length);
686     assertFalse(anns[0] == seq1.getAnnotation()[0]);
687     assertEquals(anns[0].label, seq1.getAnnotation()[0].label);
688     assertEquals(anns[0].description, seq1.getAnnotation()[0].description);
689     assertEquals(anns[0].score, seq1.getAnnotation()[0].score);
690
691     // copy has a copy of the sequence feature:
692     SequenceFeature[] sfs = copy.getSequenceFeatures();
693     assertEquals(1, sfs.length);
694     if (seq1.getDatasetSequence() != null
695             && copy.getDatasetSequence() == seq1.getDatasetSequence())
696     {
697       assertTrue(sfs[0] == seq1.getSequenceFeatures()[0]);
698     }
699     else
700     {
701       assertFalse(sfs[0] == seq1.getSequenceFeatures()[0]);
702     }
703     assertTrue(sfs[0].equals(seq1.getSequenceFeatures()[0]));
704
705     // copy has a copy of the PDB entry
706     Vector<PDBEntry> pdbs = copy.getAllPDBEntries();
707     assertEquals(1, pdbs.size());
708     assertFalse(pdbs.get(0) == seq1.getAllPDBEntries().get(0));
709     assertTrue(pdbs.get(0).equals(seq1.getAllPDBEntries().get(0)));
710   }
711
712   @Test(groups = "Functional")
713   public void testGetCharAt()
714   {
715     SequenceI sq = new Sequence("", "abcde");
716     assertEquals('a', sq.getCharAt(0));
717     assertEquals('e', sq.getCharAt(4));
718     assertEquals(' ', sq.getCharAt(5));
719     assertEquals(' ', sq.getCharAt(-1));
720   }
721
722   /**
723    * Tests for adding (or updating) dbrefs
724    * 
725    * @see DBRefEntry#updateFrom(DBRefEntry)
726    */
727   @Test(groups = { "Functional" })
728   public void testAddDBRef()
729   {
730     SequenceI sq = new Sequence("", "abcde");
731     assertNull(sq.getDBRefs());
732     DBRefEntry dbref = new DBRefEntry("Uniprot", "1", "P00340");
733     sq.addDBRef(dbref);
734     assertEquals(1, sq.getDBRefs().length);
735     assertSame(dbref, sq.getDBRefs()[0]);
736
737     /*
738      * change of version - new entry
739      */
740     DBRefEntry dbref2 = new DBRefEntry("Uniprot", "2", "P00340");
741     sq.addDBRef(dbref2);
742     assertEquals(2, sq.getDBRefs().length);
743     assertSame(dbref, sq.getDBRefs()[0]);
744     assertSame(dbref2, sq.getDBRefs()[1]);
745
746     /*
747      * matches existing entry - not added
748      */
749     sq.addDBRef(new DBRefEntry("UNIPROT", "1", "p00340"));
750     assertEquals(2, sq.getDBRefs().length);
751
752     /*
753      * different source = new entry
754      */
755     DBRefEntry dbref3 = new DBRefEntry("UniRef", "1", "p00340");
756     sq.addDBRef(dbref3);
757     assertEquals(3, sq.getDBRefs().length);
758     assertSame(dbref3, sq.getDBRefs()[2]);
759
760     /*
761      * different ref = new entry
762      */
763     DBRefEntry dbref4 = new DBRefEntry("UniRef", "1", "p00341");
764     sq.addDBRef(dbref4);
765     assertEquals(4, sq.getDBRefs().length);
766     assertSame(dbref4, sq.getDBRefs()[3]);
767
768     /*
769      * matching ref with a mapping - map updated
770      */
771     DBRefEntry dbref5 = new DBRefEntry("UniRef", "1", "p00341");
772     Mapping map = new Mapping(new MapList(new int[] { 1, 3 }, new int[] {
773         1, 1 }, 3, 1));
774     dbref5.setMap(map);
775     sq.addDBRef(dbref5);
776     assertEquals(4, sq.getDBRefs().length);
777     assertSame(dbref4, sq.getDBRefs()[3]);
778     assertSame(map, dbref4.getMap());
779
780     /*
781      * 'real' version replaces "0" version
782      */
783     dbref2.setVersion("0");
784     DBRefEntry dbref6 = new DBRefEntry(dbref2.getSource(), "3",
785             dbref2.getAccessionId());
786     sq.addDBRef(dbref6);
787     assertEquals(4, sq.getDBRefs().length);
788     assertSame(dbref2, sq.getDBRefs()[1]);
789     assertEquals("3", dbref2.getVersion());
790
791     /*
792      * 'real' version replaces "source:0" version
793      */
794     dbref3.setVersion("Uniprot:0");
795     DBRefEntry dbref7 = new DBRefEntry(dbref3.getSource(), "3",
796             dbref3.getAccessionId());
797     sq.addDBRef(dbref7);
798     assertEquals(4, sq.getDBRefs().length);
799     assertSame(dbref3, sq.getDBRefs()[2]);
800     assertEquals("3", dbref2.getVersion());
801   }
802
803   @Test(groups = { "Functional" })
804   public void testGetPrimaryDBRefs_peptide()
805   {
806     SequenceI sq = new Sequence("aseq", "ASDFKYLMQPRST", 10, 22);
807
808     // no dbrefs
809     List<DBRefEntry> primaryDBRefs = sq.getPrimaryDBRefs();
810     assertTrue(primaryDBRefs.isEmpty());
811
812     // empty dbrefs
813     sq.setDBRefs(new DBRefEntry[] {});
814     primaryDBRefs = sq.getPrimaryDBRefs();
815     assertTrue(primaryDBRefs.isEmpty());
816
817     // primary - uniprot
818     DBRefEntry upentry1 = new DBRefEntry("UNIPROT", "0", "Q04760");
819     sq.addDBRef(upentry1);
820
821     // primary - uniprot with congruent map
822     DBRefEntry upentry2 = new DBRefEntry("UNIPROT", "0", "Q04762");
823     upentry2.setMap(new Mapping(null, new MapList(new int[] { 10, 22 },
824             new int[] { 10, 22 }, 1, 1)));
825     sq.addDBRef(upentry2);
826
827     // primary - uniprot with map of enclosing sequence
828     DBRefEntry upentry3 = new DBRefEntry("UNIPROT", "0", "Q04763");
829     upentry3.setMap(new Mapping(null, new MapList(new int[] { 8, 24 },
830             new int[] { 8, 24 }, 1, 1)));
831     sq.addDBRef(upentry3);
832
833     // not primary - uniprot with map of sub-sequence (5')
834     DBRefEntry upentry4 = new DBRefEntry("UNIPROT", "0", "Q04764");
835     upentry4.setMap(new Mapping(null, new MapList(new int[] { 10, 18 },
836             new int[] { 10, 18 }, 1, 1)));
837     sq.addDBRef(upentry4);
838
839     // not primary - uniprot with map that overlaps 3'
840     DBRefEntry upentry5 = new DBRefEntry("UNIPROT", "0", "Q04765");
841     upentry5.setMap(new Mapping(null, new MapList(new int[] { 12, 22 },
842             new int[] { 12, 22 }, 1, 1)));
843     sq.addDBRef(upentry5);
844
845     // not primary - uniprot with map to different coordinates frame
846     DBRefEntry upentry6 = new DBRefEntry("UNIPROT", "0", "Q04766");
847     upentry6.setMap(new Mapping(null, new MapList(new int[] { 12, 18 },
848             new int[] { 112, 118 }, 1, 1)));
849     sq.addDBRef(upentry6);
850
851     // not primary - dbref to 'non-core' database
852     DBRefEntry upentry7 = new DBRefEntry("Pfam", "0", "PF00903");
853     sq.addDBRef(upentry7);
854
855     // primary - type is PDB
856     DBRefEntry pdbentry = new DBRefEntry("PDB", "0", "1qip");
857     sq.addDBRef(pdbentry);
858
859     // not primary - PDBEntry has no file
860     sq.addDBRef(new DBRefEntry("PDB", "0", "1AAA"));
861
862     // not primary - no PDBEntry
863     sq.addDBRef(new DBRefEntry("PDB", "0", "1DDD"));
864
865     // add corroborating PDB entry for primary DBref -
866     // needs to have a file as well as matching ID
867     // note PDB ID is not treated as case sensitive
868     sq.addPDBId(new PDBEntry("1QIP", null, Type.PDB, new File("/blah")
869             .toString()));
870
871     // not valid DBRef - no file..
872     sq.addPDBId(new PDBEntry("1AAA", null, null, null));
873
874     primaryDBRefs = sq.getPrimaryDBRefs();
875     assertEquals(4, primaryDBRefs.size());
876     assertTrue("Couldn't find simple primary reference (UNIPROT)",
877             primaryDBRefs.contains(upentry1));
878     assertTrue("Couldn't find mapped primary reference (UNIPROT)",
879             primaryDBRefs.contains(upentry2));
880     assertTrue("Couldn't find mapped context reference (UNIPROT)",
881             primaryDBRefs.contains(upentry3));
882     assertTrue("Couldn't find expected PDB primary reference",
883             primaryDBRefs.contains(pdbentry));
884   }
885
886   @Test(groups = { "Functional" })
887   public void testGetPrimaryDBRefs_nucleotide()
888   {
889     SequenceI sq = new Sequence("aseq", "TGATCACTCGACTAGCATCAGCATA", 10, 34);
890
891     // primary - Ensembl
892     DBRefEntry dbr1 = new DBRefEntry("ENSEMBL", "0", "ENSG1234");
893     sq.addDBRef(dbr1);
894
895     // not primary - Ensembl 'transcript' mapping of sub-sequence
896     DBRefEntry dbr2 = new DBRefEntry("ENSEMBL", "0", "ENST1234");
897     dbr2.setMap(new Mapping(null, new MapList(new int[] { 15, 25 },
898             new int[] { 1, 11 }, 1, 1)));
899     sq.addDBRef(dbr2);
900
901     // primary - EMBL with congruent map
902     DBRefEntry dbr3 = new DBRefEntry("EMBL", "0", "J1234");
903     dbr3.setMap(new Mapping(null, new MapList(new int[] { 10, 34 },
904             new int[] { 10, 34 }, 1, 1)));
905     sq.addDBRef(dbr3);
906
907     // not primary - to non-core database
908     DBRefEntry dbr4 = new DBRefEntry("CCDS", "0", "J1234");
909     sq.addDBRef(dbr4);
910
911     // not primary - to protein
912     DBRefEntry dbr5 = new DBRefEntry("UNIPROT", "0", "Q87654");
913     sq.addDBRef(dbr5);
914
915     List<DBRefEntry> primaryDBRefs = sq.getPrimaryDBRefs();
916     assertEquals(2, primaryDBRefs.size());
917     assertTrue(primaryDBRefs.contains(dbr1));
918     assertTrue(primaryDBRefs.contains(dbr3));
919   }
920
921   /**
922    * Test the method that updates the list of PDBEntry from any new DBRefEntry
923    * for PDB
924    */
925   @Test(groups = { "Functional" })
926   public void testUpdatePDBIds()
927   {
928     PDBEntry pdbe1 = new PDBEntry("3A6S", null, null, null);
929     seq.addPDBId(pdbe1);
930     seq.addDBRef(new DBRefEntry("Ensembl", "8", "ENST1234"));
931     seq.addDBRef(new DBRefEntry("PDB", "0", "1A70"));
932     seq.addDBRef(new DBRefEntry("PDB", "0", "4BQGa"));
933     seq.addDBRef(new DBRefEntry("PDB", "0", "3a6sB"));
934     // 7 is not a valid chain code:
935     seq.addDBRef(new DBRefEntry("PDB", "0", "2GIS7"));
936
937     seq.updatePDBIds();
938     List<PDBEntry> pdbIds = seq.getAllPDBEntries();
939     assertEquals(4, pdbIds.size());
940     assertSame(pdbe1, pdbIds.get(0));
941     // chain code got added to 3A6S:
942     assertEquals("B", pdbe1.getChainCode());
943     assertEquals("1A70", pdbIds.get(1).getId());
944     // 4BQGA is parsed into id + chain
945     assertEquals("4BQG", pdbIds.get(2).getId());
946     assertEquals("a", pdbIds.get(2).getChainCode());
947     assertEquals("2GIS7", pdbIds.get(3).getId());
948     assertNull(pdbIds.get(3).getChainCode());
949   }
950
951   /**
952    * Test the method that either adds a pdbid or updates an existing one
953    */
954   @Test(groups = { "Functional" })
955   public void testAddPDBId()
956   {
957     PDBEntry pdbe = new PDBEntry("3A6S", null, null, null);
958     seq.addPDBId(pdbe);
959     assertEquals(1, seq.getAllPDBEntries().size());
960     assertSame(pdbe, seq.getPDBEntry("3A6S"));
961     assertSame(pdbe, seq.getPDBEntry("3a6s")); // case-insensitive
962
963     // add the same entry
964     seq.addPDBId(pdbe);
965     assertEquals(1, seq.getAllPDBEntries().size());
966     assertSame(pdbe, seq.getPDBEntry("3A6S"));
967
968     // add an identical entry
969     seq.addPDBId(new PDBEntry("3A6S", null, null, null));
970     assertEquals(1, seq.getAllPDBEntries().size());
971     assertSame(pdbe, seq.getPDBEntry("3A6S"));
972
973     // add a different entry
974     PDBEntry pdbe2 = new PDBEntry("1A70", null, null, null);
975     seq.addPDBId(pdbe2);
976     assertEquals(2, seq.getAllPDBEntries().size());
977     assertSame(pdbe, seq.getAllPDBEntries().get(0));
978     assertSame(pdbe2, seq.getAllPDBEntries().get(1));
979
980     // update pdbe with chain code, file, type
981     PDBEntry pdbe3 = new PDBEntry("3a6s", "A", Type.PDB, "filepath");
982     seq.addPDBId(pdbe3);
983     assertEquals(2, seq.getAllPDBEntries().size());
984     assertSame(pdbe, seq.getAllPDBEntries().get(0)); // updated in situ
985     assertEquals("3A6S", pdbe.getId()); // unchanged
986     assertEquals("A", pdbe.getChainCode()); // updated
987     assertEquals(Type.PDB.toString(), pdbe.getType()); // updated
988     assertEquals("filepath", pdbe.getFile()); // updated
989     assertSame(pdbe2, seq.getAllPDBEntries().get(1));
990
991     // add with a different file path
992     PDBEntry pdbe4 = new PDBEntry("3a6s", "A", Type.PDB, "filepath2");
993     seq.addPDBId(pdbe4);
994     assertEquals(3, seq.getAllPDBEntries().size());
995     assertSame(pdbe4, seq.getAllPDBEntries().get(2));
996
997     // add with a different chain code
998     PDBEntry pdbe5 = new PDBEntry("3a6s", "B", Type.PDB, "filepath");
999     seq.addPDBId(pdbe5);
1000     assertEquals(4, seq.getAllPDBEntries().size());
1001     assertSame(pdbe5, seq.getAllPDBEntries().get(3));
1002   }
1003
1004   @Test(
1005     groups = { "Functional" },
1006     expectedExceptions = { IllegalArgumentException.class })
1007   public void testSetDatasetSequence_toSelf()
1008   {
1009     seq.setDatasetSequence(seq);
1010   }
1011
1012   @Test(
1013     groups = { "Functional" },
1014     expectedExceptions = { IllegalArgumentException.class })
1015   public void testSetDatasetSequence_cascading()
1016   {
1017     SequenceI seq2 = new Sequence("Seq2", "xyz");
1018     seq2.createDatasetSequence();
1019     seq.setDatasetSequence(seq2);
1020   }
1021 }